@jeffober/thatch 0.1.20 → 0.1.21

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 CHANGED
@@ -67,7 +67,7 @@ Include thatch's instructions in your agent's system prompt manually; see
67
67
  - **Prediction engine**: a statistical model of user decision-making
68
68
  preferences. When a prompt matches learned contexts, predictions fire
69
69
  alongside the recall nudge.
70
- - **Skills**: 20 skills for memory workflows, structured multi-specialist
70
+ - **Skills**: 19 skills for memory workflows, structured multi-specialist
71
71
  code review, change walkthroughs, and writing tasks (PR descriptions,
72
72
  tickets, PR splitting).
73
73
 
@@ -39,7 +39,7 @@ Sections, in order: SYNOPSIS, PURPOSE, DESCRIPTION, WALK-THROUGH (conditional),
39
39
 
40
40
  **SYNOPSIS, PURPOSE, and DESCRIPTION are mandatory** - even for a one-line, single-concern change. A tiny PR gets short sections, not fewer sections. WALK-THROUGH and NOTES appear only when warranted; omit them when empty. robots.txt appears only after an automated reviewer has produced a false positive worth heading off (see its section); it is always the final section.
41
41
 
42
- **Scale the body to the diff's conceptual size**, not its structure's capacity. A 2-file, single-concern change (add a flag, guard a branch, rename a symbol, bump a constant) is a few lines per section. SYNOPSIS one line, PURPOSE one to three, DESCRIPTION one short paragraph, NOTES if needed. The layered DESCRIPTION machinery below is for changes with real before/after mechanics to teach; when the diff is self-evident once you know the intent, collapse it. If your draft for a small change runs past ~15 lines of body, you are padding. Cut until each remaining line earns its place. Most of this skill's guidance exists to tame large PRs; applying it wholesale to a small one produces exactly the bloat the skill warns against.
42
+ **Scale the body to the PR's complexity, not its size in files or lines.** Estimate complexity by how much new mental model the reviewer must build (see the point-based budgets in Length and style). A 1-point mechanical change gets short sections, not fewer sections. The layered DESCRIPTION machinery below is for changes with real before/after mechanics to teach; when the diff is self-evident once you know the intent, collapse it. Most of this skill's guidance exists to tame large PRs; applying it wholesale to a small one produces exactly the bloat the skill warns against.
43
43
 
44
44
  ### SYNOPSIS
45
45
 
@@ -77,7 +77,7 @@ When a ticket description is available, use its problem statement as the seed fo
77
77
 
78
78
  ### DESCRIPTION
79
79
 
80
- How much structure DESCRIPTION needs depends on the change's conceptual size.
80
+ How much structure DESCRIPTION needs depends on the PR's complexity.
81
81
 
82
82
  **Small, self-evident change** (add a flag, guard a branch, rename a symbol, bump a constant): collapse to a sentence or two stating what the PR adds/changes. Do not manufacture a "how the code behaves today" paragraph for a change whose intent already makes the diff obvious. That is padding, not scaffolding.
83
83
 
@@ -240,13 +240,20 @@ These are hard rules, not suggestions.
240
240
 
241
241
  Target for most PRs: the reader can take in the whole description in under 60 seconds. Err on the shorter side.
242
242
 
243
- Scale the body to the diff's conceptual size:
244
- - Trivial 2-file change: ~15-line ceiling. SYNOPSIS one line, PURPOSE one to three, DESCRIPTION one short paragraph, NOTES if needed.
245
- - Moderate refactor: one tight paragraph or bullet set per section.
246
- - Large change with WALK-THROUGH: as long as the flow needs, but no longer. Aim for 3-6 steps; more means you are over-decomposing. Each step earns its place.
247
- - Enormous PR: accept that the reader may need more than 60 seconds. Keep the same rules, but spend the extra words on context, concrete behavior, and reviewer safety. Do not chase a tiny body by compressing meaning.
243
+ Estimate the PR's **complexity** (how much new mental model the reviewer must build), not its size in files or lines. Complexity drives the word budget the way scrum points drive effort estimates:
248
244
 
