@jeffober/thatch 0.1.28 → 0.1.29
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 +24 -10
- package/artifacts/skills/thatch-code-review.md +9 -7
- package/artifacts/skills/thatch-review-economy.md +97 -0
- package/artifacts/skills/thatch-review-followup.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +15 -5
- package/bin/qa-run +38 -0
- package/bin/thatch +10 -5
- package/docs/dev/README.md +71 -18
- package/docs/dev/features/README.md +33 -0
- package/docs/dev/features/behavior-engine.md +271 -0
- package/docs/dev/features/cicd.md +89 -0
- package/docs/dev/features/cli.md +81 -0
- package/docs/dev/features/compaction-recovery.md +48 -0
- package/docs/dev/features/database.md +244 -0
- package/docs/dev/features/deduplication.md +102 -0
- package/docs/dev/features/extraction.md +253 -0
- package/docs/dev/features/hygiene.md +81 -0
- package/docs/dev/features/memory-store.md +154 -0
- package/docs/dev/features/multi-host.md +228 -0
- package/docs/dev/features/nudge-pipeline.md +282 -0
- package/docs/dev/features/prediction-engine.md +253 -0
- package/docs/dev/features/qa-system.md +205 -0
- package/docs/dev/features/session-lifecycle.md +110 -0
- package/docs/dev/features/setup.md +199 -0
- package/docs/dev/features/sideband.md +104 -0
- package/docs/dev/gotchas.md +3 -3
- package/docs/dev/mcp-parity.md +6 -4
- package/docs/dev/setup-and-hooks.md +4 -4
- package/docs/dev/skills.md +10 -8
- package/docs/plans/README.md +2 -2
- package/docs/user/README.md +58 -20
- package/docs/user/behavior-engine.md +85 -0
- package/docs/user/cli.md +98 -0
- package/docs/user/code-review.md +73 -0
- package/docs/user/deduplication.md +70 -0
- package/docs/user/default-behaviors.md +75 -0
- package/docs/user/extraction.md +74 -0
- package/docs/user/hygiene.md +76 -0
- package/docs/user/memory.md +81 -0
- package/docs/user/prediction-engine.md +87 -0
- package/docs/user/setup.md +110 -0
- package/docs/user/skills.md +97 -0
- package/package.json +2 -2
- package/src/behavior.ts +120 -0
- package/src/db.ts +160 -417
- package/src/embeddings.ts +0 -41
- package/src/extraction.ts +2 -2
- package/src/git.ts +3 -1
- package/src/index.ts +55 -9
- package/src/mcp.ts +30 -1
- package/src/prediction.ts +127 -0
- package/src/prompts.ts +135 -188
- package/src/scoring-engine.ts +374 -0
- package/src/seed-behaviors.ts +339 -0
- package/src/setup.ts +2 -6
- package/src/sideband.ts +36 -8
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +172 -25
- package/src/vector-math.ts +30 -0
- package/docs/qa/README.md +0 -175
|
@@ -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.
|