@jeffober/thatch 0.2.4 → 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.
- package/README.md +56 -26
- package/artifacts/skills/common.md +1 -0
- package/artifacts/skills/thatch-clear-writing.md +103 -5
- package/artifacts/skills/thatch-code-archaeology.md +9 -0
- package/artifacts/skills/thatch-code-review.md +16 -8
- package/artifacts/skills/thatch-coordination.md +69 -0
- package/artifacts/skills/thatch-plan-refinement.md +33 -4
- package/artifacts/skills/thatch-pr-description.md +21 -13
- package/artifacts/skills/thatch-refine-personal.md +5 -0
- package/artifacts/skills/thatch-refine.md +7 -1
- package/artifacts/skills/thatch-review-context.md +55 -2
- package/artifacts/skills/thatch-review-followup.md +4 -4
- package/artifacts/skills/thatch-review-no-slop.md +15 -0
- package/artifacts/skills/thatch-review-response.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +21 -1
- package/bin/thatch +48 -15
- package/docs/assets/deepwiki-badge.svg +1 -0
- package/docs/dev/README.md +18 -10
- package/docs/dev/features/README.md +1 -1
- package/docs/dev/features/behavior-engine.md +10 -0
- package/docs/dev/features/cli.md +4 -1
- package/docs/dev/features/commands.md +40 -9
- package/docs/dev/features/cross-session-chat.md +194 -23
- package/docs/dev/features/database.md +4 -4
- package/docs/dev/features/notifications.md +1 -2
- package/docs/dev/features/nudge-pipeline.md +14 -0
- package/docs/dev/features/opencode-plugin.md +56 -15
- package/docs/dev/features/prediction-engine.md +71 -6
- package/docs/dev/features/qa-system.md +19 -3
- package/docs/dev/features/session-lifecycle.md +6 -6
- package/docs/dev/features/session-tabs.md +161 -0
- package/docs/dev/features/setup.md +5 -0
- package/docs/dev/features/watchers.md +62 -6
- package/docs/dev/gotchas.md +89 -0
- package/docs/dev/mcp-parity.md +5 -5
- package/docs/dev/setup-and-hooks.md +7 -2
- package/docs/dev/skills.md +12 -9
- package/docs/plans/generalized-session-heartbeat.md +212 -0
- package/docs/plans/prediction-consolidation.md +88 -68
- package/docs/user/README.md +29 -13
- package/docs/user/behavior-engine.md +7 -1
- package/docs/user/cli.md +5 -1
- package/docs/user/code-review.md +43 -2
- package/docs/user/commands.md +37 -10
- package/docs/user/cross-session-chat.md +76 -26
- package/docs/user/prediction-engine.md +23 -1
- package/docs/user/setup.md +16 -0
- package/docs/user/skills.md +9 -7
- package/docs/user/watchers.md +22 -7
- package/package.json +10 -2
- package/src/alerts.ts +1 -1
- package/src/capabilities.ts +20 -6
- package/src/chat.ts +358 -43
- package/src/commands.ts +123 -18
- package/src/db.ts +74 -3
- package/src/git.ts +3 -1
- package/src/hygiene.ts +5 -0
- package/src/mcp.ts +101 -9
- package/src/opencode/tui-plugin.ts +90 -0
- package/src/opencode/v2.ts +440 -66
- package/src/prediction.ts +90 -0
- package/src/prompts.ts +164 -29
- package/src/runtime.ts +298 -15
- package/src/scoring-engine.ts +40 -9
- package/src/session-db.ts +4 -4
- package/src/session-tab-shared.ts +231 -0
- package/src/setup.ts +9 -3
- package/src/skills.ts +6 -9
- package/src/tab-state.ts +125 -0
- package/src/tool-defs.ts +454 -76
- package/src/tools.ts +2 -0
- package/src/watchers.ts +263 -26
- package/docs/plans/intuition-drives.md +0 -147
- package/docs/plans/llm-alerts.md +0 -287
- package/docs/plans/thatch-actions.md +0 -179
- package/docs/plans/worktree-deletion-recovery.md +0 -246
package/docs/plans/llm-alerts.md
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# Thatch Alerts: Notify the User When the LLM Pauses or Finishes
|
|
2
|
-
|
|
3
|
-
## Synopsis
|
|
4
|
-
|
|
5
|
-
Add an opt-in alert layer to the thatch opencode plugin that sends a desktop
|
|
6
|
-
notification when the LLM (a) is blocked on a human decision -- an interactive
|
|
7
|
-
question or a permission request -- or (b) finishes a round of real work. Bookkeeping
|
|
8
|
-
rounds driven by thatch's own nudges and background-task completions never notify.
|
|
9
|
-
One code path serves both installed opencode lines (v1.18.x and v2.x). The brain
|
|
10
|
-
lives in the server plugin, which is a single instance per project directory, so
|
|
11
|
-
deduplication and debouncing per session are free.
|
|
12
|
-
|
|
13
|
-
## Background
|
|
14
|
-
|
|
15
|
-
Jeff's workflow: ask a question, switch to another window, let the LLM work. He
|
|
16
|
-
needs a notification when the work finishes or when it stalls waiting for him.
|
|
17
|
-
Nothing else deserves a notification.
|
|
18
|
-
|
|
19
|
-
What exists today and why it is not enough:
|
|
20
|
-
|
|
21
|
-
- opencode v2's built-in TUI attention plugin (`internal:notifications`) notifies
|
|
22
|
-
on question, permission, done, and error. It has no per-event configuration, and
|
|
23
|
-
its "done" fires on every turn end -- including turns triggered by thatch nudges
|
|
24
|
-
and background-task completions. That turns async thatch activity into
|
|
25
|
-
notification spam.
|
|
26
|
-
- The v1 line has no TUI plugin system at all. The community notifier plugin
|
|
27
|
-
Jeff used on v1 is orphaned (config remnants in `~/.config/opencode`, upstream
|
|
28
|
-
issue mohak34/opencode-notifier#108).
|
|
29
|
-
- Thatch already ships `thatch_notify_user` (voice + banner, session-labeled), but
|
|
30
|
-
it is a tool the LLM invokes on request. It cannot fire on host-level events,
|
|
31
|
-
and it must not fire on every turn end.
|
|
32
|
-
|
|
33
|
-
opencode's attention API (system notification + sound, focus-aware) exists only in
|
|
34
|
-
the TUI plugin context, so a pure server plugin cannot use it. Shipping a thatch
|
|
35
|
-
TUI plugin would deliver notifications through the terminal but would reintroduce
|
|
36
|
-
the double-TUI problem: one plugin instance per attached TUI means one notification
|
|
37
|
-
per TUI, and the same v2 session can live in two TUIs. Server-side delivery avoids
|
|
38
|
-
that by construction.
|
|
39
|
-
|
|
40
|
-
## Goals
|
|
41
|
-
|
|
42
|
-
- Notify on `question.asked` and `permission.asked` (the blocking cases).
|
|
43
|
-
- Notify when a round of real work ends (busy -> idle transition).
|
|
44
|
-
- Stay silent on rounds whose only tool calls were thatch or meta tools, and on
|
|
45
|
-
turns triggered by synthetic input (nudges, task completions, watcher wake-ups).
|
|
46
|
-
- Stay silent on recoverable errors and user-initiated aborts; notify when a
|
|
47
|
-
session dies in a state nothing will wake.
|
|
48
|
-
- Work identically on opencode v1.18.x and v2.x from one code path.
|
|
49
|
-
- Deduplicate and debounce per session in one process.
|
|
50
|
-
|
|
51
|
-
## Non-goals (deferred)
|
|
52
|
-
|
|
53
|
-
- TUI plugin delivery via opencode's attention API (v2-only, focus-aware, but
|
|
54
|
-
per-TUI instances defeat dedup). Revisit as a v2 enhancement: a TUI plugin could
|
|
55
|
-
report focus state to the server plugin for focus gating.
|
|
56
|
-
- Focus gating in general. Server-side delivery cannot see terminal focus; banners
|
|
57
|
-
may land while the terminal is focused. Accepted: Jeff's stated workflow is to be
|
|
58
|
-
away from the terminal, and one unified code path is worth the noise.
|
|
59
|
-
- Reload-state persistence. v2 auto-reload discards plugin memory on every save to
|
|
60
|
-
the dev shim tree (see the PR #16 design). Worst case here is one missed or
|
|
61
|
-
duplicate alert at a reload boundary, which Jeff has ruled acceptable. No
|
|
62
|
-
`runtime_state` journaling for alerts.
|
|
63
|
-
- Claude Code / Cursor hosts. The MCP server has no event stream. Claude Code's
|
|
64
|
-
`Notification` and `Stop` hooks could feed the same classifier later; a parity
|
|
65
|
-
row documents the gap.
|
|
66
|
-
|
|
67
|
-
## Design
|
|
68
|
-
|
|
69
|
-
### Architecture
|
|
70
|
-
|
|
71
|
-
One actor owns all alert state: the thatch plugin instance inside the shared
|
|
72
|
-
opencode server process (per project directory). Both v1 and v2 load exactly one
|
|
73
|
-
instance per directory no matter how many clients are attached, so per-session
|
|
74
|
-
dedup and debounce are plain in-memory maps. Delivery reuses the platform
|
|
75
|
-
dispatcher that `thatch_notify_user` already uses.
|
|
76
|
-
|
|
77
|
-
```text
|
|
78
|
-
event hook (question/permission/session.*)
|
|
79
|
-
|
|
|
80
|
-
v
|
|
81
|
-
per-session alert state machine ---> classifier (real work?)
|
|
82
|
-
| |
|
|
83
|
-
v v
|
|
84
|
-
src/notify.ts dispatcher <--- config (alerts section)
|
|
85
|
-
(osascript / notify-send / say)
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Event capture
|
|
89
|
-
|
|
90
|
-
| Event | Action |
|
|
91
|
-
|---|---|
|
|
92
|
-
| `question.asked` | Notify "needs your answer". Track request id; clear on `question.replied` / `question.rejected`. |
|
|
93
|
-
| `permission.asked` | Notify "needs approval". Track request id; clear on `permission.replied`. |
|
|
94
|
-
| `session.status` busy or retry | Mark the session active. Clear any recorded error (a retry means opencode is still working). |
|
|
95
|
-
| `session.status` idle (from active) | Evaluate the round: classify, then notify or stay silent. |
|
|
96
|
-
| `session.error` | Record the error on the session. Do not notify yet -- the status transition decides. |
|
|
97
|
-
|
|
98
|
-
Both lines publish `question.asked` / `permission.asked` on the event bus with the
|
|
99
|
-
full request payload (sessionID included), and `session.status` carries
|
|
100
|
-
`{ type: "idle" | "busy" | "retry" }`. Verify both against the installed versions
|
|
101
|
-
before implementation (see Verification).
|
|
102
|
-
|
|
103
|
-
### Per-session state machine
|
|
104
|
-
|
|
105
|
-
State kept per session id: `active` (bool), `lastError` (name + message or null),
|
|
106
|
-
`pendingAsks` (request ids already notified). Transitions mirror the built-in v2
|
|
107
|
-
plugin's proven pattern, with error handling changed to match Jeff's intent:
|
|
108
|
-
|
|
109
|
-
- error followed by busy/retry -> recovered, clear the error, keep going quiet.
|
|
110
|
-
- error followed directly by idle -> nothing will wake the session; notify
|
|
111
|
-
"needs attention" instead of a done notification.
|
|
112
|
-
- `MessageAbortedError` -> always silent (the user pressed abort; they know).
|
|
113
|
-
- busy -> idle with no error -> the done path: classify the round, notify only
|
|
114
|
-
real work.
|
|
115
|
-
|
|
116
|
-
Notification state is rebuilt empty on plugin setup. Reload or restart mid-turn
|
|
117
|
-
means the busy->idle transition is never observed and that round stays silent.
|
|
118
|
-
Accepted.
|
|
119
|
-
|
|
120
|
-
### Round classification (shared classifier)
|
|
121
|
-
|
|
122
|
-
At idle, inspect the round's tool calls and skip when the round did no real work:
|
|
123
|
-
|
|
124
|
-
1. Skip sessions with a `parentID` (subagent children never notify).
|
|
125
|
-
2. Skip when the triggering user message is synthetic-only. This covers thatch
|
|
126
|
-
nudge turns, background-task completions, and watcher wake-ups. Without this
|
|
127
|
-
rule, zero-tool-call narration turns (the typical completion-injection turn)
|
|
128
|
-
would still notify.
|
|
129
|
-
3. Otherwise inspect the last assistant message's tool parts. Skip when every tool
|
|
130
|
-
call is in the meta set: `thatch_*`, the dispatch tools `task` / `agent` /
|
|
131
|
-
`subagent` (name differs by version -- match the set, never one name), `skill`,
|
|
132
|
-
`todowrite`, `question`, and `execute` calls whose code only wraps thatch tools
|
|
133
|
-
(reuse the `unwrapExecuteThatchCalls` scan from `src/extraction.ts`).
|
|
134
|
-
4. Real tool calls present -> notify "work finished", titled with the session
|
|
135
|
-
title (every notification identifies its session -- standing preference).
|
|
136
|
-
|
|
137
|
-
The meta-tool exclusion and the execute-unwrap scan already exist in the
|
|
138
|
-
extraction buffer's non-bufferable filter. Extract them into one shared pure
|
|
139
|
-
function (in `src/extraction.ts` or a sibling) consumed by both the buffer and
|
|
140
|
-
this classifier, so the aggregator-tool lessons live in one place. Unit tests for
|
|
141
|
-
the shared classifier extend the existing meta-tool buffer tests.
|
|
142
|
-
|
|
143
|
-
### Config
|
|
144
|
-
|
|
145
|
-
New `alerts` section in `~/.config/thatch/config.json` (schema in `src/config.ts`,
|
|
146
|
-
written via `config_set`, hand-editable, atomic save -- same as `notifications`):
|
|
147
|
-
|
|
148
|
-
```jsonc
|
|
149
|
-
{
|
|
150
|
-
"alerts": {
|
|
151
|
-
"pause": { "mode": "banner" }, // question + permission
|
|
152
|
-
"done": { "mode": "banner" }, // real-work turn end
|
|
153
|
-
"error": { "mode": "banner" } // stalled session
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
`mode` accepts `both` / `banner` / `voice` / `none`, defaulting to `banner` for
|
|
159
|
-
every event. Voice is opt-in per event: turn-end voice would fire far too often to
|
|
160
|
-
be the default, and `thatch_notify_user` remains the LLM-invoked voice channel.
|
|
161
|
-
Delivery goes through `sendNotification` in `src/notify.ts`, which already owns
|
|
162
|
-
per-platform dispatch (darwin: `osascript` banner + `/usr/bin/say`; linux:
|
|
163
|
-
`notify-send` / `spd-say` / `espeak`) and the injectable spawner tests need.
|
|
164
|
-
Spoken alerts carry the session-identifying source label, per the standing rule.
|
|
165
|
-
|
|
166
|
-
### Version support
|
|
167
|
-
|
|
168
|
-
The event hook, session status events, and the notify dispatcher exist on both
|
|
169
|
-
lines. Divergence is confined to two seams, both already abstracted in
|
|
170
|
-
`src/capabilities.ts` / the v1-v2 adapter layer:
|
|
171
|
-
|
|
172
|
-
- Message reading for the classifier: v2 exposes session context/messages through
|
|
173
|
-
the promise client; v1 has its own message-list shape. Map both into one
|
|
174
|
-
internal part view (tool calls + synthetic flags) in the capabilities layer.
|
|
175
|
-
- Tool naming: the dispatch tool is `task` (v1) vs `subagent` (v2); the classifier
|
|
176
|
-
matches the set.
|
|
177
|
-
|
|
178
|
-
## Verification (before implementation)
|
|
179
|
-
|
|
180
|
-
1. SSE probe against the installed v1.18.x serve: confirm `question.asked`,
|
|
181
|
-
`permission.asked`, and `session.status` appear on `/api/event` and carry the
|
|
182
|
-
session's directory as `location` (the plugin event hook drops events whose
|
|
183
|
-
location does not match).
|
|
184
|
-
2. Same probe against installed v2.x.
|
|
185
|
-
3. Confirm the message-list shape on both lines exposes tool parts (with tool
|
|
186
|
-
names) and synthetic flags, so the classifier can read them.
|
|
187
|
-
4. Confirm one plugin instance per directory in the shared server (both lines) --
|
|
188
|
-
this is the dedup guarantee, so verify rather than assume.
|
|
189
|
-
|
|
190
|
-
## Implementation milestones
|
|
191
|
-
|
|
192
|
-
1. Extract the shared round classifier (meta set + execute-unwrap) from the
|
|
193
|
-
extraction filter; unit tests first.
|
|
194
|
-
2. Alert state machine in the plugin event hook (`src/index.ts` or a new
|
|
195
|
-
`src/alerts.ts`): pause tracking, active/error/idle transitions.
|
|
196
|
-
3. Capabilities-layer message mapping for both lines.
|
|
197
|
-
4. `alerts` config section + `config_get` / `config_set` wiring + dispatcher
|
|
198
|
-
calls.
|
|
199
|
-
5. QA use cases + docs.
|
|
200
|
-
|
|
201
|
-
## Testing
|
|
202
|
-
|
|
203
|
-
- Unit: classifier cases (all-meta skip, real work notify, execute-wrapped unwrap,
|
|
204
|
-
dispatch-tool set, synthetic-only skip); state-machine transitions (busy->idle
|
|
205
|
-
notify once, error->retry quiet, error->idle notify, abort silent, dedup by
|
|
206
|
-
request id); config parsing and defaults.
|
|
207
|
-
- QA (`tests/qa/auto/`): drive a session through the plugin harness with the
|
|
208
|
-
injectable spawner capturing `sendNotification` calls; assert pause and done
|
|
209
|
-
alerts fire, nudge turns stay silent. Import the file from the suite's
|
|
210
|
-
`index.test.ts` barrel.
|
|
211
|
-
- Gate: `mise run check` and `mise run qa-auto` green before commit.
|
|
212
|
-
|
|
213
|
-
## Docs impact
|
|
214
|
-
|
|
215
|
-
- `docs/user/notifications.md` -- alerts section: what fires, how to configure
|
|
216
|
-
`alerts`, limitations (no focus gating, opencode-only).
|
|
217
|
-
- `docs/dev/features/notifications.md` -- architecture: event capture, state
|
|
218
|
-
machine, shared classifier, version seams.
|
|
219
|
-
- `docs/dev/README.md` -- module table row if a new module lands.
|
|
220
|
-
- `docs/dev/mcp-parity.md` -- row for the opencode-only alerts surface.
|
|
221
|
-
- `docs/dev/gotchas.md` -- entry if the v1/v2 event or message-shape seams cost
|
|
222
|
-
debugging time.
|
|
223
|
-
|
|
224
|
-
## Decisions already made
|
|
225
|
-
|
|
226
|
-
- Server-plugin delivery over TUI-plugin delivery (dedup by construction, unified
|
|
227
|
-
across versions).
|
|
228
|
-
- Reload state loss accepted; no `runtime_state` journaling.
|
|
229
|
-
- Watcher wake-up turns stay silent; the LLM can call `thatch_notify_user` when it
|
|
230
|
-
decides something is important (tool already exists).
|
|
231
|
-
- User aborts stay silent; unrecovered errors notify.
|
|
232
|
-
- Banner default, voice opt-in per event.
|
|
233
|
-
- Retire the orphaned v1 notifier config during rollout; leave v2's built-in
|
|
234
|
-
attention settings untouched unless doubles annoy (its TUI-side notifications
|
|
235
|
-
only fire when blurred).
|
|
236
|
-
|
|
237
|
-
## Deviations from the plan as written
|
|
238
|
-
|
|
239
|
-
Recorded during implementation and review pass 1 (crash-override-00004);
|
|
240
|
-
the review's blocking findings were real and fixed:
|
|
241
|
-
|
|
242
|
-
- **The round's tool side comes from live bookkeeping, not the message
|
|
243
|
-
list.** The plan's classifier read tool calls from messages and reused
|
|
244
|
-
`unwrapExecuteThatchCalls` there; message parts do not carry tool
|
|
245
|
-
arguments, so the unwrap was impossible at that seam (and was silently
|
|
246
|
-
dropped -- the round-1 review caught the plan Mandate going unmet). The
|
|
247
|
-
shipped design marks real work at the extraction buffer's push site
|
|
248
|
-
(`sessionRealWork`), which already classifies meta tools and unwraps
|
|
249
|
-
execute-wrapped thatch calls. `RoundShape` carries only the trigger side
|
|
250
|
-
(synthetic) and the round error. `deriveRoundShape` is exported and pure
|
|
251
|
-
(the review found the inline closure untestable, which is exactly where
|
|
252
|
-
the blocking bug hid).
|
|
253
|
-
- **The round delimiter is the host's idle marker on v2** (review B1):
|
|
254
|
-
v2.0.16 appends a `{type: "idle"}` marker message at every turn end, and
|
|
255
|
-
the plan's "newest non-assistant message is the trigger" scan picked the
|
|
256
|
-
marker -- every v2 round looked empty and done alerts were dead. The
|
|
257
|
-
round is now everything after the previous marker (the host's turn
|
|
258
|
-
definition; also fixes mid-round steers on v2). V1 keeps the trigger
|
|
259
|
-
scan (no markers; mid-round steers under-classify -- accepted v1
|
|
260
|
-
limitation).
|
|
261
|
-
- **A v2 execution failure now delivers the verdict immediately** (review
|
|
262
|
-
B2): failed is the only terminal event for a failed busy period -- no
|
|
263
|
-
idle follows it -- so the adapter translates it to the v1-shaped pair
|
|
264
|
-
(`session.error` + `session.status` idle). Interrupts translate to an
|
|
265
|
-
error named by reason (`user` -> MessageAbortedError silent, `shutdown`
|
|
266
|
-
-> SessionShutdownError silent, `inactivity` -> SessionInactivityError ->
|
|
267
|
-
needs-attention) + idle.
|
|
268
|
-
- **Unknown round shape stays silent**, not "notify". The original choice
|
|
269
|
-
(never miss a completion) made every adapter-level test whose client mock
|
|
270
|
-
lacks a message surface spawn a real osascript banner during `mise run
|
|
271
|
-
check` -- the state machine reached live delivery through the pump's
|
|
272
|
-
busy->idle path. Silence on a failed fetch is also the better product
|
|
273
|
-
behavior: alerts are best-effort, and the same fetch failure already
|
|
274
|
-
breaks the wrap-up greenlight check, so the session degrades consistently.
|
|
275
|
-
- **v1 event names verified from source, not SSE probe**: v1.18.9 publishes
|
|
276
|
-
`question.asked`/`question.replied`/`question.rejected`
|
|
277
|
-
(`packages/schema/src/v1/question.ts`) and `permission.asked`/
|
|
278
|
-
`permission.replied` (`packages/schema/src/v1/permission.ts`); the
|
|
279
|
-
installed v1 SDK's `types.gen.d.ts` is stale (no question events, and
|
|
280
|
-
`permission.updated` is a legacy type with no publish site). The events
|
|
281
|
-
flow through the plugin event hook as untyped `{type, properties}`, so
|
|
282
|
-
SDK staleness is harmless. Reply events carry `requestID` on both lines
|
|
283
|
-
(the review killed a defensive `permissionID` fallback as dead code).
|
|
284
|
-
- **v2 idle translation reused**: v2's SSE stream delivers the idle signal
|
|
285
|
-
as `session.execution.*` events (the existing `translateEvent` mapping),
|
|
286
|
-
so pause events needed translation cases there rather than a new
|
|
287
|
-
subscription path.
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# Thatch Actions: Prompt Cores Shared by Nudges and /thatch Commands
|
|
2
|
-
|
|
3
|
-
## Synopsis
|
|
4
|
-
|
|
5
|
-
Extract the behavior instructions currently embedded in nudge text into
|
|
6
|
-
prompt-core constants in `src/prompts.ts`, then build `/thatch/*` user-facing
|
|
7
|
-
commands on top of the same constants. The nudge and the command become two
|
|
8
|
-
envelopes around one instruction body: the nudge adds escalation tiers and
|
|
9
|
-
stop-and-wait framing, the command adds user-invocation framing. First
|
|
10
|
-
payoff is `/thatch/defrag`, but the structure covers any behavior the nudges
|
|
11
|
-
currently ask the agent to perform on demand.
|
|
12
|
-
|
|
13
|
-
## Background
|
|
14
|
-
|
|
15
|
-
Nudges are instruction-carrying messages injected by hooks. Several encode a
|
|
16
|
-
multi-step behavior the agent must execute (dispatch a sub-agent, fetch the
|
|
17
|
-
extraction payload, run a skill, ack). The same behaviors have no user-facing
|
|
18
|
-
invocation path: the user cannot say "drain the extraction queue now" or
|
|
19
|
-
"consolidate duplicate memories now" -- they can only wait for a nudge or
|
|
20
|
-
describe the desired behavior in prose and hope the agent maps it to the right
|
|
21
|
-
skill + tool sequence.
|
|
22
|
-
|
|
23
|
-
Two existing pieces make this cheap to add:
|
|
24
|
-
|
|
25
|
-
- `src/commands.ts` already ships `/thatch/compact` and `/thatch/exit` as
|
|
26
|
-
markdown command files synced into the opencode config dir, and already
|
|
27
|
-
composes them from shared fragments (`sharedChecklist`,
|
|
28
|
-
`userMessageSection`).
|
|
29
|
-
- The "Shared prose sync across prompt variants" problem is documented:
|
|
30
|
-
prompt prose lives in parallel functions with no single source of truth,
|
|
31
|
-
and drift in unasserted sections passes tests. This plan makes the shared
|
|
32
|
-
behavior text a true constant.
|
|
33
|
-
|
|
34
|
-
## Design: prompt cores and envelopes
|
|
35
|
-
|
|
36
|
-
A **prompt core** is a constant string in `src/prompts.ts` holding the
|
|
37
|
-
host-agnostic instruction body for one behavior: which tools to call, in what
|
|
38
|
-
order, with what arguments, and what "done" looks like.
|
|
39
|
-
|
|
40
|
-
An **envelope** wraps a core with delivery-specific framing:
|
|
41
|
-
|
|
42
|
-
- The nudge envelope adds: escalation tier wording, the load-bearing
|
|
43
|
-
stop-and-wait ending (see "Extraction nudge stop-and-wait wording is
|
|
44
|
-
load-bearing" -- the stop-and-wait text is nudge-only and must NOT leak
|
|
45
|
-
into action envelopes, because an action IS user input), and the
|
|
46
|
-
"system nudge, not user input" disclaimer.
|
|
47
|
-
- The action envelope adds: frontmatter description, the `# User Message`
|
|
48
|
-
section with `$ARGUMENTS`, and any greenlight-token mechanics (wrap-up
|
|
49
|
-
commands only).
|
|
50
|
-
|
|
51
|
-
Cores are plain string constants, not template functions, so both envelopes
|
|
52
|
-
interpolate them identically. Where a core needs host-specific tool names
|
|
53
|
-
(`thatch_memory_remember` vs `mcp__thatch__memory_remember`), the core takes
|
|
54
|
-
the opencode spelling and MCP consumers apply the documented token
|
|
55
|
-
substitution, matching the existing systemPrompt/mcpInstructions convention.
|
|
56
|
-
|
|
57
|
-
## Actions v1
|
|
58
|
-
|
|
59
|
-
| Command | Core behavior | Existing machinery |
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| `/thatch/defrag` | Run `thatch_find_duplicates`, classify each pair with the thatch-dedup-classifier skill, consolidate (remember/overwrite or forget), mark pairs checked | find_duplicates + dedup classifier skill |
|
|
62
|
-
| `/thatch/extract` | Fetch the extraction payload, run the fact-extractor skill, call `extraction_done` | get_extraction_payload + fact-extractor skill |
|
|
63
|
-
| `/thatch/hygiene` | Run the hygiene report, act on stale and orphaned branch-scoped memories | `hygieneReport()` + `/thatch hygiene` CLI |
|
|
64
|
-
| `/thatch/reflect` | Run the session-reflection skill over the current session | thatch-session-reflection skill |
|
|
65
|
-
|
|
66
|
-
The wrap-up commands (`compact`, `exit`) stay as-is; their checklist text
|
|
67
|
-
becomes a core reused by both templates (already true via `sharedChecklist`).
|
|
68
|
-
|
|
69
|
-
`/thatch/extract` interacts with opencode's direct extraction (parent idle
|
|
70
|
-
spawns the extractor child; the nudge is only a fallback). The action must
|
|
71
|
-
reuse the same accept/complete semantics and must not fight the `extracting`
|
|
72
|
-
set: if direct extraction is already running for the session, the action
|
|
73
|
-
should report that and stop rather than double-dispatch.
|
|
74
|
-
|
|
75
|
-
## Host parity
|
|
76
|
-
|
|
77
|
-
Commands are markdown or MCP prompts, synced per host. Behavior parity is the
|
|
78
|
-
goal; format differences are documented, not hidden.
|
|
79
|
-
|
|
80
|
-
- **opencode** (existing): `installOpencodeCommands` syncs markdown to
|
|
81
|
-
`~/.config/opencode/command/thatch/` on every plugin load.
|
|
82
|
-
- **Claude Code**: native user-level slash commands read markdown from
|
|
83
|
-
`~/.claude/commands/thatch/` (user scope) or `.claude/commands/thatch/`
|
|
84
|
-
(project scope), with the same `$ARGUMENTS` substitution. The sync lives in
|
|
85
|
-
`setup.ts` alongside the existing installers and follows their patterns:
|
|
86
|
-
idempotent content-compare like `installSkills`, both global and local
|
|
87
|
-
scopes, reported in the setup output next to the skills report.
|
|
88
|
-
(Deviation from the draft below: no `checkSetup` entry. That function
|
|
89
|
-
answers one question - "is thatch set up at all" - and command files
|
|
90
|
-
self-heal on every setup run, so flagging their absence as a startup
|
|
91
|
-
warning would be noise.) Differences: no plugin, so no
|
|
92
|
-
`command.execute.before` arming -- greenlight-token wrap-up commands do not
|
|
93
|
-
ship to Claude Code; and tool names use the `mcp__thatch__` prefix. MCP
|
|
94
|
-
hosts do have hooks now (session-start reminders, chat-wake stop hooks), so
|
|
95
|
-
hook wiring is not the barrier; the missing piece is only the plugin's TUI
|
|
96
|
-
control routes.
|
|
97
|
-
- **Cursor**: no file-based commands. The stdio MCP server (`mcp.ts`) gains
|
|
98
|
-
MCP Prompts support (`prompts/list`, `prompts/get`), which Cursor surfaces
|
|
99
|
-
as slash commands. The dispatch switch currently handles `initialize`,
|
|
100
|
-
`tools/list`, `tools/call`, and `ping`; prompts get a `compileTools`-style
|
|
101
|
-
`compilePrompts()` and two new cases. Prompt arguments use MCP's
|
|
102
|
-
`{{argument}}`-style templating, so each action core gets an MCP-prompt
|
|
103
|
-
rendering alongside the markdown rendering.
|
|
104
|
-
|
|
105
|
-
README gains "What works in Claude Code" and "What works in Cursor" sections
|
|
106
|
-
listing per-host support without restating the shared docs. The parity matrix
|
|
107
|
-
in `docs/dev/mcp-parity.md` gets an Actions row next to the existing
|
|
108
|
-
Wrap-up commands row.
|
|
109
|
-
|
|
110
|
-
## Session ID plumbing
|
|
111
|
-
|
|
112
|
-
Since the plan was drafted, `HostToolContext` (tool-defs.ts) landed: every
|
|
113
|
-
tool's `execute` receives the invoking session's ID as a third parameter on
|
|
114
|
-
the opencode path, and the watch tools already consume it. `extraction_done`
|
|
115
|
-
also already has the optional `session_id` with the omit-vs-parent rule in
|
|
116
|
-
its description. The remaining work is narrower than first planned:
|
|
117
|
-
|
|
118
|
-
- `get_extraction_payload`: `session_id` becomes optional. `execute` uses
|
|
119
|
-
`args.session_id ?? host?.sessionID`. On the opencode path the wrapper
|
|
120
|
-
supplies the invoking session, so omitted means "this session."
|
|
121
|
-
- MCP path: the host context is omitted (no session concept), so an omitted
|
|
122
|
-
`session_id` returns a clear error telling the model to pass the parent
|
|
123
|
-
session's ID from the extraction nudge.
|
|
124
|
-
|
|
125
|
-
One wrinkle to document: the zod arg shape is shared between hosts, so
|
|
126
|
-
optionality is global -- you cannot make the arg required on MCP and optional
|
|
127
|
-
on opencode at the schema level. Enforcement happens in `execute` per host,
|
|
128
|
-
and the arg description must carry the rule so the model self-corrects.
|
|
129
|
-
|
|
130
|
-
The rule the LLM must apply, stated everywhere the tools are described:
|
|
131
|
-
|
|
132
|
-
> Omit `session_id` when acting on the session you are running in. Pass the
|
|
133
|
-
> parent session's `session_id` when running inside a sub-agent that was
|
|
134
|
-
> dispatched to process another session's extraction queue.
|
|
135
|
-
|
|
136
|
-
This rule appears in: the arg descriptions in `tool-defs.ts`, the extraction
|
|
137
|
-
prompt core (used by both nudge and action envelopes), the fact-extractor
|
|
138
|
-
skill, and the dev docs. Tests cover both paths: omit-resolves-to-current
|
|
139
|
-
and explicit-parent-ID (the existing sub-agent drain bug's regression path).
|
|
140
|
-
|
|
141
|
-
## Decisions
|
|
142
|
-
|
|
143
|
-
- Prompt cores live in `src/prompts.ts`, not a new module; `commands.ts`
|
|
144
|
-
imports them. One prompt module, consistent with the existing module map.
|
|
145
|
-
- Actions are generated, not hand-written: each action has one core and a
|
|
146
|
-
per-host renderer, so a wording change cannot drift between nudge and
|
|
147
|
-
command.
|
|
148
|
-
- No `--dry-run` flag for defrag in v1; the dedup classifier skill already
|
|
149
|
-
asks for pair-by-pair verdicts, which is the interactive safety net.
|
|
150
|
-
- MCP Prompts for Cursor are additive to `mcp.ts` and do not change the
|
|
151
|
-
tools/list contract.
|
|
152
|
-
|
|
153
|
-
## Testing
|
|
154
|
-
|
|
155
|
-
- `tests/commands.test.ts` (or the existing plugin tests): command-file
|
|
156
|
-
content assertions per host, including that each action's markdown contains
|
|
157
|
-
its core text verbatim.
|
|
158
|
-
- Claude setup tests: command sync writes, updates only on content change,
|
|
159
|
-
and `checkSetup` flags a missing or stale command file.
|
|
160
|
-
- MCP tests: `prompts/list` returns every action; `prompts/get` interpolates
|
|
161
|
-
arguments; an omitted-session-ID `get_extraction_payload` call errors with
|
|
162
|
-
the pass-the-parent-ID message (no host context on the MCP path).
|
|
163
|
-
- Plugin tests: `get_extraction_payload` without `session_id` resolves via
|
|
164
|
-
the host context to the invoking session; with an explicit parent ID,
|
|
165
|
-
drains the parent's queue (the existing sub-agent drain bug's regression
|
|
166
|
-
path).
|
|
167
|
-
- Parity guard: a test asserts every opencode command def has a
|
|
168
|
-
corresponding MCP prompt (and vice versa) except the documented exclusions
|
|
169
|
-
(wrap-up greenlight commands on non-opencode hosts).
|
|
170
|
-
|
|
171
|
-
## Docs
|
|
172
|
-
|
|
173
|
-
- `docs/dev/features/commands.md` and `docs/user/commands.md` (two-tier):
|
|
174
|
-
action list, host parity matrix, session ID rule.
|
|
175
|
-
- `docs/dev/mcp-parity.md`: Actions row in the parity matrix; revisit the
|
|
176
|
-
Wrap-up commands row if greenlight arming ever lands on MCP hosts.
|
|
177
|
-
- README: "What works in Claude Code" / "What works in Cursor" sections.
|
|
178
|
-
- QA use cases: one auto use case for `/thatch/defrag` end-to-end, one for
|
|
179
|
-
`/thatch/extract` with omitted session ID.
|