249
- If your draft for a small change runs past ~15 lines of body, you are padding. Cut until each remaining line earns its place.
245
+ - **1 point** (mechanical: rename, flag, guard, bump): the reviewer needs to know what changed, not why or how. ~50-100 words. SYNOPSIS one line, PURPOSE 1-2 sentences, DESCRIPTION 1-2 sentences.
246
+ - **2-3 points** (new behavior, non-obvious mechanics): the reviewer needs to understand a before/after they cannot infer from the diff. ~100-250 words. SYNOPSIS 1-2 lines, PURPOSE 2-4 sentences, DESCRIPTION one tight paragraph or short three-layer.
247
+ - **5 points** (new primitive, multi-workflow): the reviewer needs a mental model they do not have yet. ~250-400 words. Full sections as needed, WALK-THROUGH if there is a lifecycle.
248
+ - **8 points** (architectural shift): the reviewer needs to re-orient their understanding of a subsystem. ~400-600 words. Accept the length, spend on context and reviewer safety.
249
+
250
+ Estimate by: how many new concepts, how many decision points, whether the before/after is non-obvious, whether multiple workflows are affected. Not by files changed or lines added. A 200-line mechanical rename is 1 point. A 50-line new primitive with non-obvious semantics is 3-5 points.
251
+
252
+ **When a section is too long, step up the abstraction ladder.** Do not compress terminology or cut connective tissue to hit a word count. Instead, replace a sequence of mechanism details with the principle that subsumes them. If you are explaining three mechanisms in sequence, find the one principle that makes all three obvious to someone who already knows the stack. The mechanism details belong in DESCRIPTION or the diff, not in PURPOSE or every section.
253
+
254
+ Example: instead of six claims about how a connection pool reuses connections, how session locks are session-scoped, how a lock taken by one statement does not guard the next, and how nothing reports the mismatch, write: "Some Postgres features attach to the connection, not the statement. The pool freely reuses connections between statements, so state set by one statement may not be there for the next, and nothing reports the mismatch." Same understanding, fewer words, no compression.
255
+
256
+ Brevity means fewer ideas, not compressed claims. If the section is still too long after stepping up, cut the lowest-value claim entirely. Do not cut the connective tissue that makes the remaining claims parse on first read.
250
257
 
251
258
  Inside sections, telegraphic bullet style is fine: lowercase start, abbreviations (`w/`, `1x`, `~`), parenthetical shorthand. Full sentences only when required for clarity. The section headers are the only formal structure.
252
259
 
@@ -401,7 +408,7 @@ That reading alone conveys the shape and scope of both changes. The prose is for
401
408
  11. **Verify links**: check ticket/PR links, repo file links, and external docs where tooling allows. Do not claim an unverified link was verified.
402
409
  12. **Verify prose style and core principle**: check each rule in Prose style. Cut design-narration, authoring-sequence, and buzzwordy abstractions. Verify plain ASCII. Verify first-use definitions for subsystem jargon. Verify project-private labels are translated before use. Verify plain English nouns (no code-domain nouns in prose). Verify conclusion-first in PURPOSE. Verify NOTES bullets are either short pointers or self-contained. Verify one-paragraph-one-physical-line for GitHub rendering. The robots.txt section is exempt from these prose rules below its human-warning line.
403
410
  13. **Clarity pass**: read the draft as a cold reviewer skimming the PR. Rewrite before returning it; do not tell the user you performed this pass. Check: does every project-specific term get plain-English meaning before or with the label? Are ambiguous terms qualified or replaced with concrete behavior? Did you replace code/comment shorthand with reader-facing behavior when the shorthand is less clear? Does every process word name object/action/effect? Does every contrast name old and new behavior? Does every causal sentence show the middle step? Did you preserve connective tissue instead of compressing meaning into noun stacks? If the draft got longer, cut a lower-value claim instead of compressing a high-value one.
404
- 14. **Verify length**: if the body exceeds the ceiling for its conceptual size, cut until each line earns its place. For enormous PRs, accept extra length only when it buys context, concrete behavior, or reviewer safety.
411
+ 14. **Verify length**: estimate the PR's complexity (see the point-based budgets in Length and style). If the body exceeds the budget, step up the abstraction ladder first (replace mechanism sequences with the principle that subsumes them). If still over, cut the lowest-value claim entirely. Do not compress terminology or cut connective tissue to hit a word count.
405
412
  15. **Submit** via `gh pr create --body "$(cat <<'EOF' ... EOF)"` to preserve formatting.
