@jeffober/thatch 0.1.16 → 0.1.17

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.
@@ -18,11 +18,11 @@ 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** (8, 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** (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** |
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
- | **Extraction nudge** (buffer tool calls, inject JSON payload) | `tool.execute.after` buffers non-thatch tool calls in-process; `chat.message` flushes the buffer as a synthetic text part | `PostToolBatch` → `thatch buffer-batch` (file-backed JSONL queue); `UserPromptSubmit` → `thatch flush-tools` drains the queue and prints the nudge | `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` |
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
27
  | **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
28
  | **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** |
@@ -64,13 +64,17 @@ explicit "re-familiarization" nudge for hosts without a compaction hook.
64
64
 
65
65
  OpenCode's extraction pipeline works by:
66
66
 
67
- 1. `tool.execute.after` buffers every non-thatch tool call into a per-session
68
- in-memory array (max 20 interactions).
69
- 2. On the next `chat.message`, the buffer is flushed and a synthetic text part
70
- carrying the JSON payload is injected into the conversation (end of the
71
- current turn).
67
+ 1. `tool.execute.after` buffers every non-thatch, non-skill, non-task tool
68
+ call into a per-session in-memory array (max 20 interactions).
69
+ 2. On the next `chat.message`, the buffer is **peeked** (not drained) and a
70
+ synthetic text part carrying the JSON payload is injected into the
71
+ conversation. The buffer persists until the agent writes a memory or calls
72
+ `thatch_extraction_done`, so ignored nudges repeat and escalate (polite →
73
+ insistent → ALL-CAPS) via the `missedNudges` counter.
72
74
  3. The agent sees the nudge, loads the `thatch-fact-extractor` skill, and saves
73
- durable facts via `thatch_memory_remember`.
75
+ durable facts via `thatch_memory_remember`. A memory write in the session
76
+ (or a child sub-agent via the `childToParent` Map) or `thatch_extraction_done`
77
+ drains the buffer and resets the missed-nudge counter.
74
78
 
75
79
  Claude Code and Cursor use a **file-backed** queue (`src/extract-queue.ts`)
76
80
  because hooks fire one-shot with no cross-call state:
@@ -80,7 +84,10 @@ because hooks fire one-shot with no cross-call state:
80
84
  (max 20, oldest dropped). These commands are silent — no stdout — so the
81
85
  agent loop is not delayed.
82
86
  2. `UserPromptSubmit` / `beforeSubmitPrompt` runs `thatch flush-tools`, which
83
- drains the queue and prints the JSON payload. This arrives at the **start**
87
+ peeks the queue (does not drain it) and prints the JSON payload. The queue
88
+ persists until the agent writes a memory or calls `thatch_extraction_done`,
89
+ so ignored nudges accumulate and escalate (polite → insistent → ALL-CAPS)
90
+ via the file-backed missed-nudge counter. This arrives at the **start**
84
91
  of the next turn (before the model processes the prompt), not at the end of
85
92
  the current one.
86
93
 
@@ -106,7 +113,7 @@ Claude Code / Cursor session
106
113
  │ ├── stdio JSON-RPC (tool calls)
107
114
  │ └── Unix socket sideband (embed + search for hooks)
108
115
  ├── UserPromptSubmit / beforeSubmitPrompt hook → thatch flush-tools [--json]
109
- │ ├── drains the file-backed extraction queue (existing)
116
+ │ ├── peeks the file-backed extraction queue (does not drain)
110
117
  │ ├── connects to sideband socket
111
118
  │ ├── sends prompt text
112
119
  │ ├── receives match labels + scores
@@ -19,7 +19,7 @@ The plugin entry (`src/index.ts`) registers these hooks in code:
19
19
  | `experimental.chat.system.transform` | `{}` | `{ system: string[] }` | Pushes the system prompt (store names, usage rules). |
20
20
  | `experimental.session.compacting` | `{ sessionID }` | `{ context: string[] }` | Marks the session as compacting, pushes re-familiarization context. |
21
21
  | `experimental.compaction.autocontinue` | `{ sessionID }` | `{ enabled: boolean }` | Clears the compacting flag so `chat.message` nudges resume post-compaction. |
22
- | `tool.execute.after` | `{ tool, sessionID, callID, args }` | `{ title, output, metadata }` | Buffers non-`thatch_` tool calls into the in-memory extraction ring buffer (max 20). |
22
+ | `tool.execute.after` | `{ tool, sessionID, callID, args }` | `{ title, output, metadata }` | Buffers non-`thatch_`, non-`skill`, non-`task` tool calls into the in-memory extraction ring buffer (max 20). |
23
23
  | `chat.message` | `{ sessionID, messageID }` | `{ message, parts }` | Two tiers: extraction nudge if pending, else prompt-aware recall nudge. Skipped while the session is compacting. |
24
24
  | `event` (`session.created`) | `{ event: { type, properties: { info: { id } } } }` | — | Calls `client.session.prompt` with the reminder + hygiene heartbeat. |
25
25
  | `dispose` | — | — | Closes the DB. |
@@ -60,10 +60,10 @@ in the repo; only skills are user-scoped.
60
60
  |-------|---------|--------|------|
61
61
  | `SessionStart` | `thatch reminder` | plain text to stdout (becomes context) | Recall instructions + hygiene heartbeat |
62
62
  | `PostToolBatch` | `thatch buffer-batch` | **silent** (no stdout) | Appends a batch of tool calls to the file-backed JSONL queue |
63
- | `UserPromptSubmit` | `thatch flush-tools` | nudge text to stdout | Drains queue (extraction nudge), else recall nudge via sideband, else write nudge |
63
+ | `UserPromptSubmit` | `thatch flush-tools` | nudge text to stdout | Peeks queue (extraction nudge), else recall nudge via sideband, else write nudge |
64
64
 
65
65
  `PostToolBatch` is silent so the agent loop is not delayed; the buffered
66
- content is invisible until `UserPromptSubmit` flushes it.
66
+ content is invisible until `UserPromptSubmit` peeks it.
67
67
 
68
68
  ## Cursor (MCP server + hooks)
69
69
 
@@ -100,7 +100,7 @@ is honored for symmetry and forward-compatibility. Cursor has no equivalent of
100
100
  |-------|---------|--------|------|
101
101
  | `sessionStart` | `thatch reminder --json` | `{ additional_context: "..." }` | Recall + heartbeat, JSON-wrapped for Cursor |
102
102
  | `postToolUse` | `thatch buffer-tool` | **silent** | Appends a **single** tool call to the file-backed queue |
103
- | `beforeSubmitPrompt` | `thatch flush-tools --json` | JSON `additional_context` | Drains queue, else recall via sideband, else write nudge |
103
+ | `beforeSubmitPrompt` | `thatch flush-tools --json` | JSON `additional_context` | Peeks queue, else recall via sideband, else write nudge |
104
104
 
105
105
  Differences from Claude Code:
106
106
 
@@ -21,9 +21,9 @@ description: Extract durable project facts ... Use when ...
21
21
  - `description` drives when the agent loads the skill (both opencode and
22
22
  Cursor auto-discover skills and use the description for relevance).
23
23
 
24
- ## The 16 skills
24
+ ## The 19 skills
25
25
 
26
- **Shared (15)** — installed everywhere; no sub-agents required:
26
+ **Shared (18)** — installed everywhere; no sub-agents required:
27
27
 
28
28
  | Skill | Role |
29
29
  |-------|------|
@@ -42,6 +42,9 @@ description: Extract durable project facts ... Use when ...
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). |
47
+ | `split-overlarge-pr` | Split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
45
48
 
46
49
  **opencode-only (1)** — the coordinator needs sub-agent support:
47
50
 
@@ -81,7 +84,7 @@ The synthesizer reuses the same verification rigor but has its own structure
81
84
  ## The two arrays
82
85
 
83
86
  ```ts
84
- const SHARED_SKILLS: SkillDef[] = [ /* 15 skills above */ ];
87
+ const SHARED_SKILLS: SkillDef[] = [ /* 18 skills above */ ];
85
88
  const OPENCODE_ONLY_SKILLS: SkillDef[] = [ /* code-review coordinator */ ];
86
89
  ```
87
90
 
package/docs/qa/README.md CHANGED
@@ -91,7 +91,7 @@ correctness on `README.md` and `docs/` excluding `docs/plans/`; see
91
91
 
92
92
  ### `tests/plugin.test.ts`
93
93
 
94
- - `server` export shape: all eight tools, every hook present, tools carry
94
+ - `server` export shape: all nine tools, every hook present, tools carry
95
95
  description/args/execute
96
96
  - System transform and compaction hooks append their text
97
97
  - `tool.execute.after` → `chat.message` extraction round-trip: nudge carries
@@ -14,7 +14,7 @@ file/IPC/CLI contracts ready to graduate into `bun:test` integration tests.
14
14
  | [UC-003](UC-003-extraction-nudge.md) | Tool buffering → extraction nudge → agent-driven fact saving |
15
15
  | [UC-004](UC-004-cli-inspection.md) | CLI commands, arg validation, env overrides |
16
16
  | [UC-005](UC-005-setup-install.md) | `thatch setup --claude`/`--cursor`, project-local + `--global`, idempotent re-run, legacy hook migration |
17
- | [UC-006](UC-006-cursor-hook-lifecycle.md) | Cursor hook lifecycle: `buffer-tool` (`conversation_id`), `flush-tools --json`, queue drain |
17
+ | [UC-006](UC-006-cursor-hook-lifecycle.md) | Cursor hook lifecycle: `buffer-tool` (`conversation_id`), `flush-tools --json`, queue peek |
18
18
  | [UC-007](UC-007-recall-nudge.md) | Prompt-aware recall nudge — opencode in-process + Claude/Cursor sideband paths; no telemetry |
19
19
  | [UC-008](UC-008-hygiene-heartbeat.md) | Hygiene heartbeat: stale 90d, orphaned branches, pending dedup; advisory, agent-driven |
20
20
  | [UC-009](UC-009-flush-tools-tiers.md) | `flush-tools` three-tier priority (extraction > recall > write) + sideband degradation |
@@ -22,11 +22,13 @@ file/IPC/CLI contracts ready to graduate into `bun:test` integration tests.
22
22
  | [UC-011](UC-011-write-time-similarity-warning.md) | Write-time warning never blocks; merge or mark-distinct reconciliation |
23
23
  | [UC-012](UC-012-model-migration.md) | `THATCH_MODEL` change: old memories skipped (not corrupted) until re-saved |
24
24
  | [UC-013](UC-013-compaction-context.md) | Compaction context re-familiarization (opencode-only) |
25
- | [UC-014](UC-014-skill-install-drift.md) | Skill install: 10 shared vs 11 opencode; drift overwritten on re-setup |
25
+ | [UC-014](UC-014-skill-install-drift.md) | Skill install: 18 shared vs 19 opencode; drift overwritten on re-setup |
26
26
  | [UC-015](UC-015-env-override-matrix.md) | `THATCH_DB_PATH`, `XDG_CONFIG_HOME`, `CLAUDE_CONFIG_DIR`, `THATCH_QUEUE_DIR`, `THATCH_RECALL_THRESHOLD`, `THATCH_MODEL` |
27
27
  | [UC-016](UC-016-concurrent-session-isolation.md) | Per-session extraction isolation (in-memory + file queue); shared stateless sideband |
28
28
  | [UC-017](UC-017-buffer-tool-vs-buffer-batch.md) | `buffer-batch` (Claude, `tool_calls[]`+`session_id`) vs `buffer-tool` (Cursor, single+`conversation_id`) |
29
29
  | [UC-018](UC-018-mcp-startup-setup-detection.md) | MCP startup detects missing/broken setup instructions and warns the agent |
30
+ | [UC-019](UC-019-archived-memory-lifecycle.md) | Archive, search exclusion, update guard, unarchive |
31
+ | [UC-020](UC-020-extraction-escalation.md) | Nudge escalation (polite → insistent → ALL-CAPS), buffer persistence, `extraction_done` drain |
30
32
 
31
33
  ## Template
32
34
 
@@ -1,11 +1,14 @@
1
1
  # UC-003: Fact extraction from tool activity
2
2
 
3
+ _Automatable: buffer/peek/escalation contract could be tested with the mock
4
+ client and a synthetic tool call sequence._
5
+
3
6
  **Preconditions**
4
7
  - thatch plugin active in an opencode session
5
8
 
6
9
  **Steps**
7
10
  1. Do some real work: have the agent read files, run commands, edit code —
8
- any non-thatch tool activity.
11
+ any non-`thatch_*`, non-`skill`, non-`task` tool activity.
9
12
  2. Send your next message (anything).
10
13
 
11
14
  **Expected**
@@ -14,8 +17,18 @@
14
17
  truncated args/output).
