@jeffober/thatch 0.1.40 → 0.1.42

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.
@@ -3,10 +3,11 @@
3
3
  Watchers are the general mechanism for event-driven notifications from
4
4
  external sources: the model registers a watch on something outside the
5
5
  session, thatch polls it in the background, and the plugin prompts the
6
- session when a watched event happens. Two sources ship: GitHub pull
7
- requests (source `pr`) and GitHub branches (source `branch`, for
8
- watching CI and workflow runs on main). The registry, poller, and
9
- delivery are source-agnostic - a new source adds a fetch-and-diff
6
+ session when a watched event happens. Three sources ship: GitHub pull
7
+ requests (source `pr`), GitHub branches (source `branch`, for watching
8
+ CI and workflow runs on main), and local shell commands (source
9
+ `command`, for waiting on arbitrary conditions). The registry, poller,
10
+ and delivery are source-agnostic - a new source adds a fetch-and-diff
10
11
  function pair and event types.
11
12
 
12
13
  User-facing behavior is documented in [docs/user/watchers.md](../../user/watchers.md).
@@ -14,13 +15,16 @@ User-facing behavior is documented in [docs/user/watchers.md](../../user/watcher
14
15
  ## What it does
15
16
 
16
17
  - `thatch_watch_create` (PRs) / `thatch_watch_branch_create` (branches) /
17
- `thatch_watch_list` / `thatch_watch_cancel` tools (all opencode-only)
18
+ `thatch_watch_command_create` (commands) / `thatch_watch_list` /
19
+ `thatch_watch_cancel` tools (all opencode-only)
18
20
  - A background poller in the plugin process diffs each watcher's target
19
21
  against its last-seen state
20
22
  - Delivery prompts the session with a synthetic part - the same
21
23
  mechanism opencode uses for background task completions
22
- - Notifications carry pointer data only (author, URL, counts); the
23
- model fetches content on demand with `gh`
24
+ - Notifications carry pointer data plus machine status (author, URL,
25
+ check names and conclusions, exit codes); external content is fetched
26
+ on demand with `gh` or by re-running a watched command, never
27
+ delivered
24
28
 
25
29
  ## The process-lifetime decision
26
30
 
@@ -72,6 +76,12 @@ attempt delivery. Poll errors are per-watcher; one bad PR never
72
76
  blocks the others. The diff is a pure function, so it is unit-tested
73
77
  without any network access.
74
78
 
79
+ The interval is surfaced to the model through the registry's
80
+ `pollSeconds` getter: watch-creation output states when the first
81
+ poll lands, and notifications carry a cadence line, so a model
82
+ choosing between a watcher and an in-turn poll compares real numbers
83
+ even when `THATCH_WATCH_POLL_SECONDS` overrides the default.
84
+
75
85
  Event detection:
76
86
 
77
87
  - comments by id monotonicity (fetch is sorted descending)
@@ -89,6 +99,57 @@ Event detection:
89
99
  A diff emits at most 10 events per watcher per cycle, so comment
90
100
  floods collapse into one batch.
91
101
 
102
+ ### One-shot watches
103
+
104
+ `watch_create` and `watch_branch_create` accept `once: true`. The
105
+ watcher cancels itself when its first matching event is detected, not
106
+ when it is delivered - the event still queues and delivers through the
107
+ normal pending path, so a session busy at fire time does not lose the
108
+ notification. Cancellation at detection time is what keeps a "tell me
109
+ when this run finishes" request from leaving a standing watch polling
110
+ a target nobody is waiting on. `watch_list` marks one-shot watchers
111
+ with a `[once]` tag, and the registration output states the mode.
112
+
113
+ ### Command watches
114
+
115
+ The command source stretches the fetch/diff recipe in two ways:
116
+
117
+ 1. **The fetch has side effects.** `runWatchedCommand()` executes the
118
+ watched command via `bash -c` in the project directory each cycle.
119
+ This is the point - the command IS the condition - but it means
120
+ command watchers must not block: the tool description and system
121
+ prompt state the contract (fast, idempotent status check; never
122
+ `gh run watch` or `tail -f`), and a per-run kill timeout
123
+ (default 30s, `THATCH_WATCH_COMMAND_TIMEOUT_SECONDS`) bounds a
124
+ hung command's cost to one partial poll cycle - other watchers
125
+ wait out the kill rather than stalling, but a blocking command
126
+ still burns its timeout slice every cycle. A timed-out run means
127
+ "not done yet": the last-seen exit is left untouched and the watch
128
+ keeps polling.
129
+ 2. **The diff is a trivial exit-code check.** The command is a
130
+ condition variable, not a data pipe: stdout is drained (a full
131
+ pipe buffer would deadlock the child) but discarded, and stderr is
132
+ kept only for registration-time error messages. The notification
133
+ carries the exit code and duration; command output can be external
134
+ content, so it never rides a notification. This is the strictest
135
+ application of the notification content rule. The pipe drains are also
136
+ raced against a hard deadline, because a
137
+ command that backgrounds a long-lived child leaves a grandchild
138
+ holding the pipe write-ends past the kill - without the race, the
139
+ drains never resolve and the shared poll cycle stalls forever.
140
+
141
+ `createCommand()` runs the command once at registration as baseline
142
+ validation, like `createPr()`'s baseline fetch: exit 0 at baseline
143
+ means the condition is already met (refused, so the caller proceeds
144
+ now), exit 127 means the command does not exist (refused with the
145
+ stderr tail), and a spawn failure fails registration with a real
146
+ error. A baseline run that times out records the 124 sentinel as the
147
+ last-seen exit - the killed process may have exited 0 while dying, and
148
+ the watcher's first real exit 0 must fire. Registered command
149
+ watchers carry `once: true` inherently - "run until it returns 0,
150
+ then notify, then stop" is the whole concept. The `CommandRunner` is
151
+ injectable like `GhRunner`, so tests never spawn processes.
152
+
92
153
  ### Delivery
93
154
 
94
155
  Events queue in an in-memory pending map keyed by session. Delivery
@@ -101,9 +162,15 @@ on the session with a synthetic text part built by
101
162
  `watcherNotificationNudge()`. This triggers a full model turn - the
102
163
  model reads the notification and decides whether to act. The
103
164
  notification text borrows the background-task-completion framing
104
- (system event, not user input, not approval to advance other work),
105
- following the same load-bearing wording discipline as the extraction
106
- nudge.
165
+ (system event, not user input, not approval to advance other work)
166
+ with one carve-out: a watch registered to gate work the user already
167
+ greenlit (for example "wait for CI, then merge") makes the
168
+ notification the continuation signal for exactly that work. The
169
+ carve-out lives in the nudge itself, not only the system prompt,
170
+ because the nudge is what the model reads at the act-or-wait decision
171
+ point. The wrapper also states that check-run and workflow
172
+ conclusions are already in the event summaries, so gh fetches are
173
+ needed only for logs and bodies.
107
174
 
108
175
  Failed or gated deliveries stay pending and retry on the next poll
109
176
  cycle or when the session next goes idle (the event hook calls
@@ -114,7 +181,10 @@ cycle or when the session next goes idle (the event hook calls
114
181
  `pr_comment`, `pr_review_comment`, `pr_review_reply`,
115
182
  `pr_review_resolved`, `pr_commit`, `pr_status`, `pr_description`,
116
183
  `pr_ci`. The tool's `events` argument selects a subset; the default is
117
- all eight.
184
+ all eight. Branch watchers add `branch_commit`, `branch_ci`, and
185
+ `branch_workflow` (the full vocabulary is `WATCHER_EVENT_TYPES` in
186
+ src/watchers.ts, test-enforced); command watchers register the single
187
+ `command_success` type.
118
188
 
119
189
  ## Interactions with other features
120
190
 
@@ -134,28 +204,48 @@ all eight.
134
204
  - `THATCH_WATCH_POLL_SECONDS` - poll interval (default 60)
135
205
  - `THATCH_WATCH_TTL_MINUTES` - watcher time-to-live (default 480)
136
206
  - `THATCH_WATCH_MAX_PER_SESSION` - active watchers per session
137
- (default 5)
207
+ (default 5; all sources share the budget)
208
+ - `THATCH_WATCH_COMMAND_TIMEOUT_SECONDS` - per-run kill timeout for
209
+ watched commands (default 30)
138
210
 
139
211
  ## Source files
140
212
 
141
- - `src/watchers.ts` - registry, poller, gh CLI runner, diff functions
142
- - `src/tool-defs.ts` - the three watch tool definitions
213
+ - `src/watchers.ts` - registry, poller, gh CLI runner, command runner,
214
+ diff functions
215
+ - `src/tool-defs.ts` - the watch tool definitions (see `TOOL_DEFS`)
143
216
  - `src/index.ts` - registry construction, delivery closure,
144
217
  session.status tracking, session.deleted cleanup, dispose
145
218
  - `src/prompts.ts` - `watcherNotificationNudge()`, system prompt
146
219
  Watchers section
147
220
  - `tests/watchers.test.ts` - registry, poll, and diff unit tests
148
- (mocked gh, no network)
221
+ (mocked gh and command runners, no network)
222
+ - `tests/tool-defs.test.ts` - watch tool execute-function tests
149
223
  - `tests/qa/auto/uc-095-watchers.ts` - end-to-end lifecycle against a
150
224
  mocked gh runner
225
+ - `tests/qa/auto/uc-101-command-watchers.ts` - command-watch lifecycle
226
+ against a mocked command runner
151
227
 
152
228
  ## Adding a new source type
153
229
 
154
- The registry is a discriminated union over sources: `PrWatcher` and
155
- `BranchWatcher` implement the same lifecycle (id, session, repo,
156
- events, expiry, snapshot), and `poll()` dispatches to the source's
157
- fetch/diff pair. Adding a third source means: new event types, a
158
- `fetchXState`/`diffXState` pair, a new arm of the `Watcher` union, a
159
- `#pollOne` branch, and the tool surface for registering it. Keep the
160
- pointer-only notification rule: external content enters the context on
161
- explicit fetch, never via notification.
230
+ The registry is a discriminated union over sources: `PrWatcher`,
231
+ `BranchWatcher`, and `CommandWatcher` implement the same lifecycle
232
+ (id, session, events, expiry, snapshot), and `poll()` dispatches to
233
+ the source's fetch/diff pair. Adding a new source means: new event
234
+ types, a fetch function and diff (or a trivial inline diff like the
235
+ command source's), a new arm of the `Watcher` union, a `#pollOne`
236
+ branch, and the tool surface for registering it. Keep the
237
+ notification content rule: external content enters the context on
238
+ explicit fetch, never via notification. Machine status fields (names,
239
+ conclusions, counts, exit codes) come from the API rather than
240
+ user-written text and belong in event summaries.
241
+
242
+ Two caveats from the command source, worth carrying to future
243
+ sources:
244
+
245
+ - If the source's fetch does anything beyond reading an API, inject a
246
+ runner (like `GhRunner`/`CommandRunner`) so tests never spawn real
247
+ work, and bound it with a timeout so a hung fetch cannot stall the
248
+ shared sequential poll cycle.
249
+ - If the source has no natural URL (a local command has none), emit
250
+ an empty `url` in the event; `watcherNotificationNudge()` omits
251
+ empty URLs rather than rendering a dangling space.
@@ -124,10 +124,13 @@ here first. These are the things that have already cost time.
124
124
 
125
125
  ## Setup
126
126
 
127
- - **Skills are always user-scoped**, even in project-local setup. The project's
128
- `.claude/settings.json` and `CLAUDE.md` stay in the repo, but skills live
129
- under `$CLAUDE_CONFIG_DIR/skills/` / `~/.cursor/skills/`. Installing into the
130
- worktree would mutate the user's repo.
127
+ - **Skills follow the setup scope.** Project-local installs write skills to
128
+ the repo's `.claude/skills/` / `.cursor/skills/` (they version with the
129
+ project); `--global` writes to `$CLAUDE_CONFIG_DIR/skills/` /
130
+ `~/.cursor/skills/`. A run never touches the opposite scope: if thatch
131
+ skills already exist there (e.g. user-scope copies from an older local
132
+ setup), setup reports them in a note but leaves them alone, so they can
133
+ drift stale until refreshed or deleted.
131
134
  - **`appendBlock` leaves content alone if the markers don't parse.** If the
132
135
  start marker is found but the end marker isn't, the whole block is skipped
133
136
  rather than half-replaced. Fix the markers or delete the block manually.
@@ -18,7 +18,7 @@ This document maps the feature parity and documents the remaining gaps.
18
18
 
19
19
  | Feature | OpenCode plugin | Claude Code MCP + hooks | Cursor MCP + hooks | Parity |
20
20
  |---------|----------------|------------------------|---------------------|--------|
21
- | **Tools** (single source of truth) | Plugin tool registration, `thatch_` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only tools filtered out) | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only tools filtered out) | **Full** for the shared tools; `get_session_info` (session identity) and `session_search`/`session_get` (conversation archaeology) are opencode-only — MCP hosts have no session concept. The `thatch session` CLI subcommands cover the same archaeology on any host with an opencode.db file. The `watch_*` tools (PR and branch watchers) are opencode-only — MCP hosts have no poller or proactive-prompt channel |
21
+ | **Tools** (single source of truth) | Plugin tool registration, `thatch_` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only tools filtered out) | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only tools filtered out) | **Full** for the shared tools; `get_session_info` (session identity) and `session_search`/`session_get` (conversation archaeology) are opencode-only — MCP hosts have no session concept. The `thatch session` CLI subcommands cover the same archaeology on any host with an opencode.db file. The `watch_*` tools (PR, branch, and command watchers) are opencode-only — MCP hosts have no poller or proactive-prompt channel. The `chat_*` tools (cross-session chat) are opencode-only — no host session identity and no wake-up channel |
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 |
@@ -27,7 +27,7 @@ This document maps the feature parity and documents the remaining gaps.
27
27
  | **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 |
