@davesheffer/hunch 0.12.1 β†’ 0.12.2

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 CHANGED
@@ -1,372 +1,379 @@
1
- # 🧠 Hunch β€” Engineering Memory OS
2
-
3
- > Git stores *what* the code is. **Hunch** stores ***why*** it is that way β€” a persistent,
4
- > git-native reasoning graph over your codebase, surfaced to Claude Code at reasoning time
5
- > so the AI stops re-deriving understanding and stops undoing intentional design.
6
-
7
- ## The problem
8
-
9
- Every AI coding session starts from zero. The model re-reads your code, re-guesses the
10
- intent, and happily "fixes" the thing you deliberately did last month β€” because the
11
- *reasoning* behind the code lives in PRs, Slack, and people's heads, not in the repo.
12
-
13
- **Hunch** captures that reasoning as a **byproduct of normal work** β€” commits and test
14
- failures β€” stores it as a git-tracked graph next to your code, and feeds it back to
15
- Claude Code so every session is grounded in the decisions, bugs, and invariants that
16
- came before. Local-first, no documentation toil, no SaaS.
17
-
18
- ## How it works
19
-
20
- ```
21
- commit / test failure .hunch/ (git-tracked JSON) Claude Code
22
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
23
- β”‚ post-commit hook ───┼────────▢│ Decisions (why a change) │────────▢│ MCP tools β”‚
24
- β”‚ record-bug ───┼────────▢│ Bugs (root causes) β”‚ read β”‚ /hunch-* cmds β”‚
25
- β”‚ structured diff + β”‚ write β”‚ Constraints(invariants) │◀────────│ CLAUDE.md β”‚
26
- β”‚ Claude (or heuristic) β”‚ β”‚ Components / Symbols/Edges β”‚ β”‚ CLI β”‚
27
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
28
- ```
29
-
30
- - **Index** (no LLM): tree-sitter parses your repo into a symbol/dependency graph β€”
31
- functions, call edges, imports, components β€” plus churn and fan-in metrics.
32
- - **Learn**: each commit becomes a structured **Decision** (an ADR); a failing test
33
- becomes a **Bug** with a ranked suspect list; recurring or severe bugs are promoted
34
- into **Constraints** (do-not-break invariants) and raise a component's *fragility*.
35
- - **Ground**: Claude Code reads it through an **MCP server**, an auto-maintained
36
- **`CLAUDE.md`**, and **slash commands** β€” every answer cites `provenance`
37
- (source + confidence + evidence), so nothing is a blind assertion.
38
-
39
- ## Getting started
40
-
41
- ### 1. Install it
42
-
43
- ```bash
44
- npm install -g @davesheffer/hunch # puts `hunch` on your PATH
45
- ```
46
-
47
- Or run from source (for hacking on Hunch itself):
48
-
49
- ```bash
50
- npm install
51
- npm run build # compiles to dist/
52
- npm link # optional: puts a global `hunch` on your PATH
53
- ```
54
-
55
- Either way you then type `hunch …`. From a source checkout without `npm link`, use
56
- `node dist/cli/index.js …` (or `npm run hunch -- …` to run via tsx). The rest of this
57
- README uses `hunch` for brevity.
58
-
59
- ### 2. (Recommended) make a coding-assistant CLI available
60
-
61
- Hunch's LLM synthesis is billed to **your subscription** through a coding-assistant
62
- CLI β€” **never** a pay-per-token API key (the API key is stripped from the child env).
63
- Hunch auto-detects the first one present, in this order:
64
-
65
- | CLI | Subscription | Detected by |
66
- |---|---|---|
67
- | `claude` (Claude Code) | Claude Pro/Max | `claude --version` |
68
- | `codex` (OpenAI Codex) | ChatGPT Plus/Pro | `codex --version` |
69
- | `cursor-agent` (Cursor) | Cursor | `cursor-agent --version` |
70
-
71
- If none is installed, Hunch still works using a deterministic structural heuristic
72
- (lower-confidence drafts). `hunch doctor` tells you which mode you're in; force one
73
- with `HUNCH_SYNTH_PROVIDER=claude-cli|codex-cli|cursor-agent|deterministic`.
74
-
75
- ### 3. Initialize the repo you want a memory for
76
-
77
- ```bash
78
- hunch init # scaffold .hunch/, index, install the post-commit hook,
79
- # write .mcp.json + slash commands + CLAUDE.md, register the merge driver
80
- hunch backfill --since 90d # cold start: seed decisions from recent git history
81
- ```
82
-
83
- `init` writes a `.mcp.json` pointing at *this machine's* node + Hunch β€” so **reload
84
- Claude Code in the repo** afterward to pick up the `hunch_*` tools. (Each teammate runs
85
- `hunch init` once to wire up their own clone; the captured `.hunch/` content is shared
86
- via git.)
87
-
88
- ### 4. Use it
89
-
90
- ```bash
91
- hunch why src/auth/session.ts # the decisions / bugs / invariants behind a file
92
- hunch doctor # check git, schema version, and synthesis mode
93
- ```
94
-
95
- …and in Claude Code, just ask: *"why is the session module built this way?"*
96
-
97
- ## Two ways to use it
98
-
99
- **Through Claude Code (the point).** Once the MCP server is registered, ask questions
100
- normally and Claude consults Hunch, or invoke the slash commands:
101
-
102
- | Slash command | What it does |
103
- |---|---|
104
- | `/hunch-why <file\|symbol>` | the decisions, invariants, and bug history behind it β€” with citations |
105
- | `/hunch-fix <bug>` | fix a bug grounded in past root causes, blast radius, and constraints |
106
- | `/hunch-fragile` | a fragility report (the riskiest code, with evidence) |
107
-
108
- The MCP tools Claude calls under the hood: `hunch_why`, `hunch_query`,
109
- `hunch_check_constraints`, `hunch_get_dependents` (blast radius), `hunch_blast_radius`
110
- (dependent files + near-violations a change could break indirectly), `hunch_bug_lineage`,
111
- `hunch_context` (surgical minimal slice for a task), `hunch_timeline` (a target's decision
112
- history over time), `hunch_record_decision` (write-back). `hunch_why` and `hunch_context`
113
- take an optional `as_of` (commit/tag/branch) to **time-travel** the graph to a past state.
114
-
115
- ### Works with any MCP assistant
116
-
117
- The Hunch MCP server is **client-agnostic** β€” one `.hunch/` graph powers every
118
- assistant. `hunch init` scaffolds each tool's MCP config + ambient grounding so
119
- they all consult the same memory:
120
-
121
- | Assistant | MCP config | Grounding file |
122
- |---|---|---|
123
- | Claude Code | `.mcp.json` | `CLAUDE.md` + `/hunch-*` slash commands |
124
- | Cursor | `.cursor/mcp.json` | `.cursor/rules/hunch.mdc` (always-applied) |
125
- | VS Code (Copilot) | `.vscode/mcp.json` | `.github/copilot-instructions.md` |
126
- | Codex CLI | `.codex/config.toml` | `AGENTS.md` |
127
- | Windsurf | `.windsurf/mcp_config.json` | `.windsurf/rules/hunch.md` (always-on) |
128
- | Anything else | β€” | `AGENTS.md` (cross-tool standard) |
129
-
130
- Each writer **merges** into existing files (other MCP servers and your own prose are
131
- preserved) and is idempotent. Opt out with `hunch init --no-providers`.
132
-
133
- **Through the CLI** β€” the same graph, from your terminal:
134
-
135
- | Command | What |
136
- |---|---|
137
- | `hunch init` | scaffold `.hunch/`, index, install hook + merge driver, auto-install the advisory pre-commit guard, install the **Claude Code agent hooks**, and wire up **every assistant** (Claude Code, Cursor, VS Code/Copilot, Codex, Windsurf, AGENTS.md). Flags: `--no-enforce`, `--enforce-strict`, `--no-providers`, `--no-agent-hooks`, `--firmness <level>` |
138
- | `hunch index` | parse repo β†’ symbols / edges / components (deterministic, no LLM) |
139
- | `hunch backfill --since 90d` | replay git history β†’ seed decisions |
140
- | `hunch sync [sha]` | turn a commit into a Decision (run automatically by the hook) |
141
- | `hunch record-bug --test <id> --message <m>` | capture a Bug from a failing test |
142
- | `hunch record-constraint "<statement>" [--scope <globs>] [--severity advisory\|warning\|blocking] [--type …] [--rationale <t>] [--source-decision <id>]` | record an invariant the code must not break (what `hunch check` + the strict agent hook enforce) |
143
- | `hunch firmness [off\|advisory\|firm\|strict]` | get/set how firmly the agent hook enforces Hunch before edits (no arg prints the current level) |
144
- | `hunch test [cmd…]` | run the suite (default `npm test`); auto-capture failures as Bugs (suspects + recurrenceβ†’Constraints), mark passing tests' bugs fixed |
145
- | `hunch why <path\|symbol> [--as-of <ref>]` | decisions / bugs / constraints explaining a target (flags `⚠STALE`); `--as-of` time-travels to what was believed at a commit/tag/branch |
146
- | `hunch timeline <path\|symbol>` | the decision history for a target β€” what was believed, its valid-time window, and what superseded it |
147
- | `hunch supersede <old> --by <new>` | mark one decision as replaced by another: closes the old one's valid-time window (invalidate, don't delete) |
148
- | `hunch query "<q>" [--semantic]` | full-text + graph search (`--semantic` blends in local embeddings) |
149
- | `hunch embed` | generate local embeddings for semantic recall (opt-in; needs `@huggingface/transformers`) |
150
- | `hunch context <path\|symbol> [--as-of <ref>]` | minimal relevant slice for a task: invariants β†’ decisions β†’ bugs β†’ blast radius (`--as-of` time-travels) |
151
- | `hunch fragile` | ranked fragility report with evidence |
152
- | `hunch check [--staged\|--commit <sha>\|--base <ref>] [--strict] [--format text\|markdown] [--blast]` | guardrail: flag changes touching a do-not-break invariant **directly or via blast radius** (a guarded file that depends on what you changed), **and changes that re-introduce something a decision deliberately retired** (the Regression Guard). `--base <ref>` checks a PR's diff (for CI); `--format markdown` emits a PR comment; `--strict` fails only on a direct, high-confidence, non-stale **blocking** invariant; `--blast` prints the dependency fan-out |
153
- | `hunch ci` | scaffold the **CI Constraint Guard** β€” a GitHub Action that runs `hunch check` on every PR, comments the affected invariants/decisions, and fails on a blocking one |
154
- | `hunch stale [--resync]` | drift: records whose files changed after last verification (`--resync` regenerates stale decisions from their commits) |
155
- | `hunch review [--accept <id>\|--reject <id>]` | curate: triage / promote / drop low-confidence drafts |
156
- | `hunch migrate` | upgrade `.hunch/` records to the current schema version |
157
- | `hunch compact [--apply]` | prune low-value drafts to bound growth (dry-run by default) |
158
- | `hunch doctor` | environment diagnostics (git, auth mode, schema version, counts) |
159
- | `hunch mcp` | start the MCP server over stdio (Claude Code connects here) |
160
-
161
- ## Grounding the agent automatically (firmness)
162
-
163
- Telling an assistant "consult Hunch first" in a prompt is advisory β€” it drifts. `hunch
164
- init` instead installs two **Claude Code agent hooks** (in `.claude/settings.json`) so the
165
- grounding is enforced by the harness, not by the model's memory:
166
-
167
- - **Before every edit** (`PreToolUse` on `Edit`/`Write`/`MultiEdit`) Hunch injects the
168
- relevant slice for the file being touched β€” its decisions, invariants, bug history, and
169
- blast radius β€” straight into the model's context.
170
- - **On every prompt** (`UserPromptSubmit`) it reminds the agent to query Hunch.
171
-
172
- How hard it pushes is one committed knob β€” set it once, it applies to the whole team:
173
-
174
- ```bash
175
- hunch firmness # print the current level
176
- hunch firmness strict # change it (takes effect on the next edit; no restart)
177
- ```
178
-
179
- | Level | Before an edit |
180
- |---|---|
181
- | `off` | nothing (hook is a no-op) |
182
- | `advisory` *(default)* | inject the relevant Hunch slice as context |
183
- | `firm` | advisory **+** explicitly flag invariants in the file's scope |
184
- | `strict` | firm **+** **deny** an edit that hits a *blocking* invariant (directly or via blast radius), feeding the invariant back as the refusal reason |
185
-
186
- Before an edit, the hook also grounds the agent in anything an in-force decision
187
- **deliberately retired** from that file ("don't re-introduce `login` here β€” dec_017 removed
188
- it"). The actual gate is at commit time: `hunch check` runs the **Regression Guard** over
189
- the staged diff and, under `--strict`, fails the commit when a change re-adds a retired
190
- symbol/dependency tied to a blocking invariant (otherwise it warns).
191
-
192
- The hook never breaks your flow: any error or unrecognized input emits nothing and exits
193
- 0, and it stays silent on files Hunch hasn't learned yet. `strict` only bites once you have
194
- **blocking** constraints recorded (`hunch record-constraint … --severity blocking`) β€” with
195
- none, every level degrades to context-only. Opt out of the hooks entirely with `hunch init
196
- --no-agent-hooks`.
197
-
198
- ## Semantic search (optional)
199
-
200
- By default `hunch query` and the `hunch_query` MCP tool use fast keyword (FTS) search β€”
201
- zero setup, instant, offline. For recall on *paraphrases* (a question that shares no words
202
- with the record it should find), opt into **local embeddings**:
203
-
204
- ```bash
205
- npm i -g @huggingface/transformers # one-time; a local model runtime
206
- hunch embed # embed your records (first run downloads ~90MB)
207
- hunch query --semantic "auth token expiry" # hybrid keyword + semantic recall
208
- ```
209
-
210
- > **Install it where `hunch` runs.** The runtime is resolved from `hunch`'s own
211
- > `node_modules`, so match the install scope: a globally-installed `hunch` needs the
212
- > global (`-g`) install above; running from a source checkout needs it in the repo
213
- > (`npm i @huggingface/transformers` there). If `hunch embed` reports the model "present
214
- > but failed to load," the scopes don't match. `hunch doctor` shows the active mode.
215
-
216
- Embeddings are **local and free** (no API β€” consistent with the subscription-only synthesis
217
- rule) and **opt-in** (the base install stays lean). The long-lived MCP server picks them up
218
- automatically once present. Vectors live in the derived SQLite index and are reconciled by
219
- content hash on every `hunch index`, so they never drift from the JSON source of truth. `hunch
220
- doctor` reports coverage; tune the blend with `HUNCH_RRF_W_FTS` / `HUNCH_RRF_W_SEM` / `HUNCH_RRF_K`.
221
-
222
- ## What makes the capture good (not just "changed N files")
223
-
224
- Even with **no LLM**, the write path runs a structured **diff analysis** β€” added /
225
- removed / changed symbols, new and dropped dependencies, and which invariants a change
226
- touches β€” so an auto-captured decision reads like *"introduced `verifySession`,
227
- `revokeSession`; removed `login`; new dep: redis; touches con_004"*, with breaking-change
228
- consequences. With the `claude` CLI present it upgrades to full LLM synthesis; otherwise
229
- it stays useful offline. Either way every record is **advisory and cheap to discard** β€”
230
- `hunch review` lets you promote the good ones to human-confirmed.
231
-
232
- ## Working as a team
233
-
234
- The `.hunch/` JSON is the **source of truth**: diffable, reviewable in PRs, and synced
235
- for free over `git push` / `pull`. `hunch init` also registers a **git merge driver** so
236
- concurrent edits to the graph merge **by record id** instead of throwing conflict markers
237
- (human-confirmed beats auto, then higher confidence, then recency). The routing lives in a
238
- committed `.gitattributes`; the per-clone driver definition is set up by each teammate's
239
- `hunch init`.
240
-
241
- ## Continuous learning (CI)
242
-
243
- The decision half of the loop is automatic (the post-commit hook). Light up the **bug /
244
- constraint half** by wrapping your test run with `hunch test`:
245
-
246
- ```bash
247
- hunch test # runs `npm test`; capture failures β†’ Bugs, resolve fixed ones
248
- hunch test -- pytest -q # any runner: pass the command after `--`
249
- ```
250
-
251
- It parses TAP and the `node:test` spec reporter, captures each failing test as a **Bug**
252
- (ranked suspects; a recurrence or substantiated high-severity failure auto-promotes a
253
- do-not-break **Constraint**), and marks a previously-open bug **fixed** once its test passes
254
- again. It preserves the runner's exit code, so it's a drop-in CI step:
255
-
256
- ```yaml
257
- # .github/workflows/ci.yml
258
- - run: npm ci
259
- - run: npx hunch test # exits non-zero on failure, just like the suite
260
- - run: | # persist what was learned (optional)
261
- git add .hunch && git commit -m "chore(hunch): capture test run" || true
262
- git push || true
263
- ```
264
-
265
- Repair drift after refactors with **`hunch stale --resync`** (re-synthesizes stale decisions
266
- from their commits via the LLM).
267
-
268
- ### Block a PR that breaks memory (CI Constraint Guard)
269
-
270
- Memory that only *advises* gets ignored. `hunch ci` scaffolds a GitHub Action that turns
271
- Hunch into a **merge gate**: on every pull request it runs `hunch check` over the diff,
272
- posts a sticky comment citing the affected `con_`/`dec_` ids, and **fails the check** when
273
- the PR breaks a *direct, high-confidence, non-stale* blocking invariant, re-adds
274
- deliberately-retired code, or contradicts an in-force decision.
275
-
276
- ```bash
277
- hunch ci # writes .github/workflows/hunch-guard.yml β€” commit it
278
- ```
279
-
280
- It reasons over **the diff plus the constraints committed in the same git history**, so the
281
- comment says exactly which decision a change violates ("breaks `con_004` β€” server-side
282
- revocation, from `dec_017`"). Make *Hunch Guard* a required status check in branch protection
283
- to enforce on merge. The hardened strict gate only blocks on high-confidence, non-stale
284
- invariants β€” stale / low-confidence / blast-radius hits stay advisory in the comment β€” so
285
- it's safe to require on a shared repo. Under the hood it's just
286
- `hunch check --base origin/<target> --strict --format markdown`.
287
-
288
- ## Maintenance
289
-
290
- - **`hunch doctor`** β€” is git healthy? are you on the subscription path or the offline
291
- heuristic? what schema version is on disk? how many records?
292
- - **`hunch migrate`** β€” after upgrading Hunch, bring old `.hunch/` records up to the
293
- current schema (old records are migrated in memory on every read, so reads never break;
294
- `migrate` persists the upgrade and never drops a record it can't migrate).
295
- - **`hunch compact --apply`** β€” auto-captured drafts accumulate; compaction prunes the
296
- low-value ones (rejected / superseded / stale drafts, resolved low-confidence bugs).
297
- It **never** removes an accepted/human-confirmed decision, an open bug, a constraint, or
298
- any record another record still references. Run without `--apply` first to preview.
299
-
300
- ## Where it's stored
301
-
302
- ```
303
- .hunch/
304
- β”œβ”€ components/ one JSON file per architecture node (curated, PR-reviewable)
305
- β”œβ”€ decisions/ one JSON file per Decision (ADR)
306
- β”œβ”€ bugs/ one JSON file per Bug
307
- β”œβ”€ constraints/ one JSON file per Constraint (invariant)
308
- β”œβ”€ symbols/index.json the symbol graph (high-cardinality, single file)
309
- β”œβ”€ edges/index.json the dependency graph
310
- β”œβ”€ manifest.json on-disk schema version
311
- └─ hunch.sqlite DERIVED FTS5 + graph index, rebuilt by `hunch index` (gitignored)
312
- ```
313
-
314
- Low-volume entities are one file per record so they read cleanly in a PR; the
315
- high-cardinality symbol/edge graphs are single id-sorted arrays to keep git noise down.
316
- SQLite is a throwaway index rebuilt from the JSON β€” only the JSON is committed.
317
-
318
- > Note: the on-disk directory is still `.hunch/` (and the MCP tools are still `hunch_*`)
319
- > for backward compatibility with existing graphs. A future release may migrate these to
320
- > `.hunch/` / `hunch_*`.
321
-
322
- ## Architecture
323
-
324
- ```
325
- src/
326
- β”œβ”€ core/ types (Zod schema), ids, paths, glob, schema migration, atomic file I/O
327
- β”œβ”€ store/ JSON source of truth ←→ SQLite/FTS5 derived index; merge driver; compaction
328
- β”œβ”€ extractors/ tree-sitter parse, git introspection, the indexer
329
- β”œβ”€ synthesis/ write path: Claude-CLI (subscription) or deterministic fallback
330
- β”œβ”€ mcp/ MCP stdio server (the hunch_* tools)
331
- β”œβ”€ integrations/ post-commit hook, CLAUDE.md writer, .mcp.json + slash commands, merge driver
332
- └─ cli/ commander entrypoint
333
- ```
334
-
335
- ## VS Code
336
-
337
- A companion **[VS Code extension](vscode-extension/)** (on
338
- [Open VSX](https://open-vsx.org/extension/davesheffer/hunch-vscode) β€” works in
339
- VS Code / Cursor / Windsurf / VSCodium) brings the graph into the editor: a tree of
340
- decisions / invariants / bugs / **bug-lineage** / fragility / **stale records**, a
341
- **CodeLens** summary + per-symbol bug/fragility marks, **hover** with bug history,
342
- invariants surfaced in the **Problems panel**, overview-ruler hotspot marks, an
343
- interactive **component graph**, fuzzy **search**, and a status-bar invariant counter.
344
- It reads the committed `.hunch/` JSON directly (no server, no native deps); writes
345
- delegate to the `hunch` CLI.
346
-
347
- ## Notable engineering decisions
348
-
349
- - **Subscription-billed synthesis, never the API.** The write path drives your Claude
350
- subscription via the `claude` CLI; `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` are
351
- stripped from the child env to force subscription auth (they outrank it in headless
352
- mode). A deterministic, no-LLM fallback means the loop never hard-requires credentials.
353
- - **Native `tree-sitter` (0.21.1) + `tree-sitter-typescript`, not web-tree-sitter.** The
354
- prebuilt WASM grammars have an ABI incompatible with current `web-tree-sitter`; the
355
- native bindings ship Node-20 prebuilds (no compiler) and a simpler synchronous API.
356
- - **`better-sqlite3` pinned to `12.9.0`** β€” 12.10.x ships no Node-20 prebuild and would
357
- force a source compile; 12.9.0 has the Node-20 (ABI 115) prebuild.
358
- - **Atomic, durable writes.** All `.hunch/` writes go through a temp-file + rename, with a
359
- Windows-safe fallback, so an interrupted write can't truncate the index; `put`/`delete`
360
- refuse to rewrite a corrupt index rather than flatten it.
361
-
362
- ## Develop
363
-
364
- ```bash
365
- npm run typecheck # tsc --noEmit
366
- npm test # node:test suite (store, graph, parse, indexer, synthesis, migrate, merge, compact)
367
- npm run hunch -- why src/store/hunchStore.ts # run the CLI from source via tsx, no build
368
- ```
369
-
370
- See [DESIGN.md](DESIGN.md) for the full spec. PR/CI enforcement now ships as the
371
- **CI Constraint Guard** (`hunch ci`). Still deferred by design: a hosted web dashboard
372
- and multi-repo support.
1
+ # 🧠 Hunch β€” Engineering Memory OS
2
+
3
+ > Git stores *what* the code is. **Hunch** stores ***why*** it is that way β€” a persistent,
4
+ > git-native reasoning graph over your codebase, surfaced to Claude Code at reasoning time
5
+ > so the AI stops re-deriving understanding and stops undoing intentional design.
6
+
7
+ ## The problem
8
+
9
+ Every AI coding session starts from zero. The model re-reads your code, re-guesses the
10
+ intent, and happily "fixes" the thing you deliberately did last month β€” because the
11
+ *reasoning* behind the code lives in PRs, Slack, and people's heads, not in the repo.
12
+
13
+ **Hunch** captures that reasoning as a **byproduct of normal work** β€” commits and test
14
+ failures β€” stores it as a git-tracked graph next to your code, and feeds it back to
15
+ Claude Code so every session is grounded in the decisions, bugs, and invariants that
16
+ came before. Local-first, no documentation toil, no SaaS.
17
+
18
+ ## How it works
19
+
20
+ ```
21
+ commit / test failure .hunch/ (git-tracked JSON) Claude Code
22
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
23
+ β”‚ post-commit hook ───┼────────▢│ Decisions (why a change) │────────▢│ MCP tools β”‚
24
+ β”‚ record-bug ───┼────────▢│ Bugs (root causes) β”‚ read β”‚ /hunch-* cmds β”‚
25
+ β”‚ structured diff + β”‚ write β”‚ Constraints(invariants) │◀────────│ CLAUDE.md β”‚
26
+ β”‚ Claude (or heuristic) β”‚ β”‚ Components / Symbols/Edges β”‚ β”‚ CLI β”‚
27
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
28
+ ```
29
+
30
+ - **Index** (no LLM): tree-sitter parses your repo into a symbol/dependency graph β€”
31
+ functions, call edges, imports, components β€” plus churn and fan-in metrics.
32
+ - **Learn**: each commit becomes a structured **Decision** (an ADR); a failing test
33
+ becomes a **Bug** with a ranked suspect list; recurring or severe bugs are promoted
34
+ into **Constraints** (do-not-break invariants) and raise a component's *fragility*.
35
+ - **Ground**: Claude Code reads it through an **MCP server**, an auto-maintained
36
+ **`CLAUDE.md`**, and **slash commands** β€” every answer cites `provenance`
37
+ (source + confidence + evidence), so nothing is a blind assertion.
38
+
39
+ ## Getting started
40
+
41
+ ### 1. Install it
42
+
43
+ ```bash
44
+ npm install -g @davesheffer/hunch # puts `hunch` on your PATH
45
+ ```
46
+
47
+ Or run from source (for hacking on Hunch itself):
48
+
49
+ ```bash
50
+ npm install
51
+ npm run build # compiles to dist/
52
+ npm link # optional: puts a global `hunch` on your PATH
53
+ ```
54
+
55
+ Either way you then type `hunch …`. From a source checkout without `npm link`, use
56
+ `node dist/cli/index.js …` (or `npm run hunch -- …` to run via tsx). The rest of this
57
+ README uses `hunch` for brevity.
58
+
59
+ ### 2. (Recommended) make a coding-assistant CLI available
60
+
61
+ Hunch's LLM synthesis is billed to **your subscription** through a coding-assistant
62
+ CLI β€” **never** a pay-per-token API key (the API key is stripped from the child env).
63
+ Hunch auto-detects the first one present, in this order:
64
+
65
+ | CLI | Subscription | Detected by |
66
+ |---|---|---|
67
+ | `claude` (Claude Code) | Claude Pro/Max | `claude --version` |
68
+ | `codex` (OpenAI Codex) | ChatGPT Plus/Pro | `codex --version` |
69
+ | `cursor-agent` (Cursor) | Cursor | `cursor-agent --version` |
70
+
71
+ If none is installed, Hunch still works using a deterministic structural heuristic
72
+ (lower-confidence drafts). `hunch doctor` tells you which mode you're in; force one
73
+ with `HUNCH_SYNTH_PROVIDER=claude-cli|codex-cli|cursor-agent|deterministic`.
74
+
75
+ ### 3. Initialize the repo you want a memory for
76
+
77
+ ```bash
78
+ hunch init # scaffold .hunch/, index, install the post-commit hook,
79
+ # write .mcp.json + slash commands + CLAUDE.md, register the merge driver
80
+ hunch backfill --since 90d # cold start: seed decisions from recent git history
81
+ ```
82
+
83
+ `init` writes a `.mcp.json` pointing at *this machine's* node + Hunch β€” so **reload
84
+ Claude Code in the repo** afterward to pick up the `hunch_*` tools. (Each teammate runs
85
+ `hunch init` once to wire up their own clone; the captured `.hunch/` content is shared
86
+ via git.)
87
+
88
+ ### 4. Use it
89
+
90
+ ```bash
91
+ hunch why src/auth/session.ts # the decisions / bugs / invariants behind a file
92
+ hunch doctor # check git, schema version, and synthesis mode
93
+ ```
94
+
95
+ …and in Claude Code, just ask: *"why is the session module built this way?"*
96
+
97
+ ## Two ways to use it
98
+
99
+ **Through Claude Code (the point).** Once the MCP server is registered, ask questions
100
+ normally and Claude consults Hunch, or invoke the slash commands:
101
+
102
+ | Slash command | What it does |
103
+ |---|---|
104
+ | `/hunch-why <file\|symbol>` | the decisions, invariants, and bug history behind it β€” with citations |
105
+ | `/hunch-fix <bug>` | fix a bug grounded in past root causes, blast radius, and constraints |
106
+ | `/hunch-fragile` | a fragility report (the riskiest code, with evidence) |
107
+
108
+ The MCP tools Claude calls under the hood: `hunch_why`, `hunch_query`,
109
+ `hunch_check_constraints`, `hunch_get_dependents` (blast radius), `hunch_blast_radius`
110
+ (dependent files + near-violations a change could break indirectly), `hunch_bug_lineage`,
111
+ `hunch_context` (surgical minimal slice for a task), `hunch_timeline` (a target's decision
112
+ history over time), `hunch_record_decision` (write-back). `hunch_why` and `hunch_context`
113
+ take an optional `as_of` (commit/tag/branch) to **time-travel** the graph to a past state.
114
+
115
+ ### Works with any MCP assistant
116
+
117
+ The Hunch MCP server is **client-agnostic** β€” one `.hunch/` graph powers every
118
+ assistant. `hunch init` scaffolds each tool's MCP config + ambient grounding so
119
+ they all consult the same memory:
120
+
121
+ | Assistant | MCP config | Grounding file |
122
+ |---|---|---|
123
+ | Claude Code | `.mcp.json` | `CLAUDE.md` + `/hunch-*` slash commands |
124
+ | Cursor | `.cursor/mcp.json` | `.cursor/rules/hunch.mdc` (always-applied) |
125
+ | VS Code (Copilot) | `.vscode/mcp.json` | `.github/copilot-instructions.md` |
126
+ | Codex CLI | `.codex/config.toml` | `AGENTS.md` |
127
+ | Windsurf | `.windsurf/mcp_config.json` | `.windsurf/rules/hunch.md` (always-on) |
128
+ | Anything else | β€” | `AGENTS.md` (cross-tool standard) |
129
+
130
+ Each writer **merges** into existing files (other MCP servers and your own prose are
131
+ preserved) and is idempotent. Opt out with `hunch init --no-providers`.
132
+
133
+ **Through the CLI** β€” the same graph, from your terminal:
134
+
135
+ | Command | What |
136
+ |---|---|
137
+ | `hunch init` | scaffold `.hunch/`, index, install hook + merge driver, auto-install the advisory pre-commit guard, install the **Claude Code agent hooks**, and wire up **every assistant** (Claude Code, Cursor, VS Code/Copilot, Codex, Windsurf, AGENTS.md). Flags: `--no-enforce`, `--enforce-strict`, `--no-providers`, `--no-agent-hooks`, `--firmness <level>` |
138
+ | `hunch index` | parse repo β†’ symbols / edges / components (deterministic, no LLM) |
139
+ | `hunch backfill --since 90d` | replay git history β†’ seed decisions |
140
+ | `hunch sync [sha]` | turn a commit into a Decision (run automatically by the hook) |
141
+ | `hunch record-bug --test <id> --message <m>` | capture a Bug from a failing test |
142
+ | `hunch record-constraint "<statement>" [--scope <globs>] [--severity advisory\|warning\|blocking] [--type …] [--rationale <t>] [--source-decision <id>]` | record an invariant the code must not break (what `hunch check` + the strict agent hook enforce) |
143
+ | `hunch firmness [off\|advisory\|firm\|strict]` | get/set how firmly the agent hook enforces Hunch before edits (no arg prints the current level) |
144
+ | `hunch test [cmd…]` | run the suite (default `npm test`); auto-capture failures as Bugs (suspects + recurrenceβ†’Constraints), mark passing tests' bugs fixed |
145
+ | `hunch why <path\|symbol> [--as-of <ref>]` | decisions / bugs / constraints explaining a target (flags `⚠STALE`); `--as-of` time-travels to what was believed at a commit/tag/branch |
146
+ | `hunch timeline <path\|symbol>` | the decision history for a target β€” what was believed, its valid-time window, and what superseded it |
147
+ | `hunch supersede <old> --by <new>` | mark one decision as replaced by another: closes the old one's valid-time window (invalidate, don't delete) |
148
+ | `hunch query "<q>" [--semantic]` | full-text + graph search (`--semantic` blends in local embeddings) |
149
+ | `hunch embed` | generate local embeddings for semantic recall (opt-in; needs `@huggingface/transformers`) |
150
+ | `hunch context <path\|symbol> [--as-of <ref>]` | minimal relevant slice for a task: invariants β†’ decisions β†’ bugs β†’ blast radius (`--as-of` time-travels) |
151
+ | `hunch fragile` | ranked fragility report with evidence |
152
+ | `hunch check [--staged\|--commit <sha>\|--base <ref>] [--strict] [--format text\|markdown] [--blast]` | guardrail: flag changes touching a do-not-break invariant **directly or via blast radius** (a guarded file that depends on what you changed), **and changes that re-introduce something a decision deliberately retired** (the Regression Guard). `--base <ref>` checks a PR's diff (for CI); `--format markdown` emits a PR comment; `--strict` fails only on a direct, high-confidence, non-stale **blocking** invariant; `--blast` prints the dependency fan-out |
153
+ | `hunch ci` | scaffold the **CI Constraint Guard** β€” a GitHub Action that runs `hunch check` on every PR, comments the affected invariants/decisions, and fails on a blocking one |
154
+ | `hunch stale [--resync]` | drift: records whose files changed after last verification (`--resync` regenerates stale decisions from their commits) |
155
+ | `hunch review [--accept <id>\|--reject <id>]` | curate: triage / promote / drop low-confidence drafts |
156
+ | `hunch migrate` | upgrade `.hunch/` records to the current schema version |
157
+ | `hunch compact [--apply]` | prune low-value drafts to bound growth (dry-run by default) |
158
+ | `hunch doctor` | environment diagnostics (git, auth mode, schema version, counts) |
159
+ | `hunch mcp` | start the MCP server over stdio (Claude Code connects here) |
160
+
161
+ ## Grounding the agent automatically (firmness)
162
+
163
+ Telling an assistant "consult Hunch first" in a prompt is advisory β€” it drifts. `hunch
164
+ init` instead installs two **Claude Code agent hooks** (in `.claude/settings.json`) so the
165
+ grounding is enforced by the harness, not by the model's memory:
166
+
167
+ - **Before every edit** (`PreToolUse` on `Edit`/`Write`/`MultiEdit`) Hunch injects the
168
+ relevant slice for the file being touched β€” its decisions, invariants, bug history, and
169
+ blast radius β€” straight into the model's context.
170
+ - **On every prompt** (`UserPromptSubmit`) it reminds the agent to query Hunch.
171
+
172
+ How hard it pushes is one committed knob β€” set it once, it applies to the whole team:
173
+
174
+ ```bash
175
+ hunch firmness # print the current level
176
+ hunch firmness strict # change it (takes effect on the next edit; no restart)
177
+ ```
178
+
179
+ | Level | Before an edit |
180
+ |---|---|
181
+ | `off` | nothing (hook is a no-op) |
182
+ | `advisory` *(default)* | inject the relevant Hunch slice as context |
183
+ | `firm` | advisory **+** explicitly flag invariants in the file's scope |
184
+ | `strict` | firm **+** **deny** an edit that hits a *blocking* invariant (directly or via blast radius), feeding the invariant back as the refusal reason |
185
+
186
+ Before an edit, the hook also grounds the agent in anything an in-force decision
187
+ **deliberately retired** from that file ("don't re-introduce `login` here β€” dec_017 removed
188
+ it"). The actual gate is at commit time: `hunch check` runs the **Regression Guard** over
189
+ the staged diff and, under `--strict`, fails the commit when a change re-adds a retired
190
+ symbol/dependency tied to a blocking invariant (otherwise it warns).
191
+
192
+ The hook never breaks your flow: any error or unrecognized input emits nothing and exits
193
+ 0, and it stays silent on files Hunch hasn't learned yet. `strict` only bites once you have
194
+ **blocking** constraints recorded (`hunch record-constraint … --severity blocking`) β€” with
195
+ none, every level degrades to context-only. Opt out of the hooks entirely with `hunch init
196
+ --no-agent-hooks`.
197
+
198
+ ## Semantic search (optional)
199
+
200
+ By default `hunch query` and the `hunch_query` MCP tool use fast keyword (FTS) search β€”
201
+ zero setup, instant, offline. For recall on *paraphrases* (a question that shares no words
202
+ with the record it should find), opt into **local embeddings**:
203
+
204
+ ```bash
205
+ npm i -g @huggingface/transformers # one-time; a local model runtime
206
+ hunch embed # embed your records (first run downloads ~90MB)
207
+ hunch query --semantic "auth token expiry" # hybrid keyword + semantic recall
208
+ ```
209
+
210
+ > **Install it where `hunch` runs.** The runtime is resolved from `hunch`'s own
211
+ > `node_modules`, so match the install scope: a globally-installed `hunch` needs the
212
+ > global (`-g`) install above; running from a source checkout needs it in the repo
213
+ > (`npm i @huggingface/transformers` there). If `hunch embed` reports the model "present
214
+ > but failed to load," the scopes don't match. `hunch doctor` shows the active mode.
215
+
216
+ Embeddings are **local and free** (no API β€” consistent with the subscription-only synthesis
217
+ rule) and **opt-in** (the base install stays lean). The long-lived MCP server picks them up
218
+ automatically once present. Vectors live in the derived SQLite index and are reconciled by
219
+ content hash on every `hunch index`, so they never drift from the JSON source of truth. `hunch
220
+ doctor` reports coverage; tune the blend with `HUNCH_RRF_W_FTS` / `HUNCH_RRF_W_SEM` / `HUNCH_RRF_K`.
221
+
222
+ ## What makes the capture good (not just "changed N files")
223
+
224
+ Even with **no LLM**, the write path runs a structured **diff analysis** β€” added /
225
+ removed / changed symbols, new and dropped dependencies, and which invariants a change
226
+ touches β€” so an auto-captured decision reads like *"introduced `verifySession`,
227
+ `revokeSession`; removed `login`; new dep: redis; touches con_004"*, with breaking-change
228
+ consequences. With the `claude` CLI present it upgrades to full LLM synthesis; otherwise
229
+ it stays useful offline. Either way every record is **advisory and cheap to discard** β€”
230
+ `hunch review` lets you promote the good ones to human-confirmed.
231
+
232
+ ## Working as a team
233
+
234
+ The `.hunch/` JSON is the **source of truth**: diffable, reviewable in PRs, and synced
235
+ for free over `git push` / `pull`. `hunch init` also registers a **git merge driver** so
236
+ concurrent edits to the graph merge **by record id** instead of throwing conflict markers
237
+ (human-confirmed beats auto, then higher confidence, then recency). The routing lives in a
238
+ committed `.gitattributes`; the per-clone driver definition is set up by each teammate's
239
+ `hunch init`. The graph is **OS-agnostic**: paths are stored and matched in POSIX form, and
240
+ an installed Hunch registers its MCP server by package name (`npx -y @davesheffer/hunch`)
241
+ rather than an absolute path β€” so a teammate on Windows, macOS, or Linux shares the same
242
+ `.hunch/` and the same committed config without per-machine fixups.
243
+
244
+ ## Continuous learning (CI)
245
+
246
+ The decision half of the loop is automatic (the post-commit hook). Light up the **bug /
247
+ constraint half** by wrapping your test run with `hunch test`:
248
+
249
+ ```bash
250
+ hunch test # runs `npm test`; capture failures β†’ Bugs, resolve fixed ones
251
+ hunch test -- pytest -q # any runner: pass the command after `--`
252
+ ```
253
+
254
+ It parses TAP and the `node:test` spec reporter, captures each failing test as a **Bug**
255
+ (ranked suspects; a recurrence or substantiated high-severity failure auto-promotes a
256
+ do-not-break **Constraint**), and marks a previously-open bug **fixed** once its test passes
257
+ again. It preserves the runner's exit code, so it's a drop-in CI step:
258
+
259
+ ```yaml
260
+ # .github/workflows/ci.yml
261
+ - run: npm ci
262
+ - run: npx hunch test # exits non-zero on failure, just like the suite
263
+ - run: | # persist what was learned (optional)
264
+ git add .hunch && git commit -m "chore(hunch): capture test run" || true
265
+ git push || true
266
+ ```
267
+
268
+ Repair drift after refactors with **`hunch stale --resync`** (re-synthesizes stale decisions
269
+ from their commits via the LLM).
270
+
271
+ ### Block a PR that breaks memory (CI Constraint Guard)
272
+
273
+ Memory that only *advises* gets ignored. `hunch ci` scaffolds a GitHub Action that turns
274
+ Hunch into a **merge gate**: on every pull request it runs `hunch check` over the diff,
275
+ posts a sticky comment citing the affected `con_`/`dec_` ids, and **fails the check** when
276
+ the PR breaks a *direct, high-confidence, non-stale* blocking invariant, re-adds
277
+ deliberately-retired code, or contradicts an in-force decision.
278
+
279
+ ```bash
280
+ hunch ci # writes .github/workflows/hunch-guard.yml β€” commit it
281
+ ```
282
+
283
+ It reasons over **the diff plus the constraints committed in the same git history**, so the
284
+ comment says exactly which decision a change violates ("breaks `con_004` β€” server-side
285
+ revocation, from `dec_017`"). Make *Hunch Guard* a required status check in branch protection
286
+ to enforce on merge. The hardened strict gate only blocks on high-confidence, non-stale
287
+ invariants β€” stale / low-confidence / blast-radius hits stay advisory in the comment β€” so
288
+ it's safe to require on a shared repo. Under the hood it's just
289
+ `hunch check --base origin/<target> --strict --format markdown`.
290
+
291
+ ## Maintenance
292
+
293
+ - **`hunch doctor`** β€” is git healthy? are you on the subscription path or the offline
294
+ heuristic? what schema version is on disk? how many records?
295
+ - **`hunch migrate`** β€” after upgrading Hunch, bring old `.hunch/` records up to the
296
+ current schema (old records are migrated in memory on every read, so reads never break;
297
+ `migrate` persists the upgrade and never drops a record it can't migrate).
298
+ - **`hunch compact --apply`** β€” auto-captured drafts accumulate; compaction prunes the
299
+ low-value ones (rejected / superseded / stale drafts, resolved low-confidence bugs).
300
+ It **never** removes an accepted/human-confirmed decision, an open bug, a constraint, or
301
+ any record another record still references. Run without `--apply` first to preview.
302
+
303
+ ## Where it's stored
304
+
305
+ ```
306
+ .hunch/
307
+ β”œβ”€ components/ one JSON file per architecture node (curated, PR-reviewable)
308
+ β”œβ”€ decisions/ one JSON file per Decision (ADR)
309
+ β”œβ”€ bugs/ one JSON file per Bug
310
+ β”œβ”€ constraints/ one JSON file per Constraint (invariant)
311
+ β”œβ”€ symbols/index.json the symbol graph (high-cardinality, single file)
312
+ β”œβ”€ edges/index.json the dependency graph
313
+ β”œβ”€ manifest.json on-disk schema version
314
+ └─ hunch.sqlite DERIVED FTS5 + graph index, rebuilt by `hunch index` (gitignored)
315
+ ```
316
+
317
+ Low-volume entities are one file per record so they read cleanly in a PR; the
318
+ high-cardinality symbol/edge graphs are single id-sorted arrays to keep git noise down.
319
+ SQLite is a throwaway index rebuilt from the JSON β€” only the JSON is committed.
320
+
321
+ > Note: the on-disk directory is still `.hunch/` (and the MCP tools are still `hunch_*`)
322
+ > for backward compatibility with existing graphs. A future release may migrate these to
323
+ > `.hunch/` / `hunch_*`.
324
+
325
+ ## Architecture
326
+
327
+ ```
328
+ src/
329
+ β”œβ”€ core/ types (Zod schema), ids, paths, glob, schema migration, atomic file I/O
330
+ β”œβ”€ store/ JSON source of truth ←→ SQLite/FTS5 derived index; merge driver; compaction
331
+ β”œβ”€ extractors/ tree-sitter parse, git introspection, the indexer
332
+ β”œβ”€ synthesis/ write path: Claude-CLI (subscription) or deterministic fallback
333
+ β”œβ”€ mcp/ MCP stdio server (the hunch_* tools)
334
+ β”œβ”€ integrations/ post-commit hook, CLAUDE.md writer, .mcp.json + slash commands, merge driver
335
+ └─ cli/ commander entrypoint
336
+ ```
337
+
338
+ ## VS Code
339
+
340
+ A companion **[VS Code extension](vscode-extension/)** (on
341
+ [Open VSX](https://open-vsx.org/extension/davesheffer/hunch-vscode) β€” works in
342
+ VS Code / Cursor / Windsurf / VSCodium) brings the graph into the editor: a tree of
343
+ decisions / invariants / bugs / **bug-lineage** / fragility / **stale records**, a
344
+ **CodeLens** summary + per-symbol bug/fragility marks, **hover** with bug history,
345
+ invariants surfaced in the **Problems panel**, overview-ruler hotspot marks, an
346
+ interactive **component graph**, fuzzy **search**, and a status-bar invariant counter.
347
+ It reads the committed `.hunch/` JSON directly (no server, no native deps); writes
348
+ delegate to the `hunch` CLI.
349
+
350
+ ## Notable engineering decisions
351
+
352
+ - **Subscription-billed synthesis, never the API.** The write path drives your Claude
353
+ subscription via the `claude` CLI; `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` are
354
+ stripped from the child env to force subscription auth (they outrank it in headless
355
+ mode). A deterministic, no-LLM fallback means the loop never hard-requires credentials.
356
+ - **Native `tree-sitter` (0.21.1) + `tree-sitter-typescript`, not web-tree-sitter.** The
357
+ prebuilt WASM grammars have an ABI incompatible with current `web-tree-sitter`; the
358
+ native bindings ship Node-20 prebuilds (no compiler) and a simpler synchronous API.
359
+ - **`better-sqlite3` pinned to `12.9.0`** β€” 12.10.x ships no Node-20 prebuild and would
360
+ force a source compile; 12.9.0 has the Node-20 (ABI 115) prebuild.
361
+ - **Atomic, durable writes.** All `.hunch/` writes go through a temp-file + rename, with a
362
+ Windows-safe fallback, so an interrupted write can't truncate the index; `put`/`delete`
363
+ refuse to rewrite a corrupt index rather than flatten it.
364
+ - **OS-agnostic by construction.** Git emits `/`-separated paths on every platform, so the
365
+ graph is the source of truth in POSIX form; any free-form target is canonicalized before
366
+ comparison, so `src\auth\x.ts` and `src/auth/x.ts` resolve to the same records. Generated
367
+ configs that get committed reference Hunch by package name, never a machine-local path.
368
+
369
+ ## Develop
370
+
371
+ ```bash
372
+ npm run typecheck # tsc --noEmit
373
+ npm test # node:test suite (store, graph, parse, indexer, synthesis, migrate, merge, compact)
374
+ npm run hunch -- why src/store/hunchStore.ts # run the CLI from source via tsx, no build
375
+ ```
376
+
377
+ See [DESIGN.md](DESIGN.md) for the full spec. PR/CI enforcement now ships as the
378
+ **CI Constraint Guard** (`hunch ci`). Still deferred by design: a hosted web dashboard
379
+ and multi-repo support.