406
413
 
407
414
  ## Anti-patterns
@@ -15,7 +15,9 @@ You will be given a JSON payload with:
15
15
  1. Call thatch_memory_recall to check for existing memories related to the interactions.
16
16
  2. For each new fact, call thatch_memory_remember with the appropriate store.
17
17
  3. Do not save session-specific state, ephemeral debugging details, or info already in CLAUDE.md.
18
- 4. Write for a future session with zero current context. No "we", "our session", "just now".
18
+ 4. Apply the durability test before saving: will this still be true and useful in a future session that knows nothing about this branch, commit, or session? If not, skip it. Do not store point-in-time values (migration indices, line numbers, file counts), commit hashes or branch history narratives (git is the source of truth), or anything re-derivable from the codebase faster than recalling it.
19
+ 5. Write for a future session with zero current context. No "we", "our session", "just now".
20
+ 6. Your final message must be exactly: "Extraction complete." — do not list or summarize what you saved. The memories are in the store; the parent session does not need a report.
19
21
 
20
22
  ## What to extract
21
23
 
@@ -58,11 +58,11 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
58
58
  | `setup.ts` | `thatch setup --claude` installer. Writes .mcp.json, appends to CLAUDE.md (idempotent), installs hooks in settings.json, installs skills. |
59
59
  | `hygiene.ts` | Hygiene report: pending dedup pairs, stale count, orphaned branch memories. Shared by the plugin's session-start hook and the CLI's `thatch reminder` command. |
60
60
  | `git.ts` | Parse `owner/repo` from git remote. Worktree-safe fallback chain. |
61
- | `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. |
61
+ | `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. Prediction tables: matchers, predictions, edges, provenance. `scorePredictionNudge` is the shared auto-fire entry point for both host paths. |
62
62
  | `embeddings.ts` | Lazy-load the embedding model. Expose `queryEmbed`/`passageEmbed` and the model `name` (stored as an informational tag). `MockEmbeddingModel` for tests. |
63
63
  | `extraction.ts` | Per-session in-memory ring buffer (cap 20) that buffers non-thatch tool interactions and serializes them into the JSON payload the extraction nudge carries. The in-memory pipeline is opencode-only, but the payload builders (`buildExtractionPayload`, `deriveTitle`) are shared by both paths — `extract-queue.ts` imports `deriveTitle`, `bin/thatch` imports `buildExtractionPayload`. `summarizeArgs` is used internally by `buildExtractionPayload`. |
64
64
  | `extract-queue.ts` | File-backed per-session JSONL queue (caps 20, oldest dropped). Shared by the Claude Code and Cursor hook paths, which fire one-shot per event with no cross-call state. This is the MCP-side equivalent of `extraction.ts`. |
65
- | `sideband.ts` | Unix domain socket server + client. The MCP server (long-lived, warm model) runs `SidebandServer` so one-shot hook processes can ask it to embed a prompt and search for matches without loading the model themselves. Socket path is a hash of the DB path — both processes compute it independently. |
65
+ | `sideband.ts` | Unix domain socket server + client. The MCP server (long-lived, warm model) runs `SidebandServer` so one-shot hook processes can ask it to embed a prompt and search for matches without loading the model themselves. Handles two methods: `match` (recall nudge) and `predictions` (prediction auto-fire). Socket path is a hash of the DB path — both processes compute it independently. |
66
66
  | `prompts.ts` | Text constants: opencode system prompt, compaction context, session-start reminder, prompt-aware recall nudge (`recallNudge` / `claudeRecallNudge`), Claude Code CLAUDE.md instructions, Cursor AGENTS.md instructions, Claude Code hook text. |
67
67
  | `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (18 skills: fact-extractor, dedup-classifier, project-primer, 6 review specialists, review synthesizer, review context, workflow research, change walkthrough, code walkthrough, session reflection, pr-description, ticket-description, split-overlarge-pr — work on both opencode and Claude Code) and `OPENCODE_ONLY_SKILLS` (1 skill: code-review coordinator — requires sub-agent support, not installed for Claude Code). `installSkills(dir, skills)` defaults to `SHARED_SKILLS`; the opencode plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`. |
68
68
 
@@ -74,9 +74,9 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
74
74
  |------|-------------|