28
28
  | **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 |
29
29
  | **Behavior auto-fire** (self-discipline rules) | `chat.message` hook scores the prompt embedding against behavior matchers; injects a `[thatch] Situational behaviors` nudge (separate synthetic part, same embedding call) | `UserPromptSubmit` hook → `thatch flush-tools` fires behavior query via the sideband socket's `behaviors` method in parallel with recall and predictions | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — same `scoreBehaviorNudge` entry point in both paths |
30
- | **Skills** | Installed to `$XDG_CONFIG_HOME/opencode/skills` at plugin init (shared **+** opencode-only) | Installed to `$CLAUDE_CONFIG_DIR/skills/` by `thatch setup` (shared only) | Installed to `~/.cursor/skills/` by `thatch setup` (shared only) | **Full** — same SKILL.md format; the code-review coordinator is opencode-only (needs sub-agents) |
30
+ | **Skills** | Installed to `$XDG_CONFIG_HOME/opencode/skills` at plugin init (shared **+** opencode-only) | Installed by `thatch setup` (shared only) to the repo's `.claude/skills/` (local) or `$CLAUDE_CONFIG_DIR/skills/` (global) | Installed by `thatch setup` (shared only) to the repo's `.cursor/skills/` (local) or `~/.cursor/skills/` (global) | **Full** — same SKILL.md format; the code-review coordinator is opencode-only (needs sub-agents) |
31
31
  | **Store detection** (repo identity from git remote) | `worktree` parameter from the opencode plugin | `CLAUDE_PROJECT_DIR` env (set by Claude Code for stdio MCP servers) | `CURSOR_PROJECT_DIR` then `CLAUDE_PROJECT_DIR` then cwd | **Full** |
