@jeffober/thatch 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,18 +2,23 @@
2
2
 
3
3
  [![CI](https://github.com/sysread/thatch/actions/workflows/ci.yml/badge.svg)](https://github.com/sysread/thatch/actions/workflows/ci.yml)
4
4
 
5
- Persistent memory and operational methodology for AI coding agents — local
6
- embeddings, SQLite stores, zero config.
5
+ ## Synopsis
6
+
7
+ Persistent memory and useful dev skills for AI coding agents that requires zero
8
+ manual config.
7
9
 
8
10
  Works with **OpenCode** (as a plugin), **Claude Code** (as an MCP server), and
9
11
  **Cursor** (as an MCP server).
12
+
10
13
  Each session inherits the accumulated knowledge of every session before it —
11
14
  project architecture, conventions, gotchas, user preferences — so your agent
12
- starts with context instead of a blank slate. Stores are per-repo with a
13
- shared `global` store. On top of the memory layer, thatch ships a suite of
14
- skills that prime, maintain, and reflect on memory, plus a structured
15
- multi-agent code review framework. No API keys, no cloud services —
16
- everything runs on your machine.
15
+ starts with context instead of a blank slate. Stores are per-repo with a shared
16
+ `global` store.
17
+
18
+ On top of the memory layer, thatch ships a suite of skills for managing memories,
19
+ performing in-depth, structured code reviews, and researching your code base.
20
+
21
+ No API keys, no cloud services; everything runs on your machine.
17
22
 
18
23
  ## Quick start
19
24
 
@@ -118,6 +123,8 @@ agent follows to maintain its own memory and review code systematically.
118
123
  | `thatch-review-no-slop` | AI writing anti-pattern detection — change narration, fourth wall breaks, hedging |
119
124
  | `thatch-review-breadcrumbs` | Comment narrative — do comments form a coherent outline of the code's behavior? |
120
125
  | `thatch-review-synthesizer` | Verify specialist findings against actual code, deduplicate, produce severity-grouped report |
126
+ | `thatch-review-context` | Gather project/feature context (PRs, tickets, TODOs, deferred work) before a review |
127
+ | `thatch-workflow-research` | Research code workflows affected by a change or planned change, before reviewing or planning |
121
128
  | `thatch-code-review` | Multi-agent review coordinator — triage, partition, dispatch specialists in parallel (opencode only) |
122
129
 
123
130
  The five review specialists run independently and can be used standalone. The
@@ -142,7 +149,7 @@ thatch prime Prime project memory (runs via opencode or clau
142
149
  thatch mcp Start the stdio MCP server (for Claude Code)
143
150
  thatch reminder Print session-start reminder (for hooks)
144
151
  thatch hygiene Print the hygiene report (standalone)
145
- thatch setup --claude [--global] Install config + instructions + hooks + skills
152
+ thatch setup --claude [--cursor] [--global] Install config + instructions + hooks + skills
146
153
  ```
147
154
 
148
155
  Stores default to the repo detected from `git remote`. Use `global` for the
@@ -190,17 +197,18 @@ buffer and flush. On Claude Code, `PostToolBatch` and `UserPromptSubmit`
190
197
  hooks drive a file-backed queue. Both paths produce the same payload for the
191
198
  fact-extractor skill.
192
199
 
193
- ## OpenCode vs Claude Code parity
194
-
195
- | Feature | OpenCode (plugin) | Claude Code (MCP + hooks) |
196
- |---------|-------------------|--------------------------|
197
- | Tools | Plugin tool registration | MCP `tools/list` + `tools/call` |
198
- | System prompt | Injected at runtime with repo name | Static CLAUDE.md (repo auto-detected by MCP server) |
199
- | Session-start reminder | `session.created` event | `SessionStart` hook → `thatch reminder` |
200
- | Compaction context | `experimental.session.compacting` hook | **Gap**: no equivalent (CLAUDE.md persists through compaction) |
201
- | Extraction nudge | `tool.execute.after` + `chat.message` | `PostToolBatch` + `UserPromptSubmit` (file-backed queue) |
202
- | Skills | `$XDG_CONFIG_HOME/opencode/skills` | `~/.claude/skills/` |
203
- | Store detection | `worktree` param from plugin | `CLAUDE_PROJECT_DIR` env var |
200
+ ## OpenCode vs Claude Code vs Cursor parity
201
+
202
+ | Feature | OpenCode (plugin) | Claude Code (MCP + hooks) | Cursor (MCP + hooks) |
203
+ |---------|-------------------|--------------------------|---------------------|
204
+ | Tools | Plugin tool registration | MCP `tools/list` + `tools/call` | MCP `tools/list` + `tools/call` |
205
+ | System prompt | Injected at runtime with repo name | Static CLAUDE.md (repo auto-detected by MCP server) | Static AGENTS.md (repo auto-detected at startup) |
206
+ | Session-start reminder | `session.created` event | `SessionStart` hook → `thatch reminder` | `sessionStart` hook → `thatch reminder --json` |
207
+ | Compaction context | `experimental.session.compacting` hook | **Gap**: no equivalent (CLAUDE.md persists) | **Gap**: no equivalent |
208
+ | Extraction nudge | `tool.execute.after` + `chat.message` | `PostToolBatch` + `UserPromptSubmit` (file-backed queue) | `postToolUse` + `beforeSubmitPrompt` (file-backed queue) |
209
+ | Setup detection at startup | n/a (plugin auto-installs) | `checkSetup` in MCP server: detects missing/broken instructions | Same as Claude Code |
210
+ | Skills | 13 (shared + coordinator) | 12 (shared only) | 12 (shared only) |
211
+ | Store detection | `worktree` param from plugin | `CLAUDE_PROJECT_DIR` env var | `CURSOR_PROJECT_DIR` then `CLAUDE_PROJECT_DIR` |
204
212
 
205
213
  See [docs/dev/mcp-parity.md](docs/dev/mcp-parity.md) for the full analysis.
206
214
 
@@ -26,6 +26,7 @@ This document maps the feature parity and documents the remaining gaps.
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** |
29
+ | **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` |
29
30
  | **Hook config format** | n/a — plugin registers hooks in code | Nested: `.claude/settings.json` `{hooks:{Event:{hooks:[{type,command}]}}}` | Flat: `.cursor/hooks.json` `{version:1,hooks:{event:[{command}]}}` | n/a |
30
31
 
31
32
  ## Tool name conventions
@@ -132,3 +132,39 @@ All setup operations are idempotent:
132
132
  `thatch setup` resolves `<bin>` from PATH, falling back to the absolute path of
133
133
  the running script. This is baked into every installed hook command, so the
134
134
  hooks keep working after the session that ran setup ends.
135
+
136
+ ## Setup detection at MCP startup
137
+
138
+ When the MCP server starts (`thatch mcp`, spawned by Claude Code or Cursor),
139
+ it calls `checkSetup` (`src/setup.ts`) to verify that `thatch setup` was run
140
+ for the current host. This catches two failure modes that would otherwise leave
141
+ the agent without usage instructions:
142
+
143
+ ### Host detection
144
+
145
+ The host is determined from env vars set by the host process:
146
+
147
+ - `CURSOR_PROJECT_DIR` set → Cursor
148
+ - `CLAUDE_PROJECT_DIR` set (and Cursor not) → Claude Code
149
+ - Neither set → returns `null` (no check — manual `thatch mcp` invocation)
150
+
151
+ Cursor takes priority because Cursor also sets `CLAUDE_PROJECT_DIR` as an alias.
152
+
153
+ ### What it checks
154
+
155
+ `checkSetup` looks for the instruction markers (start and end) in the host's
156
+ instructions file — `CLAUDE.md` for Claude Code, `AGENTS.md` for Cursor. It
157
+ checks local (in the project directory) first, then global (in the config
158
+ directory). Local takes priority.
159
+
160
+ ### Three outcomes
161
+
162
+ | Outcome | Condition | Action |
163
+ |---------|-----------|--------|
164
+ | **installed** | Markers found in local or global instructions file | No warning — setup is complete |
165
+ | **not-installed** | No instructions file with markers found anywhere | Warning to stderr + prepended to first `tools/call` response: "Tell the user to run `thatch setup --<host>`" |
166
+ | **markers-broken** | Start marker found but end marker missing (file edited externally) | Warning with the specific file path and fix instructions: run `thatch setup`, or manually remove the corrupted block and re-run |
167
+
168
+ The warning is cleared after the first `tools/call` response so it surfaces
169
+ once, not on every tool call. The message instructs the LLM to notify the user
170
+ with the specific `thatch setup` command to run.
@@ -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 12 skills
24
+ ## The 13 skills
25
25
 
26
- **Shared (11)** — installed everywhere; no sub-agents required:
26
+ **Shared (12)** — installed everywhere; no sub-agents required:
27
27
 
28
28
  | Skill | Role |
29
29
  |-------|------|
@@ -37,13 +37,14 @@ description: Extract durable project facts ... Use when ...
37
37
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of behavior? |
38
38
  | `thatch-review-synthesizer` | Verify specialist findings against code, dedupe, classify, calibrate severity. |
39
39
  | `thatch-review-context` | Gather project/feature context (PR descriptions, git archaeology, ticket references, memory) before fan-out. Prevents false positives about intentionally deferred work. |
40
+ | `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. |
40
41
  | `thatch-session-reflection` | End-of-session memory recording (project, user, tools, self). |
41
42
 
42
43
  **opencode-only (1)** — the coordinator needs sub-agent support:
43
44
 
44
45
  | Skill | Role |
45
46
  |-------|------|
46
- | `thatch-code-review` | Resolve review target, gather context, estimate complexity, partition, dispatch the 5 specialists in parallel, synthesize. |
47
+ | `thatch-code-review` | Resolve review target, gather project context, research affected workflows, estimate complexity, partition, dispatch the 5 specialists in parallel, synthesize. |
47
48
 
48
49
  ## REVIEW_COMMON
49
50
 
@@ -61,6 +62,10 @@ The five review specialists share a framework interpolated via `${REVIEW_COMMON}
61
62
  - **Project context awareness** — if a context brief is provided (from the
62
63
  coordinator or from loading `thatch-review-context`), use it to avoid false
63
64
  positives about intentionally deferred work.
65
+ - **Workflow context awareness** — if a workflow guide is provided (from the
66
+ coordinator or from loading `thatch-workflow-research`), use it to understand
67
+ the purpose and evolution of the code, distinguishing intentional behavior
68
+ and long-standing design decisions from new issues.
64
69
  - **TODO ($ticket) markers** — recognize `TODO ($TICKET-ID): ...` as legitimate
65
70
  breadcrumbs for deferred work, not stale artifacts. Flag only if the referenced
66
71
  ticket is closed or merged.
@@ -73,7 +78,7 @@ The synthesizer reuses the same verification rigor but has its own structure
73
78
  ## The two arrays
74
79
 
75
80
  ```ts
76
- const SHARED_SKILLS: SkillDef[] = [ /* 11 skills above */ ];
81
+ const SHARED_SKILLS: SkillDef[] = [ /* 12 skills above */ ];
77
82
  const OPENCODE_ONLY_SKILLS: SkillDef[] = [ /* code-review coordinator */ ];
78
83
  ```
79
84
 
@@ -107,8 +112,7 @@ and `--cursor` pass only the shared set.
107
112
  ## Memory review skills in practice
108
113
 
109
114
  - **Quick single lens**: load any specialist directly and point it at a branch.
110
- - **Full review on opencode**: load `thatch-code-review` — it gathers project context, dispatches all 5
111
- specialists in parallel (with context injected into each briefing), then synthesizes.
115
+ - **Full review on opencode**: load `thatch-code-review` — it gathers project context, researches affected workflows, dispatches all 5 specialists in parallel (with both the context brief and workflow guide injected into each briefing), then synthesizes.
112
116
  - **Full review on Claude Code/Cursor**: run each specialist in sequence, then
113
117
  run `thatch-review-synthesizer` to verify and aggregate.
114
118
 
@@ -26,6 +26,7 @@ file/IPC/CLI contracts ready to graduate into `bun:test` integration tests.
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
+ | [UC-018](UC-018-mcp-startup-setup-detection.md) | MCP startup detects missing/broken setup instructions and warns the agent |
29
30
 
30
31
  ## Template
31
32
 
@@ -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
- **10 shared skills** (no code-review coordinator) to `$CLAUDE_CONFIG_DIR/skills/` — user-scoped
20
+ **12 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 10 shared skills to `~/.cursor/skills/`.
25
+ `thatch flush-tools --json`; and installs 12 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
@@ -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 **10 shared skills** to the skills
16
+ - Claude Code and Cursor install exactly **12 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 **11** — the 10 shared plus the coordinator.
19
+ - opencode installs **13** — the 12 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 (10 vs 11), content-diff-overwrite, and
26
+ _Automatable: yes — file presence, count (12 vs 13), content-diff-overwrite, and
27
27
  coordinator host-gating are all file assertions (`setup.test.ts` already covers
28
28
  the unit contract)._
@@ -0,0 +1,38 @@
1
+ # UC-018: MCP startup setup detection
2
+
3
+ **Preconditions**
4
+ - Thatch installed and `bun` on PATH
5
+ - MCP server launched by Claude Code (`thatch mcp` with `CLAUDE_PROJECT_DIR` set)
6
+ or Cursor (`thatch mcp` with `CURSOR_PROJECT_DIR` set)
7
+
8
+ **Steps**
9
+ 1. Start a Claude Code session in a project where `thatch setup --claude` was
10
+ never run (no `CLAUDE.md` with thatch markers, neither local nor global).
11
+ 2. Call any thatch tool (e.g. `mcp__thatch__store_list`).
12
+ 3. Repeat in a project where setup was run but `CLAUDE.md` was edited externally
13
+ and the thatch block's end marker was deleted.
14
+ 4. Repeat in a project where setup was run correctly.
15
+ 5. Repeat all steps for Cursor (substituting `--cursor`, `AGENTS.md`,
16
+ `CURSOR_PROJECT_DIR`).
17
+
18
+ **Expected**
19
+ - **Not-installed**: The first `tools/call` response is prepended with a
20
+ warning: "[thatch] Thatch is running as an MCP server but has not been set
21
+ up for Claude Code. ... Tell the user to run: thatch setup --claude". The
22
+ warning also appears on stderr. Subsequent tool responses are clean (warning
23
+ is one-shot).
24
+ - **Markers-broken**: The first `tools/call` response is prepended with a
25
+ warning naming the corrupted file and instructing the user to run
26
+ `thatch setup` (or manually remove the corrupted block and re-run).
27
+ - **Installed**: No warning. Tool responses are clean from the first call.
28
+ - **Cursor**: Same behavior, with "Cursor" substituted for "Claude Code" and
29
+ `thatch setup --cursor` in the message. When both `CURSOR_PROJECT_DIR` and
30
+ `CLAUDE_PROJECT_DIR` are set, Cursor detection takes priority.
31
+ - **No env var**: When neither `CURSOR_PROJECT_DIR` nor `CLAUDE_PROJECT_DIR` is
32
+ set (manual `thatch mcp` invocation), `checkSetup` returns `null` and no
33
+ warning is emitted.
34
+
35
+ _Automatable: yes — `setup.test.ts` already covers the unit contract (10 tests
36
+ in the `checkSetup` describe block: null, local, global, not-installed,
37
+ markers-broken, local priority, Cursor variants, Cursor priority). This is the
38
+ end-to-end runbook version._
@@ -114,6 +114,17 @@ Beyond the tools, thatch hooks into opencode itself:
114
114
  - **Compaction context.** When opencode compacts a long session, thatch injects
115
115
  a reminder so the summarized session still knows memory tools exist.
116
116
 
117
+ ### Setup detection (Claude Code and Cursor)
118
+
119
+ When the MCP server starts (Claude Code or Cursor), it checks whether
120
+ `thatch setup` was run for the current host by looking for the instruction
121
+ markers in `CLAUDE.md` (Claude Code) or `AGENTS.md` (Cursor). It checks local
122
+ first (in the project directory), then global (in the config directory). If
123
+ setup was never run, or if the instruction markers are broken (e.g. the file
124
+ was edited externally and the thatch block was partially modified), the server
125
+ emits a warning to stderr and prepends it to the first tool response so the
126
+ agent can tell the user to run `thatch setup`.
127
+
117
128
  ## Skills
118
129
 
119
130
  On startup thatch installs [skills] into your global opencode config
@@ -143,6 +154,8 @@ Five specialist review lenses, each a self-contained static-analysis pass:
143
154
  | `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth wall breaks, em dashes, hedging, filler. |
144
155
  | `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
145
156
  | `thatch-review-synthesizer` | Verifies and synthesizes findings from multiple specialists into a single deduplicated, severity-grouped report. |
157
+ | `thatch-review-context` | Gathers project context (PR descriptions, git archaeology, ticket references, memory) before a review. Prevents false positives about intentionally deferred work. |
158
+ | `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. |
146
159
 
147
160
  ### opencode-only skills
148
161
 
@@ -152,12 +165,13 @@ Five specialist review lenses, each a self-contained static-analysis pass:
152
165
 
153
166
  ### Host availability
154
167
 
155
- | Skill | opencode | Claude Code |
156
- |-------|----------|-------------|
157
- | Memory skills (4) | Yes | Yes |
158
- | Review specialists (5) | Yes | Yes |
159
- | Review synthesizer | Yes | Yes |
160
- | Code review coordinator | Yes | No (requires sub-agents) |
168
+ | Skill | opencode | Claude Code | Cursor |
169
+ |-------|----------|-------------|--------|
170
+ | Memory skills (4) | Yes | Yes | Yes |
171
+ | Review specialists (5) | Yes | Yes | Yes |
172
+ | Review synthesizer | Yes | Yes | Yes |
173
+ | Review context + workflow research | Yes | Yes | Yes |
174
+ | Code review coordinator | Yes | No (requires sub-agents) | No (requires sub-agents) |
161
175
 
162
176
  ### Using review skills
163
177
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffober/thatch",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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/db.ts CHANGED
@@ -9,6 +9,7 @@ export interface MemoryRow {
9
9
  model: string | null;
10
10
  branch: string | null;
11
11
  confidence: number | null;
12
+ archived: boolean;
12
13
  created_at: string;
13
14
  updated_at: string;
14
15
  recall_count: number;
@@ -69,6 +70,7 @@ export class ThatchDB {
69
70
  model TEXT,
70
71
  branch TEXT,
71
72
  confidence INTEGER,
73
+ archived INTEGER NOT NULL DEFAULT 0,
72
74
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
73
75
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
74
76
  recall_count INTEGER NOT NULL DEFAULT 0,
@@ -102,6 +104,7 @@ export class ThatchDB {
102
104
  const wanted: [string, string][] = [
103
105
  ["recall_count", "INTEGER NOT NULL DEFAULT 0"],
104
106
  ["last_recalled_at", "TEXT"],
107
+ ["archived", "INTEGER NOT NULL DEFAULT 0"],
105
108
  ];
106
109
  for (const [col, decl] of wanted) {
107
110
  if (!existing.has(col)) {
@@ -170,27 +173,23 @@ export class ThatchDB {
170
173
  content: string,
171
174
  embedding: Float32Array,
172
175
  model: string,
173
- opts?: { branch?: string; confidence?: number; overwrite?: boolean },
176
+ opts?: { branch?: string; confidence?: number; overwrite?: boolean; archived?: boolean },
174
177
  ): { ok: true } | { ok: false; error: string } {
175
178
  const slug = this.slugify(label);
176
179
  this.ensureStore(store);
177
180
 
178
- // The embedding may be a view into a larger buffer (transformers.js tensor
179
- // output); serialize exactly the view's bytes, not the whole backing buffer.
180
181
  const blob = new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength);
181
182
  const branch = opts?.branch ?? null;
182
183
  const confidence = opts?.confidence ?? null;
184
+ const archived = opts?.archived === true ? 1 : (opts?.archived === false ? 0 : null);
183
185
  const now = new Date().toISOString();
184
- const params = [slug, store, label, content, blob, model, branch, confidence, now, now];
185
186
 
186
187
  if (!opts?.overwrite) {
187
- // Plain INSERT so the PK constraint enforces no-clobber atomically —
188
- // a check-then-upsert would race against concurrent sessions.
189
188
  try {
190
189
  this.#db.run(
191
- `INSERT INTO entries (slug, store, label, content, embedding, model, branch, confidence, created_at, updated_at)
192
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
193
- params as any,
190
+ `INSERT INTO entries (slug, store, label, content, embedding, model, branch, confidence, archived, created_at, updated_at)
191
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
192
+ [slug, store, label, content, blob, model, branch, confidence, archived ?? 0, now, now] as any,
194
193
  );
195
194
  return { ok: true };
196
195
  } catch (err: any) {
@@ -205,10 +204,25 @@ export class ThatchDB {
205
204
  }
206
205
  }
207
206
 
207
+ const existing = this.#db
208
+ .query("SELECT archived FROM entries WHERE slug = ? AND store = ?")
209
+ .get(slug, store) as { archived: number } | null;
210
+
211
+ if (existing && existing.archived && opts?.archived === undefined) {
212
+ return {
213
+ ok: false,
214
+ error: `"${label}" is archived. Pass archived: true to keep it archived, or archived: false to unarchive it.`,
215
+ };
216
+ }
217
+
218
+ // archived column for upsert: the INSERT VALUES always needs a non-null
219
+ // value (NOT NULL constraint), so use archived ?? 0. To preserve the
220
+ // existing value when the caller doesn't specify archived, the UPDATE
221
+ // clause accepts a nullable extra param: NULL means "keep existing."
208
222
  this.#db.run(
209
223
  `
210
- INSERT INTO entries (slug, store, label, content, embedding, model, branch, confidence, created_at, updated_at)
211
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
224
+ INSERT INTO entries (slug, store, label, content, embedding, model, branch, confidence, archived, created_at, updated_at)
225
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
212
226
  ON CONFLICT(slug, store) DO UPDATE SET
213
227
  label = excluded.label,
214
228
  content = excluded.content,
@@ -216,13 +230,12 @@ export class ThatchDB {
216
230
  model = excluded.model,
217
231
  branch = COALESCE(excluded.branch, entries.branch),
218
232
  confidence = COALESCE(excluded.confidence, entries.confidence),
233
+ archived = COALESCE(?, entries.archived),
219
234
  updated_at = excluded.updated_at
220
235
  `,
221
- params as any,
236
+ [slug, store, label, content, blob, model, branch, confidence, archived ?? 0, now, now, archived] as any,
222
237
  );
223
238
 
224
- // Content changed, so prior dedup verdicts involving this entry are stale;
225
- // clear them so findDuplicates can re-flag the pair.
226
239
  this.#db.run(
227
240
  "DELETE FROM dedup_pairs WHERE store = ? AND (slug_a = ? OR slug_b = ?)",
228
241
  [store, slug, slug],
@@ -242,22 +255,25 @@ export class ThatchDB {
242
255
  search(
243
256
  stores: string[],
244
257
  queryEmbedding: Float32Array,
245
- opts?: { branch?: string; limit?: number },
258
+ opts?: { branch?: string; limit?: number; includeArchived?: boolean },
246
259
  ): (MemoryRow & { _score: number })[] {
247
260
  if (stores.length === 0) return [];
248
261
 
249
262
  const limit = opts?.limit ?? 10;
250
263
  const branch = opts?.branch;
264
+ const includeArchived = opts?.includeArchived ?? false;
251
265
 
252
266
  const placeholders = stores.map(() => "?").join(", ");
253
267
 
254
268
  interface SqlParams { sql: string; params: any[] }
255
269
 
256
270
  const { sql, params }: SqlParams = (() => {
271
+ const clauses = ["store IN (" + placeholders + ")", "embedding IS NOT NULL"];
272
+ if (!includeArchived) clauses.push("archived = 0");
257
273
  const base = `
258
- SELECT slug, store, label, content, embedding, model, branch, confidence, created_at, updated_at, recall_count, last_recalled_at
274
+ SELECT slug, store, label, content, embedding, model, branch, confidence, archived, created_at, updated_at, recall_count, last_recalled_at
259
275
  FROM entries
260
- WHERE store IN (${placeholders}) AND embedding IS NOT NULL
276
+ WHERE ${clauses.join(" AND ")}
261
277
  `;
262
278
 
263
279
  if (branch) {
@@ -278,6 +294,7 @@ export class ThatchDB {
278
294
  // rather than ranked. Dimension is the discriminator — model tags are
279
295
  // informational only.
280
296
  const scored = rows.flatMap((row) => {
297
+ row.archived = !!row.archived;
281
298
  const emb = blobToVector(row.embedding!);
282
299
  if (emb.length !== queryEmbedding.length) return [];
283
300
  return [{ ...row, embedding: row.embedding, _score: cosineSimilarity(queryEmbedding, emb) }];
@@ -295,7 +312,7 @@ export class ThatchDB {
295
312
  recall(
296
313
  stores: string[],
297
314
  queryEmbedding: Float32Array,
298
- opts?: { branch?: string; limit?: number },
315
+ opts?: { branch?: string; limit?: number; includeArchived?: boolean },
299
316
  ): (MemoryRow & { _score: number })[] {
300
317
  const top = this.search(stores, queryEmbedding, opts);
301
318
 
@@ -326,7 +343,7 @@ export class ThatchDB {
326
343
  const limit = opts?.limit ?? 3;
327
344
 
328
345
  const rows = this.#db
329
- .query("SELECT slug, label, embedding FROM entries WHERE store = ? AND embedding IS NOT NULL")
346
+ .query("SELECT slug, label, embedding FROM entries WHERE store = ? AND archived = 0 AND embedding IS NOT NULL")
330
347
  .all(store) as any[];
331
348
 
332
349
  const hits: SimilarEntry[] = [];
@@ -345,10 +362,10 @@ export class ThatchDB {
345
362
  }
346
363
 
347
364
  /** Lists all entries in a store, returning metadata without content. */
348
- listEntries(store: string): { slug: string; label: string; branch: string | null; confidence: number | null; updated_at: string }[] {
365
+ listEntries(store: string): { slug: string; label: string; branch: string | null; confidence: number | null; archived: boolean; updated_at: string }[] {
349
366
  return this.#db
350
367
  .query(
351
- "SELECT slug, label, branch, confidence, updated_at FROM entries WHERE store = ? ORDER BY label",
368
+ "SELECT slug, label, branch, confidence, archived, updated_at FROM entries WHERE store = ? ORDER BY label",
352
369
  )
353
370
  .all(store)
354
371
  .map((r: any) => ({
@@ -356,6 +373,7 @@ export class ThatchDB {
356
373
  label: r.label,
357
374
  branch: r.branch,
358
375
  confidence: r.confidence,
376
+ archived: !!r.archived,
359
377
  updated_at: r.updated_at,
360
378
  }));
361
379
  }
@@ -363,11 +381,14 @@ export class ThatchDB {
363
381
  /** Full content of a single entry by label. */
364
382
  showEntry(store: string, label: string): MemoryRow | null {
365
383
  const slug = this.slugify(label);
366
- return this.#db
384
+ const row = this.#db
367
385
  .query(
368
- "SELECT slug, store, label, content, embedding, model, branch, confidence, created_at, updated_at, recall_count, last_recalled_at FROM entries WHERE slug = ? AND store = ?",
386
+ "SELECT slug, store, label, content, embedding, model, branch, confidence, archived, created_at, updated_at, recall_count, last_recalled_at FROM entries WHERE slug = ? AND store = ?",
369
387
  )
370
- .get(slug, store) as MemoryRow | null;
388
+ .get(slug, store) as Record<string, unknown> | null;
389
+ if (!row) return null;
390
+ row.archived = !!row.archived;
391
+ return row as unknown as MemoryRow;
371
392
  }
372
393
 
373
394
  forgetEntry(store: string, label: string): boolean {
@@ -386,7 +407,7 @@ export class ThatchDB {
386
407
  findDuplicates(store: string, threshold = 0.85): DedupCandidate[] {
387
408
  const rows = this.#db
388
409
  .query(
389
- "SELECT slug, label, content, embedding FROM entries WHERE store = ? AND embedding IS NOT NULL ORDER BY slug",
410
+ "SELECT slug, label, content, embedding FROM entries WHERE store = ? AND archived = 0 AND embedding IS NOT NULL ORDER BY slug",
390
411
  )
391
412
  .all(store) as any[];
392
413
 
@@ -446,7 +467,7 @@ export class ThatchDB {
446
467
  const row = this.#db
447
468
  .query(
448
469
  `SELECT COUNT(*) AS n FROM entries
449
- WHERE store = ? AND max(updated_at, COALESCE(last_recalled_at, updated_at)) < ?`,
470
+ WHERE store = ? AND archived = 0 AND max(updated_at, COALESCE(last_recalled_at, updated_at)) < ?`,
450
471
  )
451
472
  .get(store, cutoffIso) as any;
452
473
  return row?.n ?? 0;
@@ -465,7 +486,7 @@ export class ThatchDB {
465
486
  if (branches.length === 0) return 0;
466
487
  const placeholders = branches.map(() => "?").join(", ");
467
488
  const row = this.#db
468
- .query(`SELECT COUNT(*) AS n FROM entries WHERE store = ? AND branch IN (${placeholders})`)
489
+ .query(`SELECT COUNT(*) AS n FROM entries WHERE store = ? AND archived = 0 AND branch IN (${placeholders})`)
469
490
  .get(store, ...branches) as any;
470
491
  return row?.n ?? 0;
471
492
  }
package/src/mcp.ts CHANGED
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { ThatchDB } from "./db";
3
3
  import { BgeEmbeddingModel } from "./embeddings";
4
4
  import { detectRepo } from "./git";
5
+ import { checkSetup } from "./setup";
5
6
  import { TOOL_DEFS, type CoreContext, type ToolDef } from "./tool-defs";
6
7
  import { SidebandServer, sidebandSocketPath } from "./sideband";
7
8
 
@@ -99,6 +100,17 @@ export async function runMcpServer(): Promise<void> {
99
100
  const ctx: CoreContext = { db, model, defaultStore: repo };
100
101
  const tools = compileTools();
101
102
 
103
+ // Check whether `thatch setup` was run for the current host. If not, or if
104
+ // markers are broken, surface a warning on the first tools/call response so
105
+ // the agent can tell the user to run setup. Also emit to stderr for
106
+ // visibility in host debug logs.
107
+ let setupWarning: string | null = null;
108
+ const setupStatus = checkSetup(projectDir);
109
+ if (setupStatus && setupStatus.status !== "installed") {
110
+ setupWarning = setupStatus.message;
111
+ console.error(`[thatch] ${setupWarning}`);
112
+ }
113
+
102
114
  // The sideband socket lets one-shot hook processes (flush-tools) ask the
103
115
  // warm MCP server to embed a prompt and search for matches — without
104
116
  // loading the ~34 MB model themselves. If the socket can't be opened
@@ -137,6 +149,17 @@ export async function runMcpServer(): Promise<void> {
137
149
  const isNotification = req.id === null || req.id === undefined;
138
150
  const res = await dispatch(req, tools, ctx);
139
151
  if (res === null || isNotification) continue;
152
+
153
+ // Surface the setup warning on the first tools/call response so the
154
+ // agent sees it and can notify the user. Cleared after one surfacing.
155
+ if (setupWarning && req.method === "tools/call" && !res.error) {
156
+ const content = res.result?.content;
157
+ if (Array.isArray(content) && content[0]?.text != null) {
158
+ content[0].text = `[thatch] ${setupWarning}\n\n${content[0].text}`;
159
+ }
160
+ setupWarning = null;
161
+ }
162
+
140
163
  send(res);
141
164
  }
142
165
  }
package/src/prompts.ts CHANGED
@@ -27,6 +27,19 @@ WIP, PR status). Unscoped memories are project-wide and always included in searc
27
27
  Before diving into code, thatch_memory_recall with a query relevant to the area
28
28
  you're working in. Prior sessions may have already investigated it.
29
29
 
30
+ ## Skills
31
+
32
+ Thatch ships skills for code review, project investigation, and memory
33
+ workflows. The host auto-discovers them, but reach for them proactively:
34
+
35
+ - \`thatch-code-review\` — full multi-agent code review (dispatches 5 specialists + synthesizer). Requires sub-agent support.
36
+ - \`thatch-workflow-research\` — research code workflows affected by a change or planned change, before reviewing or planning.
37
+ - \`thatch-review-context\` — gather project context (PRs, tickets, TODOs, deferred work) before a review.
38
+ - \`thatch-project-primer\` — investigate a new project and write foundational memories.
39
+ - \`thatch-session-reflection\` — record what you learned at end of session.
40
+ - \`thatch-fact-extractor\` — extract durable facts from recent tool interactions (auto-triggered by the extraction pipeline).
41
+ - \`thatch-dedup-classifier\` — resolve duplicate memory pairs from \`thatch_find_duplicates\`.
42
+
30
43
  ## When to Write
31
44
 
32
45
  **One signal is enough.** Do not wait for confirmation.
@@ -56,6 +69,25 @@ new project knowledge, conventions, architectural decisions, investigation resul
56
69
  Session-specific context, incomplete/unverified info, anything already in
57
70
  CLAUDE.md or OPENCODE.md, speculative conclusions.
58
71
 
72
+ ## Archived Memories
73
+
74
+ Memories can be marked \`archived\` — a flag for stable, long-term historical
75
+ records that should not trigger hygiene nudges (stale, orphaned, duplicate).
76
+ Archived memories are excluded from search/recall results by default; pass
77
+ \`includeArchived: true\` to surface them for archaeological dives.
78
+
79
+ When a branch is merged or about to be deleted, consolidate its branch-scoped
80
+ memories into a single archived memory scoped to that same branch (preserving
81
+ provenance). Capture intent, design decisions, review back-and-forth, PR
82
+ number, unexpected pivots — the kind of git-archaeology context that explains
83
+ _why_ the code looks the way it does a year from now. Then memory_forget the
84
+ originals. The archived record outlives the branch. Future sessions searching
85
+ with \`includeArchived: true\` can pull it up when investigating ambiguous code.
86
+
87
+ Updating an archived memory requires passing \`archived\` explicitly — omit it
88
+ and the tool returns an error. Pass \`archived: true\` to keep it archived,
89
+ \`archived: false\` to unarchive.
90
+
59
91
  ## Explicit Requests
60
92
 
61
93
  "Remember X" — save immediately.
@@ -81,7 +113,7 @@ Use thatch_store_list then thatch_memory_list to see what's available, then that
81
113
  return `${base}
82
114
 
83
115
  [thatch hygiene] ${hygiene}
84
- When convenient this session (not before addressing the user's request), tend the store: run thatch_find_duplicates and consolidate clusters, review stale memories with thatch_memory_show and update or forget them, and forget memories scoped to deleted branches.`;
116
+ When convenient this session (not before addressing the user's request), tend the store: run thatch_find_duplicates and consolidate clusters, review stale memories with thatch_memory_show and update or forget them, and for memories scoped to deleted branches, consolidate them into an archived historical record before forgetting the originals.`;
85
117
  }
86
118
 
87
119
  /**
@@ -126,6 +158,19 @@ WIP, PR status). Unscoped memories are project-wide and always included in searc
126
158
  Before diving into code, \`memory_recall\` with a query relevant to the area
127
159
  you're working in. Prior sessions may have already investigated it.
128
160
 
161
+ ## Skills
162
+
163
+ Thatch ships skills for code review, project investigation, and memory
164
+ workflows. The host auto-discovers them, but reach for them proactively:
165
+
166
+ - \`thatch-workflow-research\` — research code workflows affected by a change or planned change, before reviewing or planning.
167
+ - \`thatch-review-context\` — gather project context (PRs, tickets, TODOs, deferred work) before a review.
168
+ - \`thatch-review-pedantic\` / \`-acceptance\` / \`-state-flow\` / \`-no-slop\` / \`-breadcrumbs\` — five specialist review lenses. Run individually, then \`thatch-review-synthesizer\` to verify and aggregate.
169
+ - \`thatch-project-primer\` — investigate a new project and write foundational memories.
170
+ - \`thatch-session-reflection\` — record what you learned at end of session.
171
+ - \`thatch-fact-extractor\` — extract durable facts from recent tool interactions (auto-triggered by the extraction pipeline).
172
+ - \`thatch-dedup-classifier\` — resolve duplicate memory pairs from \`find_duplicates\`.
173
+
129
174
  ## When to Write
130
175
 
131
176
  **One signal is enough.** Do not wait for confirmation.
@@ -155,6 +200,25 @@ new project knowledge, conventions, architectural decisions, investigation resul
155
200
  Session-specific context, incomplete/unverified info, anything already in
156
201
  CLAUDE.md, speculative conclusions.
157
202
 
203
+ ## Archived Memories
204
+
205
+ Memories can be marked \`archived\` — a flag for stable, long-term historical
206
+ records that should not trigger hygiene nudges (stale, orphaned, duplicate).
207
+ Archived memories are excluded from search/recall results by default; pass
208
+ \`includeArchived: true\` to surface them for archaeological dives.
209
+
210
+ When a branch is merged or about to be deleted, consolidate its branch-scoped
211
+ memories into a single archived memory scoped to that same branch (preserving
212
+ provenance). Capture intent, design decisions, review back-and-forth, PR
213
+ number, unexpected pivots — the kind of git-archaeology context that explains
214
+ _why_ the code looks the way it does a year from now. Then memory_forget the
215
+ originals. The archived record outlives the branch. Future sessions searching
216
+ with \`includeArchived: true\` can pull it up when investigating ambiguous code.
217
+
218
+ Updating an archived memory requires passing \`archived\` explicitly — omit it
219
+ and the tool returns an error. Pass \`archived: true\` to keep it archived,
220
+ \`archived: false\` to unarchive.
221
+
158
222
  ## Explicit Requests
159
223
 
160
224
  "Remember X" — save immediately.
@@ -179,7 +243,7 @@ Use store_list then memory_list to see what's available, then memory_recall as n
179
243
  return `${base}
180
244
 
181
245
  [thatch hygiene] ${hygiene}
182
- When convenient this session (not before addressing the user's request), tend the store: run find_duplicates and consolidate clusters, review stale memories with memory_show and update or forget them, and forget memories scoped to deleted branches.`;
246
+ When convenient this session (not before addressing the user's request), tend the store: run find_duplicates and consolidate clusters, review stale memories with memory_show and update or forget them, and for memories scoped to deleted branches, consolidate them into an archived historical record before forgetting the originals.`;
183
247
  }
184
248
 
185
249
  /**
@@ -241,6 +305,19 @@ WIP, PR status). Unscoped memories are project-wide and always included in searc
241
305
  Before diving into code, \`memory_recall\` with a query relevant to the area
242
306
  you're working in. Prior sessions may have already investigated it.
243
307
 
308
+ ## Skills
309
+
310
+ Thatch ships skills for code review, project investigation, and memory
311
+ workflows. The host auto-discovers them, but reach for them proactively:
312
+
313
+ - \`thatch-workflow-research\` — research code workflows affected by a change or planned change, before reviewing or planning.
314
+ - \`thatch-review-context\` — gather project context (PRs, tickets, TODOs, deferred work) before a review.
315
+ - \`thatch-review-pedantic\` / \`-acceptance\` / \`-state-flow\` / \`-no-slop\` / \`-breadcrumbs\` — five specialist review lenses. Run individually, then \`thatch-review-synthesizer\` to verify and aggregate.
316
+ - \`thatch-project-primer\` — investigate a new project and write foundational memories.
317
+ - \`thatch-session-reflection\` — record what you learned at end of session.
318
+ - \`thatch-fact-extractor\` — extract durable facts from recent tool interactions (auto-triggered by the extraction pipeline).
319
+ - \`thatch-dedup-classifier\` — resolve duplicate memory pairs from \`find_duplicates\`.
320
+
244
321
  ## When to Write
245
322
 
246
323
  **One signal is enough.** Do not wait for confirmation.
@@ -270,6 +347,25 @@ new project knowledge, conventions, architectural decisions, investigation resul
270
347
  Session-specific context, incomplete/unverified info, anything already in
271
348
  AGENTS.md, speculative conclusions.
272
349
 
350
+ ## Archived Memories
351
+
352
+ Memories can be marked \`archived\` — a flag for stable, long-term historical
353
+ records that should not trigger hygiene nudges (stale, orphaned, duplicate).
354
+ Archived memories are excluded from search/recall results by default; pass
355
+ \`includeArchived: true\` to surface them for archaeological dives.
356
+
357
+ When a branch is merged or about to be deleted, consolidate its branch-scoped
358
+ memories into a single archived memory scoped to that same branch (preserving
359
+ provenance). Capture intent, design decisions, review back-and-forth, PR
360
+ number, unexpected pivots — the kind of git-archaeology context that explains
361
+ _why_ the code looks the way it does a year from now. Then memory_forget the
362
+ originals. The archived record outlives the branch. Future sessions searching
363
+ with \`includeArchived: true\` can pull it up when investigating ambiguous code.
364
+
365
+ Updating an archived memory requires passing \`archived\` explicitly — omit it
366
+ and the tool returns an error. Pass \`archived: true\` to keep it archived,
367
+ \`archived: false\` to unarchive.
368
+
273
369
  ## Explicit Requests
274
370
 
275
371
  "Remember X" — save immediately.
package/src/setup.ts CHANGED
@@ -415,8 +415,120 @@ export function setupCursor(
415
415
  return {
416
416
  mcpConfig: paths.mcpConfigPath,
417
417
  agentsMd: paths.agentsMdPath,
418
- hooks: paths.hooksPath,
419
- skills,
420
- global,
418
+ hooks: paths.hooksPath,
419
+ skills,
420
+ global,
421
+ };
422
+ }
423
+
424
+ // ---------------------------------------------------------------------------
425
+ // Setup detection — check whether setup was completed for the current host
426
+ // ---------------------------------------------------------------------------
427
+
428
+ export type HostKind = "claude" | "cursor";
429
+
430
+ export type SetupStatus =
431
+ | { status: "installed"; scope: "local" | "global"; host: HostKind }
432
+ | { status: "not-installed"; host: HostKind; message: string }
433
+ | { status: "markers-broken"; host: HostKind; file: string; message: string };
434
+
435
+ type FileCheck = "installed" | "absent" | "broken";
436
+
437
+ function checkInstructionsFile(path: string, startMarker: string, endMarker: string): FileCheck {
438
+ if (!existsSync(path)) return "absent";
439
+ const content = readFileSync(path, "utf8");
440
+ const startIdx = content.indexOf(startMarker);
441
+ if (startIdx === -1) return "absent";
442
+ const endIdx = content.indexOf(endMarker, startIdx);
443
+ if (endIdx === -1) return "broken";
444
+ return "installed";
445
+ }
446
+
447
+ /**
448
+ * Detects the host from env vars (CURSOR_PROJECT_DIR for Cursor,
449
+ * CLAUDE_PROJECT_DIR for Claude Code) and checks whether `thatch setup` was
450
+ * run for that host. Checks local instructions (in the project dir) first,
451
+ * then global (in the config dir). Returns null if the host cannot be
452
+ * determined (neither env var is set — e.g. manual `thatch mcp` invocation).
453
+ */
454
+ export function checkSetup(projectDir: string, homeDir?: string): SetupStatus | null {
455
+ const home = homeDir ?? process.env.HOME ?? "/tmp";
456
+ const isCursor = !!process.env.CURSOR_PROJECT_DIR;
457
+ const isClaude = !!process.env.CLAUDE_PROJECT_DIR && !isCursor;
458
+ if (!isCursor && !isClaude) return null;
459
+
460
+ const host: HostKind = isCursor ? "cursor" : "claude";
461
+
462
+ if (host === "claude") {
463
+ return checkClaudeSetup(projectDir, home);
464
+ }
465
+ return checkCursorSetup(projectDir, home);
466
+ }
467
+
468
+ function checkClaudeSetup(projectDir: string, home: string): SetupStatus {
469
+ const configDir = claudeConfigDir(home);
470
+
471
+ const localMd = join(projectDir, "CLAUDE.md");
472
+ const localCheck = checkInstructionsFile(localMd, THATCH_MARKER, THATCH_END_MARKER);
473
+ if (localCheck === "installed") return { status: "installed", scope: "local", host: "claude" };
474
+ if (localCheck === "broken") {
475
+ return {
476
+ status: "markers-broken",
477
+ host: "claude",
478
+ file: localMd,
479
+ message: `Thatch's instructions in ${localMd} have corrupted markers: the start marker was found but the end marker is missing. This usually happens when the file was edited externally and the thatch block was partially modified. Tell the user to fix this by running: thatch setup --claude. If that doesn't resolve it, they may need to manually remove the corrupted thatch block from ${localMd} and re-run setup.`,
480
+ };
481
+ }
482
+
483
+ const globalMd = join(configDir, "CLAUDE.md");
484
+ const globalCheck = checkInstructionsFile(globalMd, THATCH_MARKER, THATCH_END_MARKER);
485
+ if (globalCheck === "installed") return { status: "installed", scope: "global", host: "claude" };
486
+ if (globalCheck === "broken") {
487
+ return {
488
+ status: "markers-broken",
489
+ host: "claude",
490
+ file: globalMd,
491
+ message: `Thatch's instructions in ${globalMd} have corrupted markers: the start marker was found but the end marker is missing. This usually happens when the file was edited externally and the thatch block was partially modified. Tell the user to fix this by running: thatch setup --claude --global. If that doesn't resolve it, they may need to manually remove the corrupted thatch block from ${globalMd} and re-run setup.`,
492
+ };
493
+ }
494
+
495
+ return {
496
+ status: "not-installed",
497
+ host: "claude",
498
+ message: "Thatch is running as an MCP server but has not been set up for Claude Code. The instructions that tell you how to use thatch's memory tools are missing from CLAUDE.md. Tell the user to run: thatch setup --claude",
499
+ };
500
+ }
501
+
502
+ function checkCursorSetup(projectDir: string, home: string): SetupStatus {
503
+ const configDir = cursorConfigDir(home);
504
+
505
+ const localMd = join(projectDir, "AGENTS.md");
506
+ const localCheck = checkInstructionsFile(localMd, CURSOR_MARKER, CURSOR_END_MARKER);
507
+ if (localCheck === "installed") return { status: "installed", scope: "local", host: "cursor" };
508
+ if (localCheck === "broken") {
509
+ return {
510
+ status: "markers-broken",
511
+ host: "cursor",
512
+ file: localMd,
513
+ message: `Thatch's instructions in ${localMd} have corrupted markers: the start marker was found but the end marker is missing. This usually happens when the file was edited externally and the thatch block was partially modified. Tell the user to fix this by running: thatch setup --cursor. If that doesn't resolve it, they may need to manually remove the corrupted thatch block from ${localMd} and re-run setup.`,
514
+ };
515
+ }
516
+
517
+ const globalMd = join(configDir, "AGENTS.md");
518
+ const globalCheck = checkInstructionsFile(globalMd, CURSOR_MARKER, CURSOR_END_MARKER);
519
+ if (globalCheck === "installed") return { status: "installed", scope: "global", host: "cursor" };
520
+ if (globalCheck === "broken") {
521
+ return {
522
+ status: "markers-broken",
523
+ host: "cursor",
524
+ file: globalMd,
525
+ message: `Thatch's instructions in ${globalMd} have corrupted markers: the start marker was found but the end marker is missing. This usually happens when the file was edited externally and the thatch block was partially modified. Tell the user to fix this by running: thatch setup --cursor --global. If that doesn't resolve it, they may need to manually remove the corrupted thatch block from ${globalMd} and re-run setup.`,
526
+ };
527
+ }
528
+
529
+ return {
530
+ status: "not-installed",
531
+ host: "cursor",
532
+ message: "Thatch is running as an MCP server but has not been set up for Cursor. The instructions that tell you how to use thatch's memory tools are missing from AGENTS.md. Tell the user to run: thatch setup --cursor",
421
533
  };
422
534
  }
package/src/skills.ts CHANGED
@@ -88,6 +88,13 @@ If your briefing includes a project context brief (from the coordinator or from
88
88
  - **Dependencies** in the brief tell you whether base-branch behavior is expected to be present or not. Do not assert that base-branch behavior exists without verifying it landed.
89
89
  - **Relevant constraints** may explain why code is structured a certain way. Consider them before flagging design choices.
90
90
 
91
+ ## Workflow context awareness
92
+ If your briefing includes a workflow guide (from the coordinator or from loading thatch-workflow-research), use it to understand the purpose and evolution of the code you are reviewing:
93
+ - **Purpose** sections explain why the workflow exists, which helps distinguish intentional behavior from bugs.
94
+ - **Major evolutions** sections explain how the code reached its current state, which helps distinguish recent changes from long-standing design decisions.
95
+ - **Constraints and design decisions** sections explain tradeoffs and intentional choices, which prevents flagging known design decisions as issues.
96
+ - **Current state** sections list known limitations and incomplete work, which prevents flagging known gaps as new bugs.
97
+
91
98
  ## TODO ($ticket) markers
92
99
  Code may contain \`TODO ($TICKET-ID): description\` comments that mark intentionally deferred work: temporary code, placeholders for future tickets, or planned cleanups. These are legitimate breadcrumbs, not stale artifacts.
93
100
  - Do NOT flag a TODO ($ticket) marker as a stale artifact if the ticket it references is still open or future work.
@@ -861,6 +868,103 @@ These markers serve as breadcrumbs for reviewers (human and LLM) who lack projec
861
868
  - They SHOULD be flagged if the referenced ticket is closed or merged (the TODO is now stale)
862
869
  `;
863
870
 
871
+ // ---------------------------------------------------------------------------
872
+ // Workflow research — code archaeology for review or planning
873
+ // ---------------------------------------------------------------------------
874
+
875
+ const WORKFLOW_RESEARCH = `---
876
+ name: thatch-workflow-research
877
+ description: Research the code workflows and features affected by a change or planned change. Identifies affected workflows, reads code flows and comments, traces git history, recalls memories, and produces a guide to the code. Use before reviewing a change or planning new work in an unfamiliar area.
878
+ ---
879
+
880
+ You are a code workflow researcher. Your job is to identify the features and logical workflows affected by a change (or a planned change), do deep code archaeology on them, and produce a guide that gives other agents the context they need to review or plan effectively.
881
+
882
+ ## Why this matters
883
+
884
+ A specialist reviewer without code-level context will misidentify intentional behavior as bugs, miss known design constraints, and duplicate the same code-tracing work across every specialist lens. A planner without code-level context will propose changes that conflict with existing flows. This skill produces the code guide that prevents both.
885
+
886
+ ## Relationship to thatch-review-context
887
+
888
+ These two skills are complementary, not overlapping:
889
+ - **thatch-review-context** gathers project metadata: tickets, PRs, TODO markers, deferred work, issue-tracker context. It answers "what is this change trying to do and what is intentionally left out?"
890
+ - **thatch-workflow-research** (this skill) gathers code-level context: actual code flows, comments, git history, evolutions. It answers "how does the code being changed actually work, and how did it get that way?"
891
+
892
+ Use both before a review. The coordinator runs thatch-review-context in Step 2 and this skill in Step 3.
893
+
894
+ ## Two modes
895
+
896
+ ### Review mode (existing change)
897
+ Given a git range, branch, or PR:
898
+ 1. Run \`git diff --stat\` on the resolved range to identify changed files.
899
+ 2. For each changed file, read the diff to understand what is being modified.
900
+ 3. From the changes, identify which features and business-logic workflows are touched. A single file may participate in multiple workflows; a single workflow may span multiple files.
901
+
902
+ ### Planning mode (new change)
903
+ Given a description of work to be done:
904
+ 1. Identify which areas of the codebase the planned change would affect.
905
+ 2. Use grep/glob to find the files and modules that implement the relevant features.
906
+ 3. From those files, identify which existing workflows the change will interact with or modify.
907
+
908
+ ## Research process
909
+
910
+ For each identified workflow or feature, work through these steps. Not every step will yield information for every workflow. Skip steps that produce nothing rather than forcing a narrative.
911
+
912
+ ### 1. Retrieve memories
913
+ Call \`thatch_memory_recall\` with queries like:
914
+ - The workflow or feature name
915
+ - The primary files or modules involved
916
+ - "architecture" or "design" or "convention" for the area
917
+ - Any gotchas or known issues in the area
918
+
919
+ ### 2. Read documentation
920
+ - README, docs/, design docs, ADRs related to the workflow
921
+ - CLAUDE.md, AGENTS.md, CONTRIBUTING.md for conventions
922
+ - Any inline documentation at the module level
923
+
924
+ ### 3. Read the code flow
925
+ Trace each workflow through the codebase. Do not just read the changed lines; read the full flow:
926
+ - Identify the entry point(s) for the workflow
927
+ - Follow the data path through the functions and modules involved
928
+ - Read the comments in the flow. Comments encode intention, rationale, and design decisions that are invisible from the code alone.
929
+ - Note key contracts: what does each function or module assume about its inputs? What does it guarantee about its outputs?
930
+ - Note implicit state machines: what states can the workflow be in? What transitions are guarded?
931
+
932
+ ### 4. Read git history
933
+ Trace the evolution of the workflow through git:
934
+ - \`git log --oneline -- <key files>\` for the commit history of the files involved
935
+ - \`git log -S "<function name>"\` to find when key functions were introduced or changed
936
+ - \`git blame\` on critical sections to find when and why they were written
937
+ - \`git log --all --oneline --grep="<feature name>"\` to find related work on other branches
938
+ - Look for commit messages that explain design decisions, refactors, or bug fixes that shaped the workflow
939
+
940
+ Focus on *why* the code evolved, not just *when*. Major evolutions are: creation, significant refactors, behavior changes, bug fixes that changed the flow, and additions of new paths or states.
941
+
942
+ ## Output: code guide
943
+
944
+ Produce a guide with one section per affected workflow or feature. Each section should be self-contained enough that a reviewer or planner can understand the workflow without reading the code themselves.
945
+
946
+ ### Workflow: <name>
947
+
948
+ - **Purpose**: What this workflow does and why it exists in the system
949
+ - **Key files**: The primary files and entry points, with file:line references
950
+ - **Data flow**: How data moves through the workflow (high-level, not line-by-line)
951
+ - **Major evolutions**: Key changes from git history. When was it created? What refactors or behavior changes shaped it? What drove those changes? Cite commit hashes or dates.
952
+ - **Constraints and design decisions**: From comments, memories, and docs. Known constraints, tradeoffs, intentional design choices, and gotchas.
953
+ - **Current state**: Any open issues, TODOs, known limitations, or incomplete migrations
954
+
955
+ ## Scope discipline
956
+
957
+ Only research workflows that the change actually touches. Do not research every workflow in the codebase. If a changed file participates in three workflows but the change only affects one, research only that one.
958
+
959
+ If the change is small and touches a single workflow, the guide may be one section. If the change is cross-cutting, the guide may have several sections. Let the change determine the scope.
960
+
961
+ ## What this skill is NOT
962
+
963
+ - It is not a code review. Do not evaluate the quality of the code or flag issues. That is the job of the review specialists.
964
+ - It is not a project context builder. That is the job of thatch-review-context, which gathers project metadata (tickets, PRs, TODOs, deferred work). This skill gathers code-level context (flows, comments, git history, evolutions).
965
+ - It is not a memory writer. Do not call thatch_memory_remember. If you discover durable facts, note them in the guide; the session-reflection or fact-extractor skills will persist them later.
966
+ `;
967
+
864
968
  // ---------------------------------------------------------------------------
865
969
  // Session reflection — end-of-session memory recording guidance
866
970
  // ---------------------------------------------------------------------------
@@ -976,7 +1080,20 @@ Produce the context brief in the format the skill prescribes (project context, t
976
1080
 
977
1081
  If context is sparse (no PR description, no ticket references, no relevant memories), note that the change appears standalone. Even a sparse brief is valuable: it tells specialists you looked and found nothing, so they do not need to repeat the search.
978
1082
 
979
- ## Step 3: Estimate complexity
1083
+ ## Step 3: Research affected workflows
1084
+
1085
+ Dispatch a sub-agent using the Task tool to research the code workflows affected by this change. Pass the sub-agent:
1086
+ - The git range and changed files (from Step 1)
1087
+ - The project context brief (from Step 2)
1088
+ - Instructions to load the thatch-workflow-research skill and follow its methodology
1089
+
1090
+ The sub-agent produces a code guide with one section per affected workflow or feature. Wait for it to complete and collect the guide.
1091
+
1092
+ This guide gives specialists the code-level context (purpose, data flow, evolutions, constraints) they need to avoid false positives about intentional behavior and long-standing design decisions, and reduces duplicate code-tracing across the five specialist lenses.
1093
+
1094
+ For small changes (a single file or trivial diff), you may do this research inline instead of dispatching a sub-agent. Use your judgment: if the workflow research would require reading more than 3-4 files, dispatch a sub-agent to preserve your context for orchestration.
1095
+
1096
+ ## Step 4: Estimate complexity
980
1097
 
981
1098
  Estimate the review effort in scrum points (1-13 scale):
982
1099
  - **1**: Trivial — typo fix, config tweak, single-line change.
@@ -990,7 +1107,7 @@ Points reflect *review complexity*, not implementation effort.
990
1107
 
991
1108
  Identify files to exclude from review: vendored dependencies, generated files, lockfiles, compiled assets.
992
1109
 
993
- ## Step 4: Partition (if > 3 points)
1110
+ ## Step 5: Partition (if > 3 points)
994
1111
 
995
1112
  For changes estimated at more than 3 points, partition into review units of approximately 3 points each:
996
1113
  - Group files by logical component or feature, not by directory.
@@ -1000,7 +1117,7 @@ For changes estimated at more than 3 points, partition into review units of appr
1000
1117
 
1001
1118
  For changes estimated at 3 points or fewer, skip partitioning — dispatch one set of specialists covering the full scope. A single 3-point unit is not large enough to warrant decomposition.
1002
1119
 
1003
- ## Step 5: Dispatch specialist sub-agents
1120
+ ## Step 6: Dispatch specialist sub-agents
1004
1121
 
1005
1122
  For each review unit, dispatch sub-agents using the Task tool. Each sub-agent runs one specialist lens on one review unit. The five specialists are:
1006
1123
 
@@ -1019,7 +1136,7 @@ For the integration review unit (5+ points), dispatch a sub-agent focused on:
1019
1136
  - Boundary correctness — race conditions, ordering dependencies, shared state issues at boundaries.
1020
1137
  - Top-level coherence — does the overall change make sense as a unit?
1021
1138
 
1022
- ## Step 6: Synthesize
1139
+ ## Step 7: Synthesize
1023
1140
 
1024
1141
  After all sub-agents complete, load the thatch-review-synthesizer skill to verify findings, deduplicate across specialists, classify (CONFIRMED/REJECTED/UNVERIFIABLE), and produce the final severity-grouped report.
1025
1142
 
@@ -1041,6 +1158,7 @@ When dispatching each sub-agent, include in the prompt:
1041
1158
  - The specialist focus (from the five specialists above)
1042
1159
  - The diff stat for this unit's files
1043
1160
  - **The project context brief** from Step 2, filtered to what is relevant to this unit's scope. Explicitly list any deferred work that falls within this unit's files, and call out TODO ($ticket) markers the specialists should recognize as intentional.
1161
+ - **The workflow guide** from Step 3, filtered to the workflows relevant to this unit's scope. Use it to understand the purpose and evolution of the code before flagging issues. It provides the code-level context (flows, contracts, history, constraints) that prevents false positives about intentional behavior and long-standing design decisions.
1044
1162
  - Any design context or specific concerns
1045
1163
  - Explicit scope boundaries ("your scope is X; do NOT review Y")
1046
1164
  - Instruction to produce markdown findings with: severity, category, file:line, finding, evidence, trigger scenario, reachability, source of truth, producer chain, provenance
@@ -1064,6 +1182,7 @@ const SHARED_SKILLS: SkillDef[] = [
1064
1182
  { name: "thatch-review-breadcrumbs", content: REVIEW_BREADCRUMBS },
1065
1183
  { name: "thatch-review-synthesizer", content: REVIEW_SYNTHESIZER },
1066
1184
  { name: "thatch-review-context", content: REVIEW_CONTEXT },
1185
+ { name: "thatch-workflow-research", content: WORKFLOW_RESEARCH },
1067
1186
  { name: "thatch-session-reflection", content: SESSION_REFLECTION },
1068
1187
  ];
1069
1188
 
package/src/tool-defs.ts CHANGED
@@ -40,6 +40,7 @@ function formatEntry(
40
40
  let meta = `[${entry.store}]`;
41
41
  if (entry.branch) meta += ` branch:${entry.branch}`;
42
42
  if (entry.confidence) meta += ` confidence:${entry.confidence}`;
43
+ if (entry.archived) meta += " archived:true";
43
44
  parts.push(meta, "", entry.content);
44
45
  return parts.join("\n");
45
46
  }
@@ -48,6 +49,7 @@ function formatRecallResult(entry: any): string {
48
49
  let meta = `store:${entry.store}`;
49
50
  if (entry.branch) meta += ` branch:${entry.branch}`;
50
51
  if (entry.confidence) meta += ` confidence:${entry.confidence}`;
52
+ if (entry.archived) meta += " archived:true";
51
53
  const score = entry._score.toFixed(3);
52
54
  return `[${meta}] [score:${score}]\n${entry.content}`;
53
55
  }
@@ -118,6 +120,11 @@ const rememberDef: ToolDef = {
118
120
  overwrite: z.boolean().optional().describe(
119
121
  "Set to true to replace an existing memory with the same label.",
120
122
  ),
123
+ archived: z.boolean().optional().describe(
124
+ "Mark this memory as a stable, long-term record that should not trigger hygiene nudges. " +
125
+ "When updating an already-archived memory, this param is REQUIRED — " +
126
+ "pass archived: true to keep it archived, or archived: false to unarchive.",
127
+ ),
121
128
  },
122
129
  async execute(args, ctx) {
123
130
  const store = (args.store as string) || ctx.defaultStore;
@@ -125,9 +132,6 @@ const rememberDef: ToolDef = {
125
132
  const content = `# ${label}\n\n${args.content as string}`;
126
133
  const embedding = await ctx.model.passageEmbed(content);
127
134
 
128
- // Write-time collision check: the embedding is already computed, so
129
- // scanning the store is nearly free. The save proceeds regardless —
130
- // the agent decides whether and how to reconcile.
131
135
  const similar = ctx.db.findSimilar(store, embedding, {
132
136
  excludeSlug: ctx.db.slugify(label),
133
137
  });
@@ -136,6 +140,7 @@ const rememberDef: ToolDef = {
136
140
  branch: args.branch as string | undefined,
137
141
  confidence: args.confidence as number | undefined,
138
142
  overwrite: args.overwrite as boolean | undefined,
143
+ archived: args.archived as boolean | undefined,
139
144
  });
140
145
 
141
146
  if (!result.ok) return result.error;
@@ -172,6 +177,9 @@ const recallDef: ToolDef = {
172
177
  limit: z.number().int().min(1).max(20).optional().describe(
173
178
  "Maximum number of results. Default 10.",
174
179
  ),
180
+ includeArchived: z.boolean().optional().describe(
181
+ "Include archived memories in results. Default false — archived memories are excluded.",
182
+ ),
175
183
  },
176
184
  async execute(args, ctx) {
177
185
  const stores = args.store
@@ -183,6 +191,7 @@ const recallDef: ToolDef = {
183
191
  const results = ctx.db.recall(stores, queryEmbedding, {
184
192
  branch: args.branch as string | undefined,
185
193
  limit,
194
+ includeArchived: args.includeArchived as boolean | undefined,
186
195
  });
187
196
 
188
197
  if (results.length === 0) return "No matching memories found.";
@@ -212,6 +221,7 @@ const listDef: ToolDef = {
212
221
  let line = `[${store}] ${e.label}`;
213
222
  if (e.branch) line += ` (branch:${e.branch})`;
214
223
  if (e.confidence) line += ` (c:${e.confidence})`;
224
+ if (e.archived) line += " (archived)";
215
225
  line += ` (${e.updated_at})`;
216
226
  return line;
217
227
  })