15
18
  - The agent loads `thatch-fact-extractor` and saves durable facts via
16
19
  `thatch_memory_remember` — or saves nothing if the activity was routine.
17
- - The buffer is flushed: the following message carries no repeat nudge unless
18
- new tool activity happened.
20
+ - The buffer is **not** drained on nudge delivery — it persists until the agent
21
+ writes a memory or calls `thatch_extraction_done`. If the nudge is ignored,
22
+ the next message carries a repeat nudge, escalating in urgency:
23
+ - 1st-2nd miss: polite tone
24
+ - 3rd consecutive miss (missedCount=2): insistent (directive) tone
25
+ - 4th+ consecutive miss (missedCount>=3): ALL-CAPS tone
26
+ The counter resets when the buffer drains (memory write or
27
+ `thatch_extraction_done`).
28
+ - A `thatch_memory_remember` call in a child sub-agent session also drains the
29
+ parent's buffer (via the `childToParent` Map), so dispatching the
30
+ fact-extractor as a background task clears the parent's queue.
19
31
  - Two concurrent sessions never see each other's interactions in a nudge.
20
32
  - The agent's own `thatch_*` tool calls never appear in a payload (no
21
- feedback loop).
33
+ feedback loop). `skill` and `task` tool calls are also excluded (buffering
34
+ them would create a nudge → skill load → buffer → nudge loop).
@@ -17,12 +17,12 @@
17
17
  appends a thatch block to `CLAUDE.md` bracketed by start/end markers (replaced, not
18
18
  duplicated, on re-run), writes `.claude/settings.json` hooks (`SessionStart` -> `thatch reminder`,
19
19
  `PostToolBatch` -> `thatch buffer-batch`, `UserPromptSubmit` -> `thatch flush-tools`), and installs