75
75
  | `experimental.chat.system.transform` | Appends the thatch system prompt (store names, usage rules). |
76
76
  | `experimental.session.compacting` | Marks the session as compacting and appends re-familiarization context so a compacted session still knows thatch exists. |
77
- | `experimental.compaction.autocontinue` | Clears the compacting flag so `chat.message` nudges resume. Without this, nudges that instruct tool calls would fire during summary generation where tools are blocked. |
77
+ | `experimental.compaction.autocontinue` | Clears the compacting flag so `chat.message` nudges resume. Without this, nudges that instruct tool calls would fire during summary generation where tools are blocked. The `chat.message` hook also clears the flag if it fires for a session still in the compacting set but the incoming message has no compaction-type part — this handles compaction failure, where the session would otherwise be stuck with nudges off forever. |
78
78
  | `tool.execute.after` | Buffers every non-`thatch_*`, non-`skill`, non-`task` tool call into the session's extraction buffer. (Skill/task are excluded — buffering them creates a feedback loop where the nudge triggers a skill load, which gets buffered, which triggers another nudge.) Memory writes (`thatch_memory_remember`) and `thatch_extraction_done` drain the buffer and reset the missed-nudge counter. This is a plugin hook, NOT a bus event — do not move it into the `event` handler; the event bus has no such event and it will silently never fire. |
79
- | `chat.message` | Two priority tiers: (a) if extraction buffer has interactions, **peeks** the buffer (does NOT drain it) and injects a synthetic text part carrying the extraction nudge + JSON payload — the buffer persists until the agent writes a memory or calls `thatch_extraction_done`, so ignored nudges repeat and escalate (polite → insistent → ALL-CAPS) via the `missedNudges` counter; (b) otherwise, embeds the user's prompt text with the in-process warm model, searches `db.search()` across repo + global, and pushes a recall nudge if matches exceed the threshold (default 0.55). Skipped entirely while the session is compacting (tool calls are blocked during summary generation). |
79
+ | `chat.message` | Two priority tiers: (a) if extraction buffer has interactions, **peeks** the buffer (does NOT drain it) and injects a synthetic text part carrying the extraction nudge + JSON payload — the buffer persists until the agent writes a memory or calls `thatch_extraction_done`, so ignored nudges repeat and escalate (polite → insistent → ALL-CAPS) via the `missedNudges` counter; (b) otherwise, embeds the user's prompt text with the in-process warm model, searches `db.search()` across repo + global, and pushes a recall nudge if matches exceed the threshold (default 0.55). The same embedding also feeds the prediction auto-fire: `db.scorePredictionNudge` scores the prompt against prediction matchers and injects a separate `[thatch] User decision model` nudge if any matchers clear the 0.45 threshold. Recall and prediction nudges fire independently (separate try/catch blocks, separate synthetic parts). Skipped entirely while the session is compacting (tool calls are blocked during summary generation). |
80
80
  | `event` (`session.created`) | Sends the session-start reminder via `client.session.prompt`, carrying the hygiene heartbeat (pending dedup pairs, stale count, orphaned branch memories) when any signal is non-zero. The session id is at `event.properties.info.id`. |
81
81
  | `dispose` | Closes the DB. |
82
82
 
@@ -121,6 +121,23 @@ Two of these hooks were dead for weeks because failures were invisible.
121
121
  case (agent forgot to save, next user turn arrives with buffer still
122
122
  pending). This is an experiment — model reliability on meta-instructions
123
123
  is uncertain.
124
+ 10. **Background completion narration is suppressed, not prevented.** When a
125
+ background sub-agent completes, opencode injects a `<task_result>` block
126
+ into the parent session and triggers a full model generation. Thatch
127
+ cannot cancel this turn (no pre-response hook). Two mitigations: the
128
+ fact-extractor skill's return value is constrained to the literal
129
+ "Extraction complete." so the injected block has no narratable content,
130
+ and the system prompt's "Background Task Completions" section instructs
131
+ the model not to narrate completions or treat them as approval to act.
132
+ 11. **Prediction engine is a statistical model, not an LLM call.** The query
133
+ (embed prompt, cosine-match against matchers, score linked predictions)
134
+ is mechanical — same shape as `thatch_memory_recall` but against different
135
+ tables. The agent drives formation and evaluation via tools
136
+ (`thatch_prediction_update` with confirm/disconfirm/soft/create signals)
137
+ guided by system prompt instructions. No wall-clock decay; confidence is
138
+ relevance-gated (being tested moves it, not being ignored). See the
139
+ prediction DB tables, auto-fire in `chat.message`, and the sideband
140
+ `predictions` method for the MCP path.
124
141
 