32
32
  | **Setup detection at startup** | n/a — plugin auto-installs at init | `checkSetup` in MCP server: detects missing or broken instructions in CLAUDE.md | Same as Claude Code (checks AGENTS.md) | **Full** — warns the agent to tell the user to run `thatch setup` |
33
33
  | **Hook config format** | n/a — plugin registers hooks in code | Nested: `.claude/settings.json` `{hooks:{Event:{hooks:[{type,command}]}}}` | Flat: `.cursor/hooks.json` `{version:1,hooks:{event:[{command}]}}` | n/a |
@@ -49,11 +49,12 @@ the TUI is not connected (headless mode).
49
49
  | MCP config | `.mcp.json` (`mcpServers.thatch`, stdio, `["mcp"]`) | none — prints `claude mcp add --scope user thatch -- <bin> mcp` |
50
50
  | Instructions | `CLAUDE.md` (idempotent `appendBlock`) | `$CLAUDE_CONFIG_DIR/CLAUDE.md` |
51
51
  | Hooks | `.claude/settings.json` | `$CLAUDE_CONFIG_DIR/settings.json` |
52
- | Skills | `$CLAUDE_CONFIG_DIR/skills/` (always user-scoped) | same |
52
+ | Skills | `.claude/skills/` (in the repo) | `$CLAUDE_CONFIG_DIR/skills/` |
53
53
 
