@jeffober/thatch 0.1.5 → 0.1.6
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 +60 -9
- package/bin/thatch +167 -46
- package/docs/dev/README.md +67 -15
- package/docs/dev/gotchas.md +99 -0
- package/docs/dev/mcp-parity.md +113 -57
- package/docs/dev/setup-and-hooks.md +133 -0
- package/docs/dev/skills.md +109 -0
- package/docs/qa/README.md +9 -0
- package/docs/qa/use-cases/README.md +16 -0
- package/docs/qa/use-cases/UC-005-setup-install.md +34 -0
- package/docs/qa/use-cases/UC-006-cursor-hook-lifecycle.md +31 -0
- package/docs/qa/use-cases/UC-007-recall-nudge.md +31 -0
- package/docs/qa/use-cases/UC-008-hygiene-heartbeat.md +26 -0
- package/docs/qa/use-cases/UC-009-flush-tools-tiers.md +31 -0
- package/docs/qa/use-cases/UC-010-prime.md +25 -0
- package/docs/qa/use-cases/UC-011-write-time-similarity-warning.md +24 -0
- package/docs/qa/use-cases/UC-012-model-migration.md +28 -0
- package/docs/qa/use-cases/UC-013-compaction-context.md +25 -0
- package/docs/qa/use-cases/UC-014-skill-install-drift.md +28 -0
- package/docs/qa/use-cases/UC-015-env-override-matrix.md +28 -0
- package/docs/qa/use-cases/UC-016-concurrent-session-isolation.md +36 -0
- package/docs/qa/use-cases/UC-017-buffer-tool-vs-buffer-batch.md +27 -0
- package/docs/user/README.md +42 -7
- package/package.json +3 -2
- package/src/db.ts +19 -5
- package/src/git.ts +1 -1
- package/src/index.ts +65 -19
- package/src/mcp.ts +19 -0
- package/src/prompts.ts +111 -0
- package/src/setup.ts +171 -8
- package/src/sideband.ts +207 -0
- package/src/skills.ts +20 -8
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Gotchas
|
|
2
|
+
|
|
3
|
+
Non-obvious invariants and footguns. If something behaves strangely, check
|
|
4
|
+
here first. These are the things that have already cost time.
|
|
5
|
+
|
|
6
|
+
## Embeddings
|
|
7
|
+
|
|
8
|
+
- **Embedding spaces are discriminated by vector _dimension_, not model tag.**
|
|
9
|
+
`recall` / `findDuplicates` skip entries whose vector length differs from the
|
|
10
|
+
query. The `model` column is informational only. Switching `THATCH_MODEL`
|
|
11
|
+
makes old memories _invisible_ to search (not corrupted, not deleted — just
|
|
12
|
+
skipped) until re-saved. There is no automatic re-embedding.
|
|
13
|
+
- **Embedding serialization honors `byteOffset`/`byteLength`.** transformers.js
|
|
14
|
+
can return a `Float32Array` that is a _view_ into a larger tensor buffer.
|
|
15
|
+
Serializing the whole backing buffer corrupts vectors. Always serialize the
|
|
16
|
+
view's own bytes, not the underlying buffer.
|
|
17
|
+
- **BGE asymmetric search**: queries get the prefix
|
|
18
|
+
`"Represent this sentence for searching relevant passages: "`; passages get
|
|
19
|
+
no prefix. `queryEmbed` vs `passageEmbed` — don't swap them.
|
|
20
|
+
|
|
21
|
+
## Search vs recall
|
|
22
|
+
|
|
23
|
+
- **`search` scores; `recall` scores _and_ stamps telemetry.** `search` records
|
|
24
|
+
no usage. The prompt-aware recall nudge deliberately uses `db.search()` (not
|
|
25
|
+
`recall`) so nudges don't inflate the "used recently" signal. Only explicit
|
|
26
|
+
`thatch_memory_recall` / CLI `search` stamp `recall_count`/`last_recalled_at`.
|
|
27
|
+
- **`findSimilar` excludes the slug being written** (self-exclusion on
|
|
28
|
+
overwrite), so overwriting a memory doesn't warn against itself.
|
|
29
|
+
|
|
30
|
+
## Writes
|
|
31
|
+
|
|
32
|
+
- **The write-time similarity warning never blocks the save.** `remember` always
|
|
33
|
+
proceeds; the warning lists >= 0.85-similar entries and asks the agent to
|
|
34
|
+
reconcile (merge or `dedup_mark_checked`). Don't add blocking logic here.
|
|
35
|
+
- **`overwrite: true` clears stale dedup verdicts** for that slug. Forgetting an
|
|
36
|
+
entry clears all verdicts involving it. Both make a pair eligible for
|
|
37
|
+
re-reporting by `find_duplicates`. This is intended.
|
|
38
|
+
|
|
39
|
+
## Hooks (opencode)
|
|
40
|
+
|
|
41
|
+
- **`tool.execute.after` is a plugin hook, NOT a bus event.** It must stay on
|
|
42
|
+
the hook object returned by `server`. Moving it into the `event` handler
|
|
43
|
+
(where `session.created` lives) silently never fires it — the event bus has
|
|
44
|
+
no such event. This was dead for weeks because the failure was invisible.
|
|
45
|
+
- **Hook failures are logged with a `[thatch]` prefix and never swallowed.** Two
|
|
46
|
+
hooks were dead for weeks before failures were made visible. If you add a
|
|
47
|
+
hook, log on failure.
|
|
48
|
+
- **`chat.message` has two priority tiers**: extraction nudge first (returns
|
|
49
|
+
early), then the prompt-aware recall nudge. Don't run both in one turn.
|
|
50
|
+
|
|
51
|
+
## Hooks (Claude Code / Cursor)
|
|
52
|
+
|
|
53
|
+
- **`PostToolBatch`/`postToolUse` must be silent** (no stdout). The agent loop
|
|
54
|
+
must not block on a payload that should be invisible until the next prompt.
|
|
55
|
+
Only `flush-tools` prints.
|
|
56
|
+
- **The recall nudge arrives at the _start_ of the next turn** in Claude
|
|
57
|
+
Code/Cursor, not the end of the current one like opencode's `chat.message`. A
|
|
58
|
+
file-backed queue bridges calls that have no shared state.
|
|
59
|
+
- **Cursor uses `conversation_id`** where Claude Code uses `session_id`.
|
|
60
|
+
`buffer-tool` normalizes the former to a safe filename and tries multiple
|
|
61
|
+
field names for the tool response.
|
|
62
|
+
- **`--json` flips the output shape.** `reminder --json` and `flush-tools
|
|
63
|
+
--json` emit `{ additional_context: "..." }` for Cursor; plain stdout for
|
|
64
|
+
Claude Code. The flag is baked into the installed hook command.
|
|
65
|
+
|
|
66
|
+
## Sideband
|
|
67
|
+
|
|
68
|
+
- **Socket path = SHA-256 of the DB path**, under `os.tmpdir()`. The MCP server
|
|
69
|
+
and hook processes compute it independently — no out-of-band coordination.
|
|
70
|
+
Changing `THATCH_DB_PATH` moves the socket; a stale socket from a crash is
|
|
71
|
+
cleaned up on connection error.
|
|
72
|
+
- **Sideband failure never blocks.** Server down, stale socket, or a >2 s
|
|
73
|
+
timeout all return `null`, and `flush-tools` falls back to the static write
|
|
74
|
+
nudge. Never hard-fail the agent over a recall nudge.
|
|
75
|
+
|
|
76
|
+
## Setup
|
|
77
|
+
|
|
78
|
+
- **Skills are always user-scoped**, even in project-local setup. The project's
|
|
79
|
+
`.claude/settings.json` and `CLAUDE.md` stay in the repo, but skills live
|
|
80
|
+
under `$CLAUDE_CONFIG_DIR/skills/` / `~/.cursor/skills/`. Installing into the
|
|
81
|
+
worktree would mutate the user's repo.
|
|
82
|
+
- **`appendBlock` leaves content alone if the markers don't parse.** If the
|
|
83
|
+
start marker is found but the end marker isn't, the whole block is skipped
|
|
84
|
+
rather than half-replaced. Fix the markers or delete the block manually.
|
|
85
|
+
- **The binary path is baked into installed hook commands.** `thatch setup`
|
|
86
|
+
resolves `<bin>` from PATH (or the script's absolute path) and writes it into
|
|
87
|
+
every hook command, so hooks survive after the setup session ends.
|
|
88
|
+
|
|
89
|
+
## Tests
|
|
90
|
+
|
|
91
|
+
- **DB tests use real SQLite files in `mkdtempSync`, not `:memory:`.** WAL
|
|
92
|
+
behavior differs in-memory and would mask bugs. Temp dirs are removed in
|
|
93
|
+
`afterEach`.
|
|
94
|
+
- **`BgeEmbeddingModel`'s real `PipelineFactory` is untested** — downloading the
|
|
95
|
+
model violates the no-network rule. Lazy-load and retry logic are tested with
|
|
96
|
+
an injected mock factory. The real model is exercised only by real use.
|
|
97
|
+
- **`bun test` does not typecheck**, and `tsconfig.json` excludes `tests`. Test
|
|
98
|
+
type errors are editor-only noise unless you run `tsc` on the test files
|
|
99
|
+
directly. Keep test files type-clean anyway so the editor stays quiet.
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -1,27 +1,47 @@
|
|
|
1
|
-
# MCP Parity: OpenCode Plugin vs Claude Code
|
|
1
|
+
# MCP Parity: OpenCode Plugin vs Claude Code vs Cursor
|
|
2
2
|
|
|
3
|
-
Thatch supports
|
|
3
|
+
Thatch supports three integration paths sharing one core (`src/tool-defs.ts`):
|
|
4
4
|
|
|
5
5
|
1. **OpenCode plugin** — runs inside opencode's Bun runtime. Full access to
|
|
6
|
-
plugin hooks: system prompt injection, session events, tool
|
|
7
|
-
compaction context, and skill installation.
|
|
6
|
+
plugin hooks: system prompt injection, session events, in-process tool
|
|
7
|
+
buffering, compaction context, and skill installation.
|
|
8
8
|
2. **Claude Code MCP server** — runs as a stdio JSON-RPC process. Tools are
|
|
9
9
|
exposed via MCP `tools/list` + `tools/call`. Session behavior is driven by
|
|
10
|
-
Claude Code hooks (`SessionStart`, `UserPromptSubmit`).
|
|
10
|
+
Claude Code hooks (`SessionStart`, `PostToolBatch`, `UserPromptSubmit`).
|
|
11
|
+
3. **Cursor MCP server** — same stdio MCP server as Claude Code. Session
|
|
12
|
+
behavior is driven by Cursor hooks (`sessionStart`, `postToolUse`,
|
|
13
|
+
`beforeSubmitPrompt`) in a flat `hooks.json` format.
|
|
11
14
|
|
|
12
|
-
This document maps the feature parity and documents the gaps.
|
|
15
|
+
This document maps the feature parity and documents the remaining gaps.
|
|
13
16
|
|
|
14
17
|
## Parity matrix
|
|
15
18
|
|
|
16
|
-
| Feature | OpenCode plugin | Claude Code MCP + hooks | Parity |
|
|
17
|
-
|
|
18
|
-
| **Tools** (
|
|
19
|
-
| **System prompt** (store names, usage rules
|
|
20
|
-
| **Session-start reminder** (recall nudge + hygiene heartbeat) | `session.created` event → `client.session.prompt` injects a synthetic message | `SessionStart` hook → `thatch reminder
|
|
21
|
-
| **Compaction context** (re-familiarize after compaction) | `experimental.session.compacting` hook appends context to the compaction output | `PostCompact` hook — side-effects only, **cannot inject context** |
|
|
22
|
-
| **Extraction nudge** (buffer tool calls, inject JSON payload
|
|
23
|
-
| **
|
|
24
|
-
| **
|
|
19
|
+
| Feature | OpenCode plugin | Claude Code MCP + hooks | Cursor MCP + hooks | Parity |
|
|
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** |
|
|
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
|
+
| **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
|
+
| **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` |
|
|
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
|
+
| **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
|
+
| **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
|
+
| **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
|
+
## Tool name conventions
|
|
32
|
+
|
|
33
|
+
The shared tool definitions in `src/tool-defs.ts` use bare names
|
|
34
|
+
(`memory_remember`, `memory_recall`, ...). Each host prefixes them differently:
|
|
35
|
+
|
|
36
|
+
- **opencode**: `thatch_memory_remember` — the prefix is added by the
|
|
37
|
+
`tool()` wrapper in `src/tools.ts`.
|
|
38
|
+
- **Claude Code**: `mcp__thatch__memory_remember` — Claude Code auto-prefixes
|
|
39
|
+
with the server name; the MCP server exposes `memory_remember`.
|
|
40
|
+
- **Cursor**: `mcp__thatch__memory_remember` — same MCP server.
|
|
41
|
+
|
|
42
|
+
The CLAUDE.md / AGENTS.md instructions generated by `thatch setup` reference
|
|
43
|
+
both forms — the full `mcp__thatch__*` names for the host, and bare names for
|
|
44
|
+
readability.
|
|
25
45
|
|
|
26
46
|
## Why the gaps exist
|
|
27
47
|
|
|
@@ -31,56 +51,92 @@ OpenCode's `experimental.session.compacting` hook lets the plugin append text
|
|
|
31
51
|
to the compaction output — the agent sees a "you are using thatch, here's what
|
|
32
52
|
you've learned" message after compaction. Claude Code's `PostCompact` hook is
|
|
33
53
|
side-effects only (logging, external state); it has no `additionalContext`
|
|
34
|
-
field and no decision control.
|
|
35
|
-
can't inject context.
|
|
54
|
+
field and no decision control. Cursor has no equivalent hook.
|
|
36
55
|
|
|
37
|
-
**Mitigation**: CLAUDE.md is loaded at session start and persists
|
|
38
|
-
compaction
|
|
39
|
-
with `source: "compact"` fires after
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
**Mitigation**: CLAUDE.md / AGENTS.md is loaded at session start and persists
|
|
57
|
+
through compaction, so the agent retains the usage instructions. The
|
|
58
|
+
`SessionStart` / `sessionStart` hook with `source: "compact"` fires after
|
|
59
|
+
compaction in Claude Code, so the recall reminder runs again. There is no
|
|
60
|
+
explicit "re-familiarization" nudge for hosts without a compaction hook.
|
|
42
61
|
|
|
43
|
-
### Extraction nudge
|
|
62
|
+
### Extraction nudge timing
|
|
44
63
|
|
|
45
64
|
OpenCode's extraction pipeline works by:
|
|
46
65
|
|
|
47
66
|
1. `tool.execute.after` buffers every non-thatch tool call into a per-session
|
|
48
|
-
array (max 20 interactions).
|
|
67
|
+
in-memory array (max 20 interactions).
|
|
49
68
|
2. On the next `chat.message`, the buffer is flushed and a synthetic text part
|
|
50
|
-
carrying the JSON payload is injected into the conversation
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
carrying the JSON payload is injected into the conversation (end of the
|
|
70
|
+
current turn).
|
|
71
|
+
3. The agent sees the nudge, loads the `thatch-fact-extractor` skill, and saves
|
|
72
|
+
durable facts via `thatch_memory_remember`.
|
|
73
|
+
|
|
74
|
+
Claude Code and Cursor use a **file-backed** queue (`src/extract-queue.ts`)
|
|
75
|
+
because hooks fire one-shot with no cross-call state:
|
|
76
|
+
|
|
77
|
+
1. `PostToolBatch` (Claude Code) / `postToolUse` (Cursor) appends each tool
|
|
78
|
+
interaction to a per-session JSONL file under `$XDG_CACHE_HOME/thatch/queue/`
|
|
79
|
+
(max 20, oldest dropped). These commands are silent — no stdout — so the
|
|
80
|
+
agent loop is not delayed.
|
|
81
|
+
2. `UserPromptSubmit` / `beforeSubmitPrompt` runs `thatch flush-tools`, which
|
|
82
|
+
drains the queue and prints the JSON payload. This arrives at the **start**
|
|
83
|
+
of the next turn (before the model processes the prompt), not at the end of
|
|
84
|
+
the current one.
|
|
85
|
+
|
|
86
|
+
The two paths produce the same JSON shape via `buildExtractionPayload()`, so
|
|
87
|
+
the fact-extractor skill receives an identical contract regardless of host.
|
|
88
|
+
|
|
89
|
+
### Recall nudge without a warm model
|
|
90
|
+
|
|
91
|
+
The prompt-aware recall nudge needs the embedding model to embed the user's
|
|
92
|
+
prompt text. In opencode, the model is warm in-process — the `chat.message`
|
|
93
|
+
hook calls `model.queryEmbed()` directly. In Claude Code/Cursor, hooks are
|
|
94
|
+
one-shot `bun` spawns that can't reach the MCP server's in-memory model.
|
|
95
|
+
|
|
96
|
+
Loading the ~34 MB model on every `UserPromptSubmit` would add ~300-700 ms to
|
|
97
|
+
every prompt in the critical path before the agent responds. The sideband
|
|
98
|
+
socket eliminates this cost; see the next section.
|
|
99
|
+
|
|
100
|
+
## Sideband socket architecture
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
Claude Code / Cursor session
|
|
104
|
+
├── MCP server (long-lived, warm model)
|
|
105
|
+
│ ├── stdio JSON-RPC (tool calls)
|
|
106
|
+
│ └── Unix socket sideband (embed + search for hooks)
|
|
107
|
+
├── UserPromptSubmit / beforeSubmitPrompt hook → thatch flush-tools [--json]
|
|
108
|
+
│ ├── drains the file-backed extraction queue (existing)
|
|
109
|
+
│ ├── connects to sideband socket
|
|
110
|
+
│ ├── sends prompt text
|
|
111
|
+
│ ├── receives match labels + scores
|
|
112
|
+
│ └── prints recall nudge or falls back to write nudge
|
|
113
|
+
├── PostToolBatch → thatch buffer-batch (Claude Code)
|
|
114
|
+
│ └── postToolUse → thatch buffer-tool (Cursor)
|
|
115
|
+
└── SessionStart / sessionStart hook → thatch reminder [--json]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The socket path is derived from a SHA-256 hash of the DB path — both the MCP
|
|
119
|
+
server and hook processes resolve the same DB path independently (from
|
|
120
|
+
`THATCH_DB_PATH` or the default under `XDG_CONFIG_HOME`), so they arrive at the
|
|
121
|
+
same socket path without out-of-band coordination. The path lives under
|
|
122
|
+
`os.tmpdir()`.
|
|
123
|
+
|
|
124
|
+
The protocol is newline-delimited JSON: one request per connection, one
|
|
125
|
+
response. Requests are `{"method":"match","text":"...","stores":[...],
|
|
126
|
+
"threshold":N,"limit":N}`. Responses are `{"ok":true,"matches":[...]}` or
|
|
127
|
+
`{"ok":false,"error":"..."}`.
|
|
128
|
+
|
|
129
|
+
Graceful degradation: if the socket isn't available (MCP server not running,
|
|
130
|
+
old version, stale socket from a crash, or a >2 s timeout), `sidebandMatch`
|
|
131
|
+
returns `null` and `flush-tools` falls back to the static write nudge. The
|
|
132
|
+
recall nudge is best-effort — its absence never blocks the agent's workflow. A
|
|
133
|
+
stale socket file left by a crash is cleaned up on connection error.
|
|
72
134
|
|
|
73
135
|
## Architecture implications
|
|
74
136
|
|
|
75
137
|
The shared tool definitions in `src/tool-defs.ts` are the single source of
|
|
76
|
-
truth for
|
|
77
|
-
schemas; the MCP server
|
|
78
|
-
`z.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- **MCP/Claude Code**: `mcp__thatch__memory_remember` (Claude Code auto-prefixes
|
|
82
|
-
with the server name; the MCP server exposes `memory_remember`)
|
|
83
|
-
|
|
84
|
-
The `thatch setup` command generates CLAUDE.md instructions that reference
|
|
85
|
-
both forms — the full `mcp__thatch__*` names for Claude Code, and bare names
|
|
86
|
-
for readability.
|
|
138
|
+
truth for all three paths. The opencode plugin wraps them in `tool()` with zod
|
|
139
|
+
schemas and a `thatch_` prefix; the MCP server (used by both Claude Code and
|
|
140
|
+
Cursor) wraps them in `z.object()` for validation and `z.toJSONSchema()` for
|
|
141
|
+
the protocol response. See `docs/dev/setup-and-hooks.md` for the concrete
|
|
142
|
+
files, hook event names, and command shapes each host writes.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Setup and Hook Configuration
|
|
2
|
+
|
|
3
|
+
How `thatch setup` wires each host, and which hook events each path relies on.
|
|
4
|
+
For feature parity and gaps, see `mcp-parity.md`. This document is the concrete
|
|
5
|
+
artifact reference.
|
|
6
|
+
|
|
7
|
+
## OpenCode (plugin)
|
|
8
|
+
|
|
9
|
+
No `setup` command — opencode loads the plugin from `opencode.json`:
|
|
10
|
+
|
|
11
|
+
```jsonc
|
|
12
|
+
{ "plugin": ["@jeffober/thatch"] }
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The plugin entry (`src/index.ts`) registers these hooks in code:
|
|
16
|
+
|
|
17
|
+
| Hook | Input | Output | Behavior |
|
|
18
|
+
|------|-------|--------|----------|
|
|
19
|
+
| `experimental.chat.system.transform` | `{}` | `{ system: string[] }` | Pushes the system prompt (store names, usage rules). |
|
|
20
|
+
| `experimental.session.compacting` | `{}` | `{ context: string[] }` | Pushes re-familiarization context. |
|
|
21
|
+
| `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
|
+
| `chat.message` | `{ sessionID, messageID }` | `{ message, parts }` | Two tiers: extraction nudge if pending, else prompt-aware recall nudge. |
|
|
23
|
+
| `event` (`session.created`) | `{ event: { type, properties: { info: { id } } } }` | — | Calls `client.session.prompt` with the reminder + hygiene heartbeat. |
|
|
24
|
+
| `dispose` | — | — | Closes the DB. |
|
|
25
|
+
|
|
26
|
+
Skills install to `$XDG_CONFIG_HOME/opencode/skills` at plugin init — shared
|
|
27
|
+
**and** opencode-only (the coordinator needs sub-agents).
|
|
28
|
+
|
|
29
|
+
## Claude Code (MCP server + hooks)
|
|
30
|
+
|
|
31
|
+
`thatch setup --claude` (project-local) or `thatch setup --claude --global`.
|
|
32
|
+
|
|
33
|
+
### Artifacts written (Claude Code)
|
|
34
|
+
|
|
35
|
+
| Artifact | Project-local | Global (`--global`) |
|
|
36
|
+
|----------|--------------|---------------------|
|
|
37
|
+
| MCP config | `.mcp.json` (`mcpServers.thatch`, stdio, `["mcp"]`) | none — prints `claude mcp add --scope user thatch -- <bin> mcp` |
|
|
38
|
+
| Instructions | `CLAUDE.md` (idempotent `appendBlock`) | `$CLAUDE_CONFIG_DIR/CLAUDE.md` |
|
|
39
|
+
| Hooks | `.claude/settings.json` | `$CLAUDE_CONFIG_DIR/settings.json` |
|
|
40
|
+
| Skills | `$CLAUDE_CONFIG_DIR/skills/` (always user-scoped) | same |
|
|
41
|
+
|
|
42
|
+
`CLAUDE_CONFIG_DIR` overrides the default `~/.claude` for all user-scoped
|
|
43
|
+
paths. Project-local keeps `.mcp.json`, `CLAUDE.md`, and `.claude/settings.json`
|
|
44
|
+
in the repo; only skills are user-scoped.
|
|
45
|
+
|
|
46
|
+
### Hook events (nested `settings.json`)
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{
|
|
50
|
+
"hooks": {
|
|
51
|
+
"SessionStart": { "hooks": [{ "type": "command", "command": "<bin> reminder" }] },
|
|
52
|
+
"PostToolBatch": { "hooks": [{ "type": "command", "command": "<bin> buffer-batch" }] },
|
|
53
|
+
"UserPromptSubmit": { "hooks": [{ "type": "command", "command": "<bin> flush-tools" }] }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Event | Command | Output | Role |
|
|
59
|
+
|-------|---------|--------|------|
|
|
60
|
+
| `SessionStart` | `thatch reminder` | plain text to stdout (becomes context) | Recall instructions + hygiene heartbeat |
|
|
61
|
+
| `PostToolBatch` | `thatch buffer-batch` | **silent** (no stdout) | Appends a batch of tool calls to the file-backed JSONL queue |
|
|
62
|
+
| `UserPromptSubmit` | `thatch flush-tools` | nudge text to stdout | Drains queue (extraction nudge), else recall nudge via sideband, else write nudge |
|
|
63
|
+
|
|
64
|
+
`PostToolBatch` is silent so the agent loop is not delayed; the buffered
|
|
65
|
+
content is invisible until `UserPromptSubmit` flushes it.
|
|
66
|
+
|
|
67
|
+
## Cursor (MCP server + hooks)
|
|
68
|
+
|
|
69
|
+
`thatch setup --cursor` (project-local) or `thatch setup --cursor --global`.
|
|
70
|
+
Cursor uses the same stdio MCP server as Claude Code; only the hooks differ.
|
|
71
|
+
|
|
72
|
+
### Artifacts written (Cursor)
|
|
73
|
+
|
|
74
|
+
| Artifact | Project-local | Global (`--global`) |
|
|
75
|
+
|----------|--------------|---------------------|
|
|
76
|
+
| MCP config | `.cursor/mcp.json` | `$CURSOR_CONFIG_DIR/mcp.json` (or `~/.cursor/mcp.json`) |
|
|
77
|
+
| Instructions | `AGENTS.md` (idempotent `appendBlock`, Cursor-marked) | `$CURSOR_CONFIG_DIR/AGENTS.md` |
|
|
78
|
+
| Hooks | `.cursor/hooks.json` (flat) | `$CURSOR_CONFIG_DIR/hooks.json` |
|
|
79
|
+
| Skills | `$CURSOR_CONFIG_DIR/skills/` (shared only) | same |
|
|
80
|
+
|
|
81
|
+
Cursor has no documented env override like `CLAUDE_CONFIG_DIR`; `CURSOR_CONFIG_DIR`
|
|
82
|
+
is honored for symmetry and forward-compatibility. Cursor has no equivalent of
|
|
83
|
+
`claude mcp add --scope user` — writing `~/.cursor/mcp.json` directly is enough.
|
|
84
|
+
|
|
85
|
+
### Hook events (flat `hooks.json`)
|
|
86
|
+
|
|
87
|
+
```jsonc
|
|
88
|
+
{
|
|
89
|
+
"version": 1,
|
|
90
|
+
"hooks": {
|
|
91
|
+
"sessionStart": [{ "command": "<bin> reminder --json" }],
|
|
92
|
+
"postToolUse": [{ "command": "<bin> buffer-tool" }],
|
|
93
|
+
"beforeSubmitPrompt": [{ "command": "<bin> flush-tools --json" }]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Event | Command | Output | Role |
|
|
99
|
+
|-------|---------|--------|------|
|
|
100
|
+
| `sessionStart` | `thatch reminder --json` | `{ additional_context: "..." }` | Recall + heartbeat, JSON-wrapped for Cursor |
|
|
101
|
+
| `postToolUse` | `thatch buffer-tool` | **silent** | Appends a **single** tool call to the file-backed queue |
|
|
102
|
+
| `beforeSubmitPrompt` | `thatch flush-tools --json` | JSON `additional_context` | Drains queue, else recall via sideband, else write nudge |
|
|
103
|
+
|
|
104
|
+
Differences from Claude Code:
|
|
105
|
+
|
|
106
|
+
- **Flat format** (`{version, hooks:{event:[{command}]}}`) vs nested.
|
|
107
|
+
- `postToolUse` fires **per tool** (Cursor has no `PostToolBatch`); `buffer-tool`
|
|
108
|
+
reads `conversation_id` (vs `session_id`) and normalizes it to a safe filename.
|
|
109
|
+
- `--json` on `reminder` and `flush-tools` so Cursor parses the output as
|
|
110
|
+
`additional_context`.
|
|
111
|
+
|
|
112
|
+
## Idempotence and drift
|
|
113
|
+
|
|
114
|
+
All setup operations are idempotent:
|
|
115
|
+
|
|
116
|
+
- **Instructions**: `appendBlock` searches for start/end markers and replaces
|
|
117
|
+
the block between them, so re-running setup updates drifted content without
|
|
118
|
+
clobbering surrounding text. No marker found → append.
|
|
119
|
+
- **Hooks**: `replaceThatchHooks` / `replaceCursorThatchHooks` filter out any
|
|
120
|
+
hook group whose command contains `thatch`, then add the current ones. A
|
|
121
|
+
legacy `thatch echo` hook is replaced with `flush-tools`. Non-thatch hooks are
|
|
122
|
+
preserved.
|
|
123
|
+
- **MCP config**: existing `mcpServers` are preserved; only the `thatch` entry
|
|
124
|
+
is set.
|
|
125
|
+
- **Skills**: `installSkills` only writes when on-disk content differs from the
|
|
126
|
+
definition (drift detection). Skills are plugin-owned — local edits are
|
|
127
|
+
overwritten on the next plugin init or `thatch setup`.
|
|
128
|
+
|
|
129
|
+
## Binary resolution
|
|
130
|
+
|
|
131
|
+
`thatch setup` resolves `<bin>` from PATH, falling back to the absolute path of
|
|
132
|
+
the running script. This is baked into every installed hook command, so the
|
|
133
|
+
hooks keep working after the session that ran setup ends.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Skills
|
|
2
|
+
|
|
3
|
+
Thatch installs SKILL.md files that give the agent task-specific workflows. All
|
|
4
|
+
skill content is defined in `src/skills.ts` and installed to a host-specific
|
|
5
|
+
directory. See `mcp-parity.md` for which skills each host gets.
|
|
6
|
+
|
|
7
|
+
## Skill file format
|
|
8
|
+
|
|
9
|
+
Each skill is a single `SKILL.md` with YAML frontmatter:
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
---
|
|
13
|
+
name: thatch-fact-extractor
|
|
14
|
+
description: Extract durable project facts ... Use when ...
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<role and instructions>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `name` must match the directory name (`<skillsDir>/<name>/SKILL.md`).
|
|
21
|
+
- `description` drives when the agent loads the skill (both opencode and
|
|
22
|
+
Cursor auto-discover skills and use the description for relevance).
|
|
23
|
+
|
|
24
|
+
## The 11 skills
|
|
25
|
+
|
|
26
|
+
**Shared (10)** — installed everywhere; no sub-agents required:
|
|
27
|
+
|
|
28
|
+
| Skill | Role |
|
|
29
|
+
|-------|------|
|
|
30
|
+
| `thatch-fact-extractor` | Turn buffered tool interactions into `thatch_memory_remember` calls. |
|
|
31
|
+
| `thatch-dedup-classifier` | Classify and resolve `find_duplicates` pairs/clusters. |
|
|
32
|
+
| `thatch-project-primer` | Investigate a new project and write foundational memories. |
|
|
33
|
+
| `thatch-review-pedantic` | Mechanical correctness: spelling, naming, doc accuracy, specs, stale artifacts. |
|
|
34
|
+
| `thatch-review-acceptance` | Behavioral/product review: UX, behavioral delta, integration effects. |
|
|
35
|
+
| `thatch-review-state-flow` | Data flow and contracts: module boundaries, implicit FSMs, error propagation. |
|
|
36
|
+
| `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth-wall breaks, em dashes, filler. |
|
|
37
|
+
| `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of behavior? |
|
|
38
|
+
| `thatch-review-synthesizer` | Verify specialist findings against code, dedupe, classify, calibrate severity. |
|
|
39
|
+
| `thatch-session-reflection` | End-of-session memory recording (project, user, tools, self). |
|
|
40
|
+
|
|
41
|
+
**opencode-only (1)** — the coordinator needs sub-agent support:
|
|
42
|
+
|
|
43
|
+
| Skill | Role |
|
|
44
|
+
|-------|------|
|
|
45
|
+
| `thatch-code-review` | Resolve review target, estimate complexity, partition, dispatch the 5 specialists in parallel, synthesize. |
|
|
46
|
+
|
|
47
|
+
## REVIEW_COMMON
|
|
48
|
+
|
|
49
|
+
The five review specialists share a framework interpolated via `${REVIEW_COMMON}`:
|
|
50
|
+
|
|
51
|
+
- **Static analysis only** — no running tests/linters/compilers.
|
|
52
|
+
- **Scope gathering** — resolve the git range, `git diff --stat`, read changed
|
|
53
|
+
files in full for context.
|
|
54
|
+
- **Runtime model** — CLI/long-lived server/library/batch — this determines
|
|
55
|
+
which bug classes are realistic.
|
|
56
|
+
- **Reachability gate** — every finding must describe a concrete normal-usage
|
|
57
|
+
trigger; "the code allows this" is not enough.
|
|
58
|
+
- **Intent verification** — trace callers, check git history, and `thatch_memory_recall`
|
|
59
|
+
for design rationale before flagging a behavior as a bug.
|
|
60
|
+
- **Output format** — `[SEVERITY] [CATEGORY] file:line` with finding, evidence
|
|
61
|
+
(quoted), trigger, reachability, source of truth, producer chain, provenance.
|
|
62
|
+
|
|
63
|
+
The synthesizer reuses the same verification rigor but has its own structure
|
|
64
|
+
(it does not interpolate `REVIEW_COMMON`).
|
|
65
|
+
|
|
66
|
+
## The two arrays
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const SHARED_SKILLS: SkillDef[] = [ /* 10 skills above */ ];
|
|
70
|
+
const OPENCODE_ONLY_SKILLS: SkillDef[] = [ /* code-review coordinator */ ];
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`installSkills(skillsDir, skills?)` defaults to `SHARED_SKILLS`. The opencode
|
|
74
|
+
plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`; `thatch setup --claude`
|
|
75
|
+
and `--cursor` pass only the shared set.
|
|
76
|
+
|
|
77
|
+
## Install mechanics
|
|
78
|
+
|
|
79
|
+
- **Location**: `$XDG_CONFIG_HOME/opencode/skills` (opencode),
|
|
80
|
+
`$CLAUDE_CONFIG_DIR/skills` (Claude Code), `~/.cursor/skills` (Cursor).
|
|
81
|
+
Always user-scoped — never into the worktree.
|
|
82
|
+
- **Trigger**: opencode installs at plugin init; Claude Code/Cursor install via
|
|
83
|
+
`thatch setup`.
|
|
84
|
+
- **Drift detection**: `installSkills` only writes when the on-disk content
|
|
85
|
+
differs from the definition. This is how skill improvements ship with new
|
|
86
|
+
versions without users manually deleting files.
|
|
87
|
+
- **Idempotent**: re-running init or setup overwrites drifted content but leaves
|
|
88
|
+
unrelated skill files alone.
|
|
89
|
+
|
|
90
|
+
## Adding a skill
|
|
91
|
+
|
|
92
|
+
1. Write the `SKILL.md` content as a template literal in `src/skills.ts`.
|
|
93
|
+
Interpolate `${REVIEW_COMMON}` if it's a review specialist.
|
|
94
|
+
2. Add a `{ name, content }` entry to `SHARED_SKILLS` (or `OPENCODE_ONLY_SKILLS`
|
|
95
|
+
if it needs sub-agents).
|
|
96
|
+
3. If the skill name appears in a tool's expected workflow (e.g. the extraction
|
|
97
|
+
nudge references `thatch-fact-extractor`), update that prompt in `src/prompts.ts`.
|
|
98
|
+
4. The opencode plugin and `thatch setup` pick it up automatically on next init.
|
|
99
|
+
|
|
100
|
+
## Memory review skills in practice
|
|
101
|
+
|
|
102
|
+
- **Quick single lens**: load any specialist directly and point it at a branch.
|
|
103
|
+
- **Full review on opencode**: load `thatch-code-review` — it dispatches all 5
|
|
104
|
+
specialists in parallel, then synthesizes.
|
|
105
|
+
- **Full review on Claude Code/Cursor**: run each specialist in sequence, then
|
|
106
|
+
run `thatch-review-synthesizer` to verify and aggregate.
|
|
107
|
+
|
|
108
|
+
The coordinator is the only skill that can't be used without sub-agents; that's
|
|
109
|
+
why it lives in `OPENCODE_ONLY_SKILLS`.
|
package/docs/qa/README.md
CHANGED
|
@@ -24,8 +24,17 @@ network access and no writes outside temp directories.
|
|
|
24
24
|
```bash
|
|
25
25
|
bun test # all tests
|
|
26
26
|
bun test --watch # watch mode
|
|
27
|
+
mise run check # typecheck + bun test + markdownlint (the CI gate)
|
|
28
|
+
mise run typecheck # tsc alone
|
|
29
|
+
mise run lint-md # markdownlint only
|
|
27
30
|
```
|
|
28
31
|
|
|
32
|
+
`mise run check` is the canonical quality gate (also run by CI on push/PR to
|
|
33
|
+
`main`). Typecheck uses `tsconfig.check.json` (which includes the test files
|
|
34
|
+
that the build's `tsconfig.json` excludes). Markdownlint enforces structural
|
|
35
|
+
correctness on `README.md` and `docs/` excluding `docs/plans/`; see
|
|
36
|
+
`.markdownlint-cli2.jsonc` for rule config.
|
|
37
|
+
|
|
29
38
|
## Test files and coverage
|
|
30
39
|
|
|
31
40
|
### `tests/db.test.ts`
|
|
@@ -4,12 +4,28 @@ Manual end-to-end QA scenarios for thatch. Each use case has preconditions,
|
|
|
4
4
|
steps, and expected results. These cover behavior the automated suite can't:
|
|
5
5
|
real opencode sessions, the real embedding model, and the CLI.
|
|
6
6
|
|
|
7
|
+
Newer use cases carry an _Automatable_ note flagging which are pure
|
|
8
|
+
file/IPC/CLI contracts ready to graduate into `bun:test` integration tests.
|
|
9
|
+
|
|
7
10
|
| Use case | Covers |
|
|
8
11
|
|----------|--------|
|
|
9
12
|
| [UC-001](UC-001-memory-roundtrip.md) | Remember/recall across sessions, store naming, duplicate rejection |
|
|
10
13
|
| [UC-002](UC-002-dedup-cycle.md) | Full dedup review cycle: find, classify, resolve, mark checked |
|
|
11
14
|
| [UC-003](UC-003-extraction-nudge.md) | Tool buffering → extraction nudge → agent-driven fact saving |
|
|
12
15
|
| [UC-004](UC-004-cli-inspection.md) | CLI commands, arg validation, env overrides |
|
|
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 |
|
|
18
|
+
| [UC-007](UC-007-recall-nudge.md) | Prompt-aware recall nudge — opencode in-process + Claude/Cursor sideband paths; no telemetry |
|
|
19
|
+
| [UC-008](UC-008-hygiene-heartbeat.md) | Hygiene heartbeat: stale 90d, orphaned branches, pending dedup; advisory, agent-driven |
|
|
20
|
+
| [UC-009](UC-009-flush-tools-tiers.md) | `flush-tools` three-tier priority (extraction > recall > write) + sideband degradation |
|
|
21
|
+
| [UC-010](UC-010-prime.md) | `thatch prime` CLI dispatch chain (opencode/Cursor/claude), child exit code |
|
|
22
|
+
| [UC-011](UC-011-write-time-similarity-warning.md) | Write-time warning never blocks; merge or mark-distinct reconciliation |
|
|
23
|
+
| [UC-012](UC-012-model-migration.md) | `THATCH_MODEL` change: old memories skipped (not corrupted) until re-saved |
|
|
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 |
|
|
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
|
+
| [UC-016](UC-016-concurrent-session-isolation.md) | Per-session extraction isolation (in-memory + file queue); shared stateless sideband |
|
|
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`) |
|
|
13
29
|
|
|
14
30
|
## Template
|
|
15
31
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# UC-005: Setup install (Claude Code and Cursor)
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- Thatch installed and `bun` on PATH
|
|
5
|
+
- Target config dirs writable (`$XDG_CONFIG_HOME`, `$CLAUDE_CONFIG_DIR`, `~/.cursor`)
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
1. From a project root: `thatch setup --claude`
|
|
9
|
+
2. From the same project root: `thatch setup --cursor`
|
|
10
|
+
3. Repeat both commands (re-run idempotence and drift recovery)
|
|
11
|
+
4. Add a non-thatch hook to `.claude/settings.json` and `.cursor/hooks.json`,
|
|
12
|
+
then re-run setup
|
|
13
|
+
5. From elsewhere: `thatch setup --cursor --global`, then `thatch setup --claude --global`
|
|
14
|
+
|
|
15
|
+
**Expected**
|
|
16
|
+
- `--claude` project-local writes `.mcp.json` (`mcpServers.thatch`, stdio + `["mcp"]`),
|
|
17
|
+
appends a thatch block to `CLAUDE.md` bracketed by start/end markers (replaced, not
|
|
18
|
+
duplicated, on re-run), writes `.claude/settings.json` hooks (`SessionStart` -> `thatch reminder`,
|
|
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
|
|
21
|
+
even in project-local mode.
|
|
22
|
+
- `--cursor` project-local writes `.cursor/mcp.json`, appends to `AGENTS.md`, writes
|
|
23
|
+
`.cursor/hooks.json` in the **flat format** (`{version:1, hooks:{...}}`): `sessionStart` ->
|
|
24
|
+
`thatch reminder --json`, `postToolUse` -> `thatch buffer-tool`, `beforeSubmitPrompt` ->
|
|
25
|
+
`thatch flush-tools --json`; and installs 10 shared skills to `~/.cursor/skills/`.
|
|
26
|
+
- Re-run is idempotent: instructions are not duplicated, thatch hooks are replaced (not appended),
|
|
27
|
+
and non-thatch hooks are preserved. A legacy `thatch echo` hook is replaced with `flush-tools`.
|
|
28
|
+
- `--global` (Claude) writes `~/.claude/CLAUDE.md` + `~/.claude/settings.json` but **no project
|
|
29
|
+
`.mcp.json`** — it prints a `claude mcp add --scope user` command instead. `--global` (Cursor)
|
|
30
|
+
writes `~/.cursor/mcp.json`, `~/.cursor/AGENTS.md`, `~/.cursor/hooks.json`, `~/.cursor/skills/`.
|
|
31
|
+
|
|
32
|
+
_Automatable: yes — file presence and shape assertions on `.mcp.json`, `hooks.json`,
|
|
33
|
+
`CLAUDE.md`/`AGENTS.md` blocks, and the skills directory. `setup.test.ts` already covers the
|
|
34
|
+
unit contract; this is the end-to-end runbook version._
|