20
- **12 shared skills** (no code-review coordinator) to `$CLAUDE_CONFIG_DIR/skills/` — user-scoped
20
+ **18 shared skills** (no code-review coordinator) to `$CLAUDE_CONFIG_DIR/skills/` — user-scoped
21
21
  even in project-local mode.
22
22
  - `--cursor` project-local writes `.cursor/mcp.json`, appends to `AGENTS.md`, writes
23
23
  `.cursor/hooks.json` in the **flat format** (`{version:1, hooks:{...}}`): `sessionStart` ->
24
24
  `thatch reminder --json`, `postToolUse` -> `thatch buffer-tool`, `beforeSubmitPrompt` ->
25
- `thatch flush-tools --json`; and installs 12 shared skills to `~/.cursor/skills/`.
25
+ `thatch flush-tools --json`; and installs 18 shared skills to `~/.cursor/skills/`.
26
26
  - Re-run is idempotent: instructions are not duplicated, thatch hooks are replaced (not appended),
27
27
  and non-thatch hooks are preserved. A legacy `thatch echo` hook is replaced with `flush-tools`.
28
28
  - `--global` (Claude) writes `~/.claude/CLAUDE.md` + `~/.claude/settings.json` but **no project
@@ -17,9 +17,10 @@
17
17
  **Expected**