54
54
  `CLAUDE_CONFIG_DIR` overrides the default `~/.claude` for all user-scoped
55
- paths. Project-local keeps `.mcp.json`, `CLAUDE.md`, and `.claude/settings.json`
56
- in the repo; only skills are user-scoped.
55
+ paths. Project-local keeps everything in the repo: `.mcp.json`, `CLAUDE.md`,
56
+ `.claude/settings.json`, and `.claude/skills/` (so skills version with the
57
+ project and every contributor gets them).
57
58
 
58
59
  ### Hook events (nested `settings.json`)
59
60
 
@@ -88,7 +89,7 @@ Cursor uses the same stdio MCP server as Claude Code; only the hooks differ.
88
89
  | MCP config | `.cursor/mcp.json` | `$CURSOR_CONFIG_DIR/mcp.json` (or `~/.cursor/mcp.json`) |
89
90
  | Instructions | `AGENTS.md` (idempotent `appendBlock`, Cursor-marked) | `$CURSOR_CONFIG_DIR/AGENTS.md` |
90
91
  | Hooks | `.cursor/hooks.json` (flat) | `$CURSOR_CONFIG_DIR/hooks.json` |
91
- | Skills | `$CURSOR_CONFIG_DIR/skills/` (shared only) | same |
92
+ | Skills | `.cursor/skills/` (in the repo) | `$CURSOR_CONFIG_DIR/skills/` |
92
93
 
