@jeffober/thatch 0.1.27 → 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-fact-extractor.md +2 -2
- 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 +13 -7
- package/docs/dev/README.md +76 -22
- 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 +7 -5
- 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 +66 -14
- package/src/mcp.ts +59 -2
- package/src/prediction.ts +127 -0
- package/src/prompts.ts +155 -200
- 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 +235 -9
- package/src/tools.ts +11 -1
- package/src/vector-math.ts +30 -0
- package/docs/qa/README.md +0 -175
package/docs/dev/README.md
CHANGED
|
@@ -24,9 +24,14 @@ Shared core
|
|
|
24
24
|
├── embeddings.ts → embedding model via transformers.js
|
|
25
25
|
├── git.ts → detect repo identity (store name)
|
|
26
26
|
├── hygiene.ts → hygiene report (pending dedups, stale, orphaned branches)
|
|
27
|
-
├── prompts.ts → system prompt, compaction, reminders, recall
|
|
27
|
+
├── prompts.ts → system prompt, compaction, reminders, recall/prediction/behavior nudges, CLAUDE.md instructions
|
|
28
28
|
├── sideband.ts → Unix socket server + client: warm-model semantic match for hook processes
|
|
29
|
-
|
|
29
|
+
├── skills.ts → SKILL.md content + installer (shared + opencode-only arrays)
|
|
30
|
+
├── scoring-engine.ts → generic four-table scoring base (shared by prediction + behavior engines)
|
|
31
|
+
├── prediction.ts → prediction engine wrapper (thin layer over ScoringEngine)
|
|
32
|
+
├── behavior.ts → behavior engine wrapper (thin layer over ScoringEngine)
|
|
33
|
+
├── seed-behaviors.ts → default behavior seeding on first run
|
|
34
|
+
└── vector-math.ts → blobToVector + cosineSimilarity helpers
|
|
30
35
|
|
|
31
36
|
OpenCode plugin path
|
|
32
37
|
├── index.ts → plugin entry: wires DB/model/extraction, registers tools + hooks
|
|
@@ -44,7 +49,7 @@ MCP server path
|
|
|
44
49
|
CLAUDE.md / AGENTS.md, settings/hooks JSON, installs skills
|
|
45
50
|
|
|
46
51
|
bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygiene|
|
|
47
|
-
prime|buffer-batch|buffer-tool|flush-tools|setup
|
|
52
|
+
prime|buffer-batch|buffer-tool|flush-tools|flush-predictions|setup
|
|
48
53
|
```
|
|
49
54
|
|
|
50
55
|
## Module responsibilities
|
|
@@ -55,16 +60,21 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
55
60
|
| `tools.ts` | Thin opencode wrappers. Imports tool-defs, wraps each in opencode's `tool()` with a `thatch_` prefix. |
|
|
56
61
|
| `mcp.ts` | Stdio JSON-RPC 2.0 server. Compiles zod schemas to JSON Schema via `z.toJSONSchema()` for `tools/list`. Validates args via `z.object().parse()` in `tools/call`. All logging to stderr (stdout is the transport). |
|
|
57
62
|
| `index.ts` | OpenCode plugin entry. Wires DB, model, extraction; registers tools and hooks; installs skills. Internal state beyond the extraction pipeline: `extracting` set (parent IDs with an active direct-extraction child), `childMetrics` map (new/updated/deleted counts per child session), and `triggerExtraction(parentID)` (creates a child session via the SDK client and prompts it with the extraction payload). |
|
|
58
|
-
| `setup.ts` | `thatch setup --claude` installer. Writes .mcp.json, appends to CLAUDE.md (idempotent), installs hooks in settings.json, installs skills. |
|
|
63
|
+
| `setup.ts` | `thatch setup --claude` / `--cursor` installer. Writes MCP config (`.mcp.json` / `.cursor/mcp.json`), appends to CLAUDE.md / AGENTS.md (idempotent), installs hooks in settings.json / hooks.json, installs skills. |
|
|
59
64
|
| `hygiene.ts` | Hygiene report: pending dedup pairs, stale count, orphaned branch memories. Shared by the plugin's session-start hook and the CLI's `thatch reminder` command. |
|
|
60
65
|
| `git.ts` | Parse `owner/repo` from git remote. Worktree-safe fallback chain. |
|
|
61
|
-
| `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. Prediction tables: matchers, predictions, edges, provenance. `scorePredictionNudge` is the shared auto-fire entry point for both host paths. |
|
|
66
|
+
| `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. Prediction tables: matchers, predictions, edges, provenance. `scorePredictionNudge` is the shared auto-fire entry point for both host paths. Behavior tables: same four-table shape (matchers, behaviors, edges, provenance) with `scoreBehaviorNudge` as the shared entry point. |
|
|
62
67
|
| `embeddings.ts` | Lazy-load the embedding model. Expose `queryEmbed`/`passageEmbed` and the model `name` (stored as an informational tag). `MockEmbeddingModel` for tests. |
|
|
63
|
-
| `extraction.ts` | Per-session in-memory ring buffer (cap 20) that buffers non-thatch tool interactions and serializes them into the JSON payload the
|
|
68
|
+
| `extraction.ts` | Per-session in-memory ring buffer (cap 20) that buffers non-thatch tool interactions and serializes them into the JSON payload the `get_extraction_payload` tool returns. The in-memory pipeline is opencode-only, but the payload builders (`buildExtractionPayload`, `deriveTitle`) are shared by both paths — `extract-queue.ts` imports `deriveTitle`, `mcp.ts` imports `buildExtractionPayload` for the extraction payload provider. `summarizeArgs` is used internally by `buildExtractionPayload`. |
|
|
64
69
|
| `extract-queue.ts` | File-backed per-session JSONL queue (caps 20, oldest dropped). Shared by the Claude Code and Cursor hook paths, which fire one-shot per event with no cross-call state. This is the MCP-side equivalent of `extraction.ts`. |
|
|
65
|
-
| `sideband.ts` | Unix domain socket server + client. The MCP server (long-lived, warm model) runs `SidebandServer` so one-shot hook processes can ask it to embed a prompt and search for matches without loading the model themselves. Handles
|
|
66
|
-
| `prompts.ts` | Text constants: opencode system prompt, compaction context, session-start reminder, prompt-aware recall nudge (`recallNudge` / `claudeRecallNudge`), Claude Code CLAUDE.md instructions, Cursor AGENTS.md instructions, Claude Code hook text. |
|
|
67
|
-
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (
|
|
70
|
+
| `sideband.ts` | Unix domain socket server + client. The MCP server (long-lived, warm model) runs `SidebandServer` so one-shot hook processes can ask it to embed a prompt and search for matches without loading the model themselves. Handles three methods: `match` (recall nudge), `predictions` (prediction auto-fire), and `behaviors` (behavior auto-fire). Socket path is a hash of the DB path — both processes compute it independently. |
|
|
71
|
+
| `prompts.ts` | Text constants: opencode system prompt, compaction context, session-start reminder, prompt-aware recall nudge (`recallNudge` / `claudeRecallNudge`), prediction nudge (`predictionNudge`), behavior nudge (`behaviorNudge`), prediction verb selection (`predictionVerb`), Claude Code CLAUDE.md instructions, Cursor AGENTS.md instructions, Claude Code hook text. |
|
|
72
|
+
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (23 skills: fact-extractor, dedup-classifier, project-primer, 8 review specialists, review synthesizer, review context, code archaeology, review followup, review response, change walkthrough, code walkthrough, session reflection, coding-workflow, thatch-pr-description, thatch-ticket-description, thatch-split-overlarge-pr — work on all three hosts) and `OPENCODE_ONLY_SKILLS` (1 skill: code-review coordinator — requires sub-agent support, not installed for Claude Code or Cursor). `installSkills(dir, skills)` defaults to `SHARED_SKILLS`; the opencode plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`. |
|
|
73
|
+
| `scoring-engine.ts` | Generic four-table scoring engine with Bayesian confidence. Shared base for prediction and behavior engines — each wraps it with table-specific names. |
|
|
74
|
+
| `prediction.ts` | Thin wrapper around `ScoringEngine` with prediction-specific table names. |
|
|
75
|
+
| `behavior.ts` | Thin wrapper around `ScoringEngine` with behavior-specific table names. |
|
|
76
|
+
| `seed-behaviors.ts` | Default behavior seeding on first run. Populates starter self-discipline rules. |
|
|
77
|
+
| `vector-math.ts` | `blobToVector` and `cosineSimilarity` helpers used across the codebase. |
|
|
68
78
|
|
|
69
79
|
## Plugin hooks
|
|
70
80
|
|
|
@@ -76,7 +86,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
76
86
|
| `experimental.session.compacting` | Marks the session as compacting and appends re-familiarization context so a compacted session still knows thatch exists. |
|
|
77
87
|
| `experimental.compaction.autocontinue` | Clears the compacting flag so `chat.message` nudges resume. Without this, nudges that instruct tool calls would fire during summary generation where tools are blocked. The `chat.message` hook also clears the flag if it fires for a session still in the compacting set but the incoming message has no compaction-type part — this handles compaction failure, where the session would otherwise be stuck with nudges off forever. |
|
|
78
88
|
| `tool.execute.after` | Buffers every non-`thatch_*`, non-`skill`, non-`task` tool call into the session's extraction buffer. (Skill/task are excluded — buffering them creates a feedback loop where the nudge triggers a skill load, which gets buffered, which triggers another nudge.) Memory writes (`thatch_memory_remember`) and `thatch_extraction_done` drain the buffer and reset the missed-nudge counter. For child sessions (`childToParent.has(sessionID)`), also tracks metrics: `remember` with `overwrite:false` → new++, `overwrite:true` → updated++, `forget` → deleted++. This is a plugin hook, NOT a bus event — do not move it into the `event` handler; the event bus has no such event and it will silently never fire. |
|
|
79
|
-
| `chat.message` | Two priority tiers: (a) if extraction buffer has interactions and the session is NOT in the `extracting` set (direct extraction in progress), **peeks** the buffer (does NOT drain it) and injects a synthetic text part carrying the extraction nudge
|
|
89
|
+
| `chat.message` | Two priority tiers: (a) if extraction buffer has interactions and the session is NOT in the `extracting` set (direct extraction in progress), **peeks** the buffer (does NOT drain it) and injects a synthetic text part carrying the extraction nudge with the session ID and fetch tool name (not the full payload) — the sub-agent calls `thatch_get_extraction_payload` to retrieve the interactions as a tool response, keeping the full payload out of the main session's context window. The buffer persists until the agent writes a memory or calls `thatch_extraction_done`, so ignored nudges repeat and escalate (polite → insistent → ALL-CAPS) via the `missedNudges` counter; (b) otherwise, embeds the user's prompt text with the in-process warm model, searches `db.search()` across repo + global, and pushes a recall nudge if matches exceed the threshold (default 0.55). The same embedding also feeds the prediction auto-fire (`db.scorePredictionNudge`, injects `[thatch] User decision model`) and the behavior auto-fire (`db.scoreBehaviorNudge`, injects `[thatch] Situational behaviors`). All three nudges (recall, prediction, behavior) fire independently in separate try/catch blocks with separate synthetic parts. Skipped entirely while the session is compacting (tool calls are blocked during summary generation). |
|
|
80
90
|
| `event` | Subscribes to all session bus events. `session.created`: records `childToParent` + `parentSnapshots` (shallow copy of the parent's buffer for snapshot-aware drain), then sends the session-start reminder via `client.session.prompt` carrying the hygiene heartbeat (pending dedup pairs, stale count, orphaned branch memories) when any signal is non-zero. `session.status` (idle): if the session is a child, drains its snapshot, fires a toast with `childMetrics`, and deletes the child session; if the session is a parent with pending tool interactions and not already extracting, calls `triggerExtraction` to create a direct-extraction child. `session.error`: requeues the parent's buffer (child died without draining). `session.deleted`: cleans up `childToParent`, `parentSnapshots`, `childMetrics`, and `extracting`. `session.compacted`: clears the compacting flag so `chat.message` nudges resume. |
|
|
81
91
|
| `dispose` | Closes the DB. |
|
|
82
92
|
|
|
@@ -100,12 +110,14 @@ Two of these hooks were dead for weeks because failures were invisible.
|
|
|
100
110
|
5. **Store creation is implicit.** First `remember` to a new store creates it.
|
|
101
111
|
6. **Default recall scope is repo + global.** The tool layer hardcodes this.
|
|
102
112
|
7. **Skills are plugin-owned files.** Installed to
|
|
103
|
-
`$XDG_CONFIG_HOME/opencode/skills` (opencode)
|
|
104
|
-
(Claude Code) — never into the
|
|
105
|
-
on plugin init or re-running
|
|
106
|
-
`
|
|
107
|
-
sub-agent support,
|
|
108
|
-
|
|
113
|
+
`$XDG_CONFIG_HOME/opencode/skills` (opencode), `~/.claude/skills/`
|
|
114
|
+
(Claude Code), or `~/.cursor/skills/` (Cursor) — never into the
|
|
115
|
+
worktree; drifted content is overwritten on plugin init or re-running
|
|
116
|
+
`thatch setup`. Skills are split into `SHARED_SKILLS` (work on all
|
|
117
|
+
three hosts) and `OPENCODE_ONLY_SKILLS` (require sub-agent support,
|
|
118
|
+
not installed for Claude Code or Cursor). The opencode plugin
|
|
119
|
+
installs both arrays; `thatch setup --claude` and `--cursor` install
|
|
120
|
+
only shared.
|
|
109
121
|
8. **Tool definitions are the single source of truth.** `tool-defs.ts` defines
|
|
110
122
|
each tool once (name, zod schema, execute function). The opencode plugin
|
|
111
123
|
wraps them in `tool()` with a `thatch_` prefix; the MCP server wraps them
|
|
@@ -138,6 +150,18 @@ Two of these hooks were dead for weeks because failures were invisible.
|
|
|
138
150
|
relevance-gated (being tested moves it, not being ignored). See the
|
|
139
151
|
prediction DB tables, auto-fire in `chat.message`, and the sideband
|
|
140
152
|
`predictions` method for the MCP path.
|
|
153
|
+
12. **Behavior engine mirrors the prediction engine but is self-graded.** Same
|
|
154
|
+
four-table data model, same Bayesian confidence, same auto-fire pipeline.
|
|
155
|
+
The difference: predictions model what the USER wants (graded by user
|
|
156
|
+
feedback); behaviors model what the LLM should do (graded by the LLM's own
|
|
157
|
+
ham/spam relevance judgment via `behavior_feedback`). The firewall
|
|
158
|
+
principle from predictions (the model that uses predictions cannot grade
|
|
159
|
+
them) does not apply: the LLM grading its own behavioral rules is the
|
|
160
|
+
point, not a violation. The ham/spam is a relevance judgment ("does this
|
|
161
|
+
rule apply here?"), not a value judgment ("is this a good rule?"). An
|
|
162
|
+
anti-laziness guard in the prompt prevents the agent from codifying
|
|
163
|
+
shortcuts. See the behavior DB tables, auto-fire in `chat.message`, and
|
|
164
|
+
the sideband `behaviors` method for the MCP path.
|
|
141
165
|
|
|
142
166
|
## Data flow
|
|
143
167
|
|
|
@@ -186,12 +210,13 @@ extraction cycle
|
|
|
186
210
|
→ nudge path (fallback): if triggerExtraction throws (create or prompt
|
|
187
211
|
fails), the `extracting` set clears and the next chat.message sees
|
|
188
212
|
pending entries with no extracting flag → peeks the buffer and injects
|
|
189
|
-
a nudge part with
|
|
213
|
+
a nudge part with session ID and fetch tool name; missed nudges escalate (polite →
|
|
190
214
|
insistent → ALL-CAPS) via the missedNudges counter
|
|
191
215
|
→ drain: thatch_memory_remember (or thatch_extraction_done) clears the
|
|
192
216
|
buffer and resets the missed-nudge counter
|
|
193
|
-
→ agent
|
|
194
|
-
|
|
217
|
+
→ agent dispatches a sub-agent that calls thatch_get_extraction_payload
|
|
218
|
+
to fetch the queued interactions, loads thatch-fact-extractor skill,
|
|
219
|
+
saves facts via thatch_memory_remember
|
|
195
220
|
→ MCP path (Claude Code/Cursor): unchanged — no SDK client, no child
|
|
196
221
|
sessions; extract-queue.ts + flush-tools drives the nudge via hooks
|
|
197
222
|
|
|
@@ -206,7 +231,9 @@ toast notifications (opencode-only, TUI-rendered)
|
|
|
206
231
|
→ recall matches: `[thatch] recalled N memories` (info variant, 3s) —
|
|
207
232
|
fires when chat.message matches stored memories
|
|
208
233
|
→ prediction matches: `[thatch] N predictions surfaced` (info variant,
|
|
209
|
-
|
|
234
|
+
3s) — fires when chat.message matches decision-model patterns
|
|
235
|
+
→ behavior matches: `[thatch] N behaviors surfaced` (info variant,
|
|
236
|
+
3s) — fires when chat.message matches codified behavior matchers
|
|
210
237
|
|
|
211
238
|
prompt-aware recall nudge (both paths)
|
|
212
239
|
→ opencode: chat.message hook embeds prompt text with warm in-process model,
|
|
@@ -236,7 +263,7 @@ prediction cycle (agent-driven, statistical model)
|
|
|
236
263
|
confidence is relevance-gated (being tested moves it, not being ignored).
|
|
237
264
|
→ auto-fire (opencode): chat.message reuses the prompt embedding already
|
|
238
265
|
computed for the recall nudge. db.scorePredictionNudge([repo, global],
|
|
239
|
-
|
|
266
|
+
embedding, 0.60) finds matchers above threshold, follows edges to
|
|
240
267
|
predictions, scores by cosine * weight * confidence, dedups by
|
|
241
268
|
prediction_id, and returns top 5. Injects a separate synthetic part
|
|
242
269
|
with a [thatch] User decision model block. 0-evidence predictions use
|
|
@@ -251,6 +278,29 @@ prediction cycle (agent-driven, statistical model)
|
|
|
251
278
|
thatch_prediction_list inspects the model with provenance;
|
|
252
279
|
thatch_prediction_delete removes bad predictions (cascade clears edges
|
|
253
280
|
and provenance).
|
|
281
|
+
|
|
282
|
+
behavior cycle (agent-driven, self-graded)
|
|
283
|
+
→ formation: agent calls thatch_behavior_codify(situation, behavior, rationale)
|
|
284
|
+
when it recognizes a situation it should react to in a specific, repeatable
|
|
285
|
+
way. The tool embeds the situation text, finds or creates a behavior matcher
|
|
286
|
+
(0.85 dedup), finds or creates a behavior (0.85 store-wide dedup), links
|
|
287
|
+
them via an edge. Confidence starts at p0 (0.5) with 0 evidence.
|
|
288
|
+
→ auto-fire (opencode): chat.message reuses the prompt embedding. db.
|
|
289
|
+
scoreBehaviorNudge([repo, global], embedding, 0.60) finds behavior matchers
|
|
290
|
+
above threshold, follows edges to behaviors, scores by cosine * weight *
|
|
291
|
+
confidence, dedups by behavior_id, returns top 5. Injects a separate
|
|
292
|
+
synthetic part with a [thatch] Situational behaviors block. 0-evidence
|
|
293
|
+
behaviors use "consider"; behaviors with evidence use "do".
|
|
294
|
+
→ auto-fire (Claude Code/Cursor): flush-tools fires the behavior query via
|
|
295
|
+
the sideband socket's `behaviors` method in parallel with recall and
|
|
296
|
+
predictions. Same scoreBehaviorNudge entry point prevents scoring drift.
|
|
297
|
+
→ consumption: agent evaluates each surfaced behavior against the current
|
|
298
|
+
situation. If relevant (ham), follows it and calls behavior_feedback with
|
|
299
|
+
relevant: true (confirm). If not relevant (spam), calls behavior_feedback
|
|
300
|
+
with relevant: false (disconfirm). The feedback adjusts the Bayesian
|
|
301
|
+
confidence the same way prediction signals do. thatch_behavior_list
|
|
302
|
+
inspects with provenance; thatch_behavior_delete removes bad rules
|
|
303
|
+
(cascade clears edges and provenance).
|
|
254
304
|
```
|
|
255
305
|
|
|
256
306
|
## Database
|
|
@@ -265,7 +315,11 @@ prediction cycle (agent-driven, statistical model)
|
|
|
265
315
|
`prediction_matchers(id PK, store, description, embedding BLOB, model, created_at, updated_at)`,
|
|
266
316
|
`predictions(id PK, store, statement, rationale, embedding BLOB, model, confidence REAL, confirm_count REAL, disconfirm_count REAL, created_at, updated_at)`,
|
|
267
317
|
`prediction_edges(matcher_id, prediction_id, weight REAL, PK(matcher_id, prediction_id), FK CASCADE)`,
|
|
268
|
-
`prediction_provenance(id PK, prediction_id, signal, detail, created_at, FK CASCADE)
|
|
318
|
+
`prediction_provenance(id PK, prediction_id, signal, detail, created_at, FK CASCADE)`,
|
|
319
|
+
`behavior_matchers(id PK, store, description, embedding BLOB, model, created_at, updated_at)`,
|
|
320
|
+
`behaviors(id PK, store, statement, rationale, embedding BLOB, model, confidence REAL, confirm_count REAL, disconfirm_count REAL, created_at, updated_at)`,
|
|
321
|
+
`behavior_edges(matcher_id, behavior_id, weight REAL, PK(matcher_id, behavior_id), FK CASCADE)`,
|
|
322
|
+
`behavior_provenance(id PK, behavior_id, signal, detail, created_at, FK CASCADE)`.
|
|
269
323
|
- `recall_count`, `last_recalled_at`, and `archived` are added to pre-existing
|
|
270
324
|
databases by an idempotent column migration at init (`PRAGMA table_info` +
|
|
271
325
|
`ALTER TABLE`). The `archived` column is `INTEGER NOT NULL DEFAULT 0` (0 =
|
|
@@ -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.
|