18
18
  - `buffer-tool` normalizes `conversation_id` to a safe session file name and appends exactly one
19
19
  interaction per call; thatch's own `mcp__thatch__*` tools never enter the queue (no self-echo).
20
- - `flush-tools` drains the queue first (extraction tier): prints the JSON payload wrapped for
21
- Cursor as `additional_context`, and **deletes the queue file** so the next call carries no
22
- repeat payload unless new tool activity happened.
20
+ - `flush-tools` peeks the queue first (extraction tier): prints the JSON payload wrapped for
21
+ Cursor as `additional_context`. The queue is **not** deleted — it persists until the agent
22
+ writes a memory or calls `thatch_extraction_done`, so ignored nudges repeat and escalate
23
+ (polite to insistent to ALL-CAPS) on subsequent calls.
23
24
  - With the queue empty and the sideband socket live, a semantically matching prompt yields a
24
25
  recall nudge using the bare `memory_recall` tool name; below `THATCH_RECALL_THRESHOLD`
25
26
  (default 0.55), no nudge.
@@ -16,7 +16,9 @@
16
16
 
17
17
  **Expected**
18
18
  - Tiers fire in strict priority — **extraction > recall > write** — at most one nudge per call:
19
- - extraction: queue non-empty -> JSON payload, queue file deleted.
19
+ - extraction: queue non-empty -> JSON payload. The queue is peeked, not
20
+ deleted; it persists until a memory write or `extraction_done` (drains via
21
+ `consumeQueue` in `appendBatch`).
20
22
  - recall: queue empty, socket live, match at or above `THATCH_RECALL_THRESHOLD` (default 0.55)
21
23
  -> nudge with match labels; below threshold -> no nudge.
22
24
  - write: socket unavailable, no matches, or socket error -> static
@@ -13,16 +13,16 @@
13
13
  5. Check whether the `thatch-code-review` coordinator skill is present.
14
14
 
15
15
  **Expected**
16
- - Claude Code and Cursor install exactly **12 shared skills** to the skills
16
+ - Claude Code and Cursor install exactly **18 shared skills** to the skills
17
17
  dir — the coordinator (`thatch-code-review`) is **absent** (it needs
18
18
  sub-agents, which those hosts lack).
19
- - opencode installs **13** — the 12 shared plus the coordinator.
19
+ - opencode installs **19** — the 18 shared plus the coordinator.
20
20
  - The locally edited `SKILL.md` is **overwritten** with the canonical content
21
21
  on the next `setup`/init (drift detection: a file is only rewritten when its
22
22
  content differs from the definition). Unrelated skill files are untouched.
23
23
  - Skills never land in the worktree — always under the user-scoped config dir
24
24
  (`~/.claude/skills`, `~/.cursor/skills`, `$XDG_CONFIG_HOME/opencode/skills`).
25
25
 
26
- _Automatable: yes — file presence, count (12 vs 13), content-diff-overwrite, and
26
+ _Automatable: yes — file presence, count (18 vs 19), content-diff-overwrite, and
27
27
  coordinator host-gating are all file assertions (`setup.test.ts` already covers
28
28
  the unit contract)._
@@ -24,9 +24,10 @@
24
24
  no nudge from session A's buffered interaction; session B's message only sees
25
25
  its own buffer. The in-memory `ExtractionPipeline` and the file-backed queue are
26
26
  both keyed by session ID.
