@jeffober/thatch 0.1.28 → 0.1.30

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.
Files changed (61) hide show
  1. package/README.md +24 -10
  2. package/artifacts/skills/thatch-code-review.md +9 -7
  3. package/artifacts/skills/thatch-review-economy.md +97 -0
  4. package/artifacts/skills/thatch-review-followup.md +1 -1
  5. package/artifacts/skills/thatch-review-synthesizer.md +15 -5
  6. package/bin/qa-run +38 -0
  7. package/bin/release +21 -0
  8. package/bin/thatch +17 -5
  9. package/docs/dev/README.md +71 -18
  10. package/docs/dev/features/README.md +33 -0
  11. package/docs/dev/features/behavior-engine.md +271 -0
  12. package/docs/dev/features/cicd.md +89 -0
  13. package/docs/dev/features/cli.md +81 -0
  14. package/docs/dev/features/compaction-recovery.md +48 -0
  15. package/docs/dev/features/database.md +244 -0
  16. package/docs/dev/features/deduplication.md +102 -0
  17. package/docs/dev/features/extraction.md +253 -0
  18. package/docs/dev/features/hygiene.md +81 -0
  19. package/docs/dev/features/memory-store.md +154 -0
  20. package/docs/dev/features/multi-host.md +228 -0
  21. package/docs/dev/features/nudge-pipeline.md +282 -0
  22. package/docs/dev/features/prediction-engine.md +253 -0
  23. package/docs/dev/features/qa-system.md +205 -0
  24. package/docs/dev/features/session-lifecycle.md +110 -0
  25. package/docs/dev/features/setup.md +199 -0
  26. package/docs/dev/features/sideband.md +104 -0
  27. package/docs/dev/gotchas.md +3 -3
  28. package/docs/dev/mcp-parity.md +6 -4
  29. package/docs/dev/setup-and-hooks.md +4 -4
  30. package/docs/dev/skills.md +10 -8
  31. package/docs/plans/README.md +2 -2
  32. package/docs/user/README.md +58 -20
  33. package/docs/user/behavior-engine.md +85 -0
  34. package/docs/user/cli.md +98 -0
  35. package/docs/user/code-review.md +73 -0
  36. package/docs/user/deduplication.md +70 -0
  37. package/docs/user/default-behaviors.md +75 -0
  38. package/docs/user/extraction.md +74 -0
  39. package/docs/user/hygiene.md +76 -0
  40. package/docs/user/memory.md +81 -0
  41. package/docs/user/prediction-engine.md +87 -0
  42. package/docs/user/setup.md +110 -0
  43. package/docs/user/skills.md +97 -0
  44. package/package.json +2 -2
  45. package/src/behavior.ts +120 -0
  46. package/src/db.ts +160 -417
  47. package/src/embeddings.ts +0 -41
  48. package/src/extraction.ts +2 -2
  49. package/src/git.ts +3 -1
  50. package/src/index.ts +55 -9
  51. package/src/mcp.ts +30 -1
  52. package/src/prediction.ts +127 -0
  53. package/src/prompts.ts +135 -188
  54. package/src/scoring-engine.ts +374 -0
  55. package/src/seed-behaviors.ts +339 -0
  56. package/src/setup.ts +2 -6
  57. package/src/sideband.ts +36 -8
  58. package/src/skills.ts +1 -0
  59. package/src/tool-defs.ts +172 -25
  60. package/src/vector-math.ts +30 -0
  61. package/docs/qa/README.md +0 -175
