@jeffober/thatch 0.2.5 → 0.2.6

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 (51) hide show
  1. package/README.md +48 -25
  2. package/artifacts/skills/common.md +1 -0
  3. package/artifacts/skills/thatch-code-archaeology.md +9 -0
  4. package/artifacts/skills/thatch-code-review.md +16 -8
  5. package/artifacts/skills/thatch-plan-refinement.md +33 -4
  6. package/artifacts/skills/thatch-refine-personal.md +5 -0
  7. package/artifacts/skills/thatch-refine.md +7 -1
  8. package/artifacts/skills/thatch-review-context.md +55 -2
  9. package/artifacts/skills/thatch-review-followup.md +4 -4
  10. package/artifacts/skills/thatch-review-response.md +1 -1
  11. package/artifacts/skills/thatch-review-synthesizer.md +21 -1
  12. package/bin/thatch +21 -12
  13. package/docs/dev/README.md +11 -8
  14. package/docs/dev/features/README.md +1 -1
  15. package/docs/dev/features/behavior-engine.md +10 -0
  16. package/docs/dev/features/cli.md +2 -1
  17. package/docs/dev/features/commands.md +40 -9
  18. package/docs/dev/features/cross-session-chat.md +85 -21
  19. package/docs/dev/features/database.md +4 -4
  20. package/docs/dev/features/nudge-pipeline.md +14 -0
  21. package/docs/dev/features/opencode-plugin.md +41 -15
  22. package/docs/dev/features/prediction-engine.md +27 -1
  23. package/docs/dev/features/qa-system.md +19 -3
  24. package/docs/dev/features/session-lifecycle.md +6 -6
  25. package/docs/dev/features/session-tabs.md +11 -5
  26. package/docs/dev/gotchas.md +21 -0
  27. package/docs/dev/mcp-parity.md +3 -3
  28. package/docs/dev/skills.md +11 -8
  29. package/docs/user/README.md +19 -12
  30. package/docs/user/behavior-engine.md +7 -1
  31. package/docs/user/cli.md +2 -1
  32. package/docs/user/code-review.md +43 -2
  33. package/docs/user/commands.md +27 -7
  34. package/docs/user/cross-session-chat.md +47 -20
  35. package/docs/user/prediction-engine.md +12 -1
  36. package/docs/user/skills.md +8 -6
  37. package/package.json +1 -1
  38. package/src/capabilities.ts +20 -6
  39. package/src/chat.ts +39 -17
  40. package/src/commands.ts +109 -18
  41. package/src/db.ts +23 -2
  42. package/src/mcp.ts +6 -2
  43. package/src/opencode/tui-plugin.ts +29 -9
  44. package/src/opencode/v2.ts +247 -66
  45. package/src/prompts.ts +54 -6
  46. package/src/runtime.ts +122 -17
  47. package/src/scoring-engine.ts +40 -9
  48. package/src/session-tab-shared.ts +55 -4
  49. package/src/skills.ts +5 -9
  50. package/src/tab-state.ts +125 -0
  51. package/src/tool-defs.ts +42 -21
@@ -58,7 +58,7 @@ the shared runtime needs. It is the lifecycle-level sibling of `CoreContext`
58
58
  | Capability | v1 source | v2 source | v2 strategy |
59
59
  |---|---|---|---|
60
60
  | bus events | `event` hook | `event.subscribe` (raw SSE) | client-side location filter; location-less events resolve via `session.get` (`{sessionID}` input) and drop unresolvable (mirrors v1's server-side filter); events for plugin-created extraction children pass by ID (they live in the project directory, which differs from the instance directory on below-root launches) |
61
- | incoming message | `chat.message` hook | `session.hook("prompt")` | the hook awaits the runtime; nudge injections append to the OUTBOUND request's last user message in the generate hook (v2 wire `Message` carries parts in `content`, not `parts`) |
61
+ | incoming message | `chat.message` hook | `session.hook("prompt")` | the hook awaits the runtime; nudge injections append to the OUTBOUND request's user message whose text matches the entry's stored prompt, in the context hook (v2 wire `Message` carries parts in `content`, not `parts`). Entries form a per-session set (cap 3, oldest evicted) so a prompt queued mid-turn keeps its own entry instead of evicting the running turn's; a prompt whose nudge computation comes up empty clears the session's whole set, so the running turn's remaining rounds lose their block in that one case (smaller than the staleness it prevents: an entry left behind would match its own prompt text in later requests' session history and replay a stale block there) |
62
62
  | system prompt | `experimental.chat.system.transform` | `session.hook("context")` | mutates the request's system array; the runtime's plain strings convert to `{type: "text", text}` SystemPart objects at the boundary |
63
63
  | compaction | `experimental.session.compacting` + `.autocontinue` + `session.compacted` | `session.hook("compaction")` | flag lands; context-injection surface unverified |
64
64
  | wrap-up commands | `command.execute.before` + command files | `command.transform` + `CommandEditor.add` | registered in code: `execute` arms the greenlight check (`onCommandExecuteBefore`) and delivers the same prompt body the v1 file carries, substituting the invocation's prompt text for `$ARGUMENTS` (v2's `session.prompt` does no template expansion); the runtime installs only ACTION command files on v2 and REMOVES stale wrap-up files from earlier v1 runs (a file and a registered command with the same name would collide) |
@@ -66,14 +66,14 @@ the shared runtime needs. It is the lifecycle-level sibling of `CoreContext`
66
66
  | tool buffering | `tool.execute.after` hook | `tool.hook("execute.after")` | wired: feeds the same extraction buffer; `Tool.Result.content` is flattened (string passes through, content-part arrays contribute their text parts); the title is derived from the tool name and args (`deriveTitle`), since v2's `Tool.Result.output` is a typed output value, not a title |
67
67
  | noReply deliveries | `promptAsync` with `noReply` | none | gated off via `HostCapabilities.noReplyDelivery` -- chat echoes and the session-start reminder are skipped on v2 (delivering them would start real model turns: a feedback loop) |
68
68
  | synthetic wake deliveries | `promptAsync` with `synthetic` parts | `session.synthetic` endpoint | watcher + chat wake nudges route to v2's synthetic endpoint (TUI-hidden), matching v1 |