27
- - `flush-tools` for session A drains only A's queue file and deletes it; a second
28
- flush for A is empty. Session B's queue file is untouched and drains on its own
29
- flush.
27
+ - `flush-tools` for session A peeks only A's queue file (does not delete it); a
28
+ second flush for A returns the same content with an escalated missed-nudge
29
+ counter (the queue persists until a memory write or `extraction_done`). Session
30
+ B's queue file is untouched and is peeked on its own flush.
30
31
  - The sideband socket is **shared** (one warm MCP server) but **stateless per
31
32
  query**: each request embeds a fresh prompt and returns its own matches. Two
32
33
  concurrent match requests do not interfere.
@@ -0,0 +1,36 @@
1
+ # UC-019: Archived memory lifecycle
2
+
3
+ _Automatable: archive guard + search/dedup/staleness exclusion are pure DB
4
+ contract testable with a temp DB._
5
+
6
+ **Preconditions**
7
+ - thatch plugin active in an opencode session (or CLI with a test store)
8
+
9
+ **Steps**
10
+ 1. Save a memory scoped to a feature branch:
11
+ `thatch_memory_remember` with `branch: "feature-x"`, `label: "Feature X design decisions"`, `content: "..."`.
12
+ 2. Merge or delete the branch. Save a consolidated archived memory:
13
+ `thatch_memory_remember` with `overwrite: true`, `archived: true`,
14
+ `branch: "feature-x"`, same label, consolidated content.
15
+ 3. Search for content related to the archived memory:
16
+ `thatch_memory_recall` with a relevant query (no `includeArchived`).
17
+ 4. Search again with `includeArchived: true`.
18
+ 5. Attempt to update the archived memory's content without passing the
19
+ `archived` param.
20
+ 6. Unarchive the memory: `thatch_memory_remember` with `overwrite: true`,
21
+ `archived: false`.
22
+
23
+ **Expected**
24
+ - Step 2: the memory is saved with `archived: true`. The `archived` column is
25
+ set to 1 in the database.
26
+ - Step 3: the archived memory does NOT appear in search results — `search`
27
+ filters `WHERE archived = 0`.
28
+ - Step 4: the archived memory DOES appear when `includeArchived: true` is
29
+ passed.
30
+ - Step 5: the tool returns an error: `"Feature X design decisions" is
31
+ archived. Pass archived: true to keep it archived, or archived: false to
32
+ unarchive it.` The content is NOT updated.
33
+ - Step 6: the memory is unarchived (`archived` column set to 0). It reappears
34
+ in normal search results.
35
+ - `find_duplicates` does not surface archived memories as candidates.
36
+ - `staleEntryCount` (hygiene) does not count archived memories as stale.
@@ -0,0 +1,38 @@
1
+ # UC-020: Extraction nudge escalation and acknowledgment
2
+
3
+ _Automatable: missedNudges counter + escalation tiers + extraction_done drain
4
+ are testable with the mock client and a synthetic tool call sequence._
5
+
6
+ **Preconditions**
7
+ - thatch active in an opencode, Claude Code, or Cursor session
8
+ - For the child-sub-agent path: opencode with
9
+ `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`
10
+
11
+ **Steps**
12
+ 1. Do some non-thatch tool work (read files, run commands). Do NOT write any
13
+ memories.
14
+ 2. Send a message. Observe the extraction nudge — polite tone (missedCount=0).
15
+ 3. Ignore the nudge. Send another message without writing a memory (missedCount=1).
16
+ 4. Repeat step 3 (missedCount=2).
17
+ 5. Repeat step 3 again (missedCount=3+).
18
+ 6. Acknowledge by calling `thatch_extraction_done` in the session.
19
+ 7. Send another message.
20
+
21
+ **Expected**
22
+ - Step 2: the nudge is polite, referencing the queued tool interactions.
23
+ - Step 4: after 2 consecutive misses (missedCount=2), the nudge is insistent
24
+ (directive tone).
25
+ - Step 5: after 3+ consecutive misses (missedCount>=3), the nudge is ALL-CAPS.
26
+ - The buffer was NOT drained at any point during steps 2-5 — the buffer
27
+ persists until a memory is written or `extraction_done` is called. The nudge
28
+ repeats each turn with escalated tone (and the payload grows if new tool
29
+ activity adds entries between nudges).
30
+ - Step 6: `thatch_extraction_done` drains the buffer and resets the
31
+ `missedNudges` counter. The tool returns `"[acknowledged] extraction buffer
32
+ drained"`.
33
+ - Step 7: no extraction nudge appears (buffer is empty). If the user prompt
34
+ semantically matches existing memories, a recall nudge may appear instead.
35
+ - Alternative drain path: if a child sub-agent (dispatched via the `task`
36
+ tool) writes a memory via `thatch_memory_remember`, the parent's buffer is
37
+ also drained via the `childToParent` Map — the `missedNudges` counter resets
38
+ for the parent session.
@@ -102,6 +102,12 @@ Stores are created automatically — no setup required.
102
102
  | `thatch_find_duplicates` | Surface pairs of memories with suspiciously similar content. |
