Commit graph

37 commits

Author SHA1 Message Date
7e1e7d0502 chore: add per-package README and CHANGELOG for pub.dev
pub.dev requires README.md and CHANGELOG.md in each package directory.
Added minimal but accurate files to all four packages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 16:08:43 -04:00
49c6e995a8 fix: tolerate non-string YAML list elements when parsing tickets
YAML values like '1.0' (unquoted) parse as double, not String.
Changed parseStringList to use string interpolation ('$e') instead
of 'e as String', so numeric YAML scalars in labels/milestones
don't crash ticket parsing.

Also quote all '1.0' label values in existing ticket files so they
are unambiguously strings in YAML.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 16:05:01 -04:00
1e263f08c0 fix: tolerate numeric args from MCP JSON in all kanban commands
Replace fragile 'as String'/'as String?' casts in callAsTool() with
string interpolation so that numeric values sent by MCP JSON clients
(double instead of String) no longer throw a type cast error.

Fixes: type 'double' is not a subtype of type 'String' in type cast

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 16:01:36 -04:00
0ad1fae213 chore: 1.0 release prep
Metadata:
- Bump dew CLI version 0.0.1 → 1.0.0
- Add repository + issue_tracker URLs to all pubspec.yaml files
- Switch inter-package path deps to versioned deps (^1.0.0)
- Remove publish_to: none from all packages
- Add MIT LICENSE to root and all packages
- Confirm all four pub.dev names available (dew, dew_core, dew_kanban, dew_mcp)

Documentation:
- Add CHANGELOG.md (Keep a Changelog format, full 1.0.0 feature history)
- Overhaul README.md (pitch, pub.dev badge, quick-start, feature sections)
- Add TUI section + full keybinding tables to docs/features/kanban.md
- Add CONTRIBUTING.md (setup, test, lint, branch strategy, command guide)

Tests:
- Add packages/cli/test/cli_test.dart (6 smoke tests)
- Add packages/kanban/test/integration_test.dart (6 TicketStore e2e tests)
- Expand packages/mcp/test/mcp_test.dart (5 tool registration tests)
- Add dew_kanban as dev dependency in packages/mcp/pubspec.yaml
- 57/57 tests passing

Code quality:
- dart format applied across all 23 changed source files
- dart analyze: zero errors, zero warnings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 15:58:54 -04:00
4193282325 TUI: add F1 hint to all mode footers, wire F1 in editor, update board hints
- Board footer now shows all actions: D, L, F1, and the full keybinding list
- Detail footer shows [F1] help alongside scroll and edit hints
- Editor footer shows [F1] help hint
- F1 key now handled in editor mode (was only wired in board/detail)
- Stage deleted .project/kanban/backlog/DEW-0016.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 15:08:14 -04:00
0a00c4f744 TUI: remove hjkl nav, add delete/link/type-picker/resize/help
- Remove all h/j/k/l navigation bindings; arrow keys are the sole nav
- Update all footer hint text to reflect arrow-key navigation
- Delete ticket: D key → confirm prompt [y/N] → store.delete()
- Link tickets: L key → enter target ID → ←/→ cycle relation types → Enter commits via store.linkTickets()
- Type picker: ←/→ during new-title prompt cycles ticket types when multiple exist
- SIGWINCH: ProcessSignal.sigwinch triggers redraw on terminal resize (Unix only, wrapped in try/catch)
- Help overlay: F1 opens centered modal listing all keybindings by mode, any key closes
- prevMode tracked so F1 returns to board or detail correctly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:49:30 -04:00
5d9d9ded3f feat(tui): widen editor modal to max 100 cols
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:27:43 -04:00
54b0299b74 fix(tui): body text overflow; add [e] edit hotkey to detail view
- Editor: compute maxLen accounting for hint text length so body preview
  and title value never overflow the modal border
- Detail view footer: add [e] edit hint (key was already wired, just undiscoverable)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:24:36 -04:00
eeed3fdba0 fix(tui): pause/resume stdin subscription around external editor
io.stdin is a single-subscription stream — calling listen() a second time
after cancel() throws 'Stream has already been listened to'. Use
keySub.pause() before launching the editor and keySub.resume() after
it exits instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:22:09 -04:00
5c3d00e2a4 fix(tui): modal header background color bleed
Replace writeLine with write for the header row — writeLine appends a
newline which causes the active background color to flood the remainder
of the line and bleed into subsequent rows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:20:54 -04:00
8535254f48 fix(tui): enter key (0x0a LF), comma-delimited labels, arrow selector fix
- _parseKeys: map both 0x0a (LF) and 0x0d (CR) to ControlCharacter.enter
  Enter was silently discarded because Konsole sends LF, which mapped to ctrlJ
