@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.
- package/README.md +48 -25
- package/artifacts/skills/common.md +1 -0
- package/artifacts/skills/thatch-code-archaeology.md +9 -0
- package/artifacts/skills/thatch-code-review.md +16 -8
- package/artifacts/skills/thatch-plan-refinement.md +33 -4
- 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-response.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +21 -1
- package/bin/thatch +21 -12
- package/docs/dev/README.md +11 -8
- package/docs/dev/features/README.md +1 -1
- package/docs/dev/features/behavior-engine.md +10 -0
- package/docs/dev/features/cli.md +2 -1
- package/docs/dev/features/commands.md +40 -9
- package/docs/dev/features/cross-session-chat.md +85 -21
- package/docs/dev/features/database.md +4 -4
- package/docs/dev/features/nudge-pipeline.md +14 -0
- package/docs/dev/features/opencode-plugin.md +41 -15
- package/docs/dev/features/prediction-engine.md +27 -1
- 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 +11 -5
- package/docs/dev/gotchas.md +21 -0
- package/docs/dev/mcp-parity.md +3 -3
- package/docs/dev/skills.md +11 -8
- package/docs/user/README.md +19 -12
- package/docs/user/behavior-engine.md +7 -1
- package/docs/user/cli.md +2 -1
- package/docs/user/code-review.md +43 -2
- package/docs/user/commands.md +27 -7
- package/docs/user/cross-session-chat.md +47 -20
- package/docs/user/prediction-engine.md +12 -1
- package/docs/user/skills.md +8 -6
- package/package.json +1 -1
- package/src/capabilities.ts +20 -6
- package/src/chat.ts +39 -17
- package/src/commands.ts +109 -18
- package/src/db.ts +23 -2
- package/src/mcp.ts +6 -2
- package/src/opencode/tui-plugin.ts +29 -9
- package/src/opencode/v2.ts +247 -66
- package/src/prompts.ts +54 -6
- package/src/runtime.ts +122 -17
- package/src/scoring-engine.ts +40 -9
- package/src/session-tab-shared.ts +55 -4
- package/src/skills.ts +5 -9
- package/src/tab-state.ts +125 -0
- package/src/tool-defs.ts +42 -21
package/docs/user/commands.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Slash commands
|
|
2
2
|
|
|
3
3
|
Thatch ships `/thatch/*` commands you run yourself, on top of the nudges the
|
|
4
|
-
plugin fires on its own schedule.
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
plugin fires on its own schedule. The nudge-backed commands (`defrag`,
|
|
5
|
+
`extract`, `hygiene`, `reflect`) share their instruction text with the
|
|
6
|
+
nudges, so a behavior works the same whether it fires automatically or you
|
|
7
|
+
invoke it. The remaining commands (`refine`, `self-review`, `peer-review`,
|
|
8
|
+
`whois`) stand alone.
|
|
7
9
|
|
|
8
10
|
## Commands
|
|
9
11
|
|
|
@@ -26,9 +28,24 @@ or you invoke it.
|
|
|
26
28
|
project type's requirements: the agent classifies the project (team,
|
|
27
29
|
personal, shared library, spike, infrastructure, or open source
|
|
28
30
|
contribution), confirms the pick, and runs the refinement loop with
|
|
29
|
-
that type's expectations applied.
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
that type's expectations applied. Project type relaxes round counts
|
|
32
|
+
and documentation bars, never the deep-mode triggers. Text after the
|
|
33
|
+
command can name the type directly (for example, `/thatch/refine
|
|
34
|
+
shared-lib`) to skip classification.
|
|
35
|
+
- `/thatch/peer-review` — review another author's change: the full review
|
|
36
|
+
pipeline with findings weighted for org velocity. Text after the command
|
|
37
|
+
names the review target (for example, `/thatch/peer-review 1234` for a
|
|
38
|
+
PR). The review walks the project's persistent memory for
|
|
39
|
+
incidents, constraints, and footguns the author could not have known,
|
|
40
|
+
and checks them against the change. Settled designs are not
|
|
41
|
+
re-litigated, and "if someone changes this later it breaks" findings
|
|
42
|
+
are never reported. See [code-review.md](code-review.md).
|
|
43
|
+
- `/thatch/self-review` — review your own completed work before
|
|
44
|
+
submitting it: the full review pipeline weighted for reputation and
|
|
45
|
+
simplicity. Takes the same target argument as peer-review. Rounds
|
|
46
|
+
iterate (fix or decline each finding, then re-review the updated
|
|
47
|
+
state) until a round produces no meaningful findings, at most three.
|
|
48
|
+
See [code-review.md](code-review.md).
|
|
32
49
|
- `/thatch/compact` and `/thatch/exit` — the wrap-up commands. They run a
|
|
33
50
|
pre-flight checklist (flush extraction, finish promised memory writes,
|
|
34
51
|
surface loose ends) before a compaction or an exit, gated by a greenlight
|
|
@@ -45,11 +62,14 @@ or you invoke it.
|
|
|
45
62
|
| `/thatch/reflect` | yes | yes | yes (as an MCP prompt) |
|
|
46
63
|
| `/thatch/whois` | yes | no | no |
|
|
47
64
|
| `/thatch/refine` | yes | yes | yes (as an MCP prompt) |
|
|
65
|
+
| `/thatch/peer-review` | yes | yes | yes (as an MCP prompt) |
|
|
66
|
+
| `/thatch/self-review` | yes | yes | yes (as an MCP prompt) |
|
|
48
67
|
| `/thatch/compact`, `/thatch/exit` | yes | no | no |
|
|
49
68
|
|
|
50
69
|
Why the gaps: `/thatch/extract` and `/thatch/whois` need the invoking
|
|
51
70
|
session's ID, which only the opencode plugin can supply. The wrap-up
|
|
52
|
-
commands trigger
|
|
71
|
+
commands trigger host actions (the compaction and the session close-out)
|
|
72
|
+
through the plugin. Cursor has no file-based
|
|
53
73
|
commands; its prompts come from the MCP server (`prompts/list` and
|
|
54
74
|
`prompts/get`).
|
|
55
75
|
|
|
@@ -25,13 +25,20 @@ explicitly with `thatch_chat_register` (no arguments) - useful after
|
|
|
25
25
|
`chat_unregister`, or just to check your assigned name. Once registered:
|
|
26
26
|
|
|
27
27
|
- `thatch_chat_list` shows every registered session on the machine, grouped
|
|
28
|
-
into Active and Stale sections: active sessions
|
|
29
|
-
have missed two heartbeats, about a minute (the opencode process
|
|
30
|
-
them has probably stopped). Each row carries
|
|
28
|
+
into Active and Stale sections: active sessions' harness is alive; stale
|
|
29
|
+
ones have missed two heartbeats, about a minute (the opencode process
|
|
30
|
+
hosting them has probably stopped). Each active row also carries a
|
|
31
|
+
liveness verdict: `open` means the session has an opencode tab open
|
|
32
|
+
right now, `recent` means the harness is alive but no tab is open. Each
|
|
33
|
+
row carries how long ago the session last
|
|
31
34
|
checked in, its project, whether it runs in the project root or a linked
|
|
32
35
|
git worktree, its topic, and your unread count
|
|
33
36
|
- `thatch_chat_send` delivers a message to another registered session, by
|
|
34
37
|
name or session id, and states the recipient's liveness at send time.
|
|
38
|
+
For a recipient with no open tab it says so plainly: the message waits
|
|
39
|
+
in the inbox, but the wake is not guaranteed to start a turn
|
|
40
|
+
(daemon-resident sessions like subordinates usually still pick the
|
|
41
|
+
message up on their next activity).
|
|
35
42
|
An identical resend within a few seconds is treated as a client retry:
|
|
36
43
|
while the first copy is still unread it succeeds but delivers nothing
|
|
37
44
|
new (the tool output says so); once the recipient has read it, the same
|
|
@@ -46,13 +53,16 @@ explicitly with `thatch_chat_register` (no arguments) - useful after
|
|
|
46
53
|
the rest with the `message` and `offset` arguments (each drain line
|
|
47
54
|
carries the message id). Paging never marks anything read
|
|
48
55
|
- `thatch_chat_broadcast` delivers one message to every other live
|
|
49
|
-
registered session
|
|
50
|
-
like a split `chat_send`)
|
|
56
|
+
registered session that has a tab open, plus every MCP session (a long
|
|
57
|
+
body is split per recipient, exactly like a split `chat_send`)
|
|
51
58
|
- `thatch_chat_unregister` leaves the directory
|
|
52
59
|
|
|
53
60
|
When mail arrives for an idle session, thatch wakes it with a notification -
|
|
54
61
|
the agent gets a model turn even with nobody at the keyboard, reads its
|
|
55
|
-
inbox, and decides what to do.
|
|
62
|
+
inbox, and decides what to do. If the session's directory has been deleted
|
|
63
|
+
(a merged branch's worktree, for example), the wake is skipped quietly:
|
|
64
|
+
no error appears, the mail stays queued, and it delivers normally once the
|
|
65
|
+
directory exists again. The notification names the senders and the
|
|
56
66
|
count only; the agent reads the actual messages itself. A split message
|
|
57
67
|
arrives as one grouped notification ("N unread messages from X"), and the
|
|
58
68
|
parts carry their own markers so the reader assembles the whole before
|
|
@@ -61,10 +71,14 @@ the last is suffixed "(continued in next message)".
|
|
|
61
71
|
|
|
62
72
|
Broadcasts are for announcements and open questions ("which of you is
|
|
63
73
|
working on the payments code?", "main just moved, rebase if you are based
|
|
64
|
-
on it"). Every
|
|
74
|
+
on it"). Every OPEN session gets the message and spends a model turn on it,
|
|
65
75
|
so broadcast sparingly - for anything with a known audience, `chat_send` is
|
|
66
|
-
the polite tool.
|
|
67
|
-
never
|
|
76
|
+
the polite tool. Sessions without an open tab are skipped and reported by
|
|
77
|
+
name: a stale session will never read the mail, and a fresh-but-tab-less
|
|
78
|
+
session (a closed tab, or a headless subordinate) is not reliably wakeable,
|
|
79
|
+
so messaging it would spend a turn on a delivery that likely never runs.
|
|
80
|
+
With opencode tabs disabled, broadcasts reach only MCP sessions - that is
|
|
81
|
+
the honest reading of "no tab signal", never a false fan-out.
|
|
68
82
|
|
|
69
83
|
## Watching the conversation
|
|
70
84
|
|
|
@@ -149,21 +163,34 @@ host the sessions run in.
|
|
|
149
163
|
|
|
150
164
|
## Liveness
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
its
|
|
166
|
+
Liveness has three levels, and the roster shows which one a row is in.
|
|
167
|
+
|
|
168
|
+
The base signal is the heartbeat: a session shows as fresh while the
|
|
169
|
+
opencode process hosting it is running - hosted sessions keep beating
|
|
170
|
+
(idle ones included), so the freshness is real. When that process dies,
|
|
171
|
+
its sessions stop beating and show as stale about a minute later (two
|
|
172
|
+
missed heartbeats), grouped into a separate Stale section. A laptop that
|
|
173
|
+
slept reads the same way until its processes catch up.
|
|
174
|
+
|
|
175
|
+
The second signal, read at roster time, is opencode's own tab state: a
|
|
176
|
+
fresh row whose session has a tab open right now shows as `open`, one
|
|
177
|
+
without shows as `recent`. `recent` covers finished-but-recently-active
|
|
178
|
+
conversations, closed tabs, and daemon-resident sessions that never had a
|
|
179
|
+
window (subordinates) - the harness is alive, but presence is unknown.
|
|
180
|
+
With tabs disabled (or no opencode state on the machine) every fresh row
|
|
181
|
+
shows as `recent`: an unknown tab state never reads as open.
|
|
182
|
+
|
|
158
183
|
Messages to a stale session wait in its inbox unread - a dead session
|
|
159
|
-
never reads them, and `chat_send` says so at send time.
|
|
184
|
+
never reads them, and `chat_send` says so at send time. Messages to a
|
|
185
|
+
`recent` session are delivered, but `chat_send` warns that the wake is
|
|
186
|
+
not guaranteed without an open tab. Continuing a
|
|
160
187
|
session with `opencode -s <id>` reclaims its name and row, and any mail
|
|
161
188
|
that queued while it was down is delivered at startup. Explicitly
|
|
162
189
|
closing a session (deleting it in the TUI) unregisters it immediately,
|
|
163
|
-
and a greenlit `/thatch/exit` unregisters the session too: the
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
addressing mail to a
|
|
190
|
+
and a greenlit `/thatch/exit` unregisters the session too: the plugin
|
|
191
|
+
removes the row itself when the exit token passes the greenlight check,
|
|
192
|
+
so other sessions stop
|
|
193
|
+
addressing mail to a session that is closing out.
|
|
167
194
|
|
|
168
195
|
## Turning chat off
|
|
169
196
|
|
|
@@ -46,10 +46,21 @@ like:
|
|
|
46
46
|
flag tech debt before reviewing the diff
|
|
47
47
|
- [0.61 conf, 2 tests] When choosing test scope: you may prefer
|
|
48
48
|
integration tests over unit tests
|
|
49
|
+
Snapshot: these labels reflect the prediction store as of this message's
|
|
50
|
+
submission. Predictions updated after submission (including during this
|
|
51
|
+
turn) do not refresh it; re-check with thatch_prediction_query before
|
|
52
|
+
relying on them.
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
The agent reads this and decides whether to follow the prediction,
|
|
52
|
-
surface it to you, or ignore it. You never see the nudge directly.
|
|
56
|
+
surface it to you, or ignore it. You never see the nudge directly. The
|
|
57
|
+
snapshot line matters when the agent corrects the model mid-conversation
|
|
58
|
+
(you say "no, that's wrong" and it calls `thatch_prediction_delete`):
|
|
59
|
+
the block it saw at the start of the turn is a submission-time snapshot
|
|
60
|
+
and does not update. The agent should re-query with
|
|
61
|
+
`thatch_prediction_query` instead of trusting the block's labels. On
|
|
62
|
+
Claude Code and Cursor the tool name appears with its MCP spelling
|
|
63
|
+
(`mcp__thatch__prediction_query`).
|
|
53
64
|
|
|
54
65
|
## Tools
|
|
55
66
|
|
package/docs/user/skills.md
CHANGED
|
@@ -48,11 +48,11 @@ Skills are split into shared (all hosts) and opencode-only:
|
|
|
48
48
|
| `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar. |
|
|
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, never blocking. |
|
|
50
50
|
| `thatch-review-synthesizer` | Verifies and synthesizes findings from multiple specialists into a deduplicated, severity-grouped report. |
|
|
51
|
-
| `thatch-review-context` | Gathers project context (PRs, tickets, linked docs/designs followed from the change, TODOs, deferred work) before a review. Prevents false positives about intentionally deferred work. |
|
|
51
|
+
| `thatch-review-context` | Gathers project context (PRs, tickets, linked docs/designs followed from the change, TODOs, deferred work, 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. |
|
|
52
52
|
| `thatch-code-archaeology` | Investigates an existing feature, debugs an unfamiliar area, or begins a new ticket. The research skill; pair with `thatch-coding-workflow`. |
|
|
53
53
|
| `thatch-review-followup` | Verifies whether the author's responses and code changes since your last review adequately addressed your prior findings. |
|
|
54
54
|
| `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. |
|
|
55
|
-
| `thatch-code-review` | Multi-agent review coordinator (opencode-only). Dispatches parallel sub-agents for specialist fan-out and synthesis. |
|
|
55
|
+
| `thatch-code-review` | Multi-agent review coordinator (opencode-only). Dispatches parallel sub-agents for specialist fan-out and synthesis. Also the machinery behind the `/thatch/self-review` and `/thatch/peer-review` mode commands, which couch it with a weighting overlay and run the specialists inline on hosts where the coordinator is not installed. |
|
|
56
56
|
|
|
57
57
|
### Walkthrough skills
|
|
58
58
|
|
|
@@ -75,15 +75,15 @@ Skills are split into shared (all hosts) and opencode-only:
|
|
|
75
75
|
| Skill | Purpose |
|
|
76
76
|
|-------|---------|
|
|
77
77
|
| `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). |
|
|
78
|
-
| `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). |
|
|
78
|
+
| `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. |
|
|
79
79
|
|
|
80
80
|
### Plan refinement skills
|
|
81
81
|
|
|
82
82
|
| Skill | Purpose |
|
|
83
83
|
|-------|---------|
|
|
84
|
-
| `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. |
|
|
84
|
+
| `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. |
|
|
85
85
|
| `thatch-refine-team-app` | Team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate. |
|
|
86
|
-
| `thatch-refine-personal` | Solo projects: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. |
|
|
86
|
+
| `thatch-refine-personal` | Solo projects: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. Relaxes round counts, never mode selection. |
|
|
87
87
|
| `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. |
|
|
88
88
|
| `thatch-refine-spike` | Throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on. |
|
|
89
89
|
| `thatch-refine-infra` | CI, Terraform, and k8s changes: blast radius, mandatory rollback plan and staged verification, pattern-following is near-absolute. |
|
|
@@ -104,7 +104,9 @@ window.
|
|
|
104
104
|
re-installs from its canonical source). Edit the canonical source
|
|
105
105
|
in the thatch repo instead.
|
|
106
106
|
- The opencode-only coordinator (`thatch-code-review`) requires
|
|
107
|
-
sub-agent support. It is not available on Claude Code or Cursor
|
|
107
|
+
sub-agent support. It is not available on Claude Code or Cursor; the
|
|
108
|
+
`/thatch/self-review` and `/thatch/peer-review` commands run the
|
|
109
|
+
same specialists inline there instead.
|
|
108
110
|
- Skill content is markdown read at runtime, not compiled into the
|
|
109
111
|
binary. If the skill file is missing or corrupted, the skill will
|
|
110
112
|
not load (silent failure).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/capabilities.ts
CHANGED
|
@@ -54,7 +54,13 @@ export interface HostCapabilities {
|
|
|
54
54
|
* only the title carries over.
|
|
55
55
|
*/
|
|
56
56
|
sessionCreate(input: { parentID: string; title: string }): Promise<{ id: string }>;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Delete a session (extraction-child cleanup, so the picker stays clean
|
|
59
|
+
* and `-c` resume does not land in a child). v1: client delete. v2:
|
|
60
|
+
* session.remove on the promise domain (upstream #52387), runtime-guarded
|
|
61
|
+
* because the pinned dev types predate the surface - an older host
|
|
62
|
+
* degrades to a no-op (best-effort by the caller's contract).
|
|
63
|
+
*/
|
|
58
64
|
sessionDelete(id: string): Promise<void>;
|
|
59
65
|
/**
|
|
60
66
|
* Prompt a session with text parts. `sync` mode blocks until the child
|
|
@@ -90,15 +96,23 @@ export interface HostCapabilities {
|
|
|
90
96
|
/**
|
|
91
97
|
* Trigger the host's compaction for a session (the wrap-up compact
|
|
92
98
|
* action). v1 dispatches the TUI's session_compact command (the legacy
|
|
93
|
-
* alias route; the id is ignored). v2
|
|
94
|
-
*
|
|
99
|
+
* alias route; the id is ignored). v2 calls session.compact on the
|
|
100
|
+
* promise domain (upstream #52385), runtime-guarded because the pinned
|
|
101
|
+
* dev types predate that surface; an older host degrades to a logged
|
|
102
|
+
* no-op.
|
|
95
103
|
*/
|
|
96
104
|
compactSession(sessionID: string): Promise<void>;
|
|
97
105
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
106
|
+
* End the session's presence on the host (the wrap-up exit action). The
|
|
107
|
+
* runtime records the session's watcher deaths and unregisters it from
|
|
108
|
+
* chat BEFORE calling here, so implementations only close out the host
|
|
109
|
+
* side. v1 publishes the TUI's app.exit command - the id is unused
|
|
110
|
+
* because the whole app is leaving. v2 emits the session-tab bridge's
|
|
111
|
+
* exit-tab-closed event, a TUI-only close of the session's OWN tab (the
|
|
112
|
+
* daemon hosts every tab, so an app-wide exit would take down unrelated
|
|
113
|
+
* sessions too); a host with no bridge degrades to a logged no-op.
|
|
100
114
|
*/
|
|
101
|
-
exitHost(): Promise<void>;
|
|
115
|
+
exitHost(sessionID: string): Promise<void>;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
/**
|
package/src/chat.ts
CHANGED
|
@@ -351,18 +351,28 @@ export function humanAge(iso: string, now = Date.now()): string {
|
|
|
351
351
|
return `${Math.floor(minutes / (24 * 60))}d ago`;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
/** The liveness verdict for one roster row, from heartbeat age
|
|
355
|
-
*
|
|
356
|
-
* - "
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
|
|
354
|
+
/** The liveness verdict for one roster row, from heartbeat age plus (for
|
|
355
|
+
* opencode rows) the open-tab set:
|
|
356
|
+
* - "open" - fresh heartbeat AND a tab is open now (session presence)
|
|
357
|
+
* - "recent" - fresh heartbeat, no known tab: an instance that saw this
|
|
358
|
+
* session is alive and beating it, but the session itself may or may
|
|
359
|
+
* not be wakeable (a closed tab's wake can be swallowed; a daemon-
|
|
360
|
+
* resident subordinate runs headless and may still pick up mail)
|
|
361
|
+
* - "stale" - the hosting harness has missed two beats and is presumed
|
|
362
|
+
* gone
|
|
363
|
+
* - "active"/"idle" for MCP rows (turn-driven; idle between prompts is
|
|
364
|
+
* the NORMAL state, so an idle MCP row is reachable, not dead)
|
|
365
|
+
* Heartbeat age alone decides staleness - a process id cannot serve: the
|
|
366
|
+
* same session is re-hosted by a new process on every `-s` resume, so a
|
|
367
|
+
* pid stamped on the row lies as soon as a different harness beats it.
|
|
368
|
+
* The tab set is a READ-ONLY refinement (src/tab-state.ts) consumed at
|
|
369
|
+
* render/broadcast time; omitting it degrades every fresh opencode row
|
|
370
|
+
* to "recent" - honest, never a false "open". */
|
|
371
|
+
export function chatLiveness(row: ChatSessionRow, now = Date.now(), openTabs?: ReadonlySet<string>): "open" | "recent" | "stale" | "active" | "idle" {
|
|
363
372
|
const stale = isStale(row, now);
|
|
364
373
|
if (row.host_kind === "mcp") return stale ? "idle" : "active";
|
|
365
|
-
|
|
374
|
+
if (stale) return "stale";
|
|
375
|
+
return openTabs?.has(row.session_id) ? "open" : "recent";
|
|
366
376
|
}
|
|
367
377
|
|
|
368
378
|
/** Orders roster rows for display: project first (so a shared-tree reader
|
|
@@ -858,10 +868,16 @@ export class ChatStore {
|
|
|
858
868
|
}
|
|
859
869
|
|
|
860
870
|
/**
|
|
861
|
-
* Posts a message to every other registered session at once.
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
*
|
|
871
|
+
* Posts a message to every other registered session at once. Opencode
|
|
872
|
+
* rows are skipped unless they are OPEN - a fresh heartbeat AND a tab
|
|
873
|
+
* open right now. Both skip classes are honest: a STALE row's host
|
|
874
|
+
* process has stopped heartbeat-ing and will never read the mail, and a
|
|
875
|
+
* RECENT row (fresh heartbeat, no open tab) is not reliably wakeable -
|
|
876
|
+
* the wake can be swallowed without a tab to receive it, so the fan-out
|
|
877
|
+
* would spend a model turn on a delivery that likely never runs. MCP
|
|
878
|
+
* rows always receive (they read mail at their next prompt). Every skip
|
|
879
|
+
* is listed by name in `skipped` so the caller can see who was left
|
|
880
|
+
* out and why. Each recipient gets its own inbox row per part, so the
|
|
865
881
|
* existing wake machinery (grouping, gating, rate cap) treats the
|
|
866
882
|
* broadcast as ordinary per-recipient mail. An oversized body is
|
|
867
883
|
* auto-split (splitChatBody) and every recipient gets every part inside
|
|
@@ -871,6 +887,7 @@ export class ChatStore {
|
|
|
871
887
|
broadcast(
|
|
872
888
|
fromSession: string,
|
|
873
889
|
body: string,
|
|
890
|
+
openTabs?: ReadonlySet<string>,
|
|
874
891
|
): { ok: true; recipients: string[]; skipped: string[]; deduped: number; parts: number } | { ok: false; error: string } {
|
|
875
892
|
const trimmed = body.trim();
|
|
876
893
|
if (!trimmed) return { ok: false, error: `Message body cannot be empty (attempted ${body.length} characters).` };
|
|
@@ -885,6 +902,9 @@ export class ChatStore {
|
|
|
885
902
|
};
|
|
886
903
|
}
|
|
887
904
|
const parts = splitChatBody(trimmed);
|
|
905
|
+
// No provider = no tab signal: every fresh opencode row degrades to
|
|
906
|
+
// RECENT (honest - an unknown tab state never reads as open).
|
|
907
|
+
const open = openTabs ?? new Set<string>();
|
|
888
908
|
const recipients: string[] = [];
|
|
889
909
|
const skipped: string[] = [];
|
|
890
910
|
let deduped = 0;
|
|
@@ -894,10 +914,12 @@ export class ChatStore {
|
|
|
894
914
|
this.#db.transaction(() => {
|
|
895
915
|
for (const row of this.list()) {
|
|
896
916
|
if (row.session_id === fromSession) continue;
|
|
897
|
-
//
|
|
898
|
-
//
|
|
917
|
+
// Two honest skip classes for opencode rows: STALE (host process
|
|
918
|
+
// gone - never reads the mail) and RECENT (fresh heartbeat, no
|
|
919
|
+
// open tab - the wake is not guaranteed to run a turn). An mcp
|
|
920
|
+
// row between turns reads its mail at the next prompt, so it
|
|
899
921
|
// always receives.
|
|
900
|
-
if (row.host_kind === "opencode" && isStale(row)) {
|
|
922
|
+
if (row.host_kind === "opencode" && (isStale(row) || !open.has(row.session_id))) {
|
|
901
923
|
skipped.push(row.name);
|
|
902
924
|
continue;
|
|
903
925
|
}
|
package/src/commands.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import {
|
|
4
|
+
REVIEW_SPECIALISTS,
|
|
4
5
|
defragCore,
|
|
5
6
|
extractionCore,
|
|
6
7
|
hygieneCore,
|
|
@@ -24,12 +25,17 @@ import {
|
|
|
24
25
|
*
|
|
25
26
|
* Two command families live here:
|
|
26
27
|
*
|
|
27
|
-
* - Actions (/thatch/defrag, extract, hygiene,
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* nudge and command wording cannot drift.
|
|
28
|
+
* - Actions (/thatch/defrag, extract, hygiene, peer-review, refine,
|
|
29
|
+
* reflect, self-review, whois): user-invoked commands. The nudge-backed
|
|
30
|
+
* ones (defrag, extract, hygiene, reflect) render prompt cores from
|
|
31
|
+
* src/prompts.ts, so nudge and command wording cannot drift. The rest
|
|
32
|
+
* (peer-review, refine, self-review, whois) are self-contained here:
|
|
33
|
+
* they have no nudge counterpart. peer-review and self-review couch the
|
|
34
|
+
* code review pipeline with a mode overlay: the same machinery, with
|
|
35
|
+
* findings weighted for whose work is under review.
|
|
31
36
|
* - Wrap-ups (/thatch/compact, exit): greenlight-token commands that need
|
|
32
|
-
* the opencode plugin's
|
|
37
|
+
* the opencode plugin's host action routes (the compact trigger and the
|
|
38
|
+
* session close-out), so they are opencode-only.
|
|
33
39
|
*/
|
|
34
40
|
|
|
35
41
|
/** Greenlight token ending /thatch/compact's assistant response when compaction is safe. */
|
|
@@ -39,11 +45,13 @@ export const COMPACT_READY_TOKEN = "THATCH_COMPACT_READY";
|
|
|
39
45
|
export const EXIT_READY_TOKEN = "THATCH_EXIT_READY";
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
48
|
+
* The greenlight token's host action, per host: v1 drives the TUI
|
|
49
|
+
* (compaction via the "session_compact" legacy alias through
|
|
50
|
+
* executeCommand, the exit via the "app.exit" keymap command through
|
|
51
|
+
* /tui/publish). v2 calls session.compact on the plugin's promise domain
|
|
52
|
+
* and emits the session-tab bridge's TUI-only exit-tab-closed event; the
|
|
53
|
+
* runtime records the exiting session's watcher deaths before either host
|
|
54
|
+
* action runs (src/runtime.ts).
|
|
47
55
|
*/
|
|
48
56
|
|
|
49
57
|
const sharedChecklist = `1. Flush pending persistence. Call thatch_get_extraction_payload; if it returns buffered tool interactions, process them now (write any memories worth keeping), then call thatch_extraction_done to mark them complete. Complete any memory writes you promised earlier but have not made.
|
|
@@ -55,10 +63,42 @@ $ARGUMENTS
|
|
|
55
63
|
|
|
56
64
|
(That section carries the text typed after the command. When it is empty, treat the user message as n/a - the command was run bare.)`;
|
|
57
65
|
|
|
66
|
+
/**
|
|
67
|
+
* How the review-mode commands (peer-review, self-review) run the code
|
|
68
|
+
* review machinery on the host at hand. opencode has the
|
|
69
|
+
* thatch-code-review coordinator skill and sub-agent dispatch; Claude Code
|
|
70
|
+
* and Cursor have neither, so the same procedure runs inline through the
|
|
71
|
+
* shared specialist skills. The mode overlay that differentiates the two
|
|
72
|
+
* commands lives in each command's own body and applies in both paths:
|
|
73
|
+
* sub-agent briefings on opencode, specialist passes inline elsewhere.
|
|
74
|
+
*
|
|
75
|
+
* Both couchings below are shared consts interpolated into the two bodies
|
|
76
|
+
* so the commands' procedure and target resolution cannot drift; each body
|
|
77
|
+
* keeps only its mode rules. The specialist roster and count come from
|
|
78
|
+
* REVIEW_SPECIALISTS in src/prompts.ts, the same source as the install
|
|
79
|
+
* list in src/skills.ts.
|
|
80
|
+
*/
|
|
81
|
+
const reviewPipelineCouching = `If the thatch-code-review skill is available, load it and follow its steps, with one addition: apply the mode rules below throughout - to context gathering, every specialist briefing, and the synthesis.
|
|
82
|
+
|
|
83
|
+
If that skill is not available on this host, run the same procedure yourself, in order:
|
|
84
|
+
- Resolve the review range. Compute the merge-base against the base branch's remote tracking ref (for example origin/main), not the working branch's own upstream, and cite every file:line against the range's head SHA with git show, never the working tree. A PR or MR number resolves through the host's CLI (gh pr view, glab mr view) plus fetching the head ref (git fetch origin refs/pull/N/head on GitHub, refs/merge-requests/N/head on GitLab, or the source branch). A repository with no remote reviews the working tree's recent commits instead.
|
|
85
|
+
- Build the context brief with the thatch-review-context skill.
|
|
86
|
+
- Partition large diffs into review units.
|
|
87
|
+
- Run the ${REVIEW_SPECIALISTS.length} specialist skills (${REVIEW_SPECIALISTS.join(", ")}) on each unit.
|
|
88
|
+
- Synthesize with thatch-review-synthesizer.
|
|
89
|
+
The mode rules below apply in every specialist pass and in the synthesis, on every host. If the specialist skills are not installed on this host, tell the user to run thatch setup for it.`;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* How the review-mode commands resolve what to review. Shared so the two
|
|
93
|
+
* commands accept the same target syntax and default identically.
|
|
94
|
+
*/
|
|
95
|
+
const reviewTargetCouching = `The user message may name the review target: a branch, a PR or MR number, or a git range. On MCP hosts the Focus: line carries that text instead. If no target is given, review the current branch against its base.`;
|
|
96
|
+
|
|
58
97
|
/**
|
|
59
98
|
* One on-demand action: the command name (minus the thatch/ prefix), the
|
|
60
99
|
* description shown in the command menu, and the instruction body (a prompt
|
|
61
|
-
* core
|
|
100
|
+
* core for the nudge-backed actions, self-contained text for the rest).
|
|
101
|
+
* opencodeOnly marks actions that need a session identity or other
|
|
62
102
|
* plugin-only capability; those are excluded from Claude Code command files
|
|
63
103
|
* and MCP prompts.
|
|
64
104
|
*/
|
|
@@ -71,8 +111,11 @@ export interface ActionDef {
|
|
|
71
111
|
|
|
72
112
|
/**
|
|
73
113
|
* The on-demand action set, shared by every host that renders actions. The
|
|
74
|
-
* bodies
|
|
75
|
-
*
|
|
114
|
+
* nudge-backed bodies (defrag, extract, hygiene, reflect) come from prompt
|
|
115
|
+
* cores in src/prompts.ts so a wording change lands in the nudge and the
|
|
116
|
+
* command at once; the rest (peer-review, refine, self-review, whois) are
|
|
117
|
+
* self-contained here because they have no nudge counterpart. `tool`
|
|
118
|
+
* renders tool names in the host's spelling.
|
|
76
119
|
*/
|
|
77
120
|
export function actionDefs(tool: ToolNamer): ActionDef[] {
|
|
78
121
|
return [
|
|
@@ -100,6 +143,25 @@ After dispatching, call thatch_extraction_done to acknowledge.`,
|
|
|
100
143
|
description: "Tend the memory store: stale entries, orphans, pending dedup pairs",
|
|
101
144
|
body: hygieneCore(),
|
|
102
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: "peer-review",
|
|
148
|
+
description: "Review another author's change (PR, MR, branch, or git range) through the full review pipeline",
|
|
149
|
+
// Skill-couching action (refine precedent): no nudge counterpart, so
|
|
150
|
+
// the body lives here. The memory-walk tool name is spelled through
|
|
151
|
+
// the namer so MCP renderings carry the mcp__thatch__ form.
|
|
152
|
+
body: `Review a change authored by someone else: a PR, MR, or branch on origin. The review runs the full thatch review pipeline with findings weighted for org velocity.
|
|
153
|
+
|
|
154
|
+
${reviewTargetCouching}
|
|
155
|
+
|
|
156
|
+
${reviewPipelineCouching}
|
|
157
|
+
|
|
158
|
+
## Peer review mode rules
|
|
159
|
+
|
|
160
|
+
- Weight for org velocity. Findings that speed up or unblock the whole team outrank findings about individual lines. The highest-value findings: a simpler method that achieves the same outcome; an existing function or helper that already does what this change reimplements; documentation that is accurate and extremely clearly written; unintended consequences for callers, sibling features, or contracts the author could not see.
|
|
161
|
+
- Walk the project's memory. The author does not have this project's persistent memory; you do. During context gathering, call ${tool("memory_recall")} with several phrasings about the touched area: past incidents, design constraints, rejected approaches, known footguns. Check each recalled item against the change and report the ones that apply. Frame these findings as sharing knowledge the author could not have had, not as catching mistakes.
|
|
162
|
+
- Do not re-litigate settled designs. When the context brief or memory shows a design decision was agreed (in a ticket discussion, a prior review acceptance, recorded docs, or a memory of the agreement), treat the decision as settled. Findings may address how the change implements that decision. Findings that argue the decision itself was wrong are out of scope for this review.
|
|
163
|
+
- Never report findings of the form "if someone changes this in the future, it will silently break." This review does not solve for engineers who change code without reading it. It solves for engineers and LLMs who WILL read the code. For them, verify the change leaves a breadcrumb trail to wherever this organization records how a feature works (a context module, a feature doc, a README). Flag a missing trail. Do not flag hypothetical future misuse.`,
|
|
164
|
+
},
|
|
103
165
|
{
|
|
104
166
|
name: "refine",
|
|
105
167
|
description: "Refine a plan under the project type's requirements (classifies the project first)",
|
|
@@ -115,6 +177,26 @@ The user message may name a project type (team-app, personal, shared-lib, spike,
|
|
|
115
177
|
description: "Persist what this session learned as memories",
|
|
116
178
|
body: reflectCore(tool),
|
|
117
179
|
},
|
|
180
|
+
{
|
|
181
|
+
name: "self-review",
|
|
182
|
+
description: "Review the user's own completed work before submitting it",
|
|
183
|
+
// Skill-couching action (see peer-review). Names no tools, so the
|
|
184
|
+
// MCP spelling test exempt list covers it.
|
|
185
|
+
body: `Review the user's own completed work before submitting it: as a PR or MR on a shared project, or as a direct commit on a personal project. The review runs the full thatch review pipeline with findings weighted to protect the user's reputation as a conscientious developer.
|
|
186
|
+
|
|
187
|
+
The work under review was authored by the user, or by their agent acting in the user's name. Two goals: the work is sound, and it does not read as sloppy. Reviewers and teammates read the comments, docs, and commit messages as the user's own.
|
|
188
|
+
|
|
189
|
+
${reviewTargetCouching}
|
|
190
|
+
|
|
191
|
+
${reviewPipelineCouching}
|
|
192
|
+
|
|
193
|
+
## Self review mode rules
|
|
194
|
+
|
|
195
|
+
- The reputation lenses weigh heaviest. NoSlop, Technique, and Breadcrumbs findings outrank same-severity findings from the other lenses. AI-writing tells, vague comments, and a missing narrative are exactly what this review exists to catch before anyone else sees them.
|
|
196
|
+
- Overweight simplicity on shared projects. Prefer findings that make the design easier to understand, easier to test, and more reliable: fewer assumptions, less surface area for bugs and side effects.
|
|
197
|
+
- Technique and highlights findings are decisions, not footnotes. Each one that would produce a simpler, clearer, or more reliable design is surfaced for an adopt-or-dismiss decision before submission. Work through them with the user one at a time; do not leave them as informational asides.
|
|
198
|
+
- Iterate rounds. After each round, work the findings with the user: fix each, or explicitly decline it with a reason. Then run another full round on the updated state. A round is meaningful when it produces a MEDIUM or higher finding, or a technique or slop finding worth adopting. A round producing only LOW mechanical findings gets them fixed but does not trigger another round. Stop at the first round with no meaningful findings, and never run more than three rounds.`,
|
|
199
|
+
},
|
|
118
200
|
{
|
|
119
201
|
name: "whois",
|
|
120
202
|
description: "Report this session's chat identity (handle, topic, project), or look up another session's",
|
|
@@ -154,8 +236,18 @@ ${userMessageSection}
|
|
|
154
236
|
${action.body}`;
|
|
155
237
|
}
|
|
156
238
|
|
|
239
|
+
/** The wrap-up command's menu/palette description. Host-neutral on purpose:
|
|
240
|
+
* what actually happens after the greenlight differs per host (v1 exits the
|
|
241
|
+
* app, v2 closes the session's tab), and the template text the MODEL reads
|
|
242
|
+
* is shared verbatim by both hosts. */
|
|
243
|
+
export function wrapUpCommandDescription(kind: "compact" | "exit"): string {
|
|
244
|
+
return kind === "compact"
|
|
245
|
+
? "Flush thatch persistence, check for loose ends, then compact if clear"
|
|
246
|
+
: "Flush thatch persistence, check for loose ends, then end the session if clear";
|
|
247
|
+
}
|
|
248
|
+
|
|
157
249
|
const COMPACT_TEMPLATE = `---
|
|
158
|
-
description: ${yamlQuote("
|
|
250
|
+
description: ${yamlQuote(wrapUpCommandDescription("compact"))}
|
|
159
251
|
---
|
|
160
252
|
${userMessageSection}
|
|
161
253
|
|
|
@@ -172,7 +264,7 @@ ${COMPACT_READY_TOKEN}
|
|
|
172
264
|
If anything is outstanding, list the items concisely so the user can address them, and do NOT include the token. The session will only be compacted when the token is present.`;
|
|
173
265
|
|
|
174
266
|
const EXIT_TEMPLATE = `---
|
|
175
|
-
description: ${yamlQuote("
|
|
267
|
+
description: ${yamlQuote(wrapUpCommandDescription("exit"))}
|
|
176
268
|
---
|
|
177
269
|
${userMessageSection}
|
|
178
270
|
|
|
@@ -181,13 +273,12 @@ ${userMessageSection}
|
|
|
181
273
|
Work through this checklist before responding:
|
|
182
274
|
|
|
183
275
|
${sharedChecklist}
|
|
184
|
-
3. Leave the chat directory. Call thatch_chat_unregister for this session - the session is exiting, so other sessions must stop addressing mail to it.
|
|
185
276
|
|
|
186
277
|
Do not start new work beyond this checklist. If every item is handled and nothing needs the user's attention first, end your final response with exactly this token as the very last line, with no formatting around it:
|
|
187
278
|
|
|
188
279
|
${EXIT_READY_TOKEN}
|
|
189
280
|
|
|
190
|
-
If anything is outstanding, list the items concisely so the user can address them, and do NOT include the token.
|
|
281
|
+
If anything is outstanding, list the items concisely so the user can address them, and do NOT include the token. The session will only close when the token is present.`;
|
|
191
282
|
|
|
192
283
|
/** One command: the command name (minus the thatch/ prefix) and its full markdown file content. */
|
|
193
284
|
export interface CommandDef {
|
|
@@ -221,7 +312,7 @@ export function opencodeCommandDefs(): CommandDef[] {
|
|
|
221
312
|
/**
|
|
222
313
|
* The Claude Code command set: the actions minus the opencode-only ones.
|
|
223
314
|
* The wrap-up commands are excluded too - their greenlight check needs the
|
|
224
|
-
* plugin's command.execute.before arming and
|
|
315
|
+
* plugin's command.execute.before arming and host action routes.
|
|
225
316
|
*/
|
|
226
317
|
export function claudeCommandDefs(): CommandDef[] {
|
|
227
318
|
return actionDefs(mcpToolName)
|