@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.
- package/README.md +27 -10
- package/artifacts/skills/thatch-code-review.md +4 -4
- package/artifacts/skills/thatch-review-synthesizer.md +8 -8
- package/artifacts/skills/{thatch-review-mentorship.md → thatch-review-technique.md} +9 -10
- package/bin/release +4 -1
- package/bin/thatch +318 -18
- package/docs/dev/README.md +29 -15
- package/docs/dev/features/README.md +1 -0
- package/docs/dev/features/cli.md +33 -4
- package/docs/dev/features/cross-session-chat.md +315 -0
- package/docs/dev/features/database.md +52 -4
- package/docs/dev/features/multi-host.md +18 -9
- package/docs/dev/features/setup.md +12 -5
- package/docs/dev/features/watchers.md +113 -23
- package/docs/dev/gotchas.md +7 -4
- package/docs/dev/mcp-parity.md +2 -2
- package/docs/dev/setup-and-hooks.md +5 -4
- package/docs/dev/skills.md +5 -4
- package/docs/user/README.md +25 -6
- package/docs/user/cli.md +78 -0
- package/docs/user/code-review.md +1 -1
- package/docs/user/cross-session-chat.md +150 -0
- package/docs/user/setup.md +16 -2
- package/docs/user/skills.md +7 -6
- package/docs/user/watchers.md +61 -16
- package/package.json +2 -1
- package/src/chat-names.ts +120 -0
- package/src/chat.ts +1210 -0
- package/src/config.ts +34 -1
- package/src/db.ts +227 -0
- package/src/index.ts +188 -17
- package/src/prompts.ts +218 -29
- package/src/setup.ts +83 -29
- package/src/skills.ts +47 -17
- package/src/tool-defs.ts +574 -31
- 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
|
@@ -124,10 +124,13 @@ here first. These are the things that have already cost time.
|
|
|
124
124
|
|
|
125
125
|
## Setup
|
|
126
126
|
|
|
127
|
-
- **Skills
|
|
128
|
-
`.claude/
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -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
|
|
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)
|
|
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 |
|
|
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`,
|
|
56
|
-
|
|
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 |
|
|
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
|
package/docs/dev/skills.md
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
package/docs/user/README.md
CHANGED
|
@@ -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
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
package/docs/user/code-review.md
CHANGED
|
@@ -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-
|
|
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.
|