125
142
  ## Data flow
126
143
 
@@ -178,6 +195,33 @@ hygiene heartbeat (session start)
178
195
  longer exist (skipped when worktree isn't a git repo)
179
196
  → non-zero signals appended to the session-start reminder; the agent tends
180
197
  the store when convenient — the plugin never deletes memories itself
198
+
199
+ prediction cycle (agent-driven, statistical model)
200
+ → formation: agent calls thatch_prediction_update(matcher, prediction, signal)
201
+ with signal = create|confirm|disconfirm|soft. The tool embeds the matcher
202
+ text (raw, no header prepend — unlike memory_remember), finds or creates
203
+ a matcher (0.85 dedup), finds or creates a prediction (0.85 store-wide
204
+ dedup), links them via an edge, and adjusts confidence via a Bayesian
205
+ posterior: (confirm + K*P0) / (confirm + disconfirm + K), K=5, P0=0.5.
206
+ Soft signals count as 0.25 of a full signal. No wall-clock decay;
207
+ confidence is relevance-gated (being tested moves it, not being ignored).
208
+ → auto-fire (opencode): chat.message reuses the prompt embedding already
209
+ computed for the recall nudge. db.scorePredictionNudge([repo, global],
210
+ embedding, 0.45) finds matchers above threshold, follows edges to
211
+ predictions, scores by cosine * weight * confidence, dedups by
212
+ prediction_id, and returns top 5. Injects a separate synthetic part
213
+ with a [thatch] User decision model block. 0-evidence predictions use
214
+ "you may prefer"; predictions with evidence use "you tend to".
215
+ → auto-fire (Claude Code/Cursor): flush-tools fires the prediction query
216
+ via the sideband socket's `predictions` method in parallel with the
217
+ recall nudge. Same scorePredictionNudge entry point prevents scoring
218
+ drift between host paths.
219
+ → consumption: agent follows strong predictions silently, surfaces
220
+ ambiguous/competing predictions to the user, and calls
221
+ thatch_prediction_update to reinforce or weaken after the user responds.
222
+ thatch_prediction_list inspects the model with provenance;
223
+ thatch_prediction_delete removes bad predictions (cascade clears edges
224
+ and provenance).
181
225
  ```
182
226
 
183
227
  ## Database
@@ -188,7 +232,11 @@ hygiene heartbeat (session start)
188
232
  `entries(slug, store, label, content, embedding BLOB, model, branch,
189
233
  confidence, archived, created_at, updated_at, recall_count,
190
234
  last_recalled_at, PK(slug, store))`,
191
- `dedup_pairs(store, slug_a, slug_b, status, checked_at, PK(store, slug_a, slug_b))`.
235
+ `dedup_pairs(store, slug_a, slug_b, status, checked_at, PK(store, slug_a, slug_b))`,
236
+ `prediction_matchers(id PK, store, description, embedding BLOB, model, created_at, updated_at)`,
237
+ `predictions(id PK, store, statement, rationale, embedding BLOB, model, confidence REAL, confirm_count REAL, disconfirm_count REAL, created_at, updated_at)`,
238
+ `prediction_edges(matcher_id, prediction_id, weight REAL, PK(matcher_id, prediction_id), FK CASCADE)`,
239
+ `prediction_provenance(id PK, prediction_id, signal, detail, created_at, FK CASCADE)`.
192
240
  - `recall_count`, `last_recalled_at`, and `archived` are added to pre-existing
193
241
  databases by an idempotent column migration at init (`PRAGMA table_info` +
194
242
  `ALTER TABLE`). The `archived` column is `INTEGER NOT NULL DEFAULT 0` (0 =
@@ -18,12 +18,13 @@ 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** (9, single source of truth) | Plugin tool registration, `thatch_` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix | **Full** |
21
+ | **Tools** (13, single source of truth) | Plugin tool registration, `thatch_` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix | **Full** |
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
25
  | **Extraction nudge** (buffer tool calls, inject JSON payload) | `tool.execute.after` buffers non-thatch, non-skill, non-task tool calls in-process; `chat.message` peeks the buffer (does not drain it) and injects a synthetic text part | `PostToolBatch` → `thatch buffer-batch` (file-backed JSONL queue); `UserPromptSubmit` → `thatch flush-tools` peeks the queue and prints the nudge (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` | **Full** (file-backed) — timing differs: the nudge arrives at the **start** of the next turn in Claude Code/Cursor, not at the end of the current one like opencode's `chat.message` |
26
26
  | **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 |
27
+ | **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 |
27
28
  | **Skills** | Installed to `$XDG_CONFIG_HOME/opencode/skills` at plugin init (shared **+** opencode-only) | Installed to `$CLAUDE_CONFIG_DIR/skills/` by `thatch setup` (shared only) | Installed to `~/.cursor/skills/` by `thatch setup` (shared only) | **Full** — same SKILL.md format; the code-review coordinator is opencode-only (needs sub-agents) |
28
29
  | **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** |
29
30
  | **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` |
@@ -108,6 +108,15 @@ Stores are created automatically — no setup required.
108
108
  |------|-------------|
109
109
  | `thatch_extraction_done` | Acknowledge that the extraction nudge was dispatched to a sub-agent. Drains the buffer so the nudge does not replay. Called after dispatching a background task to run the fact-extractor skill. |
110
110
 
111
+ ### Prediction tools
112
+
113
+ | Tool | What it does |
114
+ |------|-------------|
115
+ | `thatch_prediction_query` | Query the user decision model for scored predictions matching a context. Returns predictions with confidence and evidence count. |
116
+ | `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). |
117
+ | `thatch_prediction_list` | List all predictions in a store with matchers, confidence, and provenance. |
118
+ | `thatch_prediction_delete` | Delete a prediction by semantic match. Edges and provenance are cascade-deleted. |
119
+
111
120
  ## Automatic behaviors
