@jeffober/thatch 0.1.41 → 0.1.43
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 +28 -11
- package/artifacts/skills/thatch-clear-writing.md +77 -0
- package/artifacts/skills/thatch-coding-workflow.md +1 -0
- package/bin/release +4 -1
- package/bin/thatch +268 -55
- package/docs/dev/README.md +7 -4
- package/docs/dev/features/README.md +1 -1
- package/docs/dev/features/cli.md +31 -11
- package/docs/dev/features/cross-session-chat.md +141 -67
- package/docs/dev/features/multi-host.md +4 -3
- package/docs/dev/features/session-lifecycle.md +24 -1
- package/docs/dev/features/watchers.md +113 -23
- package/docs/dev/gotchas.md +9 -0
- package/docs/dev/mcp-parity.md +2 -1
- package/docs/dev/skills.md +1 -0
- package/docs/user/README.md +19 -3
- package/docs/user/cli.md +68 -14
- package/docs/user/cross-session-chat.md +80 -27
- package/docs/user/skills.md +1 -0
- package/docs/user/watchers.md +61 -16
- package/package.json +2 -1
- package/src/chat-names.ts +6 -7
- package/src/chat.ts +553 -152
- package/src/commands.ts +97 -0
- package/src/config.ts +34 -1
- package/src/db.ts +67 -7
- package/src/git.ts +22 -0
- package/src/index.ts +198 -25
- package/src/prompts.ts +142 -50
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +301 -98
- package/src/tools.ts +2 -0
- package/src/watchers.ts +358 -23
|
@@ -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.
|
|
7
|
-
requests (source `pr`)
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
`
|
|
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
|
|
23
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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,
|
|
142
|
-
|
|
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
|
|
155
|
-
`BranchWatcher` implement the same lifecycle
|
|
156
|
-
events, expiry, snapshot), and `poll()` dispatches to
|
|
157
|
-
fetch/diff pair. Adding a
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
package/docs/dev/gotchas.md
CHANGED
|
@@ -51,6 +51,15 @@ here first. These are the things that have already cost time.
|
|
|
51
51
|
the hook object returned by `server`. Moving it into the `event` handler
|
|
52
52
|
(where `session.created` lives) silently never fires it — the event bus has
|
|
53
53
|
no such event. This was dead for weeks because the failure was invisible.
|
|
54
|
+
- **`client.tui.executeCommand` only accepts legacy alias names**
|
|
55
|
+
(`session_compact`, `agent_cycle`, ...). Unknown commands publish
|
|
56
|
+
`{command: undefined}` and silently no-op — no error surfaces. For TUI
|
|
57
|
+
commands without an alias (e.g. `app.exit`), publish
|
|
58
|
+
`tui.command.execute` directly via `client.tui.publish`.
|
|
59
|
+
- **Command markdown written by the plugin is invisible until the next server
|
|
60
|
+
start.** opencode loads config (including command discovery) before
|
|
61
|
+
plugins, so `installOpencodeCommands` self-heals on every load but a
|
|
62
|
+
first-ever install only lands after a restart.
|
|
54
63
|
- **`tool.execute.after` excludes `skill` and `task` tools**, not just
|
|
55
64
|
`thatch_*`. Buffering them creates a feedback loop: the nudge triggers a
|
|
56
65
|
skill load, which gets buffered, which triggers another nudge on the next
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -18,10 +18,11 @@ 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
|
|
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 |
|
|
25
|
+
| **Wrap-up commands** (`/thatch/compact`, `/thatch/exit`) | Command markdown synced to `$XDG_CONFIG_HOME/opencode/command/thatch/` at plugin init; `command.execute.before` arms the session and the next idle resolves the greenlight token into a TUI action (`session.compact` via `executeCommand`, `app.exit` via `publish`) | No equivalent — no plugin host to arm the check or trigger the action | No equivalent | **Gapped** — needs the plugin's SDK client and TUI control routes |
|
|
25
26
|
| **Extraction** (buffer tool calls, extract durable facts) | `tool.execute.after` buffers non-thatch, non-skill, non-task tool calls in-process; **direct extraction** is the primary path — when the parent session goes idle with pending buffer entries, the plugin creates a child session via the SDK (`client.session.create` with `parentID`) and prompts it to call `get_extraction_payload` (nudge suppressed via the `extracting` set); the `chat.message` nudge path is a **fallback** used when direct extraction throws | `PostToolBatch` → `thatch buffer-batch` (file-backed JSONL queue); `UserPromptSubmit` → `thatch flush-tools` peeks the queue and prints the nudge with session ID + fetch tool name (does not drain — the queue persists until a memory write or `extraction_done`) | `postToolUse` → `thatch buffer-tool` (single-tool, file-backed queue); `beforeSubmitPrompt` → `thatch flush-tools --json` | **Partial** — opencode uses direct extraction (parent idle → child session) as the primary path, with the nudge as fallback; MCP hosts are nudge-only. Both paths use `get_extraction_payload` to fetch the queued interactions as a tool response, keeping the full payload out of the main session's context window. Nudge timing differs: the nudge arrives at the **start** of the next turn in Claude Code/Cursor; opencode's direct extraction fires at **end of turn** (session idle), and the nudge fallback fires at the start of the next turn like MCP |
|
|
26
27
|
| **Extraction feedback** (toast notifications) | `client.tui.showToast` fires when the extraction child goes idle — shows `[thatch] new: N, updated: M, deleted: K` (success) or `extraction complete — nothing to save` (info); best-effort, silently ignored in headless mode | No TUI connection — MCP hosts have no equivalent | No TUI connection | **Gapped** — MCP hosts cannot show toast notifications |
|
|
27
28
|
| **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 |
|
package/docs/dev/skills.md
CHANGED
|
@@ -61,6 +61,7 @@ description: Extract durable project facts ... Use when ...
|
|
|
61
61
|
| `thatch-review-response` | Author-side review response: triage findings, fix bugs one by one, reply on each thread, post a top-level summary comment. |
|
|
62
62
|
| `thatch-memory-verify` | Fact-check a single memory against the current codebase and correct stale claims. Uses git archaeology to preserve historical context when changes were intentional. |
|
|
63
63
|
| `thatch-knowledge-export` | Compile everything thatch knows about a topic into a curated markdown file for knowledge transfer. Searches across stores, curates out personal noise, fact-checks code-related memories via `thatch-memory-verify`. |
|
|
64
|
+
| `thatch-clear-writing` | Prose rules for any human-facing text not covered by a dedicated writing skill: PR and ticket comments, documentation, plans, reports, and chat replies. Clarity over compression; dedicated skills win when loaded. |
|
|
64
65
|
|
|
65
66
|
**opencode-only** — the coordinator needs sub-agent support:
|
|
66
67
|
|
package/docs/user/README.md
CHANGED
|
@@ -11,7 +11,7 @@ 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
|
|
14
|
+
- [watchers.md](watchers.md): event-driven notifications (PR, branch, and command watching)
|
|
15
15
|
- [cross-session-chat.md](cross-session-chat.md): sessions messaging each other on one machine
|
|
16
16
|
- [notifications.md](notifications.md): banner and voice notifications, user config
|
|
17
17
|
- [prediction-engine.md](prediction-engine.md): user decision model
|
|
@@ -179,6 +179,7 @@ search` - JSONL output designed for piping to `jq` or `grep`.
|
|
|
179
179
|
|------|-------------|
|
|
180
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. |
|
|
181
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. |
|
|
182
183
|
| `thatch_watch_list` | List this session's active watchers. |
|
|
183
184
|
| `thatch_watch_cancel` | Cancel one of this session's watchers by id. |
|
|
184
185
|
|
|
@@ -189,8 +190,8 @@ requirements.
|
|
|
189
190
|
|
|
190
191
|
| Tool | What it does |
|
|
191
192
|
|------|-------------|
|
|
192
|
-
| `thatch_chat_register` | Join the cross-session chat directory, so other
|
|
193
|
-
| `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. |
|
|
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), how long ago each last checked in, each session's project and checkout kind (project root or linked worktree), its topic, and your unread count. |
|
|
194
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. |
|
|
195
196
|
| `thatch_chat_read` | Drain your inbox: all unread messages oldest-first, marked read. Senders identified by display name. |
|
|
196
197
|
| `thatch_chat_unregister` | Leave the chat directory. |
|
|
@@ -231,6 +232,20 @@ Beyond the tools, thatch hooks into opencode itself:
|
|
|
231
232
|
notification (`[thatch] recalled 3 memories`, `[thatch] 2 predictions
|
|
232
233
|
surfaced`, or `[thatch] 1 behavior surfaced`). The toast is ephemeral. It
|
|
233
234
|
fades after a few seconds.
|
|
235
|
+
- **Wrap-up commands.** `/thatch/compact` and `/thatch/exit` (opencode only)
|
|
236
|
+
run a pre-flight checklist before you compact or close a session: the agent
|
|
237
|
+
flushes pending fact extraction, finishes any promised memory writes, and
|
|
238
|
+
surfaces todos or follow-ups it never addressed. The agent ends its
|
|
239
|
+
response with a greenlight token only when the checklist is clean. Thatch
|
|
240
|
+
watches for the token and then runs the compaction or exits opencode.
|
|
241
|
+
Without the token nothing fires -- a toast points you at the items the
|
|
242
|
+
agent listed, and you re-run the command once they're handled. Text typed
|
|
243
|
+
after the command is passed to the agent ahead of the checklist (say
|
|
244
|
+
goodbye or hand off context: `/thatch/exit Good work - see you tomorrow`).
|
|
245
|
+
A greenlit `/thatch/exit` also unregisters the session from the chat
|
|
246
|
+
directory, so other sessions stop sending it mail. The commands
|
|
247
|
+
self-install into `~/.config/opencode/command/thatch/` at plugin startup;
|
|
248
|
+
a first install becomes available after the next opencode start.
|
|
234
249
|
- **Compaction context.** When opencode compacts a long session, thatch injects
|
|
235
250
|
a reminder so the summarized session still knows memory tools exist.
|
|
236
251
|
- **Prediction auto-fire.** When a prompt matches learned contexts, thatch
|
|
@@ -313,6 +328,7 @@ response, walkthrough, and workflow skills that support the review pipeline:
|
|
|
313
328
|
| `thatch-pr-description` | Drafts PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, project-context research, clarity checks, and bold/italic emphasis for scanning. |
|
|
314
329
|
| `thatch-ticket-description` | Drafts ticket or issue descriptions (Linear or Jira) with clear sections, project-context research, clarity checks, and bold/italic emphasis for scanning. |
|
|
315
330
|
| `thatch-split-overlarge-pr` | Splits already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
|
|
331
|
+
| `thatch-clear-writing` | Prose rules for every human-facing text the dedicated writing skills do not cover: PR and ticket comments, documentation, plans, reports, and chat replies. Clarity over compression. |
|
|
316
332
|
|
|
317
333
|
### opencode-only skills
|
|
318
334
|
|
package/docs/user/cli.md
CHANGED
|
@@ -47,27 +47,81 @@ Read-only access to the chat directory your opencode sessions share
|
|
|
47
47
|
```bash
|
|
48
48
|
thatch chat list # who is registered: name, age, project, topic
|
|
49
49
|
thatch chat tail # follow sent and read events, live
|
|
50
|
-
thatch chat tail --once # print the
|
|
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
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
`chat list` shows every registered session
|
|
54
|
-
heartbeat (a large age means the
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
`chat list` shows every registered session as aligned columns under a
|
|
58
|
+
header row: how long since its last heartbeat (a large age means the
|
|
59
|
+
session's process is probably gone), its status (`fresh` or `STALE`),
|
|
60
|
+
its project, and the topic it registered with - the fastest way to
|
|
61
|
+
answer "which session should I talk to about X?" Color is added only
|
|
62
|
+
when the output is a terminal; a pipe prints plain aligned text.
|
|
63
|
+
|
|
64
|
+
`chat tail` prints the conversation as styled chat cards (labels on a
|
|
65
|
+
colored background, names in color, timestamps dimmed; a drawn rule
|
|
66
|
+
separates cards, with blank lines on either side). After each name, the
|
|
67
|
+
session's registered topic appears in muted italics - so the tail tells
|
|
68
|
+
you not just who is talking but what they are working on. In a
|
|
69
|
+
terminal, message bodies render as markdown: if `glow` (or `gum`) is
|
|
70
|
+
installed, bodies with headings, lists, code blocks, and emphasis are
|
|
71
|
+
styled; plain-text bodies print exactly as sent. Piped output - and a
|
|
72
|
+
system without either tool - always gets the raw body.
|
|
59
73
|
|
|
60
74
|
```text
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
From Al Go Rithm <cross-session messaging between opencode sessions>
|
|
76
|
+
To Brute the Dream Farrier <diagnosing watcher underuse>
|
|
77
|
+
When 2026-09-12 14:02 America/Denver
|
|
78
|
+
|
|
79
|
+
CI is green on main
|
|
80
|
+
____________________________________________________________
|
|
81
|
+
|
|
82
|
+
To broadcast
|
|
83
|
+
When 2026-09-12 21:07 America/Denver
|
|
84
|
+
|
|
85
|
+
rebasing payments, hold off
|
|
86
|
+
____________________________________________________________
|
|
87
|
+
|
|
88
|
+
From Kurn the Typechecker <release QA>
|
|
89
|
+
read Marlowe the Cherry Picker <payments refactor>
|
|
90
|
+
When 2026-09-12 21:08 America/Denver
|
|
91
|
+
|
|
92
|
+
direct ping
|
|
64
93
|
```
|
|
65
94
|
|
|
66
95
|
Follow mode runs until Ctrl-C. Read events appear only in follow mode
|
|
67
|
-
(they fire when an inbox is drained after the tail started).
|
|
68
|
-
|
|
69
|
-
registers on a session's behalf;
|
|
70
|
-
via the `thatch_chat_*` tools).
|
|
96
|
+
(they fire when an inbox is drained after the tail started). When
|
|
97
|
+
timestamps are in your local timezone. The chat itself flows through the
|
|
98
|
+
agents (the CLI never sends, reads, or registers on a session's behalf;
|
|
99
|
+
that happens from opencode sessions via the `thatch_chat_*` tools).
|
|
100
|
+
|
|
101
|
+
The backlog shows the last 20 messages before follow mode takes over.
|
|
102
|
+
When messages are hidden, tail prints a one-line note on stderr saying
|
|
103
|
+
how many there were; `--limit N` raises or lowers the backlog and
|
|
104
|
+
`--limit all` shows everything. All the filters apply to live events
|
|
105
|
+
too, so a filtered tail keeps watching the same way:
|
|
106
|
+
|
|
107
|
+
- `--match PATTERN` filters message bodies. Repeat it to AND patterns
|
|
108
|
+
together: every one must match the message body.
|
|
109
|
+
- `--from NAME` and `--to NAME` match participant names (case doesn't
|
|
110
|
+
matter). A departed sender can only be found as the `unknown`
|
|
111
|
+
rendering the tail shows. Broadcast cards say `broadcast` in the To
|
|
112
|
+
slot, but the row underneath keeps the real recipient: `--to <name>`
|
|
113
|
+
finds the fan-out rows that reached them, while `--to broadcast`
|
|
114
|
+
matches nothing.
|
|
115
|
+
- `--since DT` and `--until DT` bound the window; a message counts if
|
|
116
|
+
it was sent at or after `--since` and before `--until`. The date
|
|
117
|
+
format is `YYYY-MM-DD` with an optional `HH:MM` in your local
|
|
118
|
+
timezone.
|
|
119
|
+
|
|
120
|
+
A `--until` already in the past exits after the backlog (a closed
|
|
121
|
+
window has nothing left to follow); one in the future follows until
|
|
122
|
+
the window closes. Note that the limit hides sent cards, not their
|
|
123
|
+
reads: a hidden message that gets read during follow mode still emits
|
|
124
|
+
its read card.
|
|
71
125
|
|
|
72
126
|
## Priming a new project
|
|
73
127
|
|
|
@@ -7,19 +7,22 @@ between tabs all afternoon.
|
|
|
7
7
|
|
|
8
8
|
## What it does
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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:
|
|
19
21
|
|
|
20
22
|
- `thatch_chat_list` shows every registered session on the machine, with a
|
|
21
|
-
liveness marker (fresh or stale),
|
|
22
|
-
|
|
23
|
+
liveness marker (fresh or stale), how long ago it last checked in, its
|
|
24
|
+
project, whether it runs in the project root or a linked git worktree,
|
|
25
|
+
its topic, and your unread count
|
|
23
26
|
- `thatch_chat_send` delivers a message to another registered session, by
|
|
24
27
|
name or session id
|
|
25
28
|
- `thatch_chat_read` drains the session's inbox
|
|
@@ -53,19 +56,14 @@ toggle reveals those output blocks if you want them.)
|
|
|
53
56
|
|
|
54
57
|
## How to use it
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
With auto-registration you usually do nothing: both sessions are already
|
|
60
|
+
in the directory. To introduce them, just tell each session who to talk to:
|
|
57
61
|
|
|
58
|
-
>
|
|
59
|
-
>
|
|
62
|
+
> Ask the other session whether the release has shipped; answer its
|
|
63
|
+
> questions directly.
|
|
60
64
|
|
|
61
|
-
and
|
|
62
|
-
|
|
63
|
-
> Register in the thatch chat, then ask the other session whether the
|
|
64
|
-
> release has shipped.
|
|
65
|
-
|
|
66
|
-
(Registering without arguments is the normal path - each session gets a
|
|
67
|
-
pool name automatically. To recognize sessions at a glance, ask for a
|
|
68
|
-
custom name instead, or just run `thatch_chat_list` and read the roster.)
|
|
65
|
+
Sessions find each other with `thatch_chat_list` and address each other by
|
|
66
|
+
their assigned names.
|
|
69
67
|
|
|
70
68
|
Received messages are treated as informational, not as your instructions: an
|
|
71
69
|
agent that gets mail will not treat it as approval to start work, and it
|
|
@@ -80,7 +78,63 @@ A session shows as fresh while its opencode process is running. A crashed
|
|
|
80
78
|
or closed process stops heartbeat-ing, and the session shows as stale in
|
|
81
79
|
`thatch_chat_list` after ten minutes. Messages to a stale session wait in
|
|
82
80
|
its inbox unread - a dead session never reads them. Explicitly closing a
|
|
83
|
-
session (deleting it in the TUI) unregisters it immediately
|
|
81
|
+
session (deleting it in the TUI) unregisters it immediately, and a
|
|
82
|
+
greenlit `/thatch/exit` unregisters the session too: the wrap-up
|
|
83
|
+
checklist calls `chat_unregister`, and the plugin removes the row itself
|
|
84
|
+
when the exit token passes the greenlight check, so other sessions stop
|
|
85
|
+
addressing mail to a process that is about to vanish.
|
|
86
|
+
|
|
87
|
+
## Turning chat off
|
|
88
|
+
|
|
89
|
+
Set `chat.enabled: false` in the thatch config file
|
|
90
|
+
(`~/.config/thatch/config.json`) - or ask an agent:
|
|
91
|
+
`config_set with chat: { enabled: false }`. When off:
|
|
92
|
+
|
|
93
|
+
- every chat tool refuses, stating that chat is disabled and how to
|
|
94
|
+
re-enable it
|
|
95
|
+
- the poller never starts, so nothing is woken or heartbeated
|
|
96
|
+
- the system prompt omits the chat section entirely, and the hook lines
|
|
97
|
+
print nothing - the feature stops existing as far as any agent can tell
|
|
98
|
+
|
|
99
|
+
Takes effect for the tools immediately (they re-read the config per
|
|
100
|
+
call); a restart applies it to the poller and prompt. Delete the setting
|
|
101
|
+
(or set it true) to turn chat back on.
|
|
102
|
+
|
|
103
|
+
## Turning auto-registration off
|
|
104
|
+
|
|
105
|
+
Set `chat.autoRegister: false` in the same config file when you want the
|
|
106
|
+
old opt-in behavior: opencode sessions stay out of the directory until
|
|
107
|
+
they call `chat_register`. (The Claude Code / Cursor hook still ensures
|
|
108
|
+
its conversation's identity is registered on each prompt - that is the
|
|
109
|
+
host's identity anchor, not auto-joining; remove the hook with
|
|
110
|
+
`thatch setup` if you want those hosts out too.) Wake delivery for
|
|
111
|
+
registered sessions still works; there is just no automatic joining.
|
|
112
|
+
|
|
113
|
+
## Leaving the directory
|
|
114
|
+
|
|
115
|
+
`chat_unregister` is a real exit on every host: a leave tombstone
|
|
116
|
+
suppresses re-joining (auto-registration on opencode, the hook's ensure on
|
|
117
|
+
other hosts) until the session explicitly calls `chat_register` again.
|
|
118
|
+
Without the tombstone, the next idle moment would silently re-register the
|
|
119
|
+
session under a fresh name and undo the leave. Rejoining mints a new name
|
|
120
|
+
(assigned names are never reused, even by the same session).
|
|
121
|
+
|
|
122
|
+
## Security model
|
|
123
|
+
|
|
124
|
+
Chat messages are text written by OTHER agent sessions, delivered into a
|
|
125
|
+
reading agent's context verbatim - a prompt-injection surface by
|
|
126
|
+
construction. Three mitigations:
|
|
127
|
+
|
|
128
|
+
- **Framed at read time.** `chat_read` wraps its output in an explicit
|
|
129
|
+
untrusted-content frame (begin/end fences with a do-not-follow warning).
|
|
130
|
+
The frame exists only in the tool output; the persisted messages are
|
|
131
|
+
untouched.
|
|
132
|
+
- **Names are assigned, not claimed.** Display names are minted by thatch
|
|
133
|
+
with a never-reused counter, so a name cannot be grabbed by an unrelated
|
|
134
|
+
session and an old name cannot be resurrected by an impersonator.
|
|
135
|
+
- **No external content via wake.** The wake notification names senders
|
|
136
|
+
and counts only - bodies flow exclusively through the framed
|
|
137
|
+
`chat_read` surface.
|
|
84
138
|
|
|
85
139
|
## Requirements and limitations
|
|
86
140
|
|
|
@@ -95,8 +149,7 @@ session (deleting it in the TUI) unregisters it immediately.
|
|
|
95
149
|
- **Loop safety.** Thatch caps wake prompts per recipient per hour, so two
|
|
96
150
|
agents acknowledging each other cannot ping-pong forever even if both
|
|
97
151
|
models decide to be chatty.
|
|
98
|
-
- **Names are claimable.** Any session can pick any unused name (uniqueness
|
|
99
|
-
is case-insensitive). There is no impersonation defense - the assumption
|
|
100
|
-
is that every agent on the machine is yours.
|
|
101
152
|
- **Message retention.** Messages are kept as history; unregistering does
|
|
102
|
-
not delete them.
|
|
153
|
+
not delete them. Auto-registered sessions whose host has been gone for a
|
|
154
|
+
week are pruned from the directory (their names are never reused), and
|
|
155
|
+
unread mail addressed to them is removed on a later sweep.
|
package/docs/user/skills.md
CHANGED
|
@@ -68,6 +68,7 @@ Skills are split into shared (all hosts) and opencode-only:
|
|
|
68
68
|
| `thatch-pr-description` | Drafts PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, using bold and italic emphasis for scanning. |
|
|
69
69
|
| `thatch-ticket-description` | Drafts ticket or issue descriptions (Linear or Jira) with clear sections and bold/italic emphasis for scanning. |
|
|
70
70
|
| `thatch-split-overlarge-pr` | Splits already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
|
|
71
|
+
| `thatch-clear-writing` | Prose rules for every human-facing text the dedicated writing skills do not cover: PR and ticket comments, documentation, plans, reports, and chat replies. Clarity over compression. |
|
|
71
72
|
|
|
72
73
|
### Workflow skills
|
|
73
74
|
|