@jeffober/thatch 0.1.19 → 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.
@@ -1,10 +1,20 @@
1
1
  ---
2
2
  name: ticket-description
3
- description: Draft a ticket description using instructional-design scaffolding (SYNOPSIS / PROBLEM / BACKGROUND / PROPOSED APPROACH / ACCEPTANCE CRITERIA / RELEASE PLAN / VALIDATION / DEPENDENCIES / NOTES) with bold and italic emphasis on the phrases that carry the meaning, so a reader skimming only the emphasized fragments still gets the story. Use when the user asks you to write, draft, or file a ticket, issue, or work item (Linear or Jira). Do NOT use for PR descriptions (use pr-description), commit messages, or design docs.
3
+ description: Draft a ticket description (SYNOPSIS / PROBLEM / BACKGROUND / PROPOSED APPROACH / ACCEPTANCE CRITERIA / RELEASE PLAN / VALIDATION / DEPENDENCIES / NOTES) with bold and italic emphasis on the phrases that carry the meaning, so a reader skimming only the emphasized fragments still gets the story. Use when the user asks you to write, draft, or file a ticket, issue, or work item (Linear or Jira). Do NOT use for PR descriptions (use pr-description), commit messages, or design docs.
4
4
  ---
5
5
 
6
6
  # Writing ticket descriptions
7
7
 
8
+ ## Core principle
9
+
10
+ Write for humans. Not for LLMs. Not to impress. Not to pad.
11
+
12
+ Every sentence earns its place or gets cut. Plain English over jargon. Concrete over abstract. Short over long. A ticket a human reads once and can start work from beats one that looks thorough but says nothing.
13
+
14
+ If a word does not help a human understand the work, it is dead weight. If a sentence restates the previous one, one is redundant. If a phrase sounds smart but you cannot explain it in simpler words, it is a buzzword. Cut, replace, or simplify.
15
+
16
+ Target a 7th-grade reading level. Not because the reader is dumb, but because plain prose is faster to read and harder to misread. Short sentences, common words, one idea each. The concepts stay technical; the language stays simple.
17
+
8
18
  ## The reader
9
19
 
10
20
  You are writing for two people who share one trait: neither has your context, and neither knows this subsystem's jargon.
@@ -15,7 +25,7 @@ The second reader is an **implementer** picking up the ticket cold. They need to
15
25
 
16
26
  The description is not a design doc. It is a specification that makes work startable. A design doc (when one is warranted) may live in repo docs, Linear, Notion, Archbee, Google Docs, Figma, or another system; link it from the ticket instead of inlining it. The ticket's job is to define the problem, propose a direction, and set the gates that define "done."
17
27
 
18
- This is **instructional design**, not documentation. Each section has a cognitive goal:
28
+ Each section has one job:
19
29
  - SYNOPSIS: orient to the class of work and affected area.
20
30
  - PROBLEM: make the reader agree there is something worth solving.
21
31
  - BACKGROUND: give the implementer the context they would otherwise re-derive.
@@ -26,7 +36,7 @@ This is **instructional design**, not documentation. Each section has a cognitiv
26
36
  - DEPENDENCIES: explain ordering constraints, not just list them.
27
37
  - NOTES: flag scope limits, intentional non-goals, and things that look in-scope but aren't.
28
38
 
29
- Layer the knowledge so each section builds on the previous one. The reader should never encounter a concept in section N that was not introduced in section N-1.
39
+ Each section builds on the previous one. The reader should never encounter a concept in section N that was not introduced in section N-1.
30
40
 
31
41
  ## Structure
32
42
 
@@ -119,7 +129,7 @@ Only when prod verification is non-trivial. A pure refactor with good tests does
119
129
 
120
130
  ### DEPENDENCIES (conditional)
121
131
 