- arrowLeft/arrowRight now cycle selectors (type/column) and move item
  cursor (labels/milestones) instead of navigating fields — arrowUp/Down
  handle field navigation exclusively
- labels/milestones: Enter on comma-separated input adds all items at once

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:19:41 -04:00
0a2e8fbd90 fix(tui): replace brightBlack with white for terminal contrast
brightBlack (dark gray) is invisible on gray/medium terminal backgrounds.
Replace all instances with white for borders, hints, and secondary text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:13:39 -04:00
21d3814da4 feat(tui): full ticket editor modal overlay
- Add _EditorField enum and _EditorState class for mutable field tracking
- Add _Mode.editor and wire 'e' key in board+detail modes
- Editor supports: title (inline text edit), type (◀/▶ selector),
  column (◀/▶ selector), labels (chips + add/delete), milestones (same),
  body (preview + launches $VISUAL/$EDITOR/vi in raw terminal)
- j/k and arrow keys navigate between fields
- h/l cycle selector values and move item cursor in multi-value lists
- d removes selected label/milestone
- s saves all fields via store.update(), returns to board with focus on ticket
- Esc/q discards, returns to board
- _renderEditor: centered modal overlay (max 76 wide), dim background,
  double-line border in column accent colour, 'unsaved' indicator when dirty

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:03:12 -04:00
afc3d19ac9 fix(tui): proper box corners on column ticket area
Replace the full-width ▔▔▔ underline bar with ┌──┐ as the top border of
the ticket box. The ▔ row had no │ side characters so the box corners were
open/disconnected when the ticket area sides started on the next row.
Now the box is closed: ┌──┐ top, │ │ sides, ╘══╛/└──┘ bottom.
The pill name row above it still provides the visual tab header.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 13:44:06 -04:00
c9bee33371 fix(tui): make inactive column names readable
Inactive column header name was brightBlack (dark gray) — indistinguishable
from the dim separator line beneath it. Change to white so column names are
legible even when not selected; the ─ underline bar stays brightBlack to
keep the visual hierarchy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 13:41:44 -04:00
7bc7ee9898 fix(tui): keep highlight on moved ticket in destination column
After < or > move, find the ticket's index in the destination column's
filtered list via indexWhere instead of jumping to nt.length - 1.
indexWhere returns -1 if not found (e.g. filtered out), max(0,...) clamps
that safely to the first position.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 13:40:28 -04:00
7f7dd25d76 feat(tui): pill/tab column headers and inline kanban actions
Column header redesign (3 rows → 2 rows):
- Active column: ▌ NAME (n) ▐ full-width bold in column accent color
- Inactive column: lowercase dimmed name, thin ─ separator
- ▔▔▔▔▔ underline bar replaces the old ╞════╡ box separator
- Saves one terminal row per column, exposes more ticket content

Inline action prompts (bottom bar, Esc to cancel):
- [n] new ticket — prompts for title, creates in current column
  using the first configured ticket type as default
- [e] edit title — prefills current title for in-place editing
- [c] add comment — single-line comment appended to selected ticket
- [a] archive — shows 'Archive TICKET-ID? [y/N]' confirm prompt

After each mutation the board reloads and ticketIdx is clamped so
the cursor stays valid. Errors surface as yellow status messages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 13:03:01 -04:00
e8a703e6ca fix(tui): escape quits board, full column highlight, visible empty state
- Escape on board home quits the app; if a filter/status message is active
  it clears that first (consistent with Esc-to-clear UX elsewhere)
- Side-border cells (fillers, scroll-indicator blanks) in the selected column
  now use the column accent color instead of brightBlack, so the highlight
  box closes all the way around
- Empty-state '··· empty ···' label uses the column accent color (bold when
  selected) / white on non-selected, replacing the invisible brightBlack dots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 12:47:58 -04:00