93
94
  Cursor has no documented env override like `CLAUDE_CONFIG_DIR`; `CURSOR_CONFIG_DIR`
94
95
  is honored for symmetry and forward-compatibility. Cursor has no equivalent of
@@ -46,7 +46,7 @@ description: Extract durable project facts ... Use when ...
46
46
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of behavior? |
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
- | `thatch-review-mentorship` | Peer-mentorship 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. |
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
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
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. |
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`. |
@@ -112,9 +112,10 @@ and `--cursor` pass only the shared set.
112
112
 
113
113
  ## Install mechanics
114
114
 
115
- - **Location**: `$XDG_CONFIG_HOME/opencode/skills` (opencode),
116
- `$CLAUDE_CONFIG_DIR/skills` (Claude Code), `~/.cursor/skills` (Cursor).
117
- Always user-scoped — never into the worktree.
115
+ - **Location**: `$XDG_CONFIG_HOME/opencode/skills` (opencode);
116
+ scope-dependent for MCP hosts — the repo's `.claude/skills/` or
117
+ `.cursor/skills/` for project-local `thatch setup`, the host config
118
+ dirs (`$CLAUDE_CONFIG_DIR/skills`, `~/.cursor/skills`) for `--global`.
118
119
  - **Trigger**: opencode installs at plugin init; Claude Code/Cursor install via
119
120
  `thatch setup`.
120
121
  - **Drift detection**: `installSkills` only writes when the on-disk content
@@ -11,7 +11,8 @@ This README is the overview. Each feature has its own guide:
11
11
 
12
12
  - [memory.md](memory.md): persistent memory store
13
13
  - [extraction.md](extraction.md): automatic fact extraction from tool calls
14
- - [watchers.md](watchers.md): event-driven notifications (PR and branch watching)
14
+ - [watchers.md](watchers.md): event-driven notifications (PR, branch, and command watching)
15
+ - [cross-session-chat.md](cross-session-chat.md): sessions messaging each other on one machine
15
16
  - [notifications.md](notifications.md): banner and voice notifications, user config
16
17
  - [prediction-engine.md](prediction-engine.md): user decision model
17
18
  - [behavior-engine.md](behavior-engine.md): agent self-discipline rules
@@ -178,12 +179,27 @@ search` - JSONL output designed for piping to `jq` or `grep`.
178
179
  |------|-------------|
179
180
  | `thatch_watch_create` | Watch a GitHub PR for events (comments, review comments and replies, thread resolutions, commits, status changes, description edits, CI completions). The plugin polls in the background and prompts the session when a watched event happens. |
180
181
  | `thatch_watch_branch_create` | Watch a GitHub branch (typically main) for commit landings, CI check-run completions, and workflow runs, with an optional workflow-name filter. |
182
+ | `thatch_watch_command_create` | Wait on any local condition via a shell command that exits 0 when the wait is over. One-shot: one notification on the first exit 0, then it cancels itself. |
181
183
  | `thatch_watch_list` | List this session's active watchers. |
182
184
  | `thatch_watch_cancel` | Cancel one of this session's watchers by id. |
183
185
 
184
186
  See [watchers.md](watchers.md) for the full behavior, lifetime, and
185
187
  requirements.
186
188
 