122
- Blocking relationships and ordering constraints. "Blocked by PLAT-126" is the tracker field; the *why* belongs here: "PLAT-125 partitions by location_fingerprint, so it can only run after backfill completes (count == 0), not merely after PLAT-124 merges." That kind of nuance is what prevents a well-intentioned lead from reordering the queue.
132
+ Blocking relationships and ordering constraints. "Blocked by PLAT-126" is the tracker field; the _why_ belongs here: "PLAT-125 partitions by location_fingerprint, so it can only run after backfill completes (count == 0), not merely after PLAT-124 merges." That kind of nuance is what prevents a well-intentioned lead from reordering the queue.
123
133
 
124
134
  Distinguish hard data dependencies (cannot run until X completes) from soft dependencies (should run after X for cleanliness, but technically works independently). Say which kind it is.
125
135
 
@@ -141,7 +151,7 @@ If there are no notes, omit the section entirely. Do not emit an empty `## NOTES
141
151
 
142
152
  ## Emphasis for scanning: bold and italics
143
153
 
144
- Bold and italicize the "save points" - the phrases that carry meaning - not whole sentences. A reader who skims *only* the bold and italic fragments should get the story; full prose is for when they want detail.
154
+ Bold and italicize the "save points" - the phrases that carry meaning - not whole sentences. A reader who skims _only_ the bold and italic fragments should get the story; full prose is for when they want detail.
145
155
 
146
156
  **What to bold:** phrases that name the **work itself** - the component, the failure mode, the mechanism, the behavior the ticket asks for. These are the nouns and verbs that would appear in a diff of the design, not a diff of the code. "**circular wait** in the **KeyManager**" names the failure mode and the component. "**load key material outside the mutex**" names the mechanism.
147
157
 
@@ -177,10 +187,11 @@ These are hard rules, not suggestions.
177
187
  5. **No authoring-sequence narration.** Never describe the order you discovered things in, which draft had what, or what a later revision will add. Describe the ticket as it stands.
178
188
  6. **Italics for orientation asides.** Use `_italics_` for a one-phrase aside that orients the reader: _This is where the deadlock lived_. Not for emphasis.
179
189
  7. **Plain ASCII only.** No smart quotes, smart apostrophes, em dashes, en dashes, ellipsis glyphs, or arrow glyphs. Use plain hyphens (sparingly) for asides; semicolons are often better.
180
- 8. **No buzzwordy abstractions.** If a phrase sounds like it is trying to sound smart, cut or replace it. Name the code and behavior directly. "identity contract" -> "the struct field that identifies the owner". "canonical hash" -> "the SHA-256 we compute on ingestion".
181
- 9. **Prescriptive voice.** The ticket proposes work that hasn't happened yet. Use "the fix should...", "the implementation must...", "the worker should..." not "this PR moves..." or "the code now...". The PR description uses descriptive voice; the ticket uses prescriptive.
182
- 10. **Default to a numbered outline for steps.** A flat list, with an occasional inline branch ("if the path is not in the registry, return a 404; otherwise continue to step 6"), covers almost every workflow. The bar for a diagram or Mermaid chart is high: reach for one only when the flow genuinely needs two dimensions to read - a graph network with many nodes and edges, or a workflow that recurses, forks, and rejoins in ways a linear outline cannot follow without constant back-references. If the flow is one or two levels deep and does not recurse or redirect, an outline is clearer and cheaper to maintain than a diagram.
183
- 11. **Avoid loaded language.** Use neutral, current terminology unless the repo's established name is different. Prefer allowlist/blocklist over whitelist/blacklist, primary/replica over master/slave, and precise role names over moralized labels like "bad" or "evil".
190
+ 8. **No buzzwordy abstractions.** If a phrase sounds like it is trying to sound smart, cut or replace it. Name the code and behavior directly. "identity contract" -> "the struct field that identifies the owner". "canonical hash" -> "the SHA-256 we compute on ingestion". "leverage" -> "use". "utilize" -> "use". "facilitate" -> "help". If you cannot say what a phrase means in plain English, it does not belong. Buzzwords are the number-one signal that an AI wrote the ticket; do not send that signal.
191
+ 9. **Prefer specific terms over ambiguous ones.** If a word can mean more than one thing in this ticket, qualify it or replace it with the specific behavior. Do not rely on the reader guessing the local meaning. Ordinary words can be ambiguous when the code gives them a special role: claim, owner, active, current, generation, repair, coalesce, terminal, source, scope, sync, publish, resolve. "Publish the value" is vague. "Persist the value in the database so the UI can display it" is specific.
192
+ 10. **Prescriptive voice.** The ticket proposes work that hasn't happened yet. Use "the fix should...", "the implementation must...", "the worker should..." not "this PR moves..." or "the code now...". The PR description uses descriptive voice; the ticket uses prescriptive.
193
+ 11. **Default to a numbered outline for steps.** A flat list, with an occasional inline branch ("if the path is not in the registry, return a 404; otherwise continue to step 6"), covers almost every workflow. The bar for a diagram or Mermaid chart is high: reach for one only when the flow genuinely needs two dimensions to read - a graph network with many nodes and edges, or a workflow that recurses, forks, and rejoins in ways a linear outline cannot follow without constant back-references. If the flow is one or two levels deep and does not recurse or redirect, an outline is clearer and cheaper to maintain than a diagram.
194
+ 12. **Avoid loaded language.** Use neutral, current terminology unless the repo's established name is different. Prefer allowlist/blocklist over whitelist/blacklist, primary/replica over master/slave, and precise role names over moralized labels like "bad" or "evil".
184
195
 
185
196
  ## Length and style
186
197
 
@@ -207,6 +218,23 @@ Comparison process:
207
218
 
208
219
  When the codebase contradicts the ticket, flag the discrepancy to the user before drafting. Do not silently paper over the gap.
209
220
 
221
+ ## Project context for planned work
222
+
223
+ Some tickets are slices of a larger project. The code alone may not explain the project terms, ordering constraints, or prior decisions the implementer needs. Before drafting, read enough context to make the ticket startable for an engineer new to this area.
224
+
225
+ Read project context when the request, existing ticket, code comments, branch name, prior PRs, docs, or discussion mentions:
226
+ - a parent ticket, related PR, follow-up, or multi-ticket project;
227
+ - a deferred part of the design;
228
+ - an upstream dependency;
229
+ - a project-specific term whose local meaning is not obvious from the code touched by this ticket;
230
+ - a roadmap label, subsystem nickname, architecture slogan, or branch-local name.
231
+
232
+ Use available source material: linked tickets, linked PRs, nearby docs, code comments, commit messages, and project memory. Build a private term map: term -> plain behavior -> source. Do not dump the map into the ticket. Use it to write the shortest useful BACKGROUND.
233
+
234
+ If a term or dependency controls the work and you cannot resolve it from available context, stop and ask the user. Do not guess. Do not write around the missing definition with vaguer words.
235
+
236
+ Put project context in BACKGROUND only when it helps the implementer start work or helps a lead schedule it correctly. Omit generic backstory.
237
+
210
238
  ## Search before create
211
239
 
212
240
  Before filing a new ticket, search for likely duplicates in the tracker. Use the proposed title, problem keywords, affected subsystem, and any incident/customer/project references. If a plausible duplicate exists, surface it to the user before drafting or filing.
@@ -361,20 +389,22 @@ That reading alone conveys the shape, scope, and gates of all three tickets.
361
389
  1. **Gather**: read the code the ticket references. Check `git log` for recent changes to the same files. Identify the problem, the prior work, and the proposed direction.
362
390
  2. **Search before create**: search Linear/Jira for likely duplicate tickets. If one exists, surface it before drafting or filing.
363
391
  3. **Codebase comparison**: verify the problem still exists in the current code. If it was already fixed, tell the user. If it was partially mitigated, note the mitigation.
364
- 4. **Draft PROBLEM first**, in one breath, without looking at code. If you cannot state the harm in three sentences, you do not understand the problem yet. Go back and read.
365
- 5. **Write BACKGROUND** (if needed): what prior work led here, what was done, what was left behind.
366
- 6. **Write PROPOSED APPROACH**: the compass heading, not the map. Prescriptive voice.
367
- 7. **Write ACCEPTANCE CRITERIA**: observable, testable bullets. Name the test shape where relevant.
368
- 8. **Write RELEASE PLAN** (if needed): flags, ordering, rollback.
369
- 9. **Write VALIDATION** (if needed): monitoring, signals, smoke tests.
370
- 10. **Write DEPENDENCIES** (if needed): what blocks this, what this blocks, and why.
371
- 11. **Write SYNOPSIS last** - it is a compression of PROBLEM + the core approach.
372
- 12. **Bold and italicize the save points** in the allowed locations. Bold phrases that name the work (components, mechanisms, behaviors). Italicize phrases that orient to significance (conclusions, payoffs), at most one per paragraph. Apply the scan check.
373
- 13. **Add NOTES** for non-goals, declined alternatives, and scope limits. Use `## NOTES` header. Omit if empty.
374
- 14. **Suggest Linear labels** when the ticket is being filed in Linear. Use obvious stable labels only; do not guess initiative labels.
375
- 15. **Verify links**: check repo file links, external docs, tickets, PRs, and design artifacts where tooling allows. Do not claim an unverified link was verified.
376
- 16. **Verify prose style**: check each rule in Prose style. Cut design-narration, authoring-sequence, and buzzwordy abstractions. Verify plain ASCII. Verify prescriptive voice (not descriptive). Verify first-use definitions for subsystem jargon in BACKGROUND or PROBLEM. Verify plain English nouns (no code-domain nouns in prose). Verify conclusion-first in PROBLEM. Verify NOTES bullets are either short pointers or self-contained.
377
- 17. **Verify length**: if the body exceeds what the work's conceptual size warrants, cut until each line earns its place. If PROPOSED APPROACH dominates, consider splitting into a design doc plus ticket.
392
+ 4. **Read project context for planned work**: if the ticket is part of a larger project, depends on prior work, uses unexplained project terms, or has scheduling dependencies, read the linked tickets, PRs, docs, comments, and memory before drafting. Build a private term map. If a term or dependency controls the work and you cannot resolve it, ask the user.
393
+ 5. **Draft PROBLEM first**, in one breath, without looking at code. If you cannot state the harm in three sentences, you do not understand the problem yet. Go back and read.
394
+ 6. **Write BACKGROUND** (if needed): what prior work led here, what was done, what was left behind, and what project context an engineer new to this area needs to start.
395
+ 7. **Write PROPOSED APPROACH**: the compass heading, not the map. Prescriptive voice.
396
+ 8. **Write ACCEPTANCE CRITERIA**: observable, testable bullets. Name the test shape where relevant.
397
+ 9. **Write RELEASE PLAN** (if needed): flags, ordering, rollback.
398
+ 10. **Write VALIDATION** (if needed): monitoring, signals, smoke tests.
399
+ 11. **Write DEPENDENCIES** (if needed): what blocks this, what this blocks, and why.
400
+ 12. **Write SYNOPSIS last** - it is a compression of PROBLEM + the core approach.
401
+ 13. **Bold and italicize the save points** in the allowed locations. Bold phrases that name the work (components, mechanisms, behaviors). Italicize phrases that orient to significance (conclusions, payoffs), at most one per paragraph. Apply the scan check.
402
+ 14. **Add NOTES** for non-goals, declined alternatives, and scope limits. Use `## NOTES` header. Omit if empty.
403
+ 15. **Suggest Linear labels** when the ticket is being filed in Linear. Use obvious stable labels only; do not guess initiative labels.
404
+ 16. **Verify links**: check repo file links, external docs, tickets, PRs, and design artifacts where tooling allows. Do not claim an unverified link was verified.
405
+ 17. **Verify prose style and core principle**: check each rule in Prose style. Cut design-narration, authoring-sequence, and buzzwordy abstractions. Verify plain ASCII. Verify prescriptive voice (not descriptive). Verify first-use definitions for subsystem jargon in BACKGROUND or PROBLEM. Verify plain English nouns (no code-domain nouns in prose). Verify conclusion-first in PROBLEM. Verify NOTES bullets are either short pointers or self-contained.
406
+ 18. **Clarity pass**: read the draft as a cold lead skimming a backlog, then as an implementer picking up the work cold. 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 specific behavior? Does every process word name object/action/effect? Does every contrast name current and proposed 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.
407
+ 19. **Verify length**: if the body exceeds what the work's conceptual size warrants, cut until each line earns its place. If PROPOSED APPROACH dominates, consider splitting into a design doc plus ticket.
378
408
 