46fffc91e1 fix(tui): stream-based event loop fixes Escape, adds auto-refresh, ? filter
- Replace blocking readKey() loop with async stdin stream + StreamController
  event bus. This fixes Escape (lone 0x1b batch vs ESC-sequence batch) and
  unlocks concurrent async work.
- Add _parseKeys() helper that decodes raw byte batches into Key values using
  the same logic as Console.readKey() but without blocking the event loop.
- Watch the kanban directory with Directory.watch() and debounce (350ms) to
  auto-refresh the board when tickets change on disk; replaces manual r reload.
- Change live-filter key from / to ? to avoid conflicts with shell history.
- Handle Ctrl+C as a clean quit in both board and detail mode.
- Seal _TuiEvent/_TuiKey/_TuiRefresh event hierarchy for type-safe dispatch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 12:39:11 -04:00
c25f32dc12 feat(kanban): add dew kanban tui interactive board TUI
Add an interactive Trello-style kanban TUI powered by dart_console.

Features:
- Side-by-side column layout, adapts to terminal width
- Keyboard navigation: j/k (up/down), h/l or ←/→ (switch column)
- Move selected ticket between columns with < and >
- Ticket cards show ID, type badge (colour-coded), title, labels/milestone
- Scroll indicators (↑ N above / ↓ N below) when a column overflows
- Column headers highlighted in their configured colour; active column
  uses filled background + double-line border
- Live filter with / (fuzzy search across id, title, type, labels, body)
- Detail view (Enter): full ticket info, body and comments with word-wrap,
  scrollable with j/k
- r to reload tickets from disk without leaving the TUI
- Graceful terminal restoration on exit (Esc / q)
- Requires an interactive terminal; prints a clear error otherwise

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 03:02:39 -04:00
53f9493364 Add ProjectDirs extension pattern and injectable clock
- core: Add ProjectDirs helper class on ProjectContext (context.dirs)
  with project getter for .project/ dir; feature packages extend it
  via extension methods to expose their own dirs

- kanban: Add KanbanDirs extension on ProjectDirs exposing .kanban;
  replace all 14 p.join(context.root, '.project', 'kanban') call
  sites with context.dirs.kanban; drop now-unused path imports

- kanban: Add clock: DateTime Function() field to TicketStore
  (defaults to DateTime.now); use clock().toUtc() in create() for
  deterministic timestamps in tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 23:15:38 -04:00
8d787235b9 Refactor filesystem access to package:file
Replace dart:io File/Directory with package:file abstractions so that
tests can use MemoryFileSystem instead of mutating the process-global
Directory.current.

- Add file: ^7.0.1 to core and kanban dependencies
- ProjectContext.find() accepts FileSystem fs parameter
- TicketStore, KanbanInitHook, InitCommand, all kanban commands accept
  FileSystem fs (defaulting to LocalFileSystem())
- KanbanCommand and registerCommands() thread fs to subcommands
- Tests rewritten to use MemoryFileSystem() — no Directory.current mutation
- Remove dart_test.yaml (concurrency: 1 no longer needed)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 22:26:09 -04:00
037f5fde28 Four polish fixes
1. get shows milestones/labels: GetCommand._format() now shows Milestones:/Labels:
   lines when non-empty, between Created: and Links:

2. unarchive command: kanban unarchive --id <id> [--column <col>] restores a
   ticket from archive/ back to a column (default: first configured column);
   registered as 'kanban_unarchive_ticket' MCP tool (15 tools total)

3. Test isolation: add dart_test.yaml (concurrency: 1) — Directory.current is
   a process-global OS chdir(); concurrent test files in the same process
   would race. Now dart test packages/core packages/kanban passes cleanly.

4. update empty multi-option fix: --milestone '' / --label '' with empty
   strings now filters them out (treats as 'clear to empty') rather than
   writing spurious empty-string YAML list entries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 22:10:12 -04:00
ade243e2c7 Archive command and include-archived flag (DEW-0015)
- ArchiveCommand (kanban archive --id <id>): moves ticket file from its
  current column dir to .project/kanban/archive/; attachments stay put
  (under attachments/<id>/); registered as 'kanban_archive_ticket' MCP tool
- TicketStore.list() gains includeArchived param (default false);
  archive/ dir skipped unless includeArchived=true