103
103
  | `thatch_dedup_mark_checked` | Record the verdict for a reviewed pair so it stops being re-reported. |
104
104
 
105
+ ### Extraction tools
106
+
107
+ | Tool | What it does |
108
+ |------|-------------|
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
+
105
111
  ## Automatic behaviors
106
112
 
107
113
  Beyond the tools, thatch hooks into opencode itself:
@@ -120,8 +126,11 @@ Beyond the tools, thatch hooks into opencode itself:
120
126
  similar entries so it can merge them or record that they're distinct.
121
127
  - **Fact-extraction nudges.** Thatch buffers the session's recent tool calls
122
128
  (up to 20, per session). On your next message, the agent receives a summary
123
- payload and is prompted to save any durable facts it reveals. The agent does
124
- the writing — thatch never saves memories on its own.
129
+ payload and is prompted to save any durable facts it reveals. The nudge
130
+ repeats and escalates (polite to insistent to ALL-CAPS) each turn it's
131
+ ignored — the buffer drains only when the agent writes a memory or
132
+ acknowledges via `thatch_extraction_done`. The agent does the writing —
133
+ thatch never saves memories on its own.
125
134
  - **Compaction context.** When opencode compacts a long session, thatch injects
126
135
  a reminder so the summarized session still knows memory tools exist.
127
136
 