189
+ ### Chat tools
190
+
191
+ | Tool | What it does |
192
+ |------|-------------|
193
+ | `thatch_chat_register` | Join the cross-session chat directory, so other sessions on this machine can message you. Display names are assigned by thatch, never chosen (a slug plus a never-reused counter), so a name always refers to the same session. Call with no arguments to join, rejoin after unregistering, or check your name. On hosts without session context (Claude Code, Cursor), pass the name your thatch hook printed as `as`. |
194
+ | `thatch_chat_list` | List registered sessions with a liveness marker (fresh = its process is alive, stale = likely gone), each session's project and topic, and your unread count. |
195
+ | `thatch_chat_send` | Send a message to another registered session, by name or session id. The recipient is nudged when its session is idle. |
196
+ | `thatch_chat_read` | Drain your inbox: all unread messages oldest-first, marked read. Senders identified by display name. |
197
+ | `thatch_chat_unregister` | Leave the chat directory. |
198
+ | `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. |
199
+
200
+ See [cross-session-chat.md](cross-session-chat.md) for the full behavior,
201
+ liveness model, and limitations.
202
+
187
203
  ## Automatic behaviors
188
204
 
189
205
  Beyond the tools, thatch hooks into opencode itself:
@@ -248,9 +264,12 @@ agent can tell the user to run `thatch setup`.
248
264
 
249
265
  On startup thatch installs [skills] into your global opencode config
250
266
  (`~/.config/opencode/skills/`, or `$XDG_CONFIG_HOME/opencode/skills`).
251
- With `thatch setup --claude`, skills install to `~/.claude/skills/` (or
252
- `$CLAUDE_CONFIG_DIR/skills/`); with `thatch setup --cursor`, to
253
- `~/.cursor/skills/`.
267
+ With `thatch setup --claude`, skills follow the install scope: the
268
+ repo's `.claude/skills/` for project-local, `~/.claude/skills/` (or
269
+ `$CLAUDE_CONFIG_DIR/skills/`) for `--global`. `thatch setup --cursor`
270
+ works the same way with `.cursor/skills/` and `~/.cursor/skills/`.
271
+ Setup reports the skills directory and how many skills were added,
272
+ updated, removed, or already current.
254
273
 
255
274
  ### Memory skills
256
275
 
@@ -265,7 +284,7 @@ With `thatch setup --claude`, skills install to `~/.claude/skills/` (or
265
284
 
266
285
  ### Code review skills
267
286
 
268
- Eight specialist review lenses, plus the synthesizer, context, followup,
287
+ Nine specialist review lenses, plus the synthesizer, context, followup,
269
288
  response, walkthrough, and workflow skills that support the review pipeline:
270
289
 
271
290
  | Skill | Focus |
@@ -278,7 +297,7 @@ response, walkthrough, and workflow skills that support the review pipeline:
278
297
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
279
298
  | `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
280
299
  | `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar against generic praise. |
281
- | `thatch-review-mentorship` | Peer-mentorship 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. |
300
+ | `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. |
282
301
  | `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. |
283
302
  | `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. |
284
303
  | `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`. |
package/docs/user/cli.md CHANGED
@@ -39,6 +39,84 @@ hygiene report. The `--json` flag wraps the output as
39
39
  commands are normally called by hooks, not by hand, but you can run
40
40
  them to see what the agent sees at session start.
41
41
 
