@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 +1 -1
- package/artifacts/skills/pr-description.md +16 -9
- package/artifacts/skills/thatch-fact-extractor.md +3 -1
- package/docs/dev/README.md +53 -5
- package/docs/dev/mcp-parity.md +2 -1
- package/docs/user/README.md +15 -0
- package/package.json +1 -1
- package/src/index.ts +27 -4
- package/src/prompts.ts +58 -9
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**:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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**:
|
|
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.
|
|
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
|
|
package/docs/dev/README.md
CHANGED
|
@@ -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 =
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -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** (
|
|
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` |
|
package/docs/user/README.md
CHANGED
|
@@ -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.
|
|
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
|
|
55
|
-
//
|
|
56
|
-
//
|
|
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))
|
|
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
|
|
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**:
|
|
104
|
-
lessons discovered during implementation
|
|
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
|
|
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**:
|
|
278
|
-
lessons discovered during implementation
|
|
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
|
|
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**:
|
|
514
|
-
lessons discovered during implementation
|
|
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
|