@@ -0,0 +1,33 @@
1
+ # Feature Architecture
2
+
3
+ Per-feature architecture docs for thatch. Each doc covers what the feature
4
+ does, how it works, how it interacts with other features, and the source
5
+ files that implement it.
6
+
7
+ For the monolithic architecture overview, see
8
+ [../README.md](../README.md). For hook configuration and concrete artifact
9
+ paths, see [../setup-and-hooks.md](../setup-and-hooks.md). For feature
10
+ parity across hosts, see [../mcp-parity.md](../mcp-parity.md). For the skill
11
+ system, see [../skills.md](../skills.md). For non-obvious invariants, see
12
+ [../gotchas.md](../gotchas.md).
13
+
14
+ ## Docs
15
+
16
+ | Doc | Feature |
17
+ |-----|---------|
18
+ | [memory-store.md](memory-store.md) | Persistent memory store (CRUD, search, embeddings, stores, branches) |
19
+ | [multi-host.md](multi-host.md) | Three-host architecture (opencode, Claude Code, Cursor) |
20
+ | [extraction.md](extraction.md) | Extraction pipeline (buffering, child sessions, nudge escalation, file-backed queue) |
21
+ | [nudge-pipeline.md](nudge-pipeline.md) | Per-message nudge pipeline (extraction, recall, prediction, behavior tiers) |
22
+ | [prediction-engine.md](prediction-engine.md) | Prediction engine (user decision model, Bayesian confidence, auto-fire) |
23
+ | [behavior-engine.md](behavior-engine.md) | Behavior engine (LLM self-discipline rules, ham/spam, auto-fire) |
24
+ | [hygiene.md](hygiene.md) | Hygiene system (duplicate, stale, orphaned branch signals) |
25
+ | [deduplication.md](deduplication.md) | Deduplication system (find pairs, verdict lifecycle) |
26
+ | [sideband.md](sideband.md) | Sideband IPC (Unix socket for MCP host hook processes) |
27
+ | [compaction-recovery.md](compaction-recovery.md) | Compaction recovery (opencode compaction hooks) |
28
+ | [session-lifecycle.md](session-lifecycle.md) | Session lifecycle management (opencode events, child tracking) |
29
+ | [setup.md](setup.md) | Setup system (markers, checkSetup, auto-refresh, binary resolution) |
30
+ | [cli.md](cli.md) | CLI (subcommands, environment variables) |
31
+ | [database.md](database.md) | Database (schema, migrations, WAL, 11 tables) |
32
+ | [qa-system.md](qa-system.md) | QA system (93 use cases, automatable vs live, mise tasks) |
33
+ | [cicd.md](cicd.md) | CI/CD (CI pipeline, OIDC publishing, release helper) |
@@ -0,0 +1,271 @@
1
+ # Behavior Engine (LLM Self-Discipline Rules)
2
+
3
+ A statistical model of the LLM's own operational discipline. Captures "when
4
+ situation X arises, I should do Y." Mirrors the
5
+ [prediction engine](prediction-engine.md) architecture but serves a different
6
+ purpose: predictions model what the *user* wants; behaviors model what the
7
+ *LLM* should do.
8
+
9
+ ## What it does
10
+
11
+ - Four tools: `behavior_codify`, `behavior_feedback`, `behavior_list`,
12
+ `behavior_delete`. All defined in `src/tool-defs.ts`.
13
+ - Auto-fires at every user message: embeds the prompt, cosine-matches against
14
+ behavior matchers, injects scored behaviors as a synthetic nudge part.
15
+ - Ham/spam feedback (ham = relevant, spam = not relevant) adjusts Bayesian
16
+ confidence, training the classifier over time.
17
+ - Anti-laziness guard in prompt instructions: prevents codifying rules that
18
+ make the agent less thorough. Rules should encode discipline, not shortcuts.
19
+ - Default behavior seeding at startup: seeds several default behaviors into
20
+ the global store (session wrap-up, work finalization, research discipline,
21
+ day-turnover checks, dead-end capture, git archaeology). Idempotent across
22
+ releases via version stamps.
23
+
24
+ ## How it works
25
+
26
+ ### Data model: matchers, behaviors, edges, provenance
27
+
28
+ Four tables in SQLite — same shape as the prediction engine:
29
+
30
+ - **Behavior matchers** — situation descriptions (when to apply the rule).
31
+ Embedded, cosine-matched at `chat.message`. Capture "when."
32
+ - **Behaviors** — self-discipline rule statements (what to do). Capture
33
+ "what." Each carries a rationale explaining why the rule is worth
34
+ persisting.
35
+ - **Edges** — weighted matcher-to-behavior links. Many-to-many. The same
36
+ matcher can link to multiple behaviors; the same behavior can be reached
37
+ by multiple matchers.
38
+ - **Provenance** — audit trail of every signal applied to a behavior
39
+ (`codify`, `confirm` / ham, `disconfirm` / spam) with detail and timestamp.
40
+
41
+ ### Confidence model (Bayesian posterior)
42
+
43
+ Same formula and constants as the prediction engine — the constants live in
44
+ `src/scoring-engine.ts` and are shared by both engines:
45
+
46
+ ```text
47
+ confidence = (confirm_count + K * P0) / (confirm_count + disconfirm_count + K)
48
+ ```
49
+
50
+ - K = 5 (shrinkage parameter)
51
+ - P0 = 0.5 (prior)
52
+ - Ham feedback = confirm signal. Spam feedback = disconfirm signal.
53
+ - Same shrinkage and relevance-gated properties as predictions: a single ham
54
+ is plausible, not reliable. Untested behaviors stay at P0.
55
+
56
+ ### How it differs from the prediction engine
57
+
58
+ | Aspect | Prediction engine | Behavior engine |
59
+ |--------|-------------------|-----------------|
60
+ | Models | What the USER wants | What the LLM should do |
61
+ | Graded by | User feedback (confirm/disconfirm/soft) | LLM's own ham/spam relevance judgment |
62
+ | Firewall principle | Applies — the model that uses predictions cannot grade them | Does NOT apply — the LLM grading its own behaviors IS the point |
63
+ | Signal types | confirm, disconfirm, soft, create | confirm (ham), disconfirm (spam), codify |
64
+ | Default seeding | No | Yes (multiple default behaviors at startup) |
65
+
66
+ The firewall principle from predictions does not apply here. The LLM grading
67
+ its own behavioral rules is the point, not a violation. Ham/spam is a
68
+ relevance judgment ("does this rule apply here?"), not a value judgment ("is
69
+ this a good rule?"). The agent evaluates each surfaced behavior against the
70
+ current situation and records whether it was relevant.
71
+
72
+ ### Codify (`behavior_codify` tool)
73
+
74
+ Parameters: `situation` (triggering context), `behavior` (the rule),
75
+ `rationale` (why worth persisting).
76
+
77
+ Runs in a single DB transaction:
78
+
79
+ 1. **Matcher dedup** — finds an existing behavior matcher above cosine 0.85,
80
+ else creates one.
81
+ 2. **Behavior dedup** — store-wide search for a near-identical behavior
82
+ (cosine >= 0.85). If found, links this matcher to it via an edge rather
83
+ than creating a duplicate row.
84
+ 3. **New behavior** — creates behavior + edge + provenance entry tagged
85
+ `codify`.
86
+ 4. Returns `[codified]` for a new behavior or `[linked]` for an existing one
87
+ (with confidence and counts).
88
+
89
+ Confidence starts at P0 (0.5) with 0 evidence.
90
+
91
+ ### Feedback (`behavior_feedback` tool)
92
+
93
+ Parameters: `behavior` (statement, semantic match at cosine >= 0.85),
94
+ `relevant` (boolean), `context` (situation description for provenance).
95
+
96
+ - `relevant: true` = ham = confirm signal.
97
+ - `relevant: false` = spam = disconfirm signal.
98
+ - Adjusts confidence and records provenance tagged `ham:` or `spam:`.
99
+ - Returns updated confidence and confirm/disconfirm counts.
100
+ - Returns a "not found" message if no behavior matches.
101
+
102
+ ### Auto-fire (opencode: `chat.message` hook)
103
+
104
+ - Reuses the prompt embedding already computed for the recall nudge — same
105
+ embedding serves recall (tier 2), prediction (tier 3), and behavior (tier
106
+ 4). One embed, three lookups.
107
+ - Calls `db.scoreBehaviorNudge([repo, "global"], embedding,
108
+ BEHAVIOR_THRESHOLD)`.
109
+ - Threshold: 0.60 (env: `THATCH_BEHAVIOR_THRESHOLD`). Same as prediction —
110
+ surfacing a discipline rule is as disruptive as surfacing a preference.
111
+ - Injects `behaviorNudge(items)` as a separate synthetic text part with a
112
+ `[thatch] Situational behaviors` header.
113
+ - 0-evidence behaviors use "consider"; behaviors with evidence use "do".
114
+ - Independent try/catch — a behavior failure does not block recall or
115
+ prediction.
116
+ - Toast: `[thatch] N behaviors surfaced`.
117
+
118
+ ### Auto-fire (MCP: sideband)
119
+
120
+ - `flush-tools` fires the behavior query via the sideband socket's
121
+ `behaviors` method, in parallel with recall and predictions.
122
+ - The same `scoreBehaviorNudge` entry point prevents scoring drift between
123
+ host paths.
124
+ - Sideband failure returns `null`; the caller skips the behavior nudge
125
+ gracefully.
126
+
127
+ ### Default behavior seeding (`src/seed-behaviors.ts`)
128
+
129
+ Runs at startup in both the opencode plugin (`src/index.ts`) and the MCP
130
+ server (`src/mcp.ts`), after DB and model initialization.
131
+
132
+ Seeds multiple default behaviors into the global store — session wrap-up
133
+ (check for uncommitted changes, untracked files, stale artifacts), work
134
+ finalization (check before commits/merges), research discipline (investigate
135
+ before proposing changes), day-turnover checks (rebase staleness, new review
136
+ comments), dead-end capture (save red herrings as memories), and git
137
+ archaeology (understand history before debugging or planning).
138
+
139
+ Idempotent across releases via version stamps:
140
+
141
+ 1. For each default behavior, embeds the situation and behavior text.
142
+ 2. Checks whether a behavior matcher with cosine >= 0.85 already exists.
143
+ 3. If found, checks the linked behavior's rationale for a version stamp
144
+ (`seed-version:X.Y.Z`).
145
+ 4. If the stamp matches the current package version, skips — already seeded.
146
+ 5. If the stamp differs (older release seeded it) or is absent (manually
147
+ codified), deletes the old behavior and re-creates it with the current
148
+ version stamp. The matcher and edge cascade-delete with the behavior.
149
+ 6. Creates: behavior matcher (situation embedding), behavior (statement +
150
+ rationale embedding, with version stamp), edge (weight 1.0), provenance
151
+ entry tagged `codify`.
152
+
153
+ ### Anti-laziness guard
154
+
155
+ Prompt instructions in all host variants explicitly prevent the agent from
156
+ codifying rules that make it less thorough. The guard text — "Do not codify
157
+ rules that make you lazier or less thorough. Rules should encode discipline,
158
+ not shortcuts." — appears in `src/prompts.ts` in each host's system prompt.
159
+
160
+ ### Consumption
161
+
162
+ When surfaced behaviors appear in the nudge, the agent evaluates each against
163
+ the current situation:
164
+
165
+ - If relevant (ham): follows the rule and calls `behavior_feedback` with
166
+ `relevant: true` (confirm).
167
+ - If not relevant (spam): calls `behavior_feedback` with `relevant: false`
168
+ (disconfirm).
169
+
170
+ This trains the classifier so future nudges are more accurate. Behaviors
171
+ that consistently score as spam will drop in confidence and eventually fall
172
+ below the auto-fire threshold.
173
+
174
+ ## Interactions with other features
175
+
176
+ - **Nudge pipeline** ([nudge-pipeline.md](nudge-pipeline.md)): behavior
177
+ auto-fire is tier 4. Shares the prompt embedding with recall (tier 2) and
178
+ prediction (tier 3).
179
+ - **Prediction engine** ([prediction-engine.md](prediction-engine.md)): same
180
+ four-table data model, same Bayesian confidence, same scoring engine.
181
+ Different semantics and different grading — user feedback vs. self
182
+ feedback.
183
+ - **Scoring engine**: `src/scoring-engine.ts` provides the generic
184
+ four-table scoring base. `BehaviorEngine` (`src/behavior.ts`) is a thin
185
+ wrapper with behavior-specific table names.
186
+ - **Memory store** ([memory-store.md](memory-store.md)): same embedding
187
+ model (BGE-small-en-v1.5), same SQLite DB, different tables.
188
+ - **Sideband IPC** ([sideband.md](sideband.md)): MCP hosts query behaviors
189
+ via the sideband socket's `behaviors` method.
190
+
191
+ ## Source files
192
+
193
+ | File | Role |
194
+ |------|------|
195
+ | `src/behavior.ts` | Thin wrapper around `ScoringEngine` with behavior-specific table names |
196
+ | `src/scoring-engine.ts` | Generic four-table scoring engine with Bayesian confidence (shared base for prediction and behavior) |
197
+ | `src/seed-behaviors.ts` | Default behavior seeding at startup — version-stamped, idempotent across releases |
198
+ | `src/db.ts` | Behavior tables, `scoreBehaviorNudge` delegation |
199
+ | `src/tool-defs.ts` | Four behavior tools (`codify`, `feedback`, `list`, `delete`) |
200
+ | `src/prompts.ts` | `behaviorNudge` formatting, verb selection ("consider" vs "do"), anti-laziness guard |
201
+ | `src/sideband.ts` | `behaviors` method for the MCP path |
202
+
203
+ ## Database tables
204
+
205
+ ```sql
206
+ behavior_matchers(
207
+ id TEXT PRIMARY KEY,
208
+ store TEXT NOT NULL REFERENCES stores(name),
209
+ description TEXT NOT NULL,
210
+ embedding BLOB,
211
+ model TEXT,
212
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
213
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
214
+ )
215
+
216
+ behaviors(
217
+ id TEXT PRIMARY KEY,
218
+ store TEXT NOT NULL REFERENCES stores(name),
219
+ statement TEXT NOT NULL,
220
+ rationale TEXT,
221
+ embedding BLOB,
222
+ model TEXT,
223
+ confidence REAL NOT NULL DEFAULT 0.5,
224
+ confirm_count REAL NOT NULL DEFAULT 0,
225
+ disconfirm_count REAL NOT NULL DEFAULT 0,
226
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
227
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
228
+ )
229
+
230
+ behavior_edges(
231
+ matcher_id TEXT NOT NULL,
232
+ behavior_id TEXT NOT NULL,
233
+ weight REAL NOT NULL DEFAULT 1.0,
234
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
235
+ PRIMARY KEY (matcher_id, behavior_id),
236
+ FOREIGN KEY (matcher_id) REFERENCES behavior_matchers(id) ON DELETE CASCADE,
237
+ FOREIGN KEY (behavior_id) REFERENCES behaviors(id) ON DELETE CASCADE
238
+ )
239
+
240
+ behavior_provenance(
241
+ id TEXT PRIMARY KEY,
242
+ behavior_id TEXT NOT NULL,
243
+ signal TEXT NOT NULL,
244
+ detail TEXT,
245
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
246
+ FOREIGN KEY (behavior_id) REFERENCES behaviors(id) ON DELETE CASCADE
247
+ )
248
+ ```
249
+
250
+ ## Key invariants
251
+
252
+ 1. **The firewall principle does NOT apply.** The LLM grading its own
253
+ behaviors is the point. Ham/spam is a relevance judgment, not a value
254
+ judgment.
255
+
256
+ 2. **Anti-laziness guard prevents codifying shortcuts.** Rules must encode
257
+ discipline, not shortcuts. The guard text is in the system prompt for all
258
+ host variants.
259
+
260
+ 3. **Same `scoreBehaviorNudge` entry point** in both host paths (opencode
261
+ `chat.message` hook and MCP sideband) prevents scoring drift.
262
+
263
+ 4. **Default behavior seeding is idempotent.** Version stamps in the
264
+ rationale text allow re-seeding when content changes across releases
265
+ without duplicating rows.
266
+
267
+ 5. **One embedding computation serves recall, prediction, and behavior.**
268
+ The prompt is embedded once at `chat.message`; three separate cosine
269
+ scans run against different tables.
270
+
271
+ 6. **Dedup at creation.** Cosine >= 0.85 for both matchers and behaviors.
@@ -0,0 +1,89 @@
1
+ # CI/CD
2
+
3
+ Thatch uses GitHub Actions for CI and publishing, plus a local release helper script. The quality gate is `mise run check` (typecheck + tests + markdownlint).
4
+
5
+ ## CI pipeline (.github/workflows/ci.yml)
6
+
7
+ Runs on every push/PR to main. Three jobs:
8
+ 1. **Typecheck** — `bunx tsc -p tsconfig.check.json` (includes test files that the build's `tsconfig.json` excludes)
9
+ 2. **Tests** — `bun test` (bun:test framework, no network, `:memory:` DB or temp dirs)
10
+ 3. **Markdownlint** — `bunx markdownlint-cli2` on `README.md` and `docs/**/*.md` (excluding `docs/plans/**`)
11
+
12
+ The CI gate is the never-merge-broken guard before a release. All three must pass.
13
+
14
+ ## Publish pipeline (.github/workflows/publish.yml)
15
+
16
+ Triggered by pushing a `v*` tag. Publishes to npm via OIDC trusted publishing — no stored npm token.
17
+
18
+ Key details:
19
+ - `id-token: write` permission lets npm authenticate through GitHub's OIDC exchange
20
+ - Prerequisite: a Trusted Publisher configured on npmjs.com for `@jeffober/thatch` pointing at the `sysread/thatch` repo
21
+ - npm >= 11.5.1 required (OIDC exchange needs it). The workflow installs setup-node@v4 (node 24) and `npm install -g npm@11`
22
+ - npm 12 shipped a sigstore bug that broke OIDC publishing; the pin prevents it
23
+ - The workflow deliberately sets NO `registry-url`, which would write a token-expecting `.npmrc` that preempts the OIDC exchange
24
+
25
+ ## Release helper (bin/release)
26
+
27
+ A bash script (`set -euo pipefail`) that bumps, checks, commits, tags, and pushes.
28
+
29
+ Args: exactly one required: `patch | minor | major`. Anything else prints an error and exits 1.
30
+
31
+ Steps:
32
+ 1. Read current version from `package.json` via `node -p`
33
+ 2. Compute next version (major resets minor/patch to 0; minor resets patch to 0)
34
+ 3. Print current/bump/next
35
+ 4. Pre-flight: if git tag `v$curr` exists, check npm registry for `@jeffober/thatch@$curr`. If npm returns E404 (tagged but not published = prior release failed), error with recovery instructions and exit 1. If npm check fails for non-404 reason, warn and proceed.
36
+ 5. Run `mise run check` (typecheck + tests + markdownlint)
37
+ 6. Bump `package.json` via `npm version $v --no-git-tag-version`
38
+ 7. Prompt `commit, tag, and push v$next? [y/N]`. On no: revert `package.json` and exit 0. On yes: git add, commit -m "v$next", tag `v$next`, push, push --tags.
39
+ 8. Print `done - CI will publish v$next to npm`
40
+
41
+ ## mise tasks
42
+
43
+ | Task | Purpose |
44
+ |------|---------|
45
+ | `test` | Run the test suite (`bun test`) |
46
+ | `test-watch` | Run tests in watch mode |
47
+ | `coverage` | Run tests with coverage report |
48
+ | `typecheck` | Typecheck src + tests (`bunx tsc -p tsconfig.check.json`) |
49
+ | `lint-md` | Lint markdown docs (`bunx markdownlint-cli2`) |
50
+ | `check` | The CI gate: typecheck + tests + markdownlint |
51
+ | `release` | Bump version, commit, tag, push (args: `patch\|minor\|major`) |
52
+ | `qa` | Run QA use cases (auto first, then live) |
53
+ | `qa-dry-run` | List QA use cases without spawning opencode sessions |
54
+ | `qa-auto` | Run only automatable QA use cases (no LLM, fast) |
55
+ | `qa-live` | Run only live-session QA use cases (spawns opencode, costs model tokens) |
56
+ | `cli` | Run the thatch CLI (list, show, search, forget) |
57
+
58
+ `mise.toml` pins Bun 1.3.14 and sets `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`.
59
+
60
+ ## Development tooling
61
+
62
+ - **Bun 1.3.14** (pinned via mise)
63
+ - **TypeScript** with `tsconfig.check.json` for typecheck (includes test files; build's `tsconfig.json` excludes them)
64
+ - **bun:test** framework (no network, `:memory:` DB or temp dirs, mock embeddings)
65
+ - **markdownlint-cli2** with custom config (`.markdownlint-cli2.jsonc`): disables MD013 (line length), MD032 (blanks around lists), MD036 (emphasis as heading), MD060 (table alignment). Keeps MD040 (code-block language), MD047 (trailing newline), heading hierarchy.
66
+ - **Zod** for tool schema validation and JSON Schema generation (`src/tool-defs.ts`)
67
+
68
+ ## Interactions with other features
69
+
70
+ - QA system ([qa-system.md](qa-system.md)): `mise run qa` runs the QA use cases
71
+ - All features: `mise run check` is the gate that validates everything before release
72
+
73
+ ## Source files
74
+
75
+ - `.github/workflows/ci.yml` — CI pipeline
76
+ - `.github/workflows/publish.yml` — publish pipeline (OIDC)
77
+ - `bin/release` — release helper script
78
+ - `mise.toml` — task definitions, tool pins, env vars
79
+ - `.markdownlint-cli2.jsonc` — markdownlint config
80
+ - `tsconfig.check.json` — typecheck config (includes tests)
81
+
82
+ ## Key invariants
83
+
84
+ - `mise run check` (typecheck + tests + markdownlint) must pass before any release. The release helper enforces this in step 5.
85
+ - OIDC publishing uses no stored npm token. The workflow sets NO `registry-url` because it would write a token-expecting `.npmrc` that preempts the OIDC exchange.
86
+ - npm is pinned to 11.x. npm 12 shipped a sigstore bug that broke OIDC publishing.
87
+ - The release helper checks for tagged-but-not-published state (tag exists, npm returns E404) and refuses to proceed with recovery instructions.
88
+ - CI runs on push/PR to main. Publish runs only on `v*` tags. The release helper bridges them: tag + push triggers publish.
89
+ - `tsconfig.check.json` includes test files for typecheck. The build's `tsconfig.json` excludes them.
@@ -0,0 +1,81 @@
1
+ # CLI (bin/thatch)
2
+
3
+ The thatch CLI is a Bun script at `bin/thatch`. It provides memory inspection, MCP server startup, hook commands for MCP hosts, and the setup installer.
4
+
5
+ ## Subcommands
6
+
7
+ 15 subcommands:
8
+
9
+ | Command | Args | Flags | Stdin | Purpose |
10
+ |---------|------|-------|-------|---------|
11
+ | `stores` | none | none | none | List all store names |
12
+ | `list [store]` | optional store | none | none | List memory labels in a store |
13
+ | `show <label> [store]` | label, optional store | none | none | Display one memory by label |
14
+ | `forget <label> [store]` | label, optional store | none | none | Delete one memory by label |
15
+ | `search <query> [store]` | query, optional store | none | none | Semantic cosine search (limit 10; "all" searches project + global) |
16
+ | `mcp` | none | none | none | Start the stdio MCP server (for Claude Code, Cursor) |
17
+ | `reminder [--json]` | none | `--json` | none | Print session-start reminder + hygiene report |
18
+ | `hygiene` | none | none | none | Print the hygiene report standalone |
19
+ | `prime` | none | none | none | Run thatch-project-primer skill via opencode/agent/claude CLI |
20
+ | `buffer-batch` | none | none | JSON | Append PostToolBatch payload to queue (Claude Code hook) |
21
+ | `buffer-tool` | none | none | JSON | Append single postToolUse interaction to queue (Cursor hook) |
22
+ | `flush-tools [--json]` | none | `--json` | JSON | Peek queue + extraction/recall/prediction/behavior/write nudge |
23
+ | `flush-predictions [--json]` | none | `--json` | JSON | Standalone prediction-only nudge |
24
+ | `setup --claude [--cursor] [--global]` | none | `--claude`, `--cursor`, `--global` | none | Install config + instructions + hooks + skills |
25
+ | (unknown) | none | none | none | Print usage, exit 1 |
26
+
27
+ ## Global behavior
28
+
29
+ - No `--version`, `--help`, `-h`, or short flags. No subcommand aliases.
30
+ - Unknown command or missing required arg calls `usage()` and exits 1.
31
+ - Args are positional, parsed by hand from `process.argv.slice(2)`. No arg-parsing library.
32
+ - DB opened once at startup, closed at end (except `mcp` which closes early).
33
+ - Stores default to git remote detected by `detectRepo()`; "unknown" on failure. "global" is the shared store. "all" (search only) means project + global.
34
+
35
+ ## prime
36
+
37
+ Runs the thatch-project-primer skill via an external CLI. Searches `PATH` in order: `opencode`, `agent` (Cursor CLI), `claude`. Uses the first found:
38
+ - opencode: `opencode run "<primer prompt>"`
39
+ - agent: `agent -p "<primer prompt>" --approve-mcps`
40
+ - claude: `claude "<primer prompt>"`
41
+
42
+ Inherits stdio, exits with the child's exit code. Errors and exits 1 if none found.
43
+
44
+ ## Environment variables
45
+
46
+ | Variable | Purpose | Default |
47
+ |----------|---------|---------|
48
+ | `XDG_CONFIG_HOME` | Config home base | `~/.config` |
49
+ | `THATCH_DB_PATH` | SQLite DB file location | `$XDG_CONFIG_HOME/thatch/thatch.db` |
50
+ | `THATCH_MODEL` | Embedding model override | `Xenova/bge-small-en-v1.5` |
51
+ | `THATCH_RECALL_THRESHOLD` | Cosine cutoff for recall nudge | `0.55` |
52
+ | `THATCH_PREDICTION_THRESHOLD` | Cutoff for prediction auto-fire | `0.60` |
53
+ | `THATCH_BEHAVIOR_THRESHOLD` | Cutoff for behavior auto-fire | `0.60` |
54
+ | `CLAUDE_PROJECT_DIR` | Claude Code project dir | `process.cwd()` |
55
+ | `CURSOR_PROJECT_DIR` | Cursor project dir | falls through |
56
+ | `CLAUDE_CONFIG_DIR` | Claude config dir | `~/.claude` |
57
+ | `CURSOR_CONFIG_DIR` | Cursor config dir | `~/.cursor` |
58
+ | `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | Enable async extraction | unset |
59
+
60
+ ## Source files
61
+
62
+ - `bin/thatch` — the CLI (Bun script, 486 lines)
63
+ - `bin/release` — release helper (bash, separate from the main CLI)
64
+
65
+ ## Interactions with other features
66
+
67
+ - Memory store ([memory-store.md](memory-store.md)): `stores`, `list`, `show`, `forget`, `search` subcommands
68
+ - Setup ([setup.md](setup.md)): `setup` subcommand
69
+ - Nudge pipeline ([nudge-pipeline.md](nudge-pipeline.md)): `flush-tools`, `flush-predictions`, `reminder` subcommands
70
+ - Extraction ([extraction.md](extraction.md)): `buffer-batch`, `buffer-tool`, `flush-tools` subcommands
71
+ - Hygiene ([hygiene.md](hygiene.md)): `hygiene`, `reminder` subcommands
72
+ - Multi-host ([multi-host.md](multi-host.md)): `mcp` subcommand starts the MCP server for Claude Code and Cursor
73
+
74
+ ## Key invariants
75
+
76
+ - No arg-parsing library. Args are positional, parsed by hand from `process.argv.slice(2)`.
77
+ - No `--version`, `--help`, `-h`, or short flags. No subcommand aliases.
78
+ - DB opened once at startup, closed at end (except `mcp` which closes early).
79
+ - Store name auto-detected from git remote. "global" is the shared store. "all" (search only) means project + global.
80
+ - Unknown command or missing required arg calls `usage()` and exits 1.
81
+ - `prime` searches PATH in order: `opencode`, `agent`, `claude`. Uses the first found.
@@ -0,0 +1,48 @@
1
+ # Compaction Recovery (opencode)
2
+
3
+ opencode compacts long sessions by generating a summary and replacing the conversation history. Without intervention, a compacted session would lose awareness of thatch -- the agent would stop using memory tools, extraction would stop, and nudges would fire during summary generation where tools are blocked.
4
+
5
+ ## What it does
6
+
7
+ - Suppresses nudges during compaction (tools are blocked during summary generation)
8
+ - Injects compaction context so the agent retains awareness of thatch after compaction
9
+ - Clears the compacting flag on success (three mechanisms, belt-and-suspenders)
10
+ - Handles compaction failure (clears stale flag if autocontinue never fired)
11
+
12
+ ## How it works
13
+
14
+ ### Four hooks/events
15
+
16
+ 1. **experimental.session.compacting** (hook) -- fires when opencode starts compacting. Adds sessionID to the `compacting` set. Pushes `compactionContext(repo)` into `output.context`. The context tells the agent to preserve what's been learned and decided this session so there's continuity after compaction.
17
+
18
+ 2. **experimental.compaction.autocontinue** (hook) -- fires after compaction succeeds and the auto-continue turn fires. Removes sessionID from the `compacting` set so nudges resume.
19
+
20
+ 3. **session.compacted** (event) -- fires on successful compaction. Clears the `compacting` flag. Belt-and-suspenders: if autocontinue didn't fire or wasn't installed, the event still clears the flag.
21
+
22
+ 4. **Compaction failure fallback** (chat.message hook) -- if a non-compaction chat.message arrives while the session is in the `compacting` set, compaction failed (autocontinue never fired). The hook clears the stale `compacting` flag and proceeds normally. Without this, the session would be stuck with nudges off forever.
23
+
24
+ ### Why nudges are suppressed during compaction
25
+
26
+ During summary generation, tools are blocked. A nudge that instructs the agent to use `thatch_memory_recall` or `thatch_memory_remember` would cause a blocked-tool error. The `compacting` set is checked at the top of the chat.message hook -- if the session is compacting, all nudges are skipped.
27
+
28
+ ### Compaction context
29
+
30
+ `compactionContext(repo)` in `src/prompts.ts` generates text that tells the agent: you are using thatch, here's what you've learned and decided this session, preserve this context after compaction. This is pushed into `output.context` (not `output.system`) so it's part of the compaction output, not the permanent system prompt.
31
+
32
+ ## Interactions with other features
33
+
34
+ - Nudge pipeline ([nudge-pipeline.md](nudge-pipeline.md)): compaction guard is the first check in the chat.message hook; suppresses all 4 tiers
35
+ - Session lifecycle ([session-lifecycle.md](session-lifecycle.md)): `session.compacted` event is handled in the event handler
36
+ - Multi-host ([multi-host.md](multi-host.md)): compaction recovery is opencode-only. Claude Code and Cursor do not have this feature. Mitigation: `CLAUDE.md`/`AGENTS.md` persists through compaction, and SessionStart hooks fire after compaction.
37
+
38
+ ## Source files
39
+
40
+ - `src/index.ts` -- all four hooks/events (experimental.session.compacting, experimental.compaction.autocontinue, session.compacted event, chat.message compaction guard)
41
+ - `src/prompts.ts` -- compactionContext function
42
+
43
+ ## Key invariants
44
+
45
+ - The `compacting` set is checked at the top of chat.message. If set, all nudges are skipped.
46
+ - Three mechanisms clear the flag (autocontinue, session.compacted event, chat.message fallback). Belt-and-suspenders.
47
+ - Compaction failure fallback: a non-compaction message during compacting = compaction failed. Clear and proceed.
48
+ - opencode-only feature. MCP hosts rely on static instructions persisting through compaction.