69
- | child sessions | `client.session.create/promptAsync/prompt/delete` | `session.create/prompt` | create+prompt supported (a missing id throws into the extraction fallback); delete degrades (extraction children are not cleaned up on v2 -- the session picker accumulates one `thatch-extraction` entry per extraction, and `-c` "continue last session" logic that picks the newest top-level session will land in an extraction child after any session that triggered extraction) |
69
+ | child sessions | `client.session.create/promptAsync/prompt/delete` | `session.create/prompt` | create+prompt supported (a missing id throws into the extraction fallback); delete rides `session.remove` (upstream #52387, in v2.0.22), runtime-guarded like compact -- an older host keeps the documented gap (the session picker accumulates one `thatch-extraction` entry per extraction, and `-c` "continue last session" logic that picks the newest top-level session will land in an extraction child after any session that triggered extraction) |
70
70
  | session status | `client.session.status` | none | returns `{}`; the wake gate treats unknown as idle and the event-fed status map does the gating |
71
71
  | session messages | `client.session.messages` | `session.context` | mapped into the v1 `{info: {role}, parts}` shape, so the wrap-up greenlight check works (the promise domain has no `message` accessor) |
72
72
  | session list | `client.session.list` | none | degrade (`-c` resume listing loses its data source) |
73
- | compaction trigger | `client.tui.executeCommand("session_compact")` | none | degrade (no `session.compact` on the promise domain's Pick, and the built-in `/compact` is a TUI palette action calling the server endpoint directly - the server command registry only knows config/plugin-registered names; the wrap-up checklist and flush still run, the automatic compaction is skipped) |
74
- | toasts | `client.tui.showToast` | none reachable | degrade (the `tui.toast.show` event has no producer surface from the promise context; the `./tui` companion entrypoint now EXISTS for the session-tab feature - extending it to publish toasts is the remaining lift) |
75
- | TUI app exit | `client.tui.publish` | none | degrade (the wrap-up exit's checklist and flush run; the exit does not - upstream [anomalyco/opencode#50984](https://github.com/anomalyco/opencode/issues/50984). On v2 the right target closes THIS session's tab (tab-scoped), not `app.exit`: the daemon hosts every tab; the `./tui` entrypoint's `ui.tabs.close` is the known lift candidate and the session-tab rpc bridge is the transport) |
76
- | session tabs | n/a | rpc event bridge | NEW: `thatch_session_tab` (v2Only) spawns a detached subordinate session (create -> move -> chat-register -> emit `rpc.thatch-tabs.tab-opened` -> prompt); the `./tui` CLI plugin consumes the event and calls `ui.tabs.open` (open-without-focus, idempotent). The deferred `thatch_session_tab_close` will emit `rpc.thatch-tabs.tab-closed {sessionID, chatName?}` - the shape is reserved and consumed by the generalized-session-heartbeat plan |
73
+ | compaction trigger | `client.tui.executeCommand("session_compact")` | `session.compact({sessionID})` | wired behind a runtime guard: upstream [#52385](https://github.com/anomalyco/opencode/pull/52385) exposed `session.compact` on the promise domain (in the installed v2.0.25), newer than the pinned dev types - an older host degrades to a logged no-op (the wrap-up checklist and flush still run, the automatic compaction is skipped) |
74
+ | toasts | `client.tui.showToast` | toast rpc event | wired: every runtime toast call site (alerts, extraction metrics, chat registration, blocked wrap-ups, watcher rearm notices) emits the bridge's `toast` event, which the `./tui` plugin shows via `ui.toast.show`; the pump never translates it (TUI-only by nature). Best-effort by contract: a TUI-less environment or an rpc-less SDK floor shows nothing, silently |
75
+ | wrap-up exit | `client.tui.publish` | exit-tab-closed rpc event | the wrap-up exit records the session's watcher deaths synchronously (the runtime, before the chat unregister - `sessionDied` places the death row by the chat row, so the order is load-bearing) and then emits `rpc.thatch-tabs.exit-tab-closed`, a TUI-ONLY close of the session's OWN tab (the `./tui` plugin's `ui.tabs.close`): the tab-scoped semantic upstream [anomalyco/opencode#50984](https://github.com/anomalyco/opencode/issues/50984) asks for, since the daemon hosts every tab and `app.exit` would take down unrelated sessions. The pump deliberately never translates this event (a second death pass would erase the durable death row the first one wrote; the session_tab_close tool's `tab-closed` is the only death-translating event). Degrades to a logged no-op without the rpc surface; a TUI with no tab for the session (headless run, tabs off) never acts - the event is ephemeral |
76
+ | session tabs | n/a | rpc event bridge | `thatch_session_tab` (v2Only) spawns a detached subordinate session (create -> move -> chat-register -> emit `rpc.thatch-tabs.tab-opened` -> prompt); the `./tui` CLI plugin consumes the event and calls `ui.tabs.open` (open-without-focus, idempotent). `thatch_session_tab_close` emits `rpc.thatch-tabs.tab-closed {sessionID, chatName}` - the pump translates it into the runtime's confirmed-death path. The registration's third event, `exit-tab-closed` (the wrap-up exit's), is TUI-only - see the wrap-up exit row |
77
77
  | tab close | n/a | no session.deleted on v2 tab close | bounded 2026-10-07: watcher death detection cancels a closed tab's watchers once its pending events age past `THATCH_WATCH_DEATH_MINUTES` (default 120) while deliveries throw 404-class errors, and announces the death to live same-project sessions; the roster row itself still lingers until the reaper. NOTE: an earlier version of this row claimed deliveries "fail their idle gate" - wrong: the v2 gate opens (the status stub reads as idle) and the PROMPT fails; same leak, different link |
78
78
  | wake gate after reload | `session.status` event map | map rehydrates empty | known gap: v2's fetchStatuses stub is structural (the promise context has no session.status), so in the reload window - until the session's next status event - the proactive-prompt gate is fully open |
79
79
  | dispose | `dispose` hook | cleanup returned from `setup` | must be idempotent: v2 auto-reloads plugins on file change, and a non-idempotent cleanup doubles pollers/pumps/nudges. Volatile runtime state (extraction buffer + accepted entries, child bookkeeping, wrap-up arms, watcher definitions) is journaled to the `runtime_state` table - instance-scoped by the writing location's directory - and rehydrated on the next `setup()`: rows from the same process AND directory (a reload) all restore; child rows carry a kind marker (extraction vs task-dispatched sub-agent, defaulting to extraction for pre-marker rows) so only extraction children restore the `extracting`/`extractionChildren` sets; rows from a dead process are pruned unless the session is the `-c`/`-s` startup resume, which inherits recovery-safe state only (buffer requeued, no live child bookkeeping - the child died with the old process). Exception: a dead process's watcher definitions are kept as DORMANT rows (no polling, no delivery) instead of pruned - resuming the session re-arms them on its first message (a `chat.message` scan: own rows re-arm, same-directory rows of still-dead sessions earn the live session a one-line death notice gated on the owning pid being dead, rows past the watcher TTL plus a 24h grace age out). Restored sessions on v1 get a synthetic noReply re-attach notice (v2 has no turn-free delivery and skips it). The chat poller re-hosts the instance's journaled hosted set on reload, so pending chat mail wakes sessions the reload left asleep and heartbeats keep the reaper from reaping live registrations. The embedding model is refcounted per db path, so N location instances share one resident model |
@@ -90,20 +90,46 @@ suite runs green against a v2 serve.
90
90
  Still unverified against the live binary (static analysis only):
91
91
 
92
92
  - Whether `session.hook("prompt")` fires for `session.synthetic` inbox
93
- items. If it does not, `pendingInjections` from the prior real turn gets
94
- appended to the synthetic turn's outbound request.
93
+ items. If it does not, `pendingInjections` from the prior real turn would
94
+ be appended to the synthetic turn's outbound request. Structurally
95
+ bounded 2026-10-12: each `pendingInjections` entry is bound to the prompt
96
+ text it was computed from, and the context hook injects it only into a
97
+ request user message whose joined text equals that prompt - a replay onto
98
+ any other message (synthetic turn, missed prompt hook, failed
99
+ `onChatMessage`) is skipped. Text equality is the tightest binding
100
+ available, not an airtight one: an identical prompt re-submitted after a
101
+ missed prompt hook still matches and replays; the next prompt-hook run
102
+ that fires is the recovery. The prompt-time block still re-fires on every
103
+ round of its OWN turn (v1's persistent-synthetic-part semantics), and the
104
+ block carries a snapshot footer stating the labels are a submission-time
105
+ snapshot and pointing at the live re-check tool.
95
106
  - Whether `SessionGenerate.messages` are fresh objects per model call within
96
- a turn. If not, the generate hook pushes duplicate nudges on every tool
97
- round trip.
107
+ a turn. The context hook appends injections to the matched message's
108
+ content, which changes the text the binding check compares. With fresh
109
+ objects per call (what the tests assume), the prompt-time block re-fires
110
+ on every round of its own turn. With reused objects, round 1's
111
+ injections would change the matched message's text, so the binding check
112
+ would skip rounds 2 and on - within-turn replay would break after round
113
+ 1 instead of duplicating.
114
+ - Entries outliving their session: `pendingInjections` is keyed by session
115
+ id and never swept on session end. Bounded - a per-session set capped at
116
+ 3 entries, and the map itself dies with the adapter activation - but a
117
+ session's entries do persist after the session is gone, pre-existing.
118
+ - Cross-directory hook dispatch scope: the event pump filters events by
119
+ directory, but the session hooks (`prompt`, `context`, `compaction`)
120
+ have no such filter - whether a hook can fire for another directory's
121
+ session is unverified, pre-existing.
98
122
  - An npm-installed copy (not the dev checkout, which has devDependencies
99
123
  present) loading under v2 - the isolation rule's real-world proof.
100
124
 
101
- The app-exit gap (no server-side publish surface for `tui.command.execute`)
102
- is upstream:
103
- [anomalyco/opencode#50984](https://github.com/anomalyco/opencode/issues/50984).
125
+ The wrap-up exit closes the session's own tab over the session-tab rpc
126
+ bridge - the tab-scoped close upstream
127
+ [anomalyco/opencode#50984](https://github.com/anomalyco/opencode/issues/50984)
128
+ asks for. Known degrade: a TUI with no tab for the session (headless run,
129
+ tabs off) never acts - the event is ephemeral, nobody is listening.
104
130
  Install opencode-v2 (brew formula `anomalyco/tap/opencode-v2`; conflicts
105
- with the v1 formula's binary name, so it cannot coexist) and re-run the QA
106
- live suite against it.
131
+ with the v1 formula's binary name, so it cannot coexist) and re-run the
132
+ QA live suite against it.
107
133
 
108
134
  ## User setup compatibility
109
135
 
@@ -141,6 +141,8 @@ later — land below that floor and accumulate. The dedup pass finds them:
141
141
  `[thatch] User decision model` header.
142
142
  - 0-evidence predictions use "you may prefer"; predictions with evidence use
143
143
  "you tend to".
144
+ - The block ends with a snapshot footer (see the block example below): the
145
+ labels are a submission-time snapshot, not a live view.
144
146
  - Independent try/catch — a prediction failure does not block recall or
145
147
  behavior.
146
148
  - Toast: `[thatch] N predictions surfaced`.
@@ -159,11 +161,27 @@ later — land below that floor and accumulate. The dedup pass finds them:
159
161
  The auto-fire block looks like:
160
162
 
161
163
  ```text
162
- ## User decision model
164
+ [thatch] User decision model
163
165
  - [0.72 conf, 4 tests] When {matcher}: you tend to {prediction}
164
166
  - [0.61 conf, 2 tests] When {matcher2}: you may prefer {prediction2}
167
+ Snapshot: these labels reflect the prediction store as of this message's
168
+ submission. Predictions updated after submission (including during this
169
+ turn) do not refresh it; re-check with thatch_prediction_query before
170
+ relying on them.
165
171
  ```
166
172
 
173
+ The footer's tool name follows the host: opencode sees
174
+ `thatch_prediction_query`; MCP hosts (Claude Code, Cursor) see
175
+ `mcp__thatch__prediction_query`. The renderer resolves the spelling
176
+ through the caller's `ToolNamer`.
177
+
178
+ The snapshot footer exists because the block is computed once when the
179
+ message is submitted and stays attached to it for the whole turn (v1:
180
+ synthetic part on the stored message; v2: `pendingInjections` re-injected
181
+ into every outbound request of the turn). Predictions the conversation
182
+ mutates mid-turn do not refresh the block; the footer tells the consumer
183
+ the labels are a submission-time snapshot and points at the live tool.
184
+
167
185
  Prompt instructions: follow strong predictions silently, surface weak or
168
186
  competing predictions to the user, update the model when the user responds.
169
187
 
@@ -280,6 +298,14 @@ prediction_dedup_pairs(
280
298
  )
281
299
  ```
282
300
 
301
+ Timestamp semantics: a matcher's `updated_at` is the last change to the
302
+ matcher row OR its edge set — `createEdge` bumps it when a new edge lands,
303
+ and deleting an item bumps the matchers whose edges cascade away. The
304
+ matcher's own text may legitimately be older than its links (a corrected
305
+ prediction re-links the existing matcher without touching its text).
306
+ An item's `updated_at` is written at creation and then moved by the last
307
+ confidence change.
308
+
283
309
  ## Key invariants
284
310
 
285
311
  1. **The firewall principle.** The model that uses predictions cannot grade
@@ -70,9 +70,11 @@ execution directly via bun assertions. These are fast and run in parallel.
70
70
  Spawn real opencode sessions. Test the full pipeline with a real model. These
71
71
  cost model tokens and have a 20-minute per-use-case timeout. Live use cases
72
72
  check for `opencode` on PATH at runtime: if it is present they run; if not,
73
- they skip with `[MANUAL]`. Three (UC-006, UC-010, UC-013) are
73
+ they skip with `[MANUAL]`. Six (UC-006, UC-010, UC-013, UC-083, UC-084,
74
+ and UC-085) are
74
75
  `manualOnly: true` for infrastructure reasons (Cursor hook lifecycle, nested
75
- opencode session timeout, compaction trigger). Run them locally with
76
+ opencode session timeout, compaction trigger, sub-agent support, skill
77
+ loading, and a PR with prior review comments). Run them locally with
76
78
  `mise run qa-live`.
77
79
 
78
80
  | UC | Name | What it tests |
@@ -86,6 +88,20 @@ opencode session timeout, compaction trigger). Run them locally with
86
88
  | UC-013 | compaction-context | compaction hook injects context |
87
89
  | UC-019 | archived-memory-lifecycle | archive -> search excludes -> unarchive -> search includes |
88
90
  | UC-020 | extraction-escalation | polite -> insistent -> ALL-CAPS over missed nudges |
91
+ | UC-038 | no-save-run | an extraction child that finds nothing worth saving (no toast) |
92
+ | UC-043 | compaction-suppression | extraction nudges suppressed while an extraction child runs |
93
+ | UC-077 | child-session-drain | direct extraction: the child drains the parent's queue |
94
+ | UC-078 | child-session-error | direct extraction: the child errors, held entries requeue |
95
+ | UC-079 | child-session-deletion | direct extraction: the child is deleted, entries requeue |
96
+ | UC-080 | parent-session-deletion | parent deleted mid-extraction, cleanup completes |
97
+ | UC-083 | review-coordinator | the multi-agent review coordinator pipeline |
98
+ | UC-084 | review-walkthrough | the change-walkthrough skill on a real diff |
99
+ | UC-085 | review-followup | follow-up round verification against prior review comments |
100
+ | UC-086 | review-response | author-side response to incoming review comments |
101
+ | UC-087 | review-no-subagents | the solo review path on hosts without sub-agents |
102
+ | UC-098 | chat-cross-session | two real opencode sessions exchange chat messages |
103
+ | UC-119 | session-tab-spawn | the session_tab spawn flow end to end |
104
+ | UC-122 | review-mode-commands | /thatch/self-review and /thatch/peer-review overlay efficacy (seeded fixture) |
89
105
 
90
106
  ### mise tasks
91
107
 
@@ -235,7 +251,7 @@ No separate QA docs directory exists. QA use cases live in `tests/qa/` as execut
235
251
  1. **Automatable use cases never spawn an LLM.** They test CLI, hooks, DB, and
236
252
  tool execution directly.
237
253
  2. **Live use cases spawn real opencode sessions and cost model tokens.** They
238
- skip with `[MANUAL]` when `opencode` is not on PATH; three are
254
+ skip with `[MANUAL]` when `opencode` is not on PATH; six are
239
255
  `manualOnly: true` for infrastructure reasons.
240
256
  3. **`QA_DRY_RUN=1` lists use cases without executing.** Use this for CI and
241
257
  coverage review.
@@ -73,12 +73,12 @@ When a parent session (no parentID) goes idle:
73
73
 
74
74
  User-invoked slash commands, shipped as command markdown synced by the plugin (see `src/commands.ts`). The template instructs the model to flush pending fact extraction (`thatch_get_extraction_payload` + `thatch_extraction_done`), finish promised memory writes, and surface unaddressed todos -- then end its response with a greenlight token (`THATCH_COMPACT_READY` / `THATCH_EXIT_READY`) only when safe to proceed. Text typed after the command is forwarded into a labeled `# User Message` section ahead of the `# Pre-*-wrap-up` checklist section (opencode's `$ARGUMENTS` substitution): the headers give the user's words provenance, so they cannot be misread as part of the wrap-up instructions. opencode's substitution is purely mechanical with no default-value form (verified against v1.18.30), so the template cannot switch on emptiness; instead the section always carries a fallback line telling the model to treat an empty section as n/a (bare command, no user text).
75
75
 
76
- 1. `command.execute.before` arms the session in `pendingWrapUp` when the command runs
77
- 2. On the session's next idle, the plugin fetches the session's messages via the SDK client and checks the final assistant message's trailing text for the token (trimmed, exact match)
78
- 3. Token present: trigger the TUI action and return early -- compaction is starting (the checklist drained the buffer) or the process is exiting
79
- - compact: `client.tui.executeCommand({ body: { command: "session_compact" } })`. The execute-command route only accepts legacy alias names; `session_compact` maps to the TUI's `session.compact` action, the same thing the built-in `/compact` runs
80
- - exit: unregister the session from the chat directory first (a greenlit exit's host is about to vanish; other sessions must stop addressing mail to it), then `client.tui.publish({ body: { type: "tui.command.execute", properties: { command: "app.exit" } } })`. No exit alias exists, so the TUI keymap command is published directly. The exit template's checklist also asks the model to call `thatch_chat_unregister` as its last persistence step; the plugin-side unregister is the deterministic backstop. Compaction deliberately does NOT unregister - the session continues
81
- 4. Token absent: warning toast pointing at the blockers in the response, then fall through -- the model may have buffered tool interactions that still need extraction
76
+ 1. `command.execute.before` arms the session in `pendingWrapUp` when the command runs (on v2 the registered command's `execute` runs the same arming through `onCommandExecuteBefore`)
77
+ 2. On the session's next idle, the plugin fetches the session's messages (v1: SDK client; v2: `session.context`, mapped into the v1 shape) and checks the final assistant message's trailing text for the token (trimmed, exact match)
78
+ 3. Token present: trigger the host action and return early -- compaction is starting (the checklist drained the buffer) or the session is closing
79
+ - compact: v1 dispatches the TUI's `session_compact` command (`client.tui.executeCommand` -- the execute-command route only accepts legacy alias names; `session_compact` maps to the TUI's `session.compact` action, the same thing the built-in `/compact` runs). v2 calls `session.compact({sessionID})` on the promise domain directly (upstream #52385), runtime-guarded because the pinned dev types predate the surface -- an older host degrades to a logged no-op
80
+ - exit: record the session's watcher deaths first (`watchers.sessionDied` -- BEFORE the unregister below, because the death row is placed by the session's chat row and the unregister deletes it; the runtime also captures the project there, so the async death-notice delivery still places after the row is gone), unregister the session from the chat directory (a greenlit exit's host is about to vanish; other sessions must stop addressing mail to it), then close the session out on the host. v1 publishes the `app.exit` TUI keymap command directly (`client.tui.publish` -- no exit alias exists). v2 emits the session-tab bridge's `exit-tab-closed` event, a TUI-ONLY close of the session's OWN tab: the daemon hosts every tab, so `app.exit` would take down unrelated sessions, and the pump deliberately never translates this event (the deaths were already recorded; a second pass would erase the durable death row). The unregister is plugin-side only, after the death recording: the template deliberately does NOT ask the model to call `thatch_chat_unregister`, because a mid-turn unregister would delete the chat row before the death placement could use it. Compaction deliberately does NOT unregister - the session continues
81
+ 4. Token absent: warning toast pointing at the blockers in the response, then fall through -- the model may have buffered tool interactions that still need extraction (on v2 the toast rides the bridge's toast event; a TUI-less environment shows nothing - the blockers themselves are in the model's response)
82
82
 
83
83
  ### Command file install
84
84
 
@@ -82,11 +82,17 @@ Key properties:
82
82
  ## The cross-entry contract
83
83
 
84
84
  `src/session-tab-shared.ts` (SDK-free, per the isolation rule in
85
- `src/index.ts`) holds the rpc definition (`thatch-tabs`: `tab-opened`,
86
- `tab-closed` - the latter emitted by the close tool (below) and consumed
87
- by the generalized-session-heartbeat plan), the subordinate prompt builder,
88
- and the validators. The TUI entrypoint may import only
89
- `@opencode/plugin/tui` and this module.
85
+ `src/index.ts`) holds the rpc definition (`thatch-tabs`: `tab-opened`;
86
+ `tab-closed` - emitted by the close tool (below) and translated by the v2
87
+ pump into the runtime's confirmed-death path, which also records a
88
+ `chat_tab_closed` marker the chat roster's open-tab reader uses to demote
89
+ the row instantly ([cross-session-chat.md](cross-session-chat.md));
90
+ `exit-tab-closed` - the
91
+ wrap-up exit's TUI-only close, which the runtime records the exit's deaths
92
+ itself before emitting, so the pump deliberately never translates that
93
+ one; and `toast` - every runtime toast's TUI-only feedback channel), the
94
+ subordinate prompt builder, and the validators. The TUI entrypoint may
95
+ import only `@opencode/plugin/tui` and this module.
90
96
 
91
97
  ## Interactions
92
98
 
@@ -160,6 +160,12 @@ here first. These are the things that have already cost time.
160
160
  - **`bun test` does not typecheck**, and `tsconfig.json` excludes `tests`. Test
161
161
  type errors are editor-only noise unless you run `tsc` on the test files
162
162
  directly. Keep test files type-clean anyway so the editor stays quiet.
163
+ - **`mise run test` (bare `bun test`) also executes the QA barrels**
164
+ (`tests/qa/*/index.test.ts`), which spawn REAL opencode sessions and spend
165
+ model tokens, one use case at a time - a full run can hang for many minutes.
166
+ The unit gate is `mise run check` (`bun test tests/*.test.ts`); QA runs
167
+ belong to the `mise run qa-auto` / `qa-live` tasks, which select a UC
168
+ subset by name.
163
169
 
164
170
  ## Config and notifications
165
171
 
@@ -361,3 +367,18 @@ instead of throwing "Cannot use a closed database". Two rules keep this intact:
361
367
  chat canary). Local plugin specs handed to a loader explicitly
362
368
  (`"plugin": ["./path"]`) can point at a directory on v1 - it is only the
363
369
  AUTO-discovery scan that is files-only.
370
+
371
+ ## A promptSession wrapper must stay synchronous or fire-and-forget delivery assertions misfire
372
+
373
+ - **Symptom**: tests that assert on wake delivery immediately after a hook
374
+ invocation see zero calls; deliveries land one microtask late.
375
+ - **Cause**: the delivery guard (`guardDelivery`, src/runtime.ts) sits on
376
+ every wake path, and its directory-existence check was first written as
377
+ `await stat(...)`. Fire-and-forget callers (`void caps.promptSession(...)`)
378
+ and the repo's mock contract (calls recorded "synchronously inside the
379
+ mock", asserted with no intervening await) observe the wrapped call
380
+ before an extra microtask resolves.
381
+ - **Fix**: the existence check is synchronous (`existsSync`), so the guard
382
+ adds no microtask between the gate and the wrapped call. If you ever
383
+ replace it with an async check, the callers' fire-and-forget timing -
384
+ and the tests that pin it - break again. (2026-10-12, wake-delivery-guard)
@@ -22,10 +22,10 @@ This document maps the feature parity and documents the remaining gaps.
22
22
  | **System prompt** (store names, usage rules) | `experimental.chat.system.transform` — dynamic, repo baked in at runtime | CLAUDE.md static text appended by `thatch setup`; repo auto-detected by the MCP server at startup | AGENTS.md static text appended by `thatch setup`; repo auto-detected at startup | **Approximate** — static text persists through compaction, so the agent retains the usage instructions, but the dynamic per-turn refresh is lost |
23
23
  | **Session-start reminder** (recall nudge + hygiene heartbeat) | `session.created` event → `client.session.prompt` injects a synthetic message | `SessionStart` hook → `thatch reminder`; stdout becomes context | `sessionStart` hook → `thatch reminder --json`; output is `additional_context` JSON | **Full** |
24
24
  | **Compaction context** (re-familiarize after compaction) | `experimental.session.compacting` hook appends context to the compaction output | `PostCompact` hook — side-effects only, **cannot inject context** | No equivalent hook | **Gapped** — see below |
25
- | **Wrap-up commands** (`/thatch/compact`, `/thatch/exit`) | Command markdown synced to `$XDG_CONFIG_HOME/opencode/command/thatch/` at plugin init; `command.execute.before` arms the session and the next idle resolves the greenlight token into a TUI action (`session.compact` via `executeCommand`, `app.exit` via `publish`) | No equivalent — no plugin host to arm the check or trigger the action | No equivalent | **Gapped** — needs the plugin's SDK client and TUI control routes |
26
- | **On-demand actions** (`/thatch/defrag`, `extract`, `hygiene`, `reflect`, `refine`) | Command markdown synced at plugin init; bodies rendered from the shared prompt cores with `thatch_` tool spellings; `extract` is opencode-only (needs session identity via `get_session_info` and the plugin's payload provider) | Command markdown synced by `thatch setup --claude` into `<claudeDir>/commands/thatch/`; same cores with `mcp__thatch__` spellings; `extract` excluded | MCP Prompts (`prompts/list` + `prompts/get`) from the same cores; Cursor surfaces them as slash commands; `extract` excluded | **Full** for the shared actions (same cores, per-host tool spellings); `extract` opencode-only |
25
+ | **Wrap-up commands** (`/thatch/compact`, `/thatch/exit`) | Command markdown synced to `$XDG_CONFIG_HOME/opencode/command/thatch/` at plugin init; `command.execute.before` arms the session and the next idle resolves the greenlight token into the host action (compact: the TUI compact command on v1, `session.compact` on v2; exit: `app.exit` on v1, the session's own tab closing on v2) | No equivalent — no plugin host to arm the check or trigger the action | No equivalent | **Gapped** — needs the plugin's SDK client and host action routes |
26
+ | **On-demand actions** (`/thatch/defrag`, `extract`, `hygiene`, `peer-review`, `refine`, `reflect`, `self-review`, `whois`) | Command markdown synced at plugin init; nudge-backed bodies rendered from the shared prompt cores with `thatch_` tool spellings, self-contained bodies (refine, whois, and the review-mode couchings peer-review/self-review) inline in the registry; `extract` and `whois` are opencode-only (they need session identity) | Command markdown synced by `thatch setup --claude` into `<claudeDir>/commands/thatch/`; same bodies with `mcp__thatch__` spellings; `extract` and `whois` excluded | MCP Prompts (`prompts/list` + `prompts/get`) from the same bodies; Cursor surfaces them as slash commands; `extract` and `whois` excluded | **Full** for the shared actions (same bodies, per-host tool spellings); `extract` and `whois` opencode-only |
27
27
  | **Extraction** (buffer tool calls, extract durable facts) | `tool.execute.after` buffers non-meta tool calls in-process (`isMetaToolName` in `src/extraction.ts`: thatch_*, skill, task, agent, subagent — execute calls unwrapping only thatch tools are also excluded); **direct extraction** is the primary path — when the parent session goes idle with pending buffer entries, the plugin creates a child session via the SDK (`client.session.create` with `parentID`) and prompts it to call `get_extraction_payload` (nudge suppressed via the `extracting` set); the `chat.message` nudge path is a **fallback** used when direct extraction throws | `PostToolBatch` → `thatch buffer-batch` (file-backed JSONL queue); `UserPromptSubmit` → `thatch flush-tools` peeks the queue and prints the nudge with session ID + fetch tool name (does not drain — the queue persists until a memory write or `extraction_done`) | `postToolUse` → `thatch buffer-tool` (single-tool, file-backed queue); `beforeSubmitPrompt` → `thatch flush-tools --json` | **Partial** — opencode uses direct extraction (parent idle → child session) as the primary path, with the nudge as fallback; MCP hosts are nudge-only. Both paths use `get_extraction_payload` to fetch the queued interactions as a tool response, keeping the full payload out of the main session's context window. Nudge timing differs: the nudge arrives at the **start** of the next turn in Claude Code/Cursor; opencode's direct extraction fires at **end of turn** (session idle), and the nudge fallback fires at the start of the next turn like MCP |
28
- | **Extraction feedback** (toast notifications) | `client.tui.showToast` fires when the extraction child goes idle — shows `[thatch] new: N, updated: M, deleted: K` (success) or `extraction complete — nothing to save` (info); best-effort, silently ignored in headless mode | No TUI connection — MCP hosts have no equivalent | No TUI connection | **Gapped** — MCP hosts cannot show toast notifications |
28
+ | **Extraction feedback** (toast notifications) | `client.tui.showToast` on v1; the session-tab bridge's toast event -> the TUI plugin's `ui.toast.show` on v2 — fires when the extraction child goes idle, but ONLY when memories were actually written (no toast for no-save runs, to avoid notification fatigue): shows `[thatch] new: N, updated: M, deleted: K` (success); best-effort, silently ignored in headless mode | No TUI connection — MCP hosts have no equivalent | No TUI connection | **Gapped** — MCP hosts cannot show toast notifications |
29
29
  | **Automatic alerts** (notify on LLM pause / turn end) | Plugin event hook drives the `alerts.ts` state machine: `question.asked`/`permission.asked` → pause alert; `session.status` idle with a real-work round → done alert; unrecovered errors → needs-attention. Delivered via the shared `notify.ts` dispatcher, channel per event from the `alerts` config section | No event stream — only `thatch_notify_user` when the agent chooses | No event stream — only `thatch_notify_user` when the agent chooses | **Gapped** — MCP hosts have no plugin event hook to watch; Claude Code's `Notification`/`Stop` hooks could feed the same classifier later |
30
30
  | **Prompt-aware recall nudge** | `chat.message` hook embeds the prompt with the in-process warm model, searches `db.search()`, pushes a nudge part if matches ≥ threshold | `UserPromptSubmit` hook → `thatch flush-tools` connects to the MCP server's sideband socket; the warm server embeds + searches; hook prints the nudge or falls back to the write nudge | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — sideband socket gives cold hook processes access to the warm MCP server's model |
31
31
  | **Prediction auto-fire** (user decision model) | `chat.message` hook scores the prompt embedding against prediction matchers; injects a `[thatch] User decision model` nudge alongside the recall nudge (separate synthetic part, same embedding call) | `UserPromptSubmit` hook → `thatch flush-tools` fires prediction query via the sideband socket's `predictions` method in parallel with the recall nudge | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — same `scorePredictionNudge` entry point in both paths |
@@ -47,18 +47,18 @@ description: Extract durable project facts ... Use when ...
47
47
  | `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
48
48
  | `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar against generic praise. |
49
49
  | `thatch-review-technique` | Technique lens: teaching-grade notes on unseen helpers/internal packages, companion techniques, house patterns, test-craft, API-shape principles, next-reader discoverability. Informational only, verified citations, never blocking. |
50
- | `thatch-review-synthesizer` | Verify specialist findings against code, dedupe, classify, calibrate severity. Starts the final report with a workflow-change preface, then cross-references findings against prior review comments when a follow-up round register is provided. |
51
- | `thatch-review-context` | Gather project/feature context (PR descriptions, git archaeology, ticket references, linked docs/tickets followed from the change, memory) before fan-out. Prevents false positives about intentionally deferred work, and checks whether main moved past the merge-base touching the same paths (staleness signal for the synthesizer). Also fetches prior review comments on a connected PR/MR for follow-up-round detection and builds a register with preliminary addressed-check status per comment. |
50
+ | `thatch-review-synthesizer` | Verify specialist findings against code, dedupe, classify, calibrate severity. Starts the final report with a workflow-change preface, then cross-references findings against prior review comments when a follow-up round register is provided, and against sibling PR registers when related PRs were detected (cross-PR notes appendix). |
51
+ | `thatch-review-context` | Gather project/feature context (PR descriptions, git archaeology, ticket references, linked docs/tickets followed from the change, memory) before fan-out. Prevents false positives about intentionally deferred work, and checks whether main moved past the merge-base touching the same paths (staleness signal for the synthesizer). Also fetches prior review comments on a connected PR/MR for follow-up-round detection and builds a register with preliminary addressed-check status per comment. Also discovers related PRs (stacks chained through base branches, release trains ordered by tracker relations) and builds a sibling register with targeted consistency verdicts. |
52
52
  | `thatch-code-archaeology` | Investigate an existing feature, debug an unfamiliar area, or begin a new ticket. Explores the code base from multiple angles (data model, state flow, git history, sibling features, skeletons) before proposing changes. The research skill; pair with `thatch-coding-workflow`. |
53
53
  | `thatch-review-followup` | Alternate entrypoint for follow-up review rounds. Verifies whether the author's responses and code changes since your last review round adequately addressed your prior findings, offers to reply on resolved items, then optionally re-runs the full structured review. |
54
54
  | `thatch-change-walkthrough` | Explain a change to the user as a teaching walkthrough: resolve the delta, research each affected workflow at the merge-base, teach current behavior, then overlay the modifications with file:line citations and analogies. |
55
55
  | `thatch-code-walkthrough` | Explain a feature, module, or workflow to the user as a teaching walkthrough: identify the code area (optionally from a branch or PR), research how it works, teach it with file:line citations and analogies, list the key files. |
56
56
  | `thatch-session-reflection` | End-of-session memory recording (project, user, tools, self). |
57
57
  | `thatch-coding-workflow` | Plan and execute code changes with a task-list-driven workflow. Use when implementing features, fixing bugs, or making multi-file changes. |
58
- | `thatch-plan-refinement` | Refine a plan before implementing it: fresh-context reviewer rounds until consensus, with an optional deep-mode lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology). The generic core loop. |
59
- | `thatch-refine` | Classify the project (audience, lifecycle, surface) and refine a plan under that type's requirements. Entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. |
58
+ | `thatch-plan-refinement` | Refine a plan before implementing it: fresh-context reviewer rounds until consensus, with an optional deep-mode lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology). The generic core loop. Plans with UI or session state carry a state-and-transition matrix; template ports carry a deltas-from-template ledger; a revision self-check runs between revise and re-review; user-extended runs rotate reviewer lenses and stop on a round with no build-changing findings. Deep-mode triggers are binding. |
59
+ | `thatch-refine` | Classify the project (audience, lifecycle, surface) and refine a plan under that type's requirements. Entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. Deep-mode triggers in the core skill are binding; type deltas relax round counts, never mode selection. |
60
60
  | `thatch-refine-team-app` | Refinement deltas for multi-developer team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate. |
61
- | `thatch-refine-personal` | Refinement deltas for solo projects with no downstream consumers: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. |
61
+ | `thatch-refine-personal` | Refinement deltas for solo projects with no downstream consumers: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. Relaxes round counts only, never mode selection. |
62
62
  | `thatch-refine-shared-lib` | Refinement deltas for changes touching a library's API surface: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable. |
63
63
  | `thatch-refine-spike` | Refinement deltas for throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on, state the question and the time box. |
64
64
  | `thatch-refine-infra` | Refinement deltas for CI, Terraform, and k8s changes: blast radius across pipelines and environments, rollback plan and staged verification are mandatory, pattern-following is near-absolute. |
@@ -75,11 +75,11 @@ description: Extract durable project facts ... Use when ...
75
75
 
76
76
  | Skill | Role |
77
77
  |-------|------|
78
- | `thatch-code-review` | Resolve review target (incl. VCS detection and connected PR/MR lookup for follow-up round detection), gather project context, research affected workflows, estimate complexity, partition, dispatch the review specialists in parallel, synthesize with a workflow-change preface and prior-comment cross-reference. |
78
+ | `thatch-code-review` | Resolve review target (incl. VCS detection and connected PR/MR lookup for follow-up round detection), gather project context, research affected workflows, estimate complexity, partition, dispatch the review specialists in parallel, synthesize with a workflow-change preface, prior-comment cross-reference, and sibling-register cross-reference. Also the machinery behind the `/thatch/self-review` and `/thatch/peer-review` mode commands, which run it with a weighting overlay (inline, without sub-agents, on hosts that lack the coordinator). |
79
79
 
80
80
  ## REVIEW_COMMON
81
81
 
82
- The seven problem-finding review specialists share a framework interpolated via `${REVIEW_COMMON}` (the eighth specialist, highlights, finds positive things and uses its own structure):
82
+ The seven problem-finding review specialists share a framework interpolated via `${REVIEW_COMMON}` (the technique and highlights specialists do not use it: technique reports informational notes and highlights finds positive things, each with its own structure):
83
83
 
84
84
  - **Static analysis only** — no running tests/linters/compilers.
85
85
  - **Scope gathering** — resolve the git range, `git diff --stat`, read changed
@@ -93,6 +93,9 @@ The seven problem-finding review specialists share a framework interpolated via
93
93
  - **Project context awareness** — if a context brief is provided (from the
94
94
  coordinator or from loading `thatch-review-context`), use it to avoid false
95
95
  positives about intentionally deferred work.
96
+ - **Sibling PR context** — when the brief's sibling register is present
97
+ (related PRs detected), check it before flagging anything as referenced but
98
+ missing; a stack member or release-train sibling may deliver the piece.
96
99
  - **Workflow context awareness** — if a workflow guide is provided (from the
97
100
  coordinator or from loading `thatch-code-archaeology`), use it to understand
98
101
  the purpose and evolution of the code, distinguishing intentional behavior
@@ -143,7 +146,7 @@ and `--cursor` pass only the shared set.
143
146
 
144
147
  ## Adding a skill
145
148
 
146
- 1. Write `artifacts/skills/thatch-<name>.md` with YAML frontmatter. If it's a problem-finding review specialist, include `${REVIEW_COMMON}` (interpolated from `artifacts/skills/common.md`). The highlights specialist does not use REVIEW_COMMON — it finds positive things, not problems, so the problem-finding framework does not apply.
149
+ 1. Write `artifacts/skills/thatch-<name>.md` with YAML frontmatter. If it's a problem-finding review specialist, include `${REVIEW_COMMON}` (interpolated from `artifacts/skills/common.md`). The technique and highlights specialists do not use REVIEW_COMMON — technique reports informational notes and highlights finds positive things, so the problem-finding framework does not apply to either.
147
150
  2. Add the skill name to the `names` array in `loadSharedSkills()` (or `loadOpencodeOnlySkills()` if sub-agents are needed). The loader in `src/skills.ts` reads `.md` files at init.
148
151
  3. If the skill name appears in a tool's workflow (e.g. `thatch-fact-extractor`), update `src/prompts.ts`.
149
152
  4. Run `mise run check` — tests verify counts; `installSkills` picks up new files on next init.
@@ -206,7 +206,7 @@ requirements.
206
206
  | `thatch_chat_send` | Send a message to another registered session, by name or session id. The recipient is nudged when its session is idle. |
207
207
  | `thatch_chat_read` | Drain your inbox: all unread messages oldest-first, marked read. Senders identified by display name. |
208
208
  | `thatch_chat_unregister` | Leave the chat directory. |
209
- | `thatch_chat_broadcast` | Send one message to every other live registered session at once. Stale sessions (dead host processes) are skipped and reported. Use sparingly: every live session pays a model turn for a broadcast. |
209
+ | `thatch_chat_broadcast` | Send one message to every other live registered session at once. Opencode sessions without an open tab (stale, or fresh but tab-less) are skipped and reported by name. Use sparingly: every open session pays a model turn for a broadcast. |
210
210
 
211
211
  See [cross-session-chat.md](cross-session-chat.md) for the full behavior,
212
212
  liveness model, and limitations.
@@ -248,16 +248,23 @@ Beyond the tools, thatch hooks into opencode itself:
248
248
  flushes pending fact extraction, finishes any promised memory writes, and
249
249
  surfaces todos or follow-ups it never addressed. The agent ends its
250
250
  response with a greenlight token only when the checklist is clean. Thatch
251
- watches for the token and then runs the compaction or exits opencode.
251
+ watches for the token and then runs the compaction or closes the session
252
+ (opencode 1 exits the whole app; opencode 2 closes the session's own tab.
253
+ Either way the session's watches are cancelled, and reopening or resuming
254
+ the session does not re-arm them - re-create watches you still want with
255
+ `thatch_watch_create`).
252
256
  Without the token nothing fires -- a toast points you at the items the
253
- agent listed, and you re-run the command once they're handled. Text typed
257
+ agent listed in its response, and you re-run the command once they're
258
+ addressed. Text typed
254
259
  after the command reaches the agent first, labeled as a `User Message`
255
260
  section ahead of the checklist (say goodbye or hand off context:
256
261
  `/thatch/exit Good work - see you tomorrow`). Alongside the wrap-ups,
257
- on-demand actions (`/thatch/defrag`, `/thatch/extract`, `/thatch/hygiene`,
258
- `/thatch/reflect`, `/thatch/whois`) run the memory-maintenance behaviors
259
- whenever you want,
260
- not just when a nudge fires -- see [commands.md](commands.md).
262
+ on-demand actions cover the memory-maintenance behaviors (`/thatch/defrag`,
263
+ `/thatch/extract`, `/thatch/hygiene`, `/thatch/reflect`) whenever you
264
+ want, not just when a nudge fires, plus the standalone commands: plan
265
+ refinement (`/thatch/refine`), the review modes (`/thatch/self-review`,
266
+ `/thatch/peer-review`), and the chat identity report (`/thatch/whois`)
267
+ -- see [commands.md](commands.md).
261
268
  A greenlit `/thatch/exit` also unregisters the session from the chat
262
269
  directory, so other sessions stop sending it mail. The commands
263
270
  self-install into `~/.config/opencode/command/thatch/` at plugin startup;
@@ -329,17 +336,17 @@ response, walkthrough, and workflow skills that support the review pipeline:
329
336
  | `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar against generic praise. |
330
337
  | `thatch-review-technique` | Technique lens: teaching-grade notes on unseen helpers/internal packages, companion techniques, house patterns, test-craft, API-shape principles, next-reader discoverability. Informational only, never blocking. |
331
338
  | `thatch-review-synthesizer` | Verifies and synthesizes findings from multiple specialists into a report that starts with workflow changes, then highlights, then deduplicated severity-grouped findings. |
332
- | `thatch-review-context` | Gathers project context (PR descriptions, git archaeology, ticket references, linked docs/tickets, memory) before a review. Prevents false positives about intentionally deferred work. |
339
+ | `thatch-review-context` | Gathers project context (PR descriptions, git archaeology, ticket references, linked docs/tickets, memory, related PRs) before a review. Prevents false positives about intentionally deferred work and about pieces delivered by sibling PRs in a stack or release train. |
333
340
  | `thatch-code-archaeology` | Investigates an existing feature, debugs an unfamiliar area, or begins a new ticket. Explores the code base from multiple angles (data model, state flow, git history, sibling features, skeletons) before proposing changes. Pairs with `thatch-coding-workflow`. |
334
341
  | `thatch-review-followup` | Alternate entrypoint for follow-up review rounds. Verifies whether the author's responses and code changes since your last review adequately addressed your prior findings, offers to reply on resolved items, then optionally re-runs the full structured review. |
335
342
  | `thatch-review-response` | Author-side review response: triage findings on your own PR, fix bugs one by one, reply on each thread, post a top-level summary comment. |
336
343
  | `thatch-change-walkthrough` | Explains a change to the user as a teaching walkthrough: researches each affected workflow at the merge-base, teaches current behavior, then overlays the modifications with file:line citations. |
337
344
  | `thatch-code-walkthrough` | Explains a feature, module, or workflow to the user as a teaching walkthrough with file:line citations. Also used to draft high-level docs for new or undocumented features. |
338
345
  | `thatch-coding-workflow` | Plans and executes code changes with a task-list-driven workflow: complexity triage, milestone planning, research before coding, post-coding verification. Pairs with `thatch-code-archaeology` (research first, then plan). |
339
- | `thatch-plan-refinement` | Refines a plan before it is implemented: a fresh-context reviewer subagent re-checks the plan each round until consensus; deep mode adds specialist lenses (reuse, alternatives, hidden problems, safe-to-modify, intent archaeology). |
340
- | `thatch-refine` | Classifies the project (audience, lifecycle, surface) and refines a plan under that type's requirements. The entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. |
346
+ | `thatch-plan-refinement` | Refines a plan before it is implemented: a fresh-context reviewer subagent re-checks the plan each round until consensus; deep mode adds specialist lenses (reuse, alternatives, hidden problems, safe-to-modify, intent archaeology). Plans with UI or session state must include a state-and-transition matrix; a revision self-check runs after each revision; extended runs rotate reviewer lenses and stop on a round with no build-changing findings. |
347
+ | `thatch-refine` | Classifies the project (audience, lifecycle, surface) and refines a plan under that type's requirements. The entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. Deep-mode triggers in the core skill are binding; type deltas relax round counts, never mode selection. |
341
348
  | `thatch-refine-team-app` | Team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate. |
342
- | `thatch-refine-personal` | Solo projects: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. |
349
+ | `thatch-refine-personal` | Solo projects: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. Relaxes round counts, never mode selection. |
343
350
  | `thatch-refine-shared-lib` | Library API surfaces: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable. |
344
351
  | `thatch-refine-spike` | Throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on. |
345
352
  | `thatch-refine-infra` | CI, Terraform, and k8s changes: blast radius, mandatory rollback plan and staged verification, pattern-following is near-absolute. |
@@ -358,7 +365,7 @@ response, walkthrough, and workflow skills that support the review pipeline:
358
365
 
359
366
  | Skill | Purpose |
360
367
  |-------|---------|
361
- | `thatch-code-review` | Multi-agent review coordinator. Dispatches parallel sub-agents for triage, decomposition, specialist fan-out, and synthesis with a workflow-change preface. Not available in Claude Code (requires sub-agent support). |
368
+ | `thatch-code-review` | Multi-agent review coordinator. Dispatches parallel sub-agents for triage, decomposition, specialist fan-out, and synthesis with a workflow-change preface. Not available in Claude Code (requires sub-agent support), though the `/thatch/self-review` and `/thatch/peer-review` commands run the same specialists inline there. |
362
369
 
363
370
  ### Host availability
364
371
 
@@ -24,13 +24,19 @@ the agent's context. The block looks like:
24
24
  - [0.58 conf, 1 tests] When about to commit changes to the repo:
25
25
  do Run mise run check (tsc + bun test + markdownlint) before
26
26
  committing. Never commit code that fails the quality gate.
27
+ Snapshot: these labels reflect the behavior store as of this message's
28
+ submission. Behaviors updated after submission (including during this
29
+ turn) do not refresh it; re-check with thatch_behavior_list before
30
+ relying on them.
27
31
  ```
28
32
 
29
33
  The agent evaluates each surfaced rule against the current situation.
30
34
  If relevant (ham), it follows the rule and calls `behavior_feedback`
31
35
  with `relevant: true`. If not relevant (spam), it calls
32
36
  `behavior_feedback` with `relevant: false`. This trains the classifier
33
- so future nudges are more accurate.
37
+ so future nudges are more accurate. On Claude Code and Cursor the
38
+ footer's tool name appears with its MCP spelling
39
+ (`mcp__thatch__behavior_list`).
34
40
 
35
41
  ## Default behaviors
36
42
 
package/docs/user/cli.md CHANGED
@@ -57,7 +57,8 @@ thatch chat tail --since 2026-09-12 --until "2026-09-13 09:00" # a window
57
57
 
58
58
  `chat list` shows every registered session in two sections, Active and
59
59
  Stale, each as aligned columns under a header row: how long since its
60
- last heartbeat, its status (`fresh` or `stale` for opencode sessions;
60
+ last heartbeat, its status (`open` - a tab is open now - or `recent` -
61
+ host alive, no open tab - for opencode sessions;
61
62
  `active` or `idle` for Claude Code and Cursor sessions, which only beat
62
63
  when a prompt runs), its project, and its topic (the session's title) -
63
64
  the fastest way to answer "which session should I talk to about X?"
@@ -16,7 +16,10 @@ the review:
16
16
  2. **Gather project context**: PR descriptions, git archaeology,
17
17
  ticket references, links to docs/designs/tickets found in the
18
18
  change (followed one hop for scope and design context), TODO
19
- markers, prior reviews, project memory. Also checks whether main
19
+ markers, prior reviews, project memory. Also discovers related
20
+ PRs (stacks chained through base branches, release trains
21
+ ordered by ticket relations) and checks consistency between
22
+ them and the change. Also checks whether main
20
23
  has moved past the merge-base touching the same paths — a stale
21
24
  branch can make findings true only at the merge-base.
22
25
  3. **Research affected workflows**: load `thatch-code-archaeology`,
@@ -30,6 +33,10 @@ the review:
30
33
  the final report. When the branch is stale relative to main, it
31
34
  re-verifies behavioral findings against current main and reports
32
35
  merge-base-only findings as rebase notes rather than defects.
36
+ When related PRs were detected, it cross-references findings
37
+ against the sibling register: a missing piece delivered by a
38
+ sibling is not a defect, and inconsistencies between connected
39
+ PRs surface as cross-PR notes.
33
40
 
34
41
  ## The nine specialists
35
42
 
@@ -48,6 +55,34 @@ the review:
48
55
  Each specialist is a self-contained static-analysis pass. They can run
49
56
  individually or as part of the coordinated review.
50
57
 
58
+ ## Review modes: /thatch/self-review and /thatch/peer-review
59
+
60
+ Two slash commands run the same pipeline with the findings weighted for
61
+ whose work is under review.
62
+
63
+ - `/thatch/self-review` reviews your own completed work before you
64
+ submit it. The slop, technique, and breadcrumbs lenses weigh
65
+ heaviest, simplicity is overweighted on shared projects, and
66
+ technique or highlights findings that would simplify the design are
67
+ surfaced for an adopt-or-dismiss decision rather than left as
68
+ footnotes. The review iterates: after each round you fix or decline
69
+ each finding, and the next round re-reviews the updated state until
70
+ a round produces no meaningful findings (at most three rounds).
71
+ - `/thatch/peer-review` reviews another author's change. Findings are
72
+ weighted for org velocity: simpler ways to the same outcome, existing
73
+ helpers that already do the work, extremely clear documentation, and
74
+ unintended consequences for callers and siblings. The review walks
75
+ the project's persistent memory for incidents, constraints, and
76
+ footguns the author could not have known, and checks them against
77
+ the change. Settled designs are not re-litigated, and "if someone
78
+ changes this later it breaks" findings are never reported; instead
79
+ the review verifies the change leaves a breadcrumb trail to wherever
80
+ the project records how the feature works.
81
+
82
+ Both commands work on every host. On opencode they drive the
83
+ coordinator with sub-agents. On Claude Code and Cursor the same
84
+ procedure runs the specialists inline, in sequence.
85
+
51
86
  ## Follow-up rounds
52
87
 
53
88
  After the author responds or pushes changes, `thatch-review-followup`
@@ -55,6 +90,10 @@ verifies whether prior findings were adequately addressed before
55
90
  deciding whether to re-run the full review. The author must either
56
91
  fix the code, prove the finding is not a concern with a checkable
57
92
  argument, or file a follow-up ticket with a risk explanation.
93
+ Between rounds the author may merge the default branch — or, on a
94
+ stacked PR, merge or rebase onto the base branch — into the branch;
95
+ the followup excludes those commits so only the author's own changes
96
+ are verified.
58
97
 
59
98
  ## Author-side response
60
99
 
@@ -67,7 +106,9 @@ one, reply on each thread, and post a top-level summary comment.
67
106
 
68
107
  - The coordinator (`thatch-code-review`) is opencode-only. It
69
108
  requires sub-agent support to dispatch parallel specialists. On
70
- Claude Code or Cursor, run specialists individually instead.
109
+ Claude Code or Cursor, run specialists individually instead; the
110
+ `/thatch/self-review` and `/thatch/peer-review` commands package
111
+ exactly that inline sequence, with a mode overlay on top.
71
112
  - The review quality depends on the model. Weak models may
72
113
  hallucinate findings or miss subtle issues.
73
114
  - The synthesizer verifies cited text exists, but it does not