- ListCommand: --include-archived flag
- SearchCommand: --include-archived flag
- Test: list excludes/includes archive correctly
- 14 MCP tools; 'archive' added to expected subcommands list

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 20:07:50 -04:00
5d8451383b Column transition validation and enhanced search filters (DEW-0013, DEW-0014)
DEW-0013: Enhanced search filters (already implemented in DEW-0011 pass)
- dew kanban search already had --column/--type/--label/--milestone filters
- Closed ticket

DEW-0014: Column transition validation
- ColumnConfig gains optional allowedTransitions: List<String>
- Parsed from allowed_transitions YAML list on each column entry
- MoveCommand validates current column's allowedTransitions before moving;
  unconfigured (empty list) = all transitions allowed (existing behaviour)
- Test: transition validation integration test with constrained + unconstrained columns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 20:05:55 -04:00
27a45e3d52 Grouped list view and ASCII board command (DEW-0012)
- ListCommand: when no --column filter, groups output by column with counts;
  with --column filter, returns flat list (no redundant column header)
- BoardCommand (kanban board): ASCII box per column in config order,
  self-sizing to longest ticket line; supports --type/--label/--milestone filters
  registered as 'kanban_board' MCP tool
- Test counts updated: 13 MCP tools, 'board' in subcommands list

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 20:03:55 -04:00
f89b3aa998 Add milestones and labels to Ticket model (DEW-0011)
- Ticket.milestones and Ticket.labels: List<String>, optional (default [])
- toFileContent() emits milestones:/labels: YAML lists when non-empty
- fromFileContent() parses them back; empty when absent (backwards compat)
- TicketStore.create() and update() accept milestones/labels params
- CreateCommand and UpdateCommand: --milestone/--label multi-options
- ListCommand and SearchCommand: --milestone/--label filter options
- 4 new tests: model roundtrip, store persistence, update patch, no fields when empty

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 20:01:47 -04:00
31f9ba4726 dew init with module init hooks (DEW-0010)
- DewInitHook interface and DewInitOptions added to packages/core/src/init.dart
- InitCommand: creates .project/dew.yaml from default template, calls hooks
  --path/-p option (default '.'), --[no-]gitkeep flag (default true)
- CommandRegistry gains initHooks list and registerInitHook()
- KanbanInitHook: creates column dirs, archive/, attachments/ under
  .project/kanban/; adds .gitkeep to freshly-created dirs when enabled
- KanbanInitHook registered in kanban.registerCommands()
- CLI wires InitCommand(registry.initHooks) as a top-level command

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 19:58:53 -04:00
a25411d4fa Config refactor: DewConfig.raw + per-package extensions (DEW-0007)
DewConfig in core is now a thin YamlMap wrapper. Feature-specific config
classes live in their own packages and expose themselves via Dart extensions:

- KanbanConfig, ColumnConfig, TicketTypeConfig + KanbanDewConfig extension
  moved to packages/kanban/lib/src/kanban_config.dart
- McpConfig + McpDewConfig extension added to
  packages/mcp/lib/src/mcp_config.dart
- DewConfig.fromYaml() now trivially wraps the raw YamlMap
- All call sites unchanged: context.config.kanban.* / context.config.mcp.*
- Added yaml dependency to dew_mcp pubspec
- Updated core test to validate raw yaml instead of typed fields
- Fixed cross-suite Directory.current isolation (existing issue, not introduced)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 19:54:35 -04:00
951f0d8bc8 Storage refactor: tickets in column subdirs, drop redundant column frontmatter
- TicketStore rewritten: tickets live in .project/kanban/<column>/<id>.md
- _findTicketFile() searches all column subdirs (one level deep)
- update() moves the file when column changes
- delete() cleans up attachments/<id>/ if present
- _nextNumber() scans all subdirs including archive
- Ticket.fromFileContent() now takes column as explicit parameter
- Ticket.toFileContent() drops column field (derived from path)
- _yamlQuote() added to safely quote titles containing ': '
- dew.yaml: columns changed to backlog/doing/done (alphabetical order)
- Existing tickets migrated to .project/kanban/backlog/

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 19:42:21 -04:00
08f4d5c7cf Upgrade ticket links to typed bidirectional relationships
Link types: blocks/is_blocked_by, relates_to (symmetric),
duplicates/is_duplicated_by, parent_of/child_of

- Add TicketLink(targetId, type) class and linkTypeInverses map to
  ticket.dart