@@ -155,7 +164,7 @@ With `thatch setup --claude`, skills install to `~/.claude/skills/` (or
155
164
 
156
165
  ### Code review skills
157
166
 
158
- Five specialist review lenses, each a self-contained static-analysis pass:
167
+ Six specialist review lenses, each a self-contained static-analysis pass:
159
168
 
160
169
  | Skill | Focus |
161
170
  |-------|-------|
@@ -164,9 +173,20 @@ Five specialist review lenses, each a self-contained static-analysis pass:
164
173
  | `thatch-review-state-flow` | Data flow and contracts: module boundaries, implicit state machines, error propagation, separation of concerns. |
165
174
  | `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth wall breaks, em dashes, hedging, filler. |
166
175
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
176
+ | `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
167
177
  | `thatch-review-synthesizer` | Verifies and synthesizes findings from multiple specialists into a single deduplicated, severity-grouped report. |
168
178
  | `thatch-review-context` | Gathers project context (PR descriptions, git archaeology, ticket references, memory) before a review. Prevents false positives about intentionally deferred work. |
169
179
  | `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
+ | `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. |
181
+ | `thatch-code-walkthrough` | Explains a feature, module, or workflow to the user as a teaching walkthrough with file:line citations. Also used to draft high-level docs for new or undocumented features. |
182
+
183
+ ### Writing skills
184
+
185
+ | Skill | Purpose |
186
+ |-------|---------|
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. |
189
+ | `split-overlarge-pr` | Splits already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
170
190
 
171
191
  ### opencode-only skills
172
192
 
@@ -179,9 +199,11 @@ Five specialist review lenses, each a self-contained static-analysis pass:
179
199
  | Skill | opencode | Claude Code | Cursor |
180
200
  |-------|----------|-------------|--------|
181
201
  | Memory skills (4) | Yes | Yes | Yes |
182
- | Review specialists (5) | Yes | Yes | Yes |
202
+ | Review specialists (6) | Yes | Yes | Yes |
183
203
  | Review synthesizer | Yes | Yes | Yes |
184
204
  | Review context + workflow research | Yes | Yes | Yes |
205
+ | Walkthrough skills (2) | Yes | Yes | Yes |
206
+ | Writing skills (3) | Yes | Yes | Yes |
185
207
  | Code review coordinator | Yes | No (requires sub-agents) | No (requires sub-agents) |
186
208
 
187
209
  ### Using review skills
@@ -208,7 +230,7 @@ specialist in sequence, then synthesize:
208
230
  ```text
209
231
  1. Load thatch-review-pedantic, review branch feature-x, report findings.
210
232
  2. Load thatch-review-acceptance, review the same branch.
211
- 3. ... repeat for state-flow, no-slop, breadcrumbs ...
233
+ 3. ... repeat for state-flow, no-slop, breadcrumbs, mark-and-sweep ...
212
234
  4. Load thatch-review-synthesizer, verify and aggregate all findings.
213
235
  ```
214
236
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffober/thatch",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
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
@@ -112,22 +112,22 @@ export const server: Plugin = async ({ client, worktree }) => {
112
112
  // agent writes a memory, so ignored nudges accumulate and the payload
113
113
  // grows with each missed cycle.
114
114
  //
115
- // Fix A: a memory_remember call in a child session (sub-agent) also
116
- // drains the parent's buffer. Without this, dispatching the
117
- // fact-extractor as a background task writes memories in the child
118
- // but never clears the parent's queue — the nudge replays every turn.
115
+ // A memory_remember call in a child session (sub-agent) also drains
116
+ // the parent's buffer. Without this, dispatching the fact-extractor as
117
+ // a background task writes memories in the child but never clears the
118
+ // parent's queue — the nudge replays every turn.
119
119
  //
120
- // Fix C: thatch_extraction_done is an explicit acknowledgment tool the
121
- // model calls in the parent session after dispatching. Belt-and-
122
- // suspenders for fix A: covers the case where the sub-agent errors out
123
- // or the host doesn't expose parent-child session relationships.
120
+ // thatch_extraction_done is an explicit acknowledgment tool the model
121
+ // calls in the parent session after dispatching. Covers the case where
122
+ // the sub-agent errors out or the host doesn't expose parent-child
123
+ // session relationships.
124
124
  "tool.execute.after": async (input, output) => {
125
125
  if (input.tool === "thatch_memory_remember") {
126
126
  extraction.consume(input.sessionID);
127
127
  missedNudges.delete(input.sessionID);
128
- // Fix A: if this is a child session, also drain the parent so the
129
- // parent's extraction nudge stops replaying after a sub-agent writes
130
- // memories on its behalf.
128
+ // If this is a child session, also drain the parent so the parent's
129
+ // extraction nudge stops replaying after a sub-agent writes memories
130
+ // on its behalf.
131
131
  const parentID = childToParent.get(input.sessionID);
132
132
  if (parentID) {
133
133
  extraction.consume(parentID);
@@ -135,9 +135,9 @@ export const server: Plugin = async ({ client, worktree }) => {
135
135
  }
136
136
  return;
137
137
  }
138
- // Fix C: explicit acknowledgment tool drains the buffer without
139
- // requiring a memory write. The model calls this after dispatching
140
- // the fact-extractor to a sub-agent.
138
+ // Explicit acknowledgment tool drains the buffer without requiring a
139
+ // memory write. The model calls this after dispatching the
140
+ // fact-extractor to a sub-agent.
141
141
  if (input.tool === "thatch_extraction_done") {
142
142
  extraction.consume(input.sessionID);
143
143
  missedNudges.delete(input.sessionID);
@@ -248,7 +248,7 @@ export const server: Plugin = async ({ client, worktree }) => {
248
248
  path: { id },
249
249
  body: {
250
250
  noReply: true,
251
- parts: [{ type: "text", text: sessionStartReminder(repo, hygiene) }],
251
+ parts: [{ type: "text", text: sessionStartReminder(repo, hygiene), synthetic: true }],
252
252
  },
253
253
  });
254
254
  } catch (err) {
package/src/mcp.ts CHANGED
@@ -34,7 +34,7 @@ const INTERNAL_ERROR = -32603;
34
34
  // ---------------------------------------------------------------------------
35
35
 
36
36
  const SERVER_NAME = "thatch";
37
- const SERVER_VERSION = "0.1.2";
37
+ const SERVER_VERSION = "0.1.16";
38
38
 
39
39
  /**
40
40
  * MCP capabilities declared in the initialize response. Thatch is a tools-only
package/src/prompts.ts CHANGED
@@ -6,7 +6,8 @@ knowledge so future sessions can build on what you've already learned.
6
6
 
7
7
  Tools: thatch_memory_remember, thatch_memory_recall, thatch_memory_list,
8
8
  thatch_memory_show, thatch_memory_forget, thatch_store_list,
9
- thatch_find_duplicates, thatch_dedup_mark_checked
9
+ thatch_find_duplicates, thatch_dedup_mark_checked,
10
+ thatch_extraction_done
10
11
 
11
12
  ## Stores
12
13
 
@@ -41,6 +42,9 @@ workflows. The host auto-discovers them, but reach for them proactively:
41
42
  - \`thatch-session-reflection\` — record what you learned at end of session.
42
43
  - \`thatch-fact-extractor\` — extract durable facts from recent tool interactions (auto-triggered by the extraction pipeline).
43
44
  - \`thatch-dedup-classifier\` — resolve duplicate memory pairs from \`thatch_find_duplicates\`.
45
+ - \`pr-description\` — draft a PR description using instructional-design scaffolding with bold and italic emphasis on the phrases that carry the meaning, so a reviewer skimming only the emphasized fragments still gets the story.
46
+ - \`ticket-description\` — draft a ticket, issue, or work item (Linear or Jira) using instructional-design scaffolding with bold and italic emphasis for scanning.
47
+ - \`split-overlarge-pr\` — split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main.
44
48
 
45
49
  ## When to Write
46
50
 
@@ -48,6 +52,14 @@ workflows. The host auto-discovers them, but reach for them proactively:
48
52
  Save immediately on: feedback, preferences, corrections, emotional signals,
49
53
  new project knowledge, conventions, architectural decisions, investigation results.
50
54
 
55
+ ## Before Responding
56
+
57
+ Before composing a final response after substantial work this turn — multiple
58
+ rounds of tool calls for investigation, debugging, or code-writing — check
59
+ whether you've discovered knowledge worth persisting. Use thatch_memory_recall
60
+ to check for duplicates, then thatch_memory_remember for new findings. Then
61
+ deliver your response.
62
+
51
63
  ## What to Store
52
64
 
53
65
  - **Global store**: user observations, agent personality, system environment
@@ -174,6 +186,9 @@ workflows. The host auto-discovers them, but reach for them proactively:
174
186
  - \`thatch-session-reflection\` — record what you learned at end of session.
175
187
  - \`thatch-fact-extractor\` — extract durable facts from recent tool interactions (auto-triggered by the extraction pipeline).
176
188
  - \`thatch-dedup-classifier\` — resolve duplicate memory pairs from \`find_duplicates\`.
189
+ - \`pr-description\` — draft a PR description using instructional-design scaffolding with bold and italic emphasis on the phrases that carry the meaning, so a reviewer skimming only the emphasized fragments still gets the story.
190
+ - \`ticket-description\` — draft a ticket, issue, or work item (Linear or Jira) using instructional-design scaffolding with bold and italic emphasis for scanning.
191
+ - \`split-overlarge-pr\` — split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main.
177
192
 
178
193
  ## When to Write
179
194
 
@@ -181,6 +196,14 @@ workflows. The host auto-discovers them, but reach for them proactively:
181
196
  Save immediately on: feedback, preferences, corrections, emotional signals,
182
197
  new project knowledge, conventions, architectural decisions, investigation results.
183
198
 
199
+ ## Before Responding
200
+
201
+ Before composing a final response after substantial work this turn — multiple
202
+ rounds of tool calls for investigation, debugging, or code-writing — check
203
+ whether you've discovered knowledge worth persisting. Use memory_recall to
204
+ check for duplicates, then memory_remember for new findings. Then deliver
205
+ your response.
206
+
184
207
  ## What to Store
185
208
 
186
209
  - **Global store**: user observations, agent personality, system environment
@@ -369,6 +392,9 @@ workflows. The host auto-discovers them, but reach for them proactively:
369
392
  - \`thatch-session-reflection\` — record what you learned at end of session.
370
393
  - \`thatch-fact-extractor\` — extract durable facts from recent tool interactions (auto-triggered by the extraction pipeline).
371
394
  - \`thatch-dedup-classifier\` — resolve duplicate memory pairs from \`find_duplicates\`.
395
+ - \`pr-description\` — draft a PR description using instructional-design scaffolding with bold and italic emphasis on the phrases that carry the meaning, so a reviewer skimming only the emphasized fragments still gets the story.
396
+ - \`ticket-description\` — draft a ticket, issue, or work item (Linear or Jira) using instructional-design scaffolding with bold and italic emphasis for scanning.
397
+ - \`split-overlarge-pr\` — split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main.
372
398
 
373
399
  ## When to Write
374
400
 
@@ -376,6 +402,14 @@ workflows. The host auto-discovers them, but reach for them proactively:
376
402
  Save immediately on: feedback, preferences, corrections, emotional signals,
377
403
  new project knowledge, conventions, architectural decisions, investigation results.
378
404
 
405
+ ## Before Responding
406
+
407
+ Before composing a final response after substantial work this turn — multiple
408
+ rounds of tool calls for investigation, debugging, or code-writing — check
409
+ whether you've discovered knowledge worth persisting. Use memory_recall to
410
+ check for duplicates, then memory_remember for new findings. Then deliver
411
+ your response.
412
+
379
413
  ## What to Store
380
414
 
381
415
  - **Global store**: user observations, agent personality, system environment
package/src/setup.ts CHANGED
@@ -173,7 +173,7 @@ function writeHooks(path: string, thatchBin: string): void {
173
173
  // (see src/extract-queue.ts). No stdout — the next model call must not block
174
174
  // on a payload that should be invisible to the agent until UserPromptSubmit.
175
175
  const bufferCmd = `${thatchBin} buffer-batch`;
176
- // UserPromptSubmit drains the queue. With buffered interactions, it prints
176
+ // UserPromptSubmit peeks the queue. With buffered interactions, it prints
177
177
  // the extraction nudge carrying the JSON payload; with an empty queue, it
178
178
  // falls back to the static write-nudge. Either output is transcript-visible
179
179
  // since UserPromptSubmit adds stdout to context.
package/src/skills.ts CHANGED
@@ -45,6 +45,9 @@ function loadSharedSkills(): SkillDef[] {
45
45
  "thatch-change-walkthrough",
46
46
  "thatch-code-walkthrough",
47
47
  "thatch-session-reflection",
48
+ "pr-description",
49
+ "ticket-description",
50
+ "split-overlarge-pr",
48
51
  ];
49
52
  return names.map((name) => ({ name, content: loadSkillFile(name) }));
50
53
  }