379
409
  ## Anti-patterns
380
410
 
@@ -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` |
@@ -36,21 +36,21 @@ description: Extract durable project facts ... Use when ...
36
36
  | `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth-wall breaks, em dashes, filler. |
37
37
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of behavior? |
38
38
  | `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
39
- | `thatch-review-synthesizer` | Verify specialist findings against code, dedupe, classify, calibrate severity. Cross-references findings against prior review comments when a follow-up round register is provided; produces a Previously identified appendix. |
39
+ | `thatch-review-synthesizer` | Verify specialist findings against code, dedupe, classify, calibrate severity. Starts the final report with a workflow-change preface, then cross-references findings against prior review comments when a follow-up round register is provided. |
40
40
  | `thatch-review-context` | Gather project/feature context (PR descriptions, git archaeology, ticket references, memory) before fan-out. Prevents false positives about intentionally deferred work. Also fetches prior review comments on a connected PR/MR for follow-up-round detection and builds a register with preliminary addressed-check status per comment. |
41
41
  | `thatch-workflow-research` | Research code workflows/features affected by a change or planned change. Reads code flows, comments, git history, memories, docs. Produces a guide to the code for reviewers or planners. |
42
42
  | `thatch-change-walkthrough` | Explain a change to the user as a teaching walkthrough: resolve the delta, research each affected workflow at the merge-base, teach current behavior, then overlay the modifications with file:line citations and analogies. |
43
43
  | `thatch-code-walkthrough` | Explain a feature, module, or workflow to the user as a teaching walkthrough: identify the code area (optionally from a branch or PR), research how it works, teach it with file:line citations and analogies, list the key files. |
44
44
  | `thatch-session-reflection` | End-of-session memory recording (project, user, tools, self). |
45
- | `pr-description` | Draft PR descriptions with instructional-design scaffolding (SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES) and bold+italic emphasis for scanning. |
46
- | `ticket-description` | Draft ticket/issue descriptions with instructional-design scaffolding (SYNOPSIS / PROBLEM / BACKGROUND / PROPOSED APPROACH / ACCEPTANCE CRITERIA / RELEASE PLAN / VALIDATION / DEPENDENCIES / NOTES). |
45
+ | `pr-description` | Draft PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, project-context research, clarity checks, and bold+italic emphasis for scanning. |
46
+ | `ticket-description` | Draft ticket/issue descriptions with clear sections, project-context research, clarity checks, and bold+italic emphasis for scanning. |
47
47
  | `split-overlarge-pr` | Split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
48
48
 
49
49
  **opencode-only (1)** — the coordinator needs sub-agent support:
50
50
 
51
51
  | Skill | Role |
52
52
  |-------|------|
53
- | `thatch-code-review` | Resolve review target (incl. VCS detection and connected PR/MR lookup for follow-up round detection), gather project context, research affected workflows, estimate complexity, partition, dispatch the 6 specialists in parallel, synthesize (incl. cross-referencing findings against prior review comments). |
53
+ | `thatch-code-review` | Resolve review target (incl. VCS detection and connected PR/MR lookup for follow-up round detection), gather project context, research affected workflows, estimate complexity, partition, dispatch the 6 specialists in parallel, synthesize with a workflow-change preface and prior-comment cross-reference. |
54
54
 
55
55
  ## REVIEW_COMMON
56
56
 
@@ -115,7 +115,7 @@ and `--cursor` pass only the shared set.
115
115
  ## Memory review skills in practice
116
116
 
117
117
  - **Quick single lens**: load any specialist directly and point it at a branch.
118
- - **Full review on opencode**: load `thatch-code-review` — it gathers project context, researches affected workflows, dispatches all 6 specialists in parallel (with both the context brief and workflow guide injected into each briefing), then synthesizes.
118
+ - **Full review on opencode**: load `thatch-code-review` — it gathers project context, researches affected workflows, dispatches all 6 specialists in parallel (with both the context brief and workflow guide injected into each briefing), then synthesizes a report that starts with the workflow changes before findings.
119
119
  - **Full review on Claude Code/Cursor**: run each specialist in sequence, then
120
120
  run `thatch-review-synthesizer` to verify and aggregate.
121
121
 
@@ -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
 
@@ -174,7 +189,7 @@ Six specialist review lenses, each a self-contained static-analysis pass:
174
189
  | `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth wall breaks, em dashes, hedging, filler. |
