choda-deck 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +296 -230
  2. package/dist/cli.cjs +36494 -28217
  3. package/dist/mcp-rules.md +170 -94
  4. package/dist/mcp-server.cjs +35105 -25993
  5. package/package.json +81 -78
package/dist/mcp-rules.md CHANGED
@@ -1,94 +1,170 @@
1
- # MCP Rules
2
-
3
- Behavioral contract for MCP tools (session + conversation). Edit this file to update compliance rules — no MCP restart needed. Each `## On <tool_name>` section is loaded by the matching tool handler and injected into its response.
4
-
5
- ## On session_start
6
-
7
- `session_start` now requires a `taskId` — the task is bound to the session at creation and auto-set to IN-PROGRESS.
8
-
9
- Before calling `session_start`:
10
-
11
- 1. Call `task_list` (or `roadmap`) to show the user available tasks, grouped by priority (high → medium → low).
12
- 2. Wait for the user to pick a task. Do not guess.
13
- 3. Call `session_start({ projectId, taskId, workspaceId?, cwd? })`. Always pass `cwd` (current shell directory) so the MCP can auto-detect `workspaceId` for projects with registered workspaces — the MCP server's own cwd is fixed and cannot be inferred.
14
- 4. Echo the `lastSession` block to the user verbatim — resume point, decisions, loose ends, tasks updated, commits. Do not summarize.
15
- 5. Create a feature branch for the task:
16
- - Branch name: `feat/<task-id>-<short-slug>` (e.g. `feat/task-564-session-conv-ui`)
17
- - Required: `git checkout -b feat/<task-id>-<short-slug>`
18
- - Optional (if user wants parallel worktree): detect repo root via `git rev-parse --show-toplevel`, then `git worktree add <repo-root>.worktrees/<slug> -b feat/<task-id>-<short-slug>`
19
- - Ask the user whether they want a worktree or just a branch before proceeding.
20
-
21
- Workspace resolution order (when project has ≥1 workspace registered):
22
- - explicit `workspaceId` wins
23
- - else `cwd` is matched against registered workspace cwds (longest prefix wins for nested repos)
24
- - else MCP throws — pick a workspace explicitly or call `workspace_add`
25
-
26
- If the project has no workspaces registered, `workspaceId` may be `null` (backward-compatible).
27
-
28
- Blocking conditions (MCP returns an error):
29
- - Task not found
30
- - Task already `DONE` — reopen it with `task_update` first
31
- - Task already bound to another active session — end that session first
32
- - Project has workspaces but neither `workspaceId` nor a matching `cwd` was provided
33
-
34
- ## On session_checkpoint
35
-
36
- `session_checkpoint` snapshots progress on an active session **without ending it**. Overwrite-in-place — each call replaces the previous checkpoint.
37
-
38
- When to checkpoint:
39
-
40
- - Before risky operations (rebase, force-push, schema migration, large refactor)
41
- - Before context window compaction (when conversation grows long)
42
- - Every ~30 minutes of active work, or after a meaningful sub-step
43
- - When pausing work mid-task (lunch, meeting) — so a future `session_resume` recovers state cleanly
44
-
45
- Required field:
46
-
47
- - **resumePoint** — one sentence describing exactly where you stopped and what to pick up next
48
-
49
- Recommended fields (include whichever apply):
50
-
51
- - **lastConversationId** — most recent conversation touched (resume context)
52
- - **dirtyFiles** — files edited but not yet committed (so resume knows what's in flight)
53
- - **lastCommit** — last commit SHA written this session (resume git position)
54
- - **notes** — free-form context that matters for resume (decisions made, dead ends ruled out)
55
-
56
- Do not call `session_checkpoint` as a substitute for `session_end`. Checkpoint = pause; end = finalize + handoff.
57
-
58
- ## On session_resume
59
-
60
- `session_resume` returns the session row, last checkpoint, linked conversations, and active context sources. Use after crash, restart, or context compaction — not as a way to spawn a new session for the same task.
61
-
62
- After calling `session_resume`:
63
-
64
- 1. **Echo the checkpoint summary verbatim** — `resumePoint`, `notes`, `dirtyFiles`, `lastCommit`, `lastConversationId`. Do not summarize. Butter needs the same view the prior session had.
65
- 2. **Confirm task binding** — name the `taskId` and current status. If the task is no longer IN-PROGRESS, surface the discrepancy before proceeding.
66
- 3. **Resume from `resumePoint`** — pick up the exact next step. Do not re-plan from scratch.
67
- 4. **Do not call `session_start`** — resume reactivates the existing session; starting a new one orphans the checkpoint and creates duplicate state.
68
-
69
- If no checkpoint exists (resumed session was never checkpointed), say so explicitly and ask Butter where to pick up before continuing.
70
-
71
- ## On session_end
72
-
73
- When preparing the session_end payload, always include:
74
-
75
- - **resumePoint** (required) — one sentence describing where you stopped and what the next session should pick up.
76
- - **tasksUpdated** (required if session had a taskId) — list of task ids and their new status.
77
- - **decisions** — architectural or implementation decisions made this session. Explicit > implicit.
78
- - **looseEnds** — anything unfinished, deferred, or noted for later. Include pre-existing issues you touched but did not resolve.
79
- - **commits** — commit SHAs + short message if commits were made.
80
-
81
- Never end a session with only resumePoint. If the session was trivial (read-only), explicitly note "no changes" in looseEnds.
82
-
83
- `looseEnds` are auto-converted to inbox entries (status=raw) under the session's project — one entry per item, tagged with the source session/task ID. Butter reviews the inbox in `/daily` and decides which deserve to become TODO tasks via `inbox_convert`. Keep each loose end short (1 line, concrete) — they are capture, not specification.
84
-
85
- ## On conversation_read
86
-
87
- Discussion etiquette (advisory, injected only when `conv.status` is `open` or `discussing`; skipped for `decided`/`closed`/`stale`):
88
-
89
- - Read the full thread first. Don't restate prior points unless correcting them.
90
- - State your position in 1 line, then 2-3 concrete reasons (not generic pros/cons).
91
- - Address the latest unresolved point. If you disagree, challenge it directlydon't add parallel views.
92
- - When you see convergence, propose a decision and name any remaining risk briefly.
93
- - Cover business + implementation + test impact when relevant; skip non-applicable angles.
94
- - Keep it proportional. Small threads stay short.
1
+ # MCP Rules
2
+
3
+ Behavioral contract for MCP tools (session + conversation). Edit this file to update compliance rules — no MCP restart needed. Each `## On <tool_name>` section is loaded by the matching tool handler and injected into its response.
4
+
5
+ ## On session_start
6
+
7
+ `session_start` now requires a `taskId` — the task is bound to the session at creation and auto-set to IN-PROGRESS.
8
+
9
+ Before calling `session_start`:
10
+
11
+ 1. Call `task_list` (or `roadmap`) to show the user available tasks, grouped by priority (high → medium → low).
12
+ 2. Wait for the user to pick a task. Do not guess.
13
+ 3. Call `session_start({ projectId, taskId, workspaceId?, cwd? })`. Always pass `cwd` (current shell directory) so the MCP can auto-detect `workspaceId` for projects with registered workspaces — the MCP server's own cwd is fixed and cannot be inferred.
14
+ 4. Echo the `lastSession` block to the user verbatim — resume point, decisions, loose ends, tasks updated, commits. Do not summarize.
15
+ 5. Create a feature branch for the task:
16
+ - Branch name: `feat/<task-id>-<short-slug>` (e.g. `feat/task-564-session-conv-ui`)
17
+ - Required: `git checkout -b feat/<task-id>-<short-slug>`
18
+ - Optional (if user wants parallel worktree): detect repo root via `git rev-parse --show-toplevel`, then `git worktree add <repo-root>.worktrees/<slug> -b feat/<task-id>-<short-slug>`
19
+ - Ask the user whether they want a worktree or just a branch before proceeding.
20
+
21
+ Workspace resolution order (when project has ≥1 workspace registered):
22
+ - explicit `workspaceId` wins
23
+ - else `cwd` is matched against registered workspace cwds (longest prefix wins for nested repos)
24
+ - else MCP throws — pick a workspace explicitly or call `workspace_add`
25
+
26
+ If the project has no workspaces registered, `workspaceId` may be `null` (backward-compatible).
27
+
28
+ Blocking conditions (MCP returns an error):
29
+ - Task not found
30
+ - Task already `DONE` — reopen it with `task_update` first
31
+ - Task already bound to another active session — end that session first
32
+ - Project has workspaces but neither `workspaceId` nor a matching `cwd` was provided
33
+
34
+ ## On session_checkpoint
35
+
36
+ `session_checkpoint` snapshots progress on an active session **without ending it**. Overwrite-in-place — each call replaces the previous checkpoint.
37
+
38
+ When to checkpoint:
39
+
40
+ - Before risky operations (rebase, force-push, schema migration, large refactor)
41
+ - Before context window compaction (when conversation grows long)
42
+ - Every ~30 minutes of active work, or after a meaningful sub-step
43
+ - When pausing work mid-task (lunch, meeting) — so a future `session_resume` recovers state cleanly
44
+
45
+ Required field:
46
+
47
+ - **resumePoint** — one sentence describing exactly where you stopped and what to pick up next
48
+
49
+ Recommended fields (include whichever apply):
50
+
51
+ - **lastConversationId** — most recent conversation touched (resume context)
52
+ - **dirtyFiles** — files edited but not yet committed (so resume knows what's in flight)
53
+ - **lastCommit** — last commit SHA written this session (resume git position)
54
+ - **notes** — free-form context that matters for resume (decisions made, dead ends ruled out)
55
+
56
+ Do not call `session_checkpoint` as a substitute for `session_end`. Checkpoint = pause; end = finalize + handoff.
57
+
58
+ ## On session_resume
59
+
60
+ `session_resume` returns the session row, last checkpoint, linked conversations, and active context sources. Use after crash, restart, or context compaction — not as a way to spawn a new session for the same task.
61
+
62
+ After calling `session_resume`:
63
+
64
+ 1. **Echo the checkpoint summary verbatim** — `resumePoint`, `notes`, `dirtyFiles`, `lastCommit`, `lastConversationId`. Do not summarize. Butter needs the same view the prior session had.
65
+ 2. **Confirm task binding** — name the `taskId` and current status. If the task is no longer IN-PROGRESS, surface the discrepancy before proceeding.
66
+ 3. **Resume from `resumePoint`** — pick up the exact next step. Do not re-plan from scratch.
67
+ 4. **Do not call `session_start`** — resume reactivates the existing session; starting a new one orphans the checkpoint and creates duplicate state.
68
+
69
+ If no checkpoint exists (resumed session was never checkpointed), say so explicitly and ask Butter where to pick up before continuing.
70
+
71
+ ## On session_end
72
+
73
+ When preparing the session_end payload, always include:
74
+
75
+ - **resumePoint** (required) — one sentence describing where you stopped and what the next session should pick up.
76
+ - **tasksUpdated** (required if session had a taskId) — list of task ids and their new status.
77
+ - **decisions** — architectural or implementation decisions made this session. Explicit > implicit.
78
+ - **looseEnds** — genuine ideas that need future research. NOT a catch-all dump. See classification rule below.
79
+ - **commits** — commit SHAs + short message if commits were made.
80
+
81
+ Never end a session with only resumePoint. If the session was trivial (read-only), explicitly note "no changes" in resumePoint or notes.
82
+
83
+ ### Classify each loose end BEFORE writing it
84
+
85
+ Every candidate loose end falls into exactly one of 3 buckets. Pick the bucket first, then route accordingly:
86
+
87
+ 1. **Action item** (has clear owner + acceptance criteria) → call `task_create` directly with status=TODO or READY. Do NOT put it in `looseEnds`. Examples: "PR #5 awaiting merge — on merge delete branch", "companion repo 2 commits ahead — needs push".
88
+ 2. **Dirty-state observation** (untracked file, stale branch, cosmetic shell handle, lingering process) → put it in `notes` field or the commit message. Do NOT put it in `looseEnds`. Examples: "stale worktree at .worktrees/foo", "Windows file handle on dist/", "untracked spike notes in /tmp".
89
+ 3. **Genuine idea needing research** (open question, design uncertainty, "should we…?") → `looseEnds` (this is the legitimate use). Each entry: 1 line, concrete, no acceptance criteria yet. Example: "investigate whether prewarm cache can survive worktree switch".
90
+
91
+ `looseEnds` are auto-converted to inbox entries (status=raw) under the session's project — one entry per item, tagged with the source session/task ID. Butter reviews the inbox in `/daily` and decides which deserve `inbox_convert` → task. If you find yourself dumping action items or observations into `looseEnds`, you skipped step 1 go back and classify.
92
+
93
+ ### Structured summary payload (ADR-028)
94
+
95
+ `session_end` accepts an optional typed `summary` field. When provided, the server writes one `session_events` row (`event_type='observation'`, `payload.kind='session_summary'`) atomic with the session close. Use it on real implementation sessions — TASK-846 auto-recall and the Dashboard Sessions tab both read this shape.
96
+
97
+ **Required (FE base):**
98
+
99
+ | Field | Type | Notes |
100
+ |---|---|---|
101
+ | `summary` | `string` | One paragraph: what shipped, what stalled, what's next |
102
+ | `tasksDone` | `string[]` | Task IDs marked DONE in this session |
103
+ | `tasksCreated` | `string[]` | Task IDs created in this session |
104
+ | `tasksCancelled` | `string[]` | Task IDs cancelled in this session |
105
+ | `commits` | `string[]` | Format: `"<short-hash> <task-id> <subject>"` |
106
+ | `conversations` | `string[]` | Conversation IDs touched / decided |
107
+ | `openItems` | `string[]` | Carry-forward items — distinct from `looseEnds` (action items already filed as tasks) |
108
+
109
+ **AI-optional — server auto-fills from channels 1+2 (TASK-913):**
110
+
111
+ | Field | Type | Notes |
112
+ |---|---|---|
113
+ | `filesChanged` | `string[]?` | Format: `"<path> (<what changed>)"`. Omit → server appends `"<path> (+N, -M)"` per unique path from `kind='file_modified'` events (channel 1). AI entries kept verbatim; only unseen paths appended. |
114
+ | `acCoverage` | `Record<taskId, string>?` | e.g. `"TASK-X": "5/5 verified (...). 0 deferred."`. Omit → server emits `"N/M verified (<evidence>)"` derived from `kind='ac_check'` events (channel 2), where `M = findAcItems(task.body).length` at session-end. AI entry kept verbatim + ` + K auto-detected` suffix added when events exist. |
115
+
116
+ **Merge rule:** AI input wins — your judgment narrative trumps mechanical aggregation. The aggregator only fills gaps; it never overwrites a value you provided.
117
+
118
+ **Optional (BE extension):**
119
+
120
+ `tasksShipped: Array<{id, title, commits, files, tests, confidence}>`, `tasksNotDone: Array<{id, reason}>`, `testCoverageSummary: string`, `outstandingRisks: string[]`, `branchState: string`.
121
+
122
+ **Canonical example** (mirrors EVT-1779256867162-5 — SESSION-1779246110272-1, TASK-800 PIM FE):
123
+
124
+ ```json
125
+ {
126
+ "summary": "TASK-800 shipped: PIM FE list-screen virtualization landed; PR #482 squash-merged. Two follow-ups carried forward (filter chip a11y, prefetch on hover).",
127
+ "tasksDone": ["TASK-800"],
128
+ "tasksCreated": ["TASK-805", "TASK-806"],
129
+ "tasksCancelled": [],
130
+ "commits": ["a1b2c3d TASK-800 feat(pim-fe): virtualized list with row prefetch"],
131
+ "filesChanged": [
132
+ "src/pim/list/VirtualList.tsx (new — react-window wrapper)",
133
+ "src/pim/list/list-page.tsx (swap table → VirtualList)"
134
+ ],
135
+ "acCoverage": {
136
+ "TASK-800": "6/6 verified (lint+vitest+build+manual smoke @ 10k rows). 0 deferred."
137
+ },
138
+ "conversations": ["CONV-1779246111-1"],
139
+ "openItems": [
140
+ "Filter chip a11y — screenreader announces stale label after clear (TASK-805)",
141
+ "Row prefetch on hover — current eager prefetch wastes ~30% requests (TASK-806)"
142
+ ]
143
+ }
144
+ ```
145
+
146
+ **Narrative-only example** (relies on the channels 1+2 aggregator to fill `filesChanged` + `acCoverage` — minimum viable payload for an agent that has been calling `ac_check` and has the file-edit hook installed):
147
+
148
+ ```json
149
+ {
150
+ "summary": "TASK-913 shipped: session-summary aggregator wires channels 1+2 into session_end. Narrative-only payloads now produce fully populated stored summaries.",
151
+ "tasksDone": ["TASK-913"],
152
+ "tasksCreated": [],
153
+ "tasksCancelled": [],
154
+ "commits": ["d4d4d4d TASK-913 feat: session-summary aggregator"],
155
+ "conversations": [],
156
+ "openItems": []
157
+ }
158
+ ```
159
+
160
+ Stored payload `filesChanged` ends up `["src/core/domain/lifecycle/session-lifecycle-service.ts (+62, -3)", …]` and `acCoverage` `{"TASK-913": "5/5 verified (lint exits 0; vitest 35/35 …)"}` — both server-derived.
161
+
162
+ **Validation:** invalid payload → 400 from MCP, session is NOT closed. Omitting `summary` entirely is fully backward compatible (no observation row created, no error). Schema lives at `src/adapters/mcp/mcp-tools/session-tools.ts` (`sessionSummarySchema`).
163
+
164
+ ## On conversation_read
165
+
166
+ - Read first. If your name is missing from `readBy` on any message, process those messages and reflect them in your reply.
167
+ - State a position. Each turn must be one of: `signoff`, `propose_rewrite` (and rewrite the summary), `abstain_blocked` (name the blocker), `needs_clarification` (ask the question).
168
+ - One live position. Your latest message supersedes your earlier ones — don't argue across messages; rewrite the summary.
169
+ - Stay short. 1500-char cap is enforced. Long content goes to `decisionSummary`, a knowledge entry, or a linked artifact.
170
+ - Decisions own actions. If follow-up work exists, add it to `actions[]` with `assignee` and `linkedTaskId`.