42
+ ## Cross-session chat
43
+
44
+ Read-only access to the chat directory your opencode sessions share
45
+ (see [cross-session-chat.md](cross-session-chat.md)):
46
+
47
+ ```bash
48
+ thatch chat list # who is registered: name, age, project, topic
49
+ thatch chat tail # follow sent and read events, live
50
+ thatch chat tail --once # print the last 20 messages and exit
51
+ thatch chat tail --once --limit 50 # a longer backlog (or --limit all)
52
+ thatch chat tail --match "rebase" --match "payments" # body must match both
53
+ thatch chat tail --from al --to bob # substring match on names
54
+ thatch chat tail --since 2026-09-12 --until "2026-09-13 09:00" # a window
55
+ ```
56
+
57
+ `chat list` shows every registered session with how long since its last
58
+ heartbeat (a large age means the session's process is probably gone), its
59
+ project, and the topic it registered with - the fastest way to answer
60
+ "which session should I talk to about X?"
61
+
62
+ `chat tail` prints the conversation as styled chat cards (labels on a
63
+ colored background, names in color, timestamps dimmed; a drawn rule
64
+ separates cards, with blank lines on either side). After each name, the
65
+ session's registered topic appears in muted italics - so the tail tells
66
+ you not just who is talking but what they are working on:
67
+
68
+ ```text
69
+ From Al Go Rithm <cross-session messaging between opencode sessions>
70
+ To Brute the Dream Farrier <diagnosing watcher underuse>
71
+ When 2026-09-12 14:02 America/Denver
72
+
73
+ CI is green on main
74
+ ____________________________________________________________
75
+
76
+ To broadcast
77
+ When 2026-09-12 21:07 America/Denver
78
+
79
+ rebasing payments, hold off
80
+ ____________________________________________________________
81
+
82
+ From Kurn the Typechecker <release QA>
83
+ read Marlowe the Cherry Picker <payments refactor>
84
+ When 2026-09-12 21:08 America/Denver
85
+
86
+ direct ping
87
+ ```
88
+
89
+ Follow mode runs until Ctrl-C. Read events appear only in follow mode
90
+ (they fire when an inbox is drained after the tail started). When
91
+ timestamps are in your local timezone. The chat itself flows through the
92
+ agents (the CLI never sends, reads, or registers on a session's behalf;
93
+ that happens from opencode sessions via the `thatch_chat_*` tools).
94
+
95
+ The backlog shows the last 20 messages before follow mode takes over.
96
+ When messages are hidden, tail prints a one-line note on stderr saying
97
+ how many there were; `--limit N` raises or lowers the backlog and
98
+ `--limit all` shows everything. All the filters apply to live events
99
+ too, so a filtered tail keeps watching the same way:
100
+
101
+ - `--match PATTERN` filters message bodies. Repeat it to AND patterns
102
+ together: every one must match the message body.
103
+ - `--from NAME` and `--to NAME` match participant names (case doesn't
104
+ matter). A departed sender can only be found as the `unknown`
105
+ rendering the tail shows. Broadcast cards say `broadcast` in the To
106
+ slot, but the row underneath keeps the real recipient: `--to <name>`
107
+ finds the fan-out rows that reached them, while `--to broadcast`
108
+ matches nothing.
109
+ - `--since DT` and `--until DT` bound the window; a message counts if
110
+ it was sent at or after `--since` and before `--until`. The date
111
+ format is `YYYY-MM-DD` with an optional `HH:MM` in your local
112
+ timezone.
113
+
114
+ A `--until` already in the past exits after the backlog (a closed
115
+ window has nothing left to follow); one in the future follows until
116
+ the window closes. Note that the limit hides sent cards, not their
117
+ reads: a hidden message that gets read during follow mode still emits
118
+ its read card.
119
+
42
120
  ## Priming a new project
43
121
 
44
122
  ```bash
@@ -43,7 +43,7 @@ the review:
43
43
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
44
44
  | `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
45
45
  | `thatch-review-highlights` | Positive finding detection: clever solutions, cleanup done along the way, documentation that helps. Medium-high bar. |
46
- | `thatch-review-mentorship` | Peer-mentorship 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. |
46
+ | `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. |
47
47
 
48
48
  Each specialist is a self-contained static-analysis pass. They can run
49
49
  individually or as part of the coordinated review.
@@ -0,0 +1,150 @@
1
+ # Cross-Session Chat
2
+
3
+ Cross-session chat lets your opencode sessions talk to each other. If one
4
+ session is QAing a release and another is planning a feature, the second can
5
+ ask the first "is the release green yet?" instead of you relaying messages
6
+ between tabs all afternoon.
7
+
8
+ ## What it does
9
+
10
+ opencode sessions join the directory automatically, on their first idle
11
+ moment. Names are assigned by thatch, never chosen: a lowercase slug of the
12
+ session's title plus a counter (a session titled "Fix auth bug" becomes
13
+ `fix-auth-bug-00001`). The counter only ever increments, so a name is
14
+ minted exactly once per machine - pruning an old session never reissues its
15
+ name to someone else, and the same name always refers to the same session.
16
+ The live title rides along as the session's topic (refreshed as the
17
+ auto-titler converges), so the roster reads as a who-is-working-on-what
18
+ board without anyone filling in forms. A session can also join or rejoin
19
+ explicitly with `thatch_chat_register` (no arguments) - useful after
20
+ `chat_unregister`, or just to check your assigned name. Once registered:
21
+
22
+ - `thatch_chat_list` shows every registered session on the machine, with a
23
+ liveness marker (fresh or stale), its project, its topic, and your unread
24
+ count
25
+ - `thatch_chat_send` delivers a message to another registered session, by
26
+ name or session id
27
+ - `thatch_chat_read` drains the session's inbox
28
+ - `thatch_chat_broadcast` delivers one message to every other live
29
+ registered session at once
30
+ - `thatch_chat_unregister` leaves the directory
31
+
32
+ When mail arrives for an idle session, thatch wakes it with a notification -
33
+ the agent gets a model turn even with nobody at the keyboard, reads its
34
+ inbox, and decides what to do. The notification names the senders and the
35
+ count only; the agent reads the actual messages itself.
36
+
37
+ Broadcasts are for announcements and open questions ("which of you is
38
+ working on the payments code?", "main just moved, rebase if you are based
39
+ on it"). Every live session gets the message and spends a model turn on it,
40
+ so broadcast sparingly - for anything with a known audience, `chat_send` is
41
+ the polite tool. Stale sessions are skipped and reported: a dead session
42
+ never reads a broadcast.
43
+
44
+ ## Watching the conversation
45
+
46
+ Chat activity is visible in each session's transcript: registering,
47
+ sending, reading, and broadcasting echo back as `[chat]` bubbles, so you
48
+ can watch the exchange happen in either tab without leaning over the
49
+ agent's shoulder. Failed sends stay silent, and routine directory lookups
50
+ stay on the muted tool line. You can also watch from the terminal:
51
+ `thatch chat list` and `thatch chat tail` are covered in
52
+ [cli.md](cli.md). (Every other plugin tool call also leaves a muted
53
+ one-line entry in the transcript; opencode's "Show generic tool output"
54
+ toggle reveals those output blocks if you want them.)
55
+
56
+ ## How to use it
57
+
58
+ With auto-registration you usually do nothing: both sessions are already
59
+ in the directory. To introduce them, just tell each session who to talk to:
60
+
61
+ > Ask the other session whether the release has shipped; answer its
62
+ > questions directly.
63
+
64
+ Sessions find each other with `thatch_chat_list` and address each other by
65
+ their assigned names.
66
+
67
+ Received messages are treated as informational, not as your instructions: an
68
+ agent that gets mail will not treat it as approval to start work, and it
69
+ will not auto-reply unless the message bears on something it is already
70
+ doing. When in doubt it summarizes the mail for you and waits. If you want
71
+ two sessions to actively cooperate on something, say so explicitly in both
72
+ sessions.
73
+
74
+ ## Liveness
75
+
76
+ A session shows as fresh while its opencode process is running. A crashed
77
+ or closed process stops heartbeat-ing, and the session shows as stale in
78
+ `thatch_chat_list` after ten minutes. Messages to a stale session wait in
79
+ its inbox unread - a dead session never reads them. Explicitly closing a
80
+ session (deleting it in the TUI) unregisters it immediately.
81
+
82
+ ## Turning chat off
83
+
84
+ Set `chat.enabled: false` in the thatch config file
85
+ (`~/.config/thatch/config.json`) - or ask an agent:
86
+ `config_set with chat: { enabled: false }`. When off:
87
+
88
+ - every chat tool refuses, stating that chat is disabled and how to
89
+ re-enable it
90
+ - the poller never starts, so nothing is woken or heartbeated
91
+ - the system prompt omits the chat section entirely, and the hook lines
92
+ print nothing - the feature stops existing as far as any agent can tell
93
+
94
+ Takes effect for the tools immediately (they re-read the config per
95
+ call); a restart applies it to the poller and prompt. Delete the setting
96
+ (or set it true) to turn chat back on.
97
+
98
+ ## Turning auto-registration off
99
+
100
+ Set `chat.autoRegister: false` in the same config file when you want the
101
+ old opt-in behavior: opencode sessions stay out of the directory until
102
+ they call `chat_register`. (The Claude Code / Cursor hook still ensures
103
+ its conversation's identity is registered on each prompt - that is the
104
+ host's identity anchor, not auto-joining; remove the hook with
105
+ `thatch setup` if you want those hosts out too.) Wake delivery for
106
+ registered sessions still works; there is just no automatic joining.
107
+
108
+ ## Leaving the directory
109
+
110
+ `chat_unregister` is a real exit on every host: a leave tombstone
111
+ suppresses re-joining (auto-registration on opencode, the hook's ensure on
112
+ other hosts) until the session explicitly calls `chat_register` again.
113
+ Without the tombstone, the next idle moment would silently re-register the
114
+ session under a fresh name and undo the leave. Rejoining mints a new name
115
+ (assigned names are never reused, even by the same session).
116
+
117
+ ## Security model
118
+
119
+ Chat messages are text written by OTHER agent sessions, delivered into a
120
+ reading agent's context verbatim - a prompt-injection surface by
121
+ construction. Three mitigations:
122
+
123
+ - **Framed at read time.** `chat_read` wraps its output in an explicit
124
+ untrusted-content frame (begin/end fences with a do-not-follow warning).
125
+ The frame exists only in the tool output; the persisted messages are
126
+ untouched.
127
+ - **Names are assigned, not claimed.** Display names are minted by thatch
128
+ with a never-reused counter, so a name cannot be grabbed by an unrelated
129
+ session and an old name cannot be resurrected by an impersonator.
130
+ - **No external content via wake.** The wake notification names senders
131
+ and counts only - bodies flow exclusively through the framed
132
+ `chat_read` surface.
133
+
134
+ ## Requirements and limitations
135
+
136
+ - **Wake-up delivery is opencode only.** The chat tools work everywhere
137
+ (other hosts declare their identity with `as`), but only opencode can
138
+ start a turn when a message arrives; Claude Code and Cursor see pending
139
+ mail at their next prompt (the flush-tools hook line, or `chat_status`).
140
+ - **Same machine only.** The directory and inbox live in thatch's local
141
+ database. There is no network relay.
142
+ - **Opt-in.** Unregistered sessions cannot be messaged and cannot send.
143
+ Sub-agent sessions should not register.
144
+ - **Loop safety.** Thatch caps wake prompts per recipient per hour, so two
145
+ agents acknowledging each other cannot ping-pong forever even if both
146
+ models decide to be chatty.
147
+ - **Message retention.** Messages are kept as history; unregistering does
148
+ not delete them. Auto-registered sessions whose host has been gone for a
149
+ week are pruned from the directory (their names are never reused), and
150
+ unread mail addressed to them is removed on a later sweep.