175
190
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
176
191
  | `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
177
- | `thatch-review-synthesizer` | Verifies and synthesizes findings from multiple specialists into a single deduplicated, severity-grouped report. |
192
+ | `thatch-review-synthesizer` | Verifies and synthesizes findings from multiple specialists into a report that starts with workflow changes, then deduplicated severity-grouped findings. |
178
193
  | `thatch-review-context` | Gathers project context (PR descriptions, git archaeology, ticket references, memory) before a review. Prevents false positives about intentionally deferred work. |
179
194
  | `thatch-workflow-research` | Researches code workflows and features affected by a change or planned change. Reads code flows, comments, git history, and produces a guide for reviewers or planners. |
180
195
  | `thatch-change-walkthrough` | Explains a change to the user as a teaching walkthrough: researches each affected workflow at the merge-base, teaches current behavior, then overlays the modifications with file:line citations. |
@@ -184,15 +199,15 @@ Six specialist review lenses, each a self-contained static-analysis pass:
184
199
 
185
200
  | Skill | Purpose |
186
201
  |-------|---------|
187
- | `pr-description` | Drafts PR descriptions using instructional-design scaffolding (SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES) with bold and italic emphasis for scanning. |
188
- | `ticket-description` | Drafts ticket or issue descriptions (Linear or Jira) using instructional-design scaffolding with bold and italic emphasis for scanning. |
202
+ | `pr-description` | Drafts PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, project-context research, clarity checks, and bold/italic emphasis for scanning. |
203
+ | `ticket-description` | Drafts ticket or issue descriptions (Linear or Jira) with clear sections, project-context research, clarity checks, and bold/italic emphasis for scanning. |
189
204
  | `split-overlarge-pr` | Splits already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
190
205
 
191
206
  ### opencode-only skills
192
207
 
193
208
  | Skill | Purpose |
194
209
  |-------|---------|
195
- | `thatch-code-review` | Multi-agent review coordinator. Dispatches parallel sub-agents for triage, decomposition, specialist fan-out, and synthesis. Not available in Claude Code (requires sub-agent support). |
210
+ | `thatch-code-review` | Multi-agent review coordinator. Dispatches parallel sub-agents for triage, decomposition, specialist fan-out, and synthesis with a workflow-change preface. Not available in Claude Code (requires sub-agent support). |
196
211
 
197
212
  ### Host availability
198
213
 
@@ -222,7 +237,8 @@ Load thatch-code-review and review branch feature-x.
222
237
  ```
223
238
 
224
239
  The coordinator will triage the change, dispatch parallel sub-agents (one per
225
- specialist lens), and synthesize a final report.
240
+ specialist lens), and synthesize a final report. The report starts with the
241
+ workflow-level changes so the findings have context.
226
242
 
227
243
  For a **full review on Claude Code** (or without the coordinator), run each
228
244
  specialist in sequence, then synthesize:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffober/thatch",
3
- "version": "0.1.19",
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