- Update Ticket.links from List<String> to List<TicketLink>
- Update toFileContent/fromFileContent for new {id, type} YAML format
- Update TicketStore.linkTickets to accept a type, write the forward
  link and automatically write the inverse on the target ticket
- Update TicketStore.unlinkTickets to remove both sides
- Update LinkCommand with mandatory --type/-y option (enum of all valid
  types); describe inverse in output message
- Update GetCommand to display typed links in ticket output
- Update all tests: typed roundtrip, bidirectional store tests for
  blocks, relates_to (symmetric), parent_of/child_of, unlink both sides
- 29 tests pass, dart analyze clean

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 19:21:25 -04:00
4efa1078ea Add stats, move, link, and unlink kanban tools
- Add links field to Ticket model (YAML frontmatter list, roundtrips
  correctly; omitted from file when empty)
- Add TicketStore.linkTickets(), unlinkTickets(), and stats() methods
- Add StatsCommand (kanban_stats) — ticket counts by column and type
- Add MoveCommand (kanban_move_ticket) — validated column transition
- Add LinkCommand (kanban_link_tickets) — track ticket dependencies
- Add UnlinkCommand (kanban_unlink_tickets) — remove ticket links
- Register all 4 new subcommands in KanbanCommand (12 total)
- All 27 tests pass, dart analyze clean

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 17:10:09 -04:00
7b83572f7a Unified command-tool registration via DewToolCommand mixin
- Add DewToolCommand mixin that auto-derives MCP tool JSON Schema from
  ArgParser, eliminating the need to define CLI commands and MCP tools
  separately
- Add schemaFromArgParser() to generate JSON Schema from ArgParser options
- Add CommandRegistry.mcpTools recursive collector for all DewToolCommand
  subcommands
- Refactor all kanban subcommands to use the mixin; switch get/update/delete
  from positional rest args to --id / -i option for schema compatibility
- Promote list to a proper CLI subcommand (was MCP-only before)
- Add search, comment, and config subcommands (CLI + MCP tools)
- Add TicketStore.addComment() for non-destructive comment appending
- Simplify mcp.registerCommands() to take only CommandRegistry
- Simplify CLI entry point (no more KanbanToolProvider/McpToolRegistry)
- Delete stale files: kanban_tool_provider.dart, mcp_tool_provider.dart,
  mcp_tool_registry.dart (superseded by DewToolCommand mixin)
- Add tools/mcp_client.dart debug client for manual MCP server testing
- Update .vscode/mcp.json with correct server config

All 26 tests pass, dart analyze clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:30:43 -04:00
a74bd94547 Implement MCP tool registry and kanban tool provider
- Add McpTool + McpToolProvider interface to core
- Add McpToolRegistry to mcp package (aggregates providers)
- Add DewMcpServer (MCPServer + ToolsSupport via dart_mcp 0.5.0)
- Add 'mcp serve' subcommand — starts a real stdio MCP server
- Implement KanbanToolProvider with 5 tools:
  kanban_create_ticket, kanban_list_tickets, kanban_get_ticket,
  kanban_update_ticket, kanban_delete_ticket
- Wire McpToolRegistry + KanbanToolProvider in CLI
- 26 tests passing, dart analyze clean

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 15:29:46 -04:00
a1c36f7136 Implement kanban subcommands (create, get, update, delete)
- Add ProjectContext and DewConfig models to core
- Add Ticket model with YAML frontmatter serialisation
- Add TicketStore with auto-incrementing 4-digit IDs
- Implement create/get/update/delete subcommands under KanbanCommand
- Expand tests: ProjectContext, Ticket roundtrip, TicketStore CRUD (19 total)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 14:47:25 -04:00
0723c7d996 Scaffold docs, packages, and command registration
- Flesh out docs/index.md, docs/config.md, docs/features/mcp.md,
  docs/features/kanban.md with full content and aligned tables
- Fix .markdownlint-cli2.mjs config (rules were outside 'config' key)
- Add packages/mcp as a standalone MCP server package
- Update all pubspec descriptions
- Implement DewCommand + CommandRegistry in core
- Implement KanbanCommand and McpCommand stubs with registerCommands()
- Wire CLI entry point using CommandRunner + CommandRegistry
- Add 'melos run dew' script for running the CLI from workspace root
- Update tests across core, kanban, mcp packages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 14:29:09 -04:00
b3201fde7b chore: Initial Commit 2026-04-23 13:09:11 -04:00