@jeffober/thatch 0.2.3 → 0.2.5
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 +8 -1
- package/artifacts/skills/thatch-clear-writing.md +103 -5
- package/artifacts/skills/thatch-coordination.md +69 -0
- package/artifacts/skills/thatch-pr-description.md +21 -13
- package/artifacts/skills/thatch-review-no-slop.md +15 -0
- package/bin/thatch +95 -44
- package/docs/assets/deepwiki-badge.svg +1 -0
- package/docs/dev/README.md +7 -2
- package/docs/dev/features/cli.md +2 -0
- package/docs/dev/features/cross-session-chat.md +118 -11
- package/docs/dev/features/notifications.md +1 -2
- package/docs/dev/features/opencode-plugin.md +18 -3
- package/docs/dev/features/prediction-engine.md +44 -5
- package/docs/dev/features/session-tabs.md +155 -0
- package/docs/dev/features/setup.md +12 -0
- package/docs/dev/features/watchers.md +62 -6
- package/docs/dev/gotchas.md +68 -0
- package/docs/dev/mcp-parity.md +2 -2
- package/docs/dev/setup-and-hooks.md +7 -2
- package/docs/dev/skills.md +1 -1
- package/docs/plans/generalized-session-heartbeat.md +212 -0
- package/docs/plans/prediction-consolidation.md +88 -68
- package/docs/user/README.md +12 -3
- package/docs/user/cli.md +5 -1
- package/docs/user/commands.md +11 -4
- package/docs/user/cross-session-chat.md +31 -8
- package/docs/user/prediction-engine.md +11 -0
- package/docs/user/setup.md +29 -0
- package/docs/user/skills.md +1 -1
- package/docs/user/watchers.md +22 -7
- package/package.json +10 -2
- package/src/alerts.ts +1 -1
- package/src/chat.ts +320 -27
- package/src/commands.ts +14 -0
- package/src/db.ts +51 -1
- package/src/git.ts +3 -1
- package/src/hygiene.ts +5 -0
- package/src/mcp.ts +95 -7
- package/src/opencode/tui-plugin.ts +70 -0
- package/src/opencode/v2.ts +197 -4
- package/src/prediction.ts +90 -0
- package/src/prompts.ts +110 -23
- package/src/runtime.ts +187 -9
- package/src/session-db.ts +4 -4
- package/src/session-tab-shared.ts +180 -0
- package/src/setup.ts +59 -3
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +412 -55
- package/src/tools.ts +2 -0
- package/src/watchers.ts +263 -26
- package/docs/plans/intuition-drives.md +0 -147
- package/docs/plans/llm-alerts.md +0 -287
- package/docs/plans/thatch-actions.md +0 -179
- package/docs/plans/worktree-deletion-recovery.md +0 -246
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Fire Tracking and Compound Predictions
|
|
2
2
|
|
|
3
3
|
## Synopsis
|
|
4
4
|
|
|
5
|
-
Post-processing layers for the prediction engine: fire tracking
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Post-processing layers for the prediction engine: fire tracking and compound
|
|
6
|
+
(tier-2) predictions minted from co-firing constellations of existing
|
|
7
|
+
predictions. Uses nudge-driven detection and the existing edge model for fire
|
|
8
|
+
behavior. No cron, no separate backend.
|
|
9
|
+
|
|
10
|
+
The consolidation (dedup) half was implemented separately and has graduated —
|
|
11
|
+
the shipped design is documented in
|
|
12
|
+
[../dev/features/prediction-engine.md](../dev/features/prediction-engine.md)
|
|
13
|
+
("Dedup pass"). What remains here is the fire-tracking prerequisite
|
|
14
|
+
and the co-fire refinements (behavioral dedup, compound detection) that
|
|
15
|
+
consume the accumulated data.
|
|
9
16
|
|
|
10
17
|
## Background
|
|
11
18
|
|
|
@@ -14,10 +21,10 @@ auto-fires at chat.message and updates via agent tool calls. What it lacks
|
|
|
14
21
|
relative to nak's samskara system:
|
|
15
22
|
|
|
16
23
|
- **Fire tracking** -- no record of which predictions fire together on the same
|
|
17
|
-
turn. This is the prerequisite for
|
|
18
|
-
- **Consolidation** --
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
turn. This is the prerequisite for co-fire dedup and compound detection.
|
|
25
|
+
- **Consolidation** -- implemented and graduated: a cosine-only dedup pass
|
|
26
|
+
over the prediction corpus, no prerequisites
|
|
27
|
+
([../dev/features/prediction-engine.md](../dev/features/prediction-engine.md)).
|
|
21
28
|
- **Compound predictions** -- no mechanism to mint a higher-order prediction
|
|
22
29
|
from a constellation of predictions that reliably co-fire. Nak's tier-2
|
|
23
30
|
samskaras fire at 77% genuine-engagement vs tier-1's 21% because compound
|
|
@@ -43,10 +50,13 @@ existing edge model.
|
|
|
43
50
|
The co-fire data is for *deciding what to compound*, not for *making the
|
|
44
51
|
compound fire*. This means:
|
|
45
52
|
|
|
46
|
-
1. A compound needs no tier column
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
1. A compound needs no tier column and no fire-path changes. It is a
|
|
54
|
+
prediction with multiple matcher edges. The scoring path does get one
|
|
55
|
+
addition: dynamic child suppression (see the compound-domination risk).
|
|
56
|
+
2. The existing `scoreNudge`/`scoreItems` already follow all edges and
|
|
57
|
+
deduplicate by prediction_id. A compound scores through its children's
|
|
58
|
+
edges. Minting records the compound-to-children relation (Phase 4) so
|
|
59
|
+
suppression can tell parent from child.
|
|
50
60
|
3. The existing `prediction_update` tool already finds existing matchers by
|
|
51
61
|
cosine (0.85 threshold) and creates edges. Calling it multiple times with
|
|
52
62
|
different matcher texts but the same prediction statement creates a
|
|
@@ -111,14 +121,18 @@ pass null or derive from the socket connection.
|
|
|
111
121
|
tables. Same conventions (uuid PK, store column, idempotent CREATE TABLE IF
|
|
112
122
|
NOT EXISTS).
|
|
113
123
|
|
|
114
|
-
### Phase 2:
|
|
115
|
-
|
|
116
|
-
**New DB method: `findPredictionDuplicates(store, threshold)`**
|
|
124
|
+
### Phase 2: Co-fire refinement of dedup
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
The cosine-only dedup pass (`findPredictionDuplicates`, the
|
|
127
|
+
`prediction_dedup_pairs` table, the hygiene line, the merge flow) is
|
|
128
|
+
IMPLEMENTED and graduated — documented in
|
|
129
|
+
[../dev/features/prediction-engine.md](../dev/features/prediction-engine.md)
|
|
130
|
+
("Dedup pass"). Once
|
|
131
|
+
fire data accumulates, this plan upgrades that same method with behavioral
|
|
132
|
+
evidence, mirroring nak's `samskara_collapse_by_cofiring`:
|
|
119
133
|
|
|
120
|
-
**
|
|
121
|
-
where both have fire_count > 0:
|
|
134
|
+
**Behavioral pass (added to `findPredictionDuplicates`):** For each pair of
|
|
135
|
+
predictions (A, B) in the store where both have fire_count > 0:
|
|
122
136
|
|
|
123
137
|
```
|
|
124
138
|
cofires(A, B) >= 3
|
|
@@ -131,26 +145,11 @@ when either fires are duplicates. Two that co-fire but also fire independently
|
|
|
131
145
|
are adjacent-but-distinct. Cosine is a sanity floor against spurious co-fires.
|
|
132
146
|
|
|
133
147
|
**Safety cap (cosine-only):** If the prediction count exceeds a target (e.g.,
|
|
134
|
-
100),
|
|
135
|
-
|
|
136
|
-
finds nothing but the pool is still growing.
|
|
137
|
-
|
|
138
|
-
**Output:** A list of candidate merge pairs with {predictionA, predictionB,
|
|
139
|
-
cofires, ratio, cosine, firesA, firesB}. Surfaced via the hygiene nudge, not
|
|
140
|
-
auto-merged. The agent reviews and merges via prediction_delete + (optionally)
|
|
141
|
-
prediction_update to fold the loser's matchers into the winner.
|
|
142
|
-
|
|
143
|
-
The merge itself is agent-driven: the agent sees the candidate pair, reads both
|
|
144
|
-
predictions, deletes the weaker one (or uses a future `prediction_merge` tool
|
|
145
|
-
if one is added). This matches the memory dedup pattern
|
|
146
|
-
(`find_duplicates` + `dedup_mark_checked`).
|
|
147
|
-
|
|
148
|
-
**Integration point:** `hygiene.ts` -- add a line to `hygieneReport`:
|
|
149
|
-
`N prediction duplicate pairs pending review` when candidates exist.
|
|
148
|
+
100), the implemented pure cosine pass already covers the pool;
|
|
149
|
+
no behavioral evidence is required to keep dedup moving.
|
|
150
150
|
|
|
151
|
-
**
|
|
152
|
-
|
|
153
|
-
(tens, not hundreds) so the cost is negligible.
|
|
151
|
+
**Output:** the dedup plan's candidate shape extended with {cofires, ratio,
|
|
152
|
+
firesA, firesB} per pair.
|
|
154
153
|
|
|
155
154
|
### Phase 3: Compound Prediction Detection
|
|
156
155
|
|
|
@@ -216,7 +215,14 @@ first child's matcher. Subsequent calls find the existing compound by
|
|
|
216
215
|
`findNearestPrediction` (0.85 store-wide dedup) and add edges from the other
|
|
217
216
|
children's matchers.
|
|
218
217
|
|
|
219
|
-
This works with the existing tool surface.
|
|
218
|
+
This works with the existing tool surface. One addition: minting must also
|
|
219
|
+
record WHICH predictions the compound supersedes, because child suppression
|
|
220
|
+
(see the compound-domination risk) needs the parent-to-children relation at
|
|
221
|
+
scoring time -- shared matcher edges are implicit and cannot distinguish a
|
|
222
|
+
compound from a busy multi-matcher prediction. `prediction_update` gains an
|
|
223
|
+
optional `supersedes` arg (array of prediction ids), carried on the first
|
|
224
|
+
mint call, writing one `prediction_compounds(compound_id, child_id)` row per
|
|
225
|
+
child. No tier column; the compound is still an ordinary prediction.
|
|
220
226
|
|
|
221
227
|
**Prompt instructions addition** (in prompts.ts, all three host variants):
|
|
222
228
|
|
|
@@ -225,8 +231,9 @@ After the existing prediction instructions, add guidance for compounds:
|
|
|
225
231
|
- When the hygiene nudge reports co-fire constellations, consider whether the
|
|
226
232
|
members share a generalizable behavior.
|
|
227
233
|
- If so, synthesize a compound statement and call `prediction_update` once per
|
|
228
|
-
child matcher with the compound statement
|
|
229
|
-
|
|
234
|
+
child matcher with the compound statement, passing the child prediction ids
|
|
235
|
+
as `supersedes` on the first call. The tool handles matcher dedup and edge
|
|
236
|
+
creation.
|
|
230
237
|
- A compound should be strictly more general than any single child. If you
|
|
231
238
|
cannot generalize, do not compound.
|
|
232
239
|
- The compound inherits the fire coverage of its children's matchers. It will
|
|
@@ -258,7 +265,7 @@ chat.message (user text)
|
|
|
258
265
|
session.created (top-level)
|
|
259
266
|
→ hygieneReport
|
|
260
267
|
→ findDuplicates (memories, existing)
|
|
261
|
-
→ findPredictionDuplicates (
|
|
268
|
+
→ findPredictionDuplicates (implemented; co-fire-refined here)
|
|
262
269
|
→ findCoFireConstellations (NEW: compound candidates)
|
|
263
270
|
→ inject hygiene nudge with all signals
|
|
264
271
|
|
|
@@ -276,44 +283,48 @@ computation, same as existing prediction methods.
|
|
|
276
283
|
db.ts.
|
|
277
284
|
|
|
278
285
|
**Code changes by file:**
|
|
279
|
-
- `src/db.ts` -- new table
|
|
280
|
-
cofireCount, totalCohorts,
|
|
286
|
+
- `src/db.ts` -- one new table (`prediction_fires`) + 4 new methods
|
|
287
|
+
(recordFires, fireCount, cofireCount, totalCohorts, findCoFireConstellations);
|
|
288
|
+
the co-fire refinement extends the implemented `findPredictionDuplicates`
|
|
289
|
+
method
|
|
281
290
|
- `src/runtime.ts` -- generate cohort_id, pass to recordFires in auto-fire path
|
|
282
291
|
- `src/sideband.ts` -- same for MCP path
|
|
283
|
-
- `src/hygiene.ts` -- add
|
|
292
|
+
- `src/hygiene.ts` -- add constellation count to report (the dedup count is
|
|
293
|
+
already implemented there)
|
|
284
294
|
- `src/prompts.ts` -- compound minting guidance in all three prompt variants
|
|
285
295
|
- `tests/prediction.test.ts` -- tests for new DB methods
|
|
286
296
|
- `tests/plugin.test.ts` -- test that fire tracking records cohorts
|
|
287
297
|
|
|
288
298
|
## Implementation Order
|
|
289
299
|
|
|
300
|
+
0. **Cosine dedup** -- IMPLEMENTED and graduated (Oct 2026):
|
|
301
|
+
`findPredictionDuplicates` + `prediction_mark_checked`; nothing left to do.
|
|
290
302
|
1. **Fire tracking** (table + recordFires + caller wiring). Foundation for
|
|
291
|
-
everything else. Ship and let fire data accumulate.
|
|
292
|
-
2. **
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
3. **
|
|
296
|
-
findPredictionDuplicates). Needs fire data to accumulate first. Wait a few
|
|
297
|
-
weeks after phase 1 ships.
|
|
298
|
-
4. **Compound detection** (findCoFireConstellations + hygiene integration).
|
|
303
|
+
everything else here. Ship and let fire data accumulate.
|
|
304
|
+
2. **Co-fire dedup** (add the behavioral pass to findPredictionDuplicates).
|
|
305
|
+
Needs fire data only -- the method and pair table already exist. Wait a
|
|
306
|
+
few weeks after fire tracking ships.
|
|
307
|
+
3. **Compound detection** (findCoFireConstellations + hygiene integration).
|
|
299
308
|
Needs more fire data than dedup (p_min_cofires = 10). Wait longer.
|
|
300
|
-
|
|
301
|
-
tools). Ships with
|
|
302
|
-
compounds.
|
|
309
|
+
4. **Compound minting** (prompt instructions + agent-driven via existing
|
|
310
|
+
tools). Ships with compound detection -- the nudge surfaces candidates,
|
|
311
|
+
the agent compounds.
|
|
303
312
|
|
|
304
|
-
|
|
313
|
+
Fire tracking ships immediately; the rest follow once fire data accumulates.
|
|
305
314
|
|
|
306
315
|
## Test Plan
|
|
307
316
|
|
|
308
317
|
- `prediction.test.ts`: test recordFires, fireCount, cofireCount,
|
|
309
|
-
totalCohorts with synthetic cohorts
|
|
310
|
-
known co-firing pairs
|
|
318
|
+
totalCohorts with synthetic cohorts, the co-fire refinement of
|
|
319
|
+
findPredictionDuplicates with known co-firing pairs, and
|
|
320
|
+
findCoFireConstellations with lift thresholds.
|
|
311
321
|
Use the existing `makeEmbed(seed, dim=384)` helper.
|
|
312
322
|
- `plugin.test.ts`: test that chat.message auto-fire records a cohort.
|
|
313
323
|
Follow the recall-nudge test pattern (mock.module transformers + server
|
|
314
324
|
hooks).
|
|
315
325
|
- `hygiene.test.ts` (if exists) or `plugin.test.ts`: test that hygiene report
|
|
316
|
-
includes
|
|
326
|
+
includes the constellation count when candidates exist (the dedup count
|
|
327
|
+
tests are implemented, riding UC-113).
|
|
317
328
|
|
|
318
329
|
## Risks and Mitigations
|
|
319
330
|
|
|
@@ -324,11 +335,18 @@ precondition gate (minimum prediction count, minimum fire count) before running
|
|
|
324
335
|
the analysis.
|
|
325
336
|
|
|
326
337
|
**Risk: Compound predictions fire too much, dominating the nudge.**
|
|
327
|
-
Mitigation:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
Mitigation (nak-proven): dynamic child suppression, not equal-footing
|
|
339
|
+
competition. When a compound and its child both score in the same round and
|
|
340
|
+
the compound's score is higher, the child is skipped. The rule resolves
|
|
341
|
+
during the scoring pass, per query -- not as a stored flag -- so the
|
|
342
|
+
surviving set adjusts as it searches and freed slots refill from the
|
|
343
|
+
remaining scored list. If the child outscores the compound (it matched a
|
|
344
|
+
matcher the compound did not inherit), both surface. Implementation: the
|
|
345
|
+
per-round dedup filter in `scoreItems` (src/scoring-engine.ts:153-178)
|
|
346
|
+
gains the suppression check, using the compound-to-children relation
|
|
347
|
+
recorded at mint time. Plain prediction_id dedup and the limit=5 slice are
|
|
348
|
+
unchanged. Fire rows record post-suppression survivors only, so co-fire
|
|
349
|
+
data means co-surfaced.
|
|
332
350
|
|
|
333
351
|
**Risk: Agent creates poor compounds (too generic, not useful).**
|
|
334
352
|
Mitigation: The prompt instructions include the "strictly more general" guard
|
|
@@ -351,9 +369,11 @@ this becomes a concern. Low priority.
|
|
|
351
369
|
a separate improvement, not part of this plan, but worth doing alongside
|
|
352
370
|
phase 1.
|
|
353
371
|
- The existing write-time dedup (`findNearestPrediction` at 0.85) catches
|
|
354
|
-
near-identical statements at creation. The
|
|
355
|
-
|
|
356
|
-
|
|
372
|
+
near-identical statements at creation. The implemented dedup pass
|
|
373
|
+
(cosine, with its co-fire refinement here) catches
|
|
374
|
+
behavioral duplicates that differ in wording. The layers are
|
|
375
|
+
complementary: write-time catches semantic twins, the dedup passes catch
|
|
376
|
+
behavioral twins.
|
|
357
377
|
- Nak's tier-2 decline ledger (`samskara_tier2_declines`, TTL'd 7 days) prevents
|
|
358
378
|
re-offering a declined constellation every sweep. Thatch's equivalent: a
|
|
359
379
|
`prediction_constellation_declines` table or a `dedup_pairs`-style
|
package/docs/user/README.md
CHANGED
|
@@ -138,6 +138,7 @@ Stores are created automatically. No setup required.
|
|
|
138
138
|
| `thatch_prediction_update` | Create, reinforce, or weaken a prediction. Takes a matcher (context description), a prediction (preference statement), and a signal (confirm/disconfirm/soft/create). |
|
|
139
139
|
| `thatch_prediction_list` | List all predictions in a store with matchers, confidence, and provenance. |
|
|
140
140
|
| `thatch_prediction_delete` | Delete a prediction by semantic match. Edges and provenance are cascade-deleted. |
|
|
141
|
+
| `thatch_prediction_mark_checked` | Record a verdict (duplicate/distinct) on a pair the hygiene nudge flagged, so it stops resurfacing. |
|
|
141
142
|
|
|
142
143
|
### Behavior tools
|
|
143
144
|
|
|
@@ -183,12 +184,19 @@ search` - JSONL output designed for piping to `jq` or `grep`.
|
|
|
183
184
|
| `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. |
|
|
184
185
|
| `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. |
|
|
185
186
|
| `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. Accepts a `cd` path to run somewhere other than the project directory. |
|
|
186
|
-
| `thatch_watch_list` | List this session's active watchers. |
|
|
187
|
+
| `thatch_watch_list` | List this session's active watchers, plus events waiting for idle delivery. |
|
|
187
188
|
| `thatch_watch_cancel` | Cancel one of this session's watchers by id. |
|
|
188
189
|
|
|
189
190
|
See [watchers.md](watchers.md) for the full behavior, lifetime, and
|
|
190
191
|
requirements.
|
|
191
192
|
|
|
193
|
+
### Session tab tools (opencode v2 only)
|
|
194
|
+
|
|
195
|
+
| Tool | What it does |
|
|
196
|
+
|------|-------------|
|
|
197
|
+
| `thatch_session_tab` | Spawn a detached subordinate LLM session in a new TUI tab: creates the session, pre-assigns its chat name, delivers your task prompt with your coordinator-identity framing, and opens its tab beside yours (unfocused). Takes `prompt` and `title` (required) plus exactly one of `worktree` (a git worktree of the current repository) or `directory` (any existing directory). Requires opencode v2 and the `thatch-coordination` skill describes the supervisor workflow. |
|
|
198
|
+
| `thatch_session_tab_close` | Close a dispatched subordinate's TUI tab when its task is verified and done. Tab-level only: the session survives and the user can reopen the tab; nothing is deleted. Target: the subordinate's chat name or session id. opencode v2 only. |
|
|
199
|
+
|
|
192
200
|
### Chat tools
|
|
193
201
|
|
|
194
202
|
| Tool | What it does |
|
|
@@ -247,7 +255,8 @@ Beyond the tools, thatch hooks into opencode itself:
|
|
|
247
255
|
section ahead of the checklist (say goodbye or hand off context:
|
|
248
256
|
`/thatch/exit Good work - see you tomorrow`). Alongside the wrap-ups,
|
|
249
257
|
on-demand actions (`/thatch/defrag`, `/thatch/extract`, `/thatch/hygiene`,
|
|
250
|
-
`/thatch/reflect`) run the memory-maintenance behaviors
|
|
258
|
+
`/thatch/reflect`, `/thatch/whois`) run the memory-maintenance behaviors
|
|
259
|
+
whenever you want,
|
|
251
260
|
not just when a nudge fires -- see [commands.md](commands.md).
|
|
252
261
|
A greenlit `/thatch/exit` also unregisters the session from the chat
|
|
253
262
|
directory, so other sessions stop sending it mail. The commands
|
|
@@ -343,7 +352,7 @@ response, walkthrough, and workflow skills that support the review pipeline:
|
|
|
343
352
|
| `thatch-pr-description` | Drafts PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, project-context research, clarity checks, and bold/italic emphasis for scanning. |
|
|
344
353
|
| `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. |
|
|
345
354
|
| `thatch-split-overlarge-pr` | Splits already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
|
|
346
|
-
| `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
|
|
355
|
+
| `thatch-clear-writing` | Prose rules for every human-facing text the dedicated writing skills do not cover: PR and ticket comments, documentation, plans, reports, chat replies, and code comments. Clarity over compression. |
|
|
347
356
|
|
|
348
357
|
### opencode-only skills
|
|
349
358
|
|
package/docs/user/cli.md
CHANGED
|
@@ -148,7 +148,8 @@ thatch buffer-batch # append tool batch to queue (Claude Code hook)
|
|
|
148
148
|
thatch buffer-tool # append single tool to queue (Cursor hook)
|
|
149
149
|
thatch flush-tools [--json] # peek queue + print nudges (hook)
|
|
150
150
|
thatch flush-predictions [--json] # prediction-only nudge (hook)
|
|
151
|
-
thatch setup --claude [--cursor] [--global]
|
|
151
|
+
thatch setup --claude [--cursor] [--global] # install config + hooks + skills
|
|
152
|
+
thatch setup --claude --skills-only # refresh just the skill files
|
|
152
153
|
```
|
|
153
154
|
|
|
154
155
|
## Environment variables
|
|
@@ -162,6 +163,9 @@ thatch setup --claude [--cursor] [--global] # install config + hooks + skills
|
|
|
162
163
|
| `THATCH_BEHAVIOR_THRESHOLD` | `0.60` | Cosine threshold for behavior auto-fire |
|
|
163
164
|
| `CLAUDE_PROJECT_DIR` | `process.cwd()` | Claude Code project directory |
|
|
164
165
|
| `CURSOR_PROJECT_DIR` | (falls through) | Cursor project directory |
|
|
166
|
+
| `THATCH_PROJECT_DIR` | (falls through) | Project dir pinned by project-local MCP configs - first in the server's resolution chain |
|
|
167
|
+
| `THATCH_WATCH_DEATH_MINUTES` | `120` | Pending-age threshold before a dead session's watchers are cancelled and announced |
|
|
168
|
+
| `THATCH_PROJECT_DIR` | (falls through) | Project dir pinned by project-local MCP configs - first in the server's resolution chain |
|
|
165
169
|
| `CLAUDE_CONFIG_DIR` | `~/.claude` | Claude Code config directory |
|
|
166
170
|
| `CURSOR_CONFIG_DIR` | `~/.cursor` | Cursor config directory |
|
|
167
171
|
|
package/docs/user/commands.md
CHANGED
|
@@ -17,6 +17,11 @@ or you invoke it.
|
|
|
17
17
|
The agent asks before forgetting anything that may still matter.
|
|
18
18
|
- `/thatch/reflect` — run the session-reflection skill to persist what the
|
|
19
19
|
current session learned.
|
|
20
|
+
- `/thatch/whois` — report the current session's chat identity: its handle,
|
|
21
|
+
topic, project, and whether it runs in the project root or a worktree.
|
|
22
|
+
Text after the command looks up another session instead (by handle or
|
|
23
|
+
session id) — this is how you map a handle from the roster to the tab you
|
|
24
|
+
see: the topic is the session's live title. opencode only.
|
|
20
25
|
- `/thatch/refine` — refine a plan for a code change under the current
|
|
21
26
|
project type's requirements: the agent classifies the project (team,
|
|
22
27
|
personal, shared library, spike, infrastructure, or open source
|
|
@@ -38,13 +43,15 @@ or you invoke it.
|
|
|
38
43
|
| `/thatch/extract` | yes | no | no |
|
|
39
44
|
| `/thatch/hygiene` | yes | yes | yes (as an MCP prompt) |
|
|
40
45
|
| `/thatch/reflect` | yes | yes | yes (as an MCP prompt) |
|
|
46
|
+
| `/thatch/whois` | yes | no | no |
|
|
41
47
|
| `/thatch/refine` | yes | yes | yes (as an MCP prompt) |
|
|
42
48
|
| `/thatch/compact`, `/thatch/exit` | yes | no | no |
|
|
43
49
|
|
|
44
|
-
Why the gaps: `/thatch/extract`
|
|
45
|
-
the opencode plugin can supply. The wrap-up
|
|
46
|
-
through the plugin. Cursor has no file-based
|
|
47
|
-
the MCP server (`prompts/list` and
|
|
50
|
+
Why the gaps: `/thatch/extract` and `/thatch/whois` need the invoking
|
|
51
|
+
session's ID, which only the opencode plugin can supply. The wrap-up
|
|
52
|
+
commands trigger TUI actions through the plugin. Cursor has no file-based
|
|
53
|
+
commands; its prompts come from the MCP server (`prompts/list` and
|
|
54
|
+
`prompts/get`).
|
|
48
55
|
|
|
49
56
|
## Installation
|
|
50
57
|
|
|
@@ -11,7 +11,9 @@ between tabs all afternoon.
|
|
|
11
11
|
opencode sessions join the directory automatically, the moment their
|
|
12
12
|
first user message arrives (before the assistant responds), and a session
|
|
13
13
|
resumed with `opencode -c` or `opencode -s <id>` is back in the directory
|
|
14
|
-
at harness start with its old name.
|
|
14
|
+
at harness start with its old name. Sessions thatch itself creates in the
|
|
15
|
+
background - like the fact-extraction runs - are machinery, not chat
|
|
16
|
+
participants, and never appear in the roster. Names are assigned by thatch, never chosen: a name drawn from a
|
|
15
17
|
built-in pool plus a counter (`al-go-rithm-00001`). The counter only ever
|
|
16
18
|
increments, so a name is
|
|
17
19
|
minted exactly once per machine - pruning an old session never reissues its
|
|
@@ -29,16 +31,33 @@ explicitly with `thatch_chat_register` (no arguments) - useful after
|
|
|
29
31
|
checked in, its project, whether it runs in the project root or a linked
|
|
30
32
|
git worktree, its topic, and your unread count
|
|
31
33
|
- `thatch_chat_send` delivers a message to another registered session, by
|
|
32
|
-
name or session id, and states the recipient's liveness at send time
|
|
33
|
-
|
|
34
|
+
name or session id, and states the recipient's liveness at send time.
|
|
35
|
+
An identical resend within a few seconds is treated as a client retry:
|
|
36
|
+
while the first copy is still unread it succeeds but delivers nothing
|
|
37
|
+
new (the tool output says so); once the recipient has read it, the same
|
|
38
|
+
body delivers again - a deliberate repeat, not a retry. A body over
|
|
39
|
+
2,000 characters is not refused: it is auto-split into several messages,
|
|
40
|
+
and the tool output reports the split ("SENT AS N PARTS"). The whole
|
|
41
|
+
message is capped at 20,000 characters; longer is refused with the
|
|
42
|
+
attempted count
|
|
43
|
+
- `thatch_chat_read` drains the session's inbox. A message longer than
|
|
44
|
+
one page (2,000 characters) is shown clipped, with a marker naming the
|
|
45
|
+
characters ("[clipped: showing 2000 of 2047 characters]"); page through
|
|
46
|
+
the rest with the `message` and `offset` arguments (each drain line
|
|
47
|
+
carries the message id). Paging never marks anything read
|
|
34
48
|
- `thatch_chat_broadcast` delivers one message to every other live
|
|
35
|
-
registered session at once
|
|
49
|
+
registered session at once (a long body is split per recipient, exactly
|
|
50
|
+
like a split `chat_send`)
|
|
36
51
|
- `thatch_chat_unregister` leaves the directory
|
|
37
52
|
|
|
38
53
|
When mail arrives for an idle session, thatch wakes it with a notification -
|
|
39
54
|
the agent gets a model turn even with nobody at the keyboard, reads its
|
|
40
55
|
inbox, and decides what to do. The notification names the senders and the
|
|
41
|
-
count only; the agent reads the actual messages itself.
|
|
56
|
+
count only; the agent reads the actual messages itself. A split message
|
|
57
|
+
arrives as one grouped notification ("N unread messages from X"), and the
|
|
58
|
+
parts carry their own markers so the reader assembles the whole before
|
|
59
|
+
acting: the first part is prefixed "(message 1 of N)" and every part but
|
|
60
|
+
the last is suffixed "(continued in next message)".
|
|
42
61
|
|
|
43
62
|
Broadcasts are for announcements and open questions ("which of you is
|
|
44
63
|
working on the payments code?", "main just moved, rebase if you are based
|
|
@@ -198,10 +217,14 @@ construction. Three mitigations:
|
|
|
198
217
|
the true session id from the host and record which process spawned them;
|
|
199
218
|
the MCP server resolves its own parent process against that record, so
|
|
200
219
|
the model cannot claim another session's identity there. On Cursor, chat
|
|
201
|
-
tools
|
|
220
|
+
tools trust the `as` argument the caller supplies (hooks and MCP
|
|
202
221
|
servers share one workspace process, so process identity is ambiguous) -
|
|
203
|
-
|
|
204
|
-
|
|
222
|
+
but the claim is directory-bound: a session may only claim an identity
|
|
223
|
+
registered to its own project, and a cross-project claim is refused.
|
|
224
|
+
The refusal teaches the fix - register a fresh identity for the project
|
|
225
|
+
once, then reuse it; do not re-register every turn. opencode is immune
|
|
226
|
+
(the host supplies identity). The threat model is one user's machine;
|
|
227
|
+
same-project spoofing on Cursor remains possible and accepted.
|
|
205
228
|
- **No external content via wake.** The wake notification names senders
|
|
206
229
|
and counts only - bodies flow exclusively through the framed
|
|
207
230
|
`chat_read` surface.
|
|
@@ -59,6 +59,7 @@ surface it to you, or ignore it. You never see the nudge directly.
|
|
|
59
59
|
| `thatch_prediction_update` | Create, reinforce, or weaken a prediction. Takes a matcher (situation), prediction (preference), signal (confirm/disconfirm/soft/create), and rationale. |
|
|
60
60
|
| `thatch_prediction_list` | List all predictions with matchers, confidence, evidence count, and provenance history. |
|
|
61
61
|
| `thatch_prediction_delete` | Delete a prediction by semantic match. Edges and provenance are cascade-deleted. |
|
|
62
|
+
| `thatch_prediction_mark_checked` | Record a verdict (duplicate/distinct) on a pair the hygiene nudge flagged, so it stops resurfacing. |
|
|
62
63
|
|
|
63
64
|
## Configuration
|
|
64
65
|
|
|
@@ -83,5 +84,15 @@ surface it to you, or ignore it. You never see the nudge directly.
|
|
|
83
84
|
store does not automatically apply to other projects. The global
|
|
84
85
|
store is shared across projects.
|
|
85
86
|
|
|
87
|
+
## Keeping the model tidy
|
|
88
|
+
|
|
89
|
+
If the same preference gets recorded twice in different words, the hygiene
|
|
90
|
+
nudge reports `prediction duplicate pairs pending review`. The agent reads
|
|
91
|
+
both, then either merges them (moves the loser's matchers onto the winner
|
|
92
|
+
with `thatch_prediction_update`, deletes the loser with
|
|
93
|
+
`thatch_prediction_delete`) or judges them distinct — and records the
|
|
94
|
+
verdict with `thatch_prediction_mark_checked` either way, so an adjudicated
|
|
95
|
+
pair never nags again.
|
|
96
|
+
|
|
86
97
|
See [memory.md](memory.md) for the base memory system and
|
|
87
98
|
[behavior-engine.md](behavior-engine.md) for the self-discipline rules.
|
package/docs/user/setup.md
CHANGED
|
@@ -44,6 +44,22 @@ The same file loads on both opencode lines: opencode 1.x reads its `server`
|
|
|
44
44
|
export, opencode 2.x reads its default export (the merged plugin
|
|
45
45
|
definition). No path change when you switch opencode versions.
|
|
46
46
|
|
|
47
|
+
For opencode 2.x session-tab support, the plugin must be a DIRECTORY shim
|
|
48
|
+
so the TUI can load the `./tui` entrypoint:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
~/.config/opencode/plugins/thatch/
|
|
52
|
+
index.ts export { server } from "<thatch-checkout>/src/index";
|
|
53
|
+
export { default } from "<thatch-checkout>/src/index";
|
|
54
|
+
tui.ts export { default } from "<thatch-checkout>/src/opencode/tui-plugin";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Delete the old `thatch.ts` file shim in the same step - a file and a
|
|
58
|
+
directory shim coexisting stops plugin loading entirely (duplicate plugin
|
|
59
|
+
id, detected before any plugin code runs). The TUI plugin is what makes
|
|
60
|
+
spawned subordinate sessions appear as tabs; without it the tool still
|
|
61
|
+
works but no tab opens.
|
|
62
|
+
|
|
47
63
|
Or place the thatch repo in `.opencode/plugins/` for auto-loading.
|
|
48
64
|
|
|
49
65
|
## Claude Code
|
|
@@ -60,6 +76,16 @@ Skills follow the install scope: project-local installs them to the
|
|
|
60
76
|
repo's `.claude/skills/` so they version with the project; `--global`
|
|
61
77
|
installs them to `~/.claude/skills/` (or `$CLAUDE_CONFIG_DIR/skills/`).
|
|
62
78
|
|
|
79
|
+
To refresh just the skill files without rewriting anything else, add
|
|
80
|
+
`--skills-only`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
thatch setup --claude --skills-only
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This skips the MCP config, instructions, hooks, and commands, and
|
|
87
|
+
reports the same skills directory and install counts as a full run.
|
|
88
|
+
|
|
63
89
|
`bun` must be on PATH (the thatch binary runs under bun). Setup is
|
|
64
90
|
idempotent. Re-running it updates drifted content without clobbering
|
|
65
91
|
unrelated config, and reports what it did: the skills directory plus
|
|
@@ -84,6 +110,9 @@ thatch setup --cursor --global # user-scoped (~/.cursor/)
|
|
|
84
110
|
Skills follow the install scope: project-local installs them to the
|
|
85
111
|
repo's `.cursor/skills/`; `--global` installs them to `~/.cursor/skills/`.
|
|
86
112
|
|
|
113
|
+
As with Claude Code, `--skills-only` refreshes just the skill files and
|
|
114
|
+
skips the MCP config, instructions, and hooks.
|
|
115
|
+
|
|
87
116
|
Cursor uses a flat hooks format (`{version, hooks:{event:[{command}]}}`)
|
|
88
117
|
and `--json` output for hook commands.
|
|
89
118
|
|
package/docs/user/skills.md
CHANGED
|
@@ -68,7 +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
|
|
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, chat replies, and code comments. Clarity over compression. |
|
|
72
72
|
|
|
73
73
|
### Workflow skills
|
|
74
74
|
|
package/docs/user/watchers.md
CHANGED
|
@@ -112,7 +112,8 @@ Other tools:
|
|
|
112
112
|
commits, CI check runs, and workflow runs
|
|
113
113
|
- `thatch_watch_command_create` - wait on any local condition via a
|
|
114
114
|
shell command that exits 0 when the wait is over
|
|
115
|
-
- `thatch_watch_list` - shows the session's active watchers
|
|
115
|
+
- `thatch_watch_list` - shows the session's active watchers, plus any
|
|
116
|
+
detected events waiting for the session to go idle before delivery
|
|
116
117
|
- `thatch_watch_cancel` - cancels one by id
|
|
117
118
|
|
|
118
119
|
## Lifetime
|
|
@@ -129,17 +130,31 @@ restarts:
|
|
|
129
130
|
watch is still there.
|
|
130
131
|
- Watch definitions whose 8-hour TTL expired while the session was
|
|
131
132
|
away are not re-armed; the re-arm notice reports how many expired.
|
|
132
|
-
- If a watched session is
|
|
133
|
-
|
|
134
|
-
session
|
|
135
|
-
-
|
|
133
|
+
- If a watched session's tab is closed (or the watcher's deliveries fail
|
|
134
|
+
past the death threshold), its watchers are cancelled, and a live
|
|
135
|
+
session in the same project hears a one-line notice that the watch
|
|
136
|
+
died with its session - the notice says the watches were cancelled,
|
|
137
|
+
not re-armed, so a watch that will never fire is at least explained.
|
|
138
|
+
- A watcher expires after 8 hours by default, and reports its own
|
|
139
|
+
expiry: the session gets a final notification naming the watched
|
|
140
|
+
target and how long the watch lived (for example, "watch on
|
|
141
|
+
acme/widgets#7 expired after 480 min"). The notification states
|
|
142
|
+
that the watched condition never occurred - it is not a signal
|
|
143
|
+
that whatever the watch was gating finished. Re-register the watch
|
|
144
|
+
if notifications are still wanted.
|
|
145
|
+
- A watch also dies with its session: on opencode v2, a closed tab's
|
|
146
|
+
watchers are cancelled automatically once its undelivered events
|
|
147
|
+
have aged past 2 hours (THATCH_WATCH_DEATH_MINUTES) while delivery
|
|
148
|
+
keeps failing, and live sessions in the same project hear a death
|
|
149
|
+
notice naming the dead session and its watch targets. A coordinator
|
|
150
|
+
can use that notice to mark a subordinate's task failed and respawn.
|
|
136
151
|
- Each session can hold up to 5 active watchers (all sources share
|
|
137
152
|
the budget).
|
|
138
153
|
- A one-shot watch cancels itself after its first event - a single
|
|
139
154
|
"tell me when this run finishes" request leaves nothing polling
|
|
140
155
|
afterwards. Standing watches (the default) keep going until they
|
|
141
|
-
are cancelled, expire, or the process ends. Command
|
|
142
|
-
always one-shot.
|
|
156
|
+
are cancelled, expire (and say so), or the process ends. Command
|
|
157
|
+
watches are always one-shot.
|
|
143
158
|
|
|
144
159
|
## Requirements and limitations
|
|
145
160
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./tui": "./src/opencode/tui-plugin.ts"
|
|
11
|
+
},
|
|
8
12
|
"bin": {
|
|
9
13
|
"thatch": "./bin/thatch"
|
|
10
14
|
},
|
|
@@ -38,11 +42,15 @@
|
|
|
38
42
|
"yaml": "^2.7.0"
|
|
39
43
|
},
|
|
40
44
|
"peerDependencies": {
|
|
41
|
-
"@opencode-ai/plugin": "^1.17.0"
|
|
45
|
+
"@opencode-ai/plugin": "^1.17.0",
|
|
46
|
+
"@opencode/plugin": "^2.0.15"
|
|
42
47
|
},
|
|
43
48
|
"peerDependenciesMeta": {
|
|
44
49
|
"@opencode-ai/plugin": {
|
|
45
50
|
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"@opencode/plugin": {
|
|
53
|
+
"optional": true
|
|
46
54
|
}
|
|
47
55
|
},
|
|
48
56
|
"keywords": [
|
package/src/alerts.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// deduplication is exact by construction - delivering from the TUI side
|
|
11
11
|
// instead would fire once per attached TUI. State is rebuilt empty on
|
|
12
12
|
// plugin setup: a v2 reload mid-turn loses that turn's transition and the
|
|
13
|
-
// alert stays silent (accepted; see docs/
|
|
13
|
+
// alert stays silent (accepted; see docs/dev/features/notifications.md).
|
|
14
14
|
//
|
|
15
15
|
// What makes a round "real work" is split across two sources, on purpose:
|
|
16
16
|
// - the TOOL side comes from live bookkeeping: the runtime marks real work
|