112
121
 
113
122
  Beyond the tools, thatch hooks into opencode itself:
@@ -133,6 +142,12 @@ Beyond the tools, thatch hooks into opencode itself:
133
142
  thatch never saves memories on its own.
134
143
  - **Compaction context.** When opencode compacts a long session, thatch injects
135
144
  a reminder so the summarized session still knows memory tools exist.
145
+ - **Prediction auto-fire.** When a prompt matches learned contexts, thatch
146
+ injects a `User decision model` block alongside the recall nudge. The block
147
+ lists scored predictions (confidence, evidence count) that the agent can
148
+ follow silently (strong prediction), surface to the user (ambiguous), or
149
+ use to update the model after the user responds. No extra model call —
150
+ the prediction search reuses the same prompt embedding as the recall nudge.
136
151
 
137
152
  ### Setup detection (Claude Code and Cursor)
138
153
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffober/thatch",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -51,9 +51,12 @@ export const server: Plugin = async ({ client, worktree }) => {
51
51
  // Sessions currently being compacted. chat.message nudges are skipped while
52
52
  // a session is in this set — the agent can't call tools during summary
53
53
  // generation, so a recall or extraction nudge would cause a blocked-tool
54
- // error. Cleared by experimental.compaction.autocontinue. If compaction
55
- // fails, the entry leaks (graceful degradation: nudges stay off for that
56
- // session, but no crash).
54
+ // error. Cleared by experimental.compaction.autocontinue (success), the
55
+ // session.compacted event (redundant belt-and-suspenders), or chat.message
56
+ // itself when a non-compaction message arrives (compaction failure fallback
57
+ // — autocontinue never fired, so the next real user message clears the
58
+ // stale flag). If all three somehow miss, the flag leaks (graceful
59
+ // degradation: nudges stay off for that session, but no crash).
57
60
  const compacting = new Set<string>();
58
61
 
59
62
  // Per-session count of consecutive extraction nudges delivered without any
