@jeffober/thatch 0.1.5 → 0.1.8
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/release +3 -3
- package/bin/thatch +167 -46
- package/docs/dev/README.md +69 -16
- package/docs/dev/gotchas.md +99 -0
- package/docs/dev/mcp-parity.md +113 -57
- package/docs/dev/setup-and-hooks.md +134 -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 +89 -21
- package/src/mcp.ts +19 -0
- package/src/prompts.ts +114 -3
- package/src/setup.ts +171 -8
- package/src/sideband.ts +207 -0
- package/src/skills.ts +34 -10
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# UC-006: Cursor hook lifecycle
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- `thatch setup --cursor` has run; `.cursor/mcp.json` registers the thatch MCP server
|
|
5
|
+
- A store with at least one memory (for the recall tier)
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
1. Start a Cursor agent session; the MCP server boots and opens its sideband socket.
|
|
9
|
+
2. Have the agent run a non-thatch tool (e.g. read a file). The `postToolUse` hook runs
|
|
10
|
+
`thatch buffer-tool`, reading Cursor's stdin (`conversation_id`), and appends one
|
|
11
|
+
interaction to the session's queue file. Stdout is empty.
|
|
12
|
+
3. Have the agent run a thatch tool (`mcp__thatch__memory_list`). The hook fires but filters
|
|
13
|
+
it out — nothing is appended.
|
|
14
|
+
4. Submit a prompt. The `beforeSubmitPrompt` hook runs `thatch flush-tools --json`.
|
|
15
|
+
5. Submit the next prompt with no fresh tool activity since step 4.
|
|
16
|
+
|
|
17
|
+
**Expected**
|
|
18
|
+
- `buffer-tool` normalizes `conversation_id` to a safe session file name and appends exactly one
|
|
19
|
+
interaction per call; thatch's own `mcp__thatch__*` tools never enter the queue (no self-echo).
|
|
20
|
+
- `flush-tools` drains the queue first (extraction tier): prints the JSON payload wrapped for
|
|
21
|
+
Cursor as `additional_context`, and **deletes the queue file** so the next call carries no
|
|
22
|
+
repeat payload unless new tool activity happened.
|
|
23
|
+
- With the queue empty and the sideband socket live, a semantically matching prompt yields a
|
|
24
|
+
recall nudge using the bare `memory_recall` tool name; below `THATCH_RECALL_THRESHOLD`
|
|
25
|
+
(default 0.55), no nudge.
|
|
26
|
+
- `buffer-tool` and `flush-tools --json` output is the shape Cursor's `additional_context`
|
|
27
|
+
field consumes.
|
|
28
|
+
|
|
29
|
+
_Automatable: partial — the `buffer-tool` and `flush-tools --json` CLI contracts are fully
|
|
30
|
+
automatable; the live Cursor agent loop (booting the MCP server, the agent acting on a nudge)
|
|
31
|
+
is manual._
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# UC-007: Prompt-aware recall nudge
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- A store with a memorable fact, e.g. "staging DB at staging-db.internal:5432"
|
|
5
|
+
- An opencode session AND (separately) a Claude Code / Cursor session with the MCP server
|
|
6
|
+
running and the sideband socket live
|
|
7
|
+
|
|
8
|
+
**Steps**
|
|
9
|
+
|
|
10
|
+
**opencode path**
|
|
11
|
+
1. Send a prompt matching the memory, e.g. "where does staging data live?"
|
|
12
|
+
2. Send an unrelated prompt.
|
|
13
|
+
3. Send a prompt shorter than 10 characters.
|
|
14
|
+
|
|
15
|
+
**Claude Code / Cursor path**
|
|
16
|
+
4. With the MCP server running and the sideband socket live, send a matching prompt (queue empty).
|
|
17
|
+
5. Stop the MCP server; send a matching prompt.
|
|
18
|
+
|
|
19
|
+
**Expected**
|
|
20
|
+
- opencode: the `chat.message` hook embeds the prompt text (skipped when shorter than 10 chars),
|
|
21
|
+
searches `db.search([repo, global])`; a match at or above 0.55 pushes a synthetic nudge part
|
|
22
|
+
with labels and scores (singular wording for one match; up to two labels plus "etc." for more).
|
|
23
|
+
- No match above threshold produces no nudge.
|
|
24
|
+
- The nudge uses `db.search`, not `db.recall`, so **no recall telemetry is stamped** — usage
|
|
25
|
+
tracking fires only on explicit `thatch_memory_recall` / CLI `search`, not on nudges.
|
|
26
|
+
- Claude Code / Cursor: `flush-tools` asks the warm MCP server via the sideband socket; the
|
|
27
|
+
returned labels become a nudge using the bare `memory_recall` name. With the MCP server down,
|
|
28
|
+
the recall nudge is absent (UC-009 covers the write-nudge fallback).
|
|
29
|
+
|
|
30
|
+
_Automatable: partial — the sideband embed + search round-trip and threshold logic are
|
|
31
|
+
unit-tested in `sideband.test.ts`; a live agent observing and acting on the nudge is manual._
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# UC-008: Hygiene heartbeat
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- A store with: one memory not updated or recalled in 90+ days; one memory scoped to a git
|
|
5
|
+
branch that no longer exists; two near-duplicate memories (similarity > 0.85)
|
|
6
|
+
- A second, clean store for the negative case
|
|
7
|
+
|
|
8
|
+
**Steps**
|
|
9
|
+
1. `thatch hygiene` (from the repo)
|
|
10
|
+
2. `thatch reminder` (Claude Code hook shape) and `thatch reminder --json` (Cursor)
|
|
11
|
+
3. Start an opencode session and a Claude Code / Cursor session (the session-start path)
|
|
12
|
+
4. Run the same commands from a directory that is **not** a git repo
|
|
13
|
+
|
|
14
|
+
**Expected**
|
|
15
|
+
- The report names three signals — pending duplicate pairs, stale-memory count, orphaned-branch
|
|
16
|
+
memory count — and **only the non-zero ones**. A fully healthy store prints "Store is healthy."
|
|
17
|
+
(or `null`) and the reminder omits the hygiene block entirely.
|
|
18
|
+
- Both `reminder` shapes fold the hygiene block into the session-start text only when it is non-null.
|
|
19
|
+
- Outside a git repo, the orphaned-branch check is **skipped** — a missing branch list would
|
|
20
|
+
otherwise make everything look orphaned; stale and dup signals still report.
|
|
21
|
+
- Signals are advisory: thatch never deletes or merges memories itself. The agent acts on the
|
|
22
|
+
nudge to fix the store, by the same `memory_forget` / `dedup_mark_checked` tools as UC-002.
|
|
23
|
+
|
|
24
|
+
_Automatable: yes — `thatch hygiene` and `hygieneReport()` are pure functions over the DB and
|
|
25
|
+
git state (`hygiene.test.ts` covers the unit contract); the live session-injection path is
|
|
26
|
+
manual._
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# UC-009: flush-tools tier priority and sideband degradation
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- A Claude Code / Cursor session; `.mcp.json` and hooks installed
|
|
5
|
+
- At least one memory for the recall tier to match
|
|
6
|
+
- For the failure modes: a way to stop the MCP server and to leave a stale socket file
|
|
7
|
+
|
|
8
|
+
**Steps**
|
|
9
|
+
1. Put buffered non-thatch tool interactions in the queue (via `buffer-tool` / `buffer-batch`),
|
|
10
|
+
then run `thatch flush-tools --json`.
|
|
11
|
+
2. With the queue empty and the MCP server running, send a prompt matching a memory (recall tier),
|
|
12
|
+
then a prompt below threshold.
|
|
13
|
+
3. Stop the MCP server; run `flush-tools` with an empty queue.
|
|
14
|
+
4. Leave a stale socket file from a crashed MCP server; run `flush-tools`.
|
|
15
|
+
5. Make the sideband server hang (> 2 s); run `flush-tools`.
|
|
16
|
+
|
|
17
|
+
**Expected**
|
|
18
|
+
- Tiers fire in strict priority — **extraction > recall > write** — at most one nudge per call:
|
|
19
|
+
- extraction: queue non-empty -> JSON payload, queue file deleted.
|
|
20
|
+
- recall: queue empty, socket live, match at or above `THATCH_RECALL_THRESHOLD` (default 0.55)
|
|
21
|
+
-> nudge with match labels; below threshold -> no nudge.
|
|
22
|
+
- write: socket unavailable, no matches, or socket error -> static
|
|
23
|
+
"did you learn anything worth persisting?" nudge.
|
|
24
|
+
- Sideband failure **never blocks** the agent: server down, a stale socket file, and a > 2 s
|
|
25
|
+
timeout all degrade to the write nudge. A stale socket file left by a crash is cleaned up on
|
|
26
|
+
connection error.
|
|
27
|
+
- `--json` wraps whichever nudge fires for Cursor's `additional_context`; plain stdout for Claude Code.
|
|
28
|
+
|
|
29
|
+
_Automatable: yes — `flush-tools` is a deterministic CLI contract and sideband failure modes
|
|
30
|
+
(server down, stale socket, timeout) plus tier ordering are filesystem/IPC only, with no LLM in
|
|
31
|
+
the loop._
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# UC-010: thatch prime
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- Thatch installed and `bun` on PATH
|
|
5
|
+
- At least one of: opencode (for `opencode run`), Cursor (`agent` CLI), or Claude Code
|
|
6
|
+
(`claude`) on PATH
|
|
7
|
+
|
|
8
|
+
**Steps**
|
|
9
|
+
1. From a project root, run `thatch prime`
|
|
10
|
+
2. Let the selected child CLI run the `thatch-project-primer` skill to completion
|
|
11
|
+
3. After it finishes, inspect the project store (`thatch list`)
|
|
12
|
+
|
|
13
|
+
**Expected**
|
|
14
|
+
- `prime` resolves the first available CLI in order — `opencode run`, then
|
|
15
|
+
`agent -p --approve-mcps` (Cursor), then `claude` — and spawns it with the project-primer
|
|
16
|
+
prompt.
|
|
17
|
+
- The primer skill recalls existing memories first, investigates the codebase from multiple
|
|
18
|
+
angles, and writes foundational memories via `thatch_memory_remember` (project vs global
|
|
19
|
+
assignment per its own rules), then runs dedup afterward.
|
|
20
|
+
- `prime` exits with the **child process's exit code** (0 on success).
|
|
21
|
+
- If no supported CLI is on PATH, `prime` fails fast with a clear error rather than spawning
|
|
22
|
+
nothing.
|
|
23
|
+
|
|
24
|
+
_Automatable: partial — the CLI dispatch selection (which child, which flags, exit-code
|
|
25
|
+
pass-through) is automatable; the LLM's real investigation and memory writing are manual._
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# UC-011: Write-time similarity warning
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- A store with a memory, e.g. `API rate limit` -> "the API rate limit is 100 req/min"
|
|
5
|
+
- A second, similar memory not yet saved
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
1. Save the second memory under a different label with similar content, without
|
|
9
|
+
`overwrite: true`.
|
|
10
|
+
2. Read the tool's response.
|
|
11
|
+
|
|
12
|
+
**Expected**
|
|
13
|
+
- The save **succeeds** (`[saved] ...`) — the warning never blocks. The response
|
|
14
|
+
also carries a warning listing the existing memory with a similarity score and
|
|
15
|
+
instructions to reconcile.
|
|
16
|
+
- Two reconciliation paths both work:
|
|
17
|
+
- Merge: `memory_remember` the merged content with `overwrite: true` on the
|
|
18
|
+
better label, then `memory_forget` the other. The survivor has the combined
|
|
19
|
+
content; the deleted label is gone.
|
|
20
|
+
- Mark distinct: `dedup_mark_checked` the pair as `unrelated`. A subsequent
|
|
21
|
+
`find_duplicates` no longer reports the pair.
|
|
22
|
+
|
|
23
|
+
_Automatable: yes — the "save proceeds + warning lists similar entries" contract
|
|
24
|
+
and both reconciliation paths are pure tool/DB calls with no LLM in the loop._
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# UC-012: THATCH_MODEL migration (mixed embedding dimensions)
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- A store with memories embedded by a model of dimension A (e.g. the default
|
|
5
|
+
384-dim `bge-small-en-v1.5`)
|
|
6
|
+
- A second model available whose vectors have a **different** dimension
|
|
7
|
+
|
|
8
|
+
**Steps**
|
|
9
|
+
1. `export THATCH_MODEL=<other-model>` (different vector dimension).
|
|
10
|
+
2. Start a fresh session. `memory_list` and `memory_show` an old memory by label.
|
|
11
|
+
3. `memory_recall` for a topic that matches an old memory.
|
|
12
|
+
4. Save a new memory with the new model; `memory_recall` for it.
|
|
13
|
+
5. `memory_show` an old memory and inspect its `model` tag.
|
|
14
|
+
|
|
15
|
+
**Expected**
|
|
16
|
+
- `list` and `show` still return the old memories — the data is intact, **not
|
|
17
|
+
corrupted and not deleted**.
|
|
18
|
+
- `recall` returns **no matches** for the old memories: entries whose vector
|
|
19
|
+
dimension differs from the query are skipped, not scored. This is a silent
|
|
20
|
+
skip, not an error.
|
|
21
|
+
- The new memory embeds at the new dimension and is recallable.
|
|
22
|
+
- `show` reveals the old memories carry the old model tag while new ones carry
|
|
23
|
+
the new tag. There is **no automatic re-embedding** — old memories stay
|
|
24
|
+
invisible to search until re-saved.
|
|
25
|
+
|
|
26
|
+
_Automatable: yes — dimension-skip and data-intact assertions are pure DB calls;
|
|
27
|
+
use two `MockEmbeddingModel` instances with different dimensions (no network, no
|
|
28
|
+
real model download)._
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# UC-013: Compaction context (opencode-only)
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- An opencode session that grows long enough to trigger context compaction
|
|
5
|
+
- Several memories already stored in the repo's store
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
1. Work in the session until opencode compacts the context window.
|
|
9
|
+
2. Inspect the compaction output.
|
|
10
|
+
3. After compaction, ask the agent something that should use a memory tool.
|
|
11
|
+
|
|
12
|
+
**Expected**
|
|
13
|
+
- The compaction output includes the thatch re-familiarization context block
|
|
14
|
+
pushed by `experimental.session.compacting` — it reminds the agent that
|
|
15
|
+
`thatch_memory_recall` exists and that memories persist.
|
|
16
|
+
- After compaction, the agent still knows the memory tools exist and uses them
|
|
17
|
+
(it does not "forget" thatch just because the window was summarized).
|
|
18
|
+
- This behavior is **opencode-only**. Claude Code's `PostCompact` hook is
|
|
19
|
+
side-effects only and cannot inject context; Cursor has no equivalent hook.
|
|
20
|
+
See `docs/dev/mcp-parity.md` for the gap and its mitigation (CLAUDE.md /
|
|
21
|
+
AGENTS.md persists through compaction; the `SessionStart` recall reminder
|
|
22
|
+
runs again after compaction in Claude Code).
|
|
23
|
+
|
|
24
|
+
_Automatable: no — requires a live opencode compaction event. The hook's string
|
|
25
|
+
content is unit-tested, but actual compaction injection is manual._
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# UC-014: Skill install and drift recovery
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- Thatch installed; the host's skills directory empty
|
|
5
|
+
- `bun` on PATH
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
1. `thatch setup --claude` (and/or start an opencode session, and/or
|
|
9
|
+
`thatch setup --cursor`).
|
|
10
|
+
2. List the skills directory.
|
|
11
|
+
3. Edit one `SKILL.md` file locally to introduce drift.
|
|
12
|
+
4. Re-run `setup` (or restart opencode).
|
|
13
|
+
5. Check whether the `thatch-code-review` coordinator skill is present.
|
|
14
|
+
|
|
15
|
+
**Expected**
|
|
16
|
+
- Claude Code and Cursor install exactly **10 shared skills** to the skills
|
|
17
|
+
dir — the coordinator (`thatch-code-review`) is **absent** (it needs
|
|
18
|
+
sub-agents, which those hosts lack).
|
|
19
|
+
- opencode installs **11** — the 10 shared plus the coordinator.
|
|
20
|
+
- The locally edited `SKILL.md` is **overwritten** with the canonical content
|
|
21
|
+
on the next `setup`/init (drift detection: a file is only rewritten when its
|
|
22
|
+
content differs from the definition). Unrelated skill files are untouched.
|
|
23
|
+
- Skills never land in the worktree — always under the user-scoped config dir
|
|
24
|
+
(`~/.claude/skills`, `~/.cursor/skills`, `$XDG_CONFIG_HOME/opencode/skills`).
|
|
25
|
+
|
|
26
|
+
_Automatable: yes — file presence, count (10 vs 11), content-diff-overwrite, and
|
|
27
|
+
coordinator host-gating are all file assertions (`setup.test.ts` already covers
|
|
28
|
+
the unit contract)._
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# UC-015: Environment variable override matrix
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- Writable temp directories; `bun` on PATH
|
|
5
|
+
|
|
6
|
+
**Steps**
|
|
7
|
+
Set each variable and confirm the effect at the documented resolution point.
|
|
8
|
+
|
|
9
|
+
| Variable | Set to | Expected effect |
|
|
10
|
+
|----------|--------|------------------|
|
|
11
|
+
| `THATCH_DB_PATH` | a temp file path | the SQLite DB is created there (CLI + plugin + MCP) |
|
|
12
|
+
| `XDG_CONFIG_HOME` | a temp dir | the default DB (`$XDG_CONFIG_HOME/thatch/thatch.db`) and opencode skill dir move there; the real `~/.config` is untouched |
|
|
13
|
+
| `CLAUDE_CONFIG_DIR` | a temp dir | `thatch setup --claude --global` writes CLAUDE.md, settings.json, and skills under that dir; project-local keeps project paths but skills under the custom dir |
|
|
14
|
+
| `THATCH_QUEUE_DIR` | a temp dir | extraction-queue JSONL files land there instead of `$XDG_CACHE_HOME/thatch/queue` |
|
|
15
|
+
| `THATCH_RECALL_THRESHOLD` | a low value (e.g. `0.2`) | the prompt-aware recall nudge fires for weaker matches (a prompt that matched nothing at 0.55 now surfaces results) |
|
|
16
|
+
| `THATCH_MODEL` | a different model name | new memories carry the new model tag and vector dimension; old memories become invisible to search (see UC-012) |
|
|
17
|
+
|
|
18
|
+
**Expected**
|
|
19
|
+
- Each override takes effect at the point the docs describe (see
|
|
20
|
+
`docs/dev/setup-and-hooks.md` and the dev README config section). None require
|
|
21
|
+
a restart beyond the process that reads them.
|
|
22
|
+
- `THATCH_DB_PATH` and `XDG_CONFIG_HOME` change the **same** sideband socket
|
|
23
|
+
path (it is a hash of the resolved DB path) — so a hook process and the MCP
|
|
24
|
+
server must both resolve the same DB path or they miss each other.
|
|
25
|
+
|
|
26
|
+
_Automatable: yes — env resolution is deterministic and side-effect-free;
|
|
27
|
+
`setup.test.ts` already covers `CLAUDE_CONFIG_DIR`; the rest follow the same
|
|
28
|
+
shape._
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# UC-016: Concurrent session isolation
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- Two opencode sessions in the same repo (A and B)
|
|
5
|
+
- (Separately) two Claude Code / Cursor hook processes with different session IDs
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
|
|
9
|
+
**opencode (in-memory buffer)**
|
|
10
|
+
1. In session A, run a non-thatch tool (the extraction ring buffer records it).
|
|
11
|
+
2. In session B, send a message — `chat.message` runs.
|
|
12
|
+
3. In session A, send a message — `chat.message` runs.
|
|
13
|
+
|
|
14
|
+
**Claude Code / Cursor (file-backed queue)**
|
|
15
|
+
4. Hook process for session A calls `buffer-tool`/`buffer-batch`.
|
|
16
|
+
5. Hook process for session B calls `buffer-tool`/`buffer-batch`.
|
|
17
|
+
6. `flush-tools` is invoked for session A, then for session B.
|
|
18
|
+
|
|
19
|
+
**Sideband (shared warm model)**
|
|
20
|
+
7. Both sessions' `flush-tools` connect to the same sideband socket.
|
|
21
|
+
|
|
22
|
+
**Expected**
|
|
23
|
+
- **No cross-session bleed** in either path: session B's `chat.message` produces
|
|
24
|
+
no nudge from session A's buffered interaction; session B's message only sees
|
|
25
|
+
its own buffer. The in-memory `ExtractionPipeline` and the file-backed queue are
|
|
26
|
+
both keyed by session ID.
|
|
27
|
+
- `flush-tools` for session A drains only A's queue file and deletes it; a second
|
|
28
|
+
flush for A is empty. Session B's queue file is untouched and drains on its own
|
|
29
|
+
flush.
|
|
30
|
+
- The sideband socket is **shared** (one warm MCP server) but **stateless per
|
|
31
|
+
query**: each request embeds a fresh prompt and returns its own matches. Two
|
|
32
|
+
concurrent match requests do not interfere.
|
|
33
|
+
|
|
34
|
+
_Automatable: yes — `extraction.test.ts` (in-memory per-session) and
|
|
35
|
+
`extract-queue.test.ts` (file-backed per-session) already cover isolation; the
|
|
36
|
+
sideband is a stateless request/response so concurrency is trivially safe._
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# UC-017: buffer-tool vs buffer-batch contract
|
|
2
|
+
|
|
3
|
+
**Preconditions**
|
|
4
|
+
- A Claude Code setup (`PostToolBatch` -> `buffer-batch`) and a Cursor setup
|
|
5
|
+
(`postToolUse` -> `buffer-tool`)
|
|
6
|
+
|
|
7
|
+
**Steps**
|
|
8
|
+
1. **Claude Code**: a `PostToolBatch` fires with a `tool_calls` array (several
|
|
9
|
+
tools, one a `mcp__thatch__*` call) and a `session_id`.
|
|
10
|
+
2. **Cursor**: a `postToolUse` fires for a single tool with a `conversation_id`.
|
|
11
|
+
3. Run `flush-tools` for both.
|
|
12
|
+
|
|
13
|
+
**Expected**
|
|
14
|
+
- `buffer-batch` (Claude Code) reads the `tool_calls` array and `session_id`,
|
|
15
|
+
appends all interactions to the session's JSONL queue, filters out
|
|
16
|
+
`mcp__thatch__*` calls, and caps at 20 (oldest dropped). It is **silent**.
|
|
17
|
+
- `buffer-tool` (Cursor) reads **one** tool call, normalizes `conversation_id`
|
|
18
|
+
to a safe filename (unsafe chars -> underscore), appends that single
|
|
19
|
+
interaction, filters `mcp__thatch__*`, same 20 cap. Also silent.
|
|
20
|
+
- Both write the same `ToolInteraction` shape to the same JSONL format, so
|
|
21
|
+
`flush-tools` drains them identically and `buildExtractionPayload` produces an
|
|
22
|
+
identical contract for the fact-extractor skill.
|
|
23
|
+
- Thatch's own tools never appear in either queue (no self-echo feedback loop).
|
|
24
|
+
|
|
25
|
+
_Automatable: yes — `extract-queue.test.ts` covers both shapes (batch with
|
|
26
|
+
`tool_calls` and single-tool), the `mcp__thatch__*` filter, the 20 cap, and
|
|
27
|
+
back-compat with `buildExtractionPayload`._
|
package/docs/user/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Thatch
|
|
2
2
|
|
|
3
|
-
Persistent memory for
|
|
4
|
-
remember information across sessions using local embeddings and SQLite.
|
|
3
|
+
Persistent memory for AI coding agents. Thatch gives your agent the ability to
|
|
4
|
+
remember information across sessions using local embeddings and SQLite. It
|
|
5
|
+
works with **OpenCode** (as a plugin), **Claude Code** (as an MCP server), and
|
|
6
|
+
**Cursor** (as an MCP server).
|
|
5
7
|
|
|
6
8
|
## Installation
|
|
7
9
|
|
|
10
|
+
### OpenCode
|
|
11
|
+
|
|
8
12
|
Publish to npm and add thatch to your opencode config:
|
|
9
13
|
|
|
10
14
|
```jsonc
|
|
@@ -24,6 +28,36 @@ For local development before publishing, use a file path:
|
|
|
24
28
|
|
|
25
29
|
Or place the thatch repo in `.opencode/plugins/` for auto-loading.
|
|
26
30
|
|
|
31
|
+
### Claude Code
|
|
32
|
+
|
|
33
|
+
Install the MCP server, hooks, instructions, and skills into Claude Code:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
thatch setup --claude # project-local (writes .mcp.json, CLAUDE.md, .claude/)
|
|
37
|
+
thatch setup --claude --global # user-scoped (~/.claude/)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`bun` must be on PATH (the thatch binary runs under bun). `setup` is idempotent
|
|
41
|
+
— re-running it updates drifted content without clobbering unrelated config.
|
|
42
|
+
For a global install, `setup` prints the `claude mcp add --scope user` command
|
|
43
|
+
to run instead of writing a project `.mcp.json`.
|
|
44
|
+
|
|
45
|
+
### Cursor
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
thatch setup --cursor # project-local (.cursor/mcp.json, AGENTS.md, .cursor/hooks.json)
|
|
49
|
+
thatch setup --cursor --global # user-scoped (~/.cursor/)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Cursor has no `claude mcp add` equivalent — `setup` writes `~/.cursor/mcp.json`
|
|
53
|
+
directly. Hook output is JSON-wrapped (`--json`) so Cursor injects it as
|
|
54
|
+
`additional_context`.
|
|
55
|
+
|
|
56
|
+
### Before running setup
|
|
57
|
+
|
|
58
|
+
The `thatch` binary must resolve `bun` on PATH. Install from npm (`npm i -g
|
|
59
|
+
@jeffober/thatch`) or from a checkout (`bun run bin/thatch`).
|
|
60
|
+
|
|
27
61
|
## How it works
|
|
28
62
|
|
|
29
63
|
### Stores
|
|
@@ -84,8 +118,9 @@ Beyond the tools, thatch hooks into opencode itself:
|
|
|
84
118
|
|
|
85
119
|
On startup thatch installs [skills] into your global opencode config
|
|
86
120
|
(`~/.config/opencode/skills/`, or `$XDG_CONFIG_HOME/opencode/skills`).
|
|
87
|
-
|
|
88
|
-
|
|
121
|
+
With `thatch setup --claude`, skills install to `~/.claude/skills/` (or
|
|
122
|
+
`$CLAUDE_CONFIG_DIR/skills/`); with `thatch setup --cursor`, to
|
|
123
|
+
`~/.cursor/skills/`.
|
|
89
124
|
|
|
90
125
|
### Memory skills
|
|
91
126
|
|
|
@@ -129,13 +164,13 @@ Five specialist review lenses, each a self-contained static-analysis pass:
|
|
|
129
164
|
For a **quick single-lens review**, load any specialist skill directly and
|
|
130
165
|
point it at a branch or commit range:
|
|
131
166
|
|
|
132
|
-
```
|
|
167
|
+
```text
|
|
133
168
|
Load thatch-review-pedantic and review the changes on this branch.
|
|
134
169
|
```
|
|
135
170
|
|
|
136
171
|
For a **full multi-specialist review on opencode**, load the coordinator:
|
|
137
172
|
|
|
138
|
-
```
|
|
173
|
+
```text
|
|
139
174
|
Load thatch-code-review and review branch feature-x.
|
|
140
175
|
```
|
|
141
176
|
|
|
@@ -145,7 +180,7 @@ specialist lens), and synthesize a final report.
|
|
|
145
180
|
For a **full review on Claude Code** (or without the coordinator), run each
|
|
146
181
|
specialist in sequence, then synthesize:
|
|
147
182
|
|
|
148
|
-
```
|
|
183
|
+
```text
|
|
149
184
|
1. Load thatch-review-pedantic, review branch feature-x, report findings.
|
|
150
185
|
2. Load thatch-review-acceptance, review the same branch.
|
|
151
186
|
3. ... repeat for state-flow, no-slop, breadcrumbs ...
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin)
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@opencode-ai/plugin": "^1.17.13",
|
|
30
30
|
"@types/bun": "latest",
|
|
31
|
+
"markdownlint-cli2": "^0.23.0",
|
|
31
32
|
"typescript": "^5.7.0"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
package/src/db.ts
CHANGED
|
@@ -234,9 +234,12 @@ export class ThatchDB {
|
|
|
234
234
|
/**
|
|
235
235
|
* Brute-force cosine similarity search across the given stores.
|
|
236
236
|
* When branch is specified, includes project-wide (branch IS NULL) plus
|
|
237
|
-
* branch-specific entries.
|
|
237
|
+
* branch-specific entries. Does NOT stamp recall telemetry — the prompt-aware
|
|
238
|
+
* nudge uses this to check whether memories relate to a prompt without
|
|
239
|
+
* polluting recall_count/last_recalled_at (the agent hasn't actually read
|
|
240
|
+
* them yet, only the plugin has checked for relevance).
|
|
238
241
|
*/
|
|
239
|
-
|
|
242
|
+
search(
|
|
240
243
|
stores: string[],
|
|
241
244
|
queryEmbedding: Float32Array,
|
|
242
245
|
opts?: { branch?: string; limit?: number },
|
|
@@ -281,10 +284,21 @@ export class ThatchDB {
|
|
|
281
284
|
});
|
|
282
285
|
|
|
283
286
|
scored.sort((a, b) => b._score - a._score);
|
|
284
|
-
|
|
287
|
+
return scored.slice(0, limit);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Semantic recall for agent-initiated searches. Delegates to search() for
|
|
292
|
+
* cosine scoring, then stamps recall telemetry — retrieval is the "used
|
|
293
|
+
* recently" signal hygiene reporting keys on.
|
|
294
|
+
*/
|
|
295
|
+
recall(
|
|
296
|
+
stores: string[],
|
|
297
|
+
queryEmbedding: Float32Array,
|
|
298
|
+
opts?: { branch?: string; limit?: number },
|
|
299
|
+
): (MemoryRow & { _score: number })[] {
|
|
300
|
+
const top = this.search(stores, queryEmbedding, opts);
|
|
285
301
|
|
|
286
|
-
// Recall telemetry: retrieval is the "used recently" signal hygiene
|
|
287
|
-
// reporting keys on, so returned rows get stamped here.
|
|
288
302
|
if (top.length > 0) {
|
|
289
303
|
const now = new Date().toISOString();
|
|
290
304
|
const rowKeys = top.map(() => "(?, ?)").join(", ");
|
package/src/git.ts
CHANGED
|
@@ -54,7 +54,7 @@ const URL_PATTERNS: UrlPattern[] = [
|
|
|
54
54
|
* 3. Fall back to the directory basename
|
|
55
55
|
*/
|
|
56
56
|
export async function detectRepo(cwd?: string): Promise<string> {
|
|
57
|
-
const dir = cwd ?? process.cwd();
|
|
57
|
+
const dir = cwd ?? process.env.CURSOR_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
58
58
|
|
|
59
59
|
try {
|
|
60
60
|
const remote = await $`git remote get-url origin`.cwd(dir).quiet();
|