@@ -192,7 +195,18 @@ export const server: Plugin = async ({ client, worktree }) => {
192
195
  // a summary, so a nudge that says "use thatch_memory_recall" triggers a
193
196
  // blocked-tool error.
194
197
  "chat.message": async (input, output) => {
195
- if (compacting.has(input.sessionID)) return;
198
+ if (compacting.has(input.sessionID)) {
199
+ // The session is marked as compacting. If this message is the
200
+ // compaction summary generation itself (has a compaction-type part),
201
+ // suppress nudges — tools are blocked during summary generation and a
202
+ // nudge would cause a blocked-tool error. If it is NOT a compaction
203
+ // message, compaction has already failed (autocontinue never fired)
204
+ // and the user is sending a new message. Clear the stale flag and
205
+ // proceed normally — tools are available again.
206
+ const isCompactionMsg = (output.parts as any[]).some((p) => p.type === "compaction");
207
+ if (isCompactionMsg) return;
208
+ compacting.delete(input.sessionID);
209
+ }
196
210
  if (extraction.pending(input.sessionID)) {
197
211
  const batch = extraction.peek(input.sessionID);
198
212
  const payload = extraction.buildPayload(batch, repo);
@@ -291,6 +305,15 @@ export const server: Plugin = async ({ client, worktree }) => {
291
305
  return;
292
306
  }
293
307
 
308
+ // session.compacted fires on successful compaction. Redundant with
309
+ // experimental.compaction.autocontinue, but belt-and-suspenders: if
310
+ // autocontinue didn't fire or wasn't installed, the event still clears
311
+ // the compacting flag so nudges resume.
312
+ if (event.type === "session.compacted") {
313
+ compacting.delete(event.properties.sessionID);
314
+ return;
315
+ }
316
+
294
317
  if (event.type !== "session.created") return;
295
318
  const id = event.properties.info.id;
296
319
 
package/src/prompts.ts CHANGED
@@ -54,7 +54,9 @@ workflows. The host auto-discovers them, but reach for them proactively:
54
54
 
55
55
  **One signal is enough.** Do not wait for confirmation.
56
56
  Save immediately on: feedback, preferences, corrections, emotional signals,
57
- new project knowledge, conventions, architectural decisions, investigation results.
57
+ new project knowledge, conventions, architectural decisions, investigation
58
+ results that produced durable knowledge (not point-in-time facts about the
59
+ current state of the code).
58
60
 
59
61
  ## Before Responding
60
62
 
@@ -64,6 +66,16 @@ whether you've discovered knowledge worth persisting. Use thatch_memory_recall
64
66
  to check for duplicates, then thatch_memory_remember for new findings. Then
65
67
  deliver your response.
66
68
 
69
+ ## Background Task Completions
70
+
71
+ When a background task completes and its result is injected into the session,
72
+ the completion is informational — it is not user input and not approval to
73
+ proceed with anything you were discussing. Do not narrate what the task did or
74
+ summarize its output. If you were mid-conversation with the user, continue
75
+ that thread. If the user's last message has been fully addressed, stop and
76
+ wait. Do not treat the completion as a trigger to advance work the user has
77
+ not greenlit.
78
+
67
79
  ## User Decision Model
68
80
 
69
81
  A statistical model of the user's decision-making preferences is maintained by
@@ -100,8 +112,9 @@ reinforce or adjust it when possible.
100
112
  component relationships, conventions, operational playbooks
101
113
  - **Branch-scoped**: branch purpose, feature design decisions, PR status,
102
114
  temporary workarounds
103
- - **Insights**: ALWAYS save non-obvious gotchas, patterns, and architectural
104
- lessons discovered during implementation. Never prompt the user; just do it.
115
+ - **Insights**: Save non-obvious gotchas, patterns, and architectural
116
+ lessons discovered during implementation, but only if they remain true
117
+ across sessions. Never prompt the user; just do it.
105
118
 
106
119
  ## How to Write
107
120
 
@@ -116,6 +129,16 @@ reinforce or adjust it when possible.
116
129
  Session-specific context, incomplete/unverified info, anything already in
117
130
  CLAUDE.md or OPENCODE.md, speculative conclusions.
118
131
 
132
+ Apply the durability test: will this still be true and useful in a future
133
+ session that knows nothing about this branch, commit, or session? If not,
134
+ do not save it. Do not store:
135
+ - Point-in-time values (migration indices, line numbers, file counts) that
136
+ will be stale by next session
137
+ - Commit hashes and branch-specific history narratives. Git is the source
138
+ of truth for this; do not duplicate it in memory
139
+ - Anything re-derivable from the codebase faster than recalling it (current
140
+ file locations, type signatures, function names)
141
+
119
142
  ## Archived Memories
120
143
 
121
144
  Memories can be marked \`archived\` — a flag for stable, long-term historical
@@ -228,7 +251,9 @@ workflows. The host auto-discovers them, but reach for them proactively:
228
251
 
229
252
  **One signal is enough.** Do not wait for confirmation.
230
253
  Save immediately on: feedback, preferences, corrections, emotional signals,
231
- new project knowledge, conventions, architectural decisions, investigation results.
254
+ new project knowledge, conventions, architectural decisions, investigation
255
+ results that produced durable knowledge (not point-in-time facts about the
256
+ current state of the code).
232
257
 
233
258
  ## Before Responding
234
259
 
@@ -274,8 +299,9 @@ reinforce or adjust it when possible.
274
299
  component relationships, conventions, operational playbooks
275
300
  - **Branch-scoped**: branch purpose, feature design decisions, PR status,
276
301
  temporary workarounds
277
- - **Insights**: ALWAYS save non-obvious gotchas, patterns, and architectural
278
- lessons discovered during implementation. Never prompt the user; just do it.
302
+ - **Insights**: Save non-obvious gotchas, patterns, and architectural
303
+ lessons discovered during implementation, but only if they remain true
304
+ across sessions. Never prompt the user; just do it.
279
305
 
280
306
  ## How to Write
281
307
 
@@ -290,6 +316,16 @@ reinforce or adjust it when possible.
290
316
  Session-specific context, incomplete/unverified info, anything already in
291
317
  CLAUDE.md, speculative conclusions.
292
318
 
319
+ Apply the durability test: will this still be true and useful in a future
320
+ session that knows nothing about this branch, commit, or session? If not,
321
+ do not save it. Do not store:
322
+ - Point-in-time values (migration indices, line numbers, file counts) that
323
+ will be stale by next session
324
+ - Commit hashes and branch-specific history narratives. Git is the source
325
+ of truth for this; do not duplicate it in memory
326
+ - Anything re-derivable from the codebase faster than recalling it (current
327
+ file locations, type signatures, function names)
328
+
293
329
  ## Archived Memories
294
330
 
295
331
  Memories can be marked \`archived\` — a flag for stable, long-term historical
@@ -464,7 +500,9 @@ workflows. The host auto-discovers them, but reach for them proactively:
464
500
 
465
501
  **One signal is enough.** Do not wait for confirmation.
466
502
  Save immediately on: feedback, preferences, corrections, emotional signals,
467
- new project knowledge, conventions, architectural decisions, investigation results.
503
+ new project knowledge, conventions, architectural decisions, investigation
504
+ results that produced durable knowledge (not point-in-time facts about the
505
+ current state of the code).
468
506
 
469
507
  ## Before Responding
470
508
 
@@ -510,8 +548,9 @@ reinforce or adjust it when possible.
510
548
  component relationships, conventions, operational playbooks
511
549
  - **Branch-scoped**: branch purpose, feature design decisions, PR status,
512
550
  temporary workarounds
513
- - **Insights**: ALWAYS save non-obvious gotchas, patterns, and architectural
514
- lessons discovered during implementation. Never prompt the user; just do it.
551
+ - **Insights**: Save non-obvious gotchas, patterns, and architectural
552
+ lessons discovered during implementation, but only if they remain true
553
+ across sessions. Never prompt the user; just do it.
515
554
 
516
555
  ## How to Write
517
556
 
@@ -526,6 +565,16 @@ reinforce or adjust it when possible.
526
565
  Session-specific context, incomplete/unverified info, anything already in
527
566
  AGENTS.md, speculative conclusions.
528
567
 
568
+ Apply the durability test: will this still be true and useful in a future
569
+ session that knows nothing about this branch, commit, or session? If not,
570
+ do not save it. Do not store:
571
+ - Point-in-time values (migration indices, line numbers, file counts) that
572
+ will be stale by next session
573
+ - Commit hashes and branch-specific history narratives. Git is the source
574
+ of truth for this; do not duplicate it in memory
575
+ - Anything re-derivable from the codebase faster than recalling it (current
576
+ file locations, type signatures, function names)
577
+
529
578
  ## Archived Memories
530
579
 
531
580
  Memories can be marked \`archived\` — a flag for stable, long-term historical