@davesheffer/hunch 0.15.2 β†’ 0.15.4

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,9 +1,35 @@
1
1
  # 🧠 Hunch β€” Engineering Memory OS
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@davesheffer/hunch?color=2742ff&label=npm)](https://www.npmjs.com/package/@davesheffer/hunch)
4
+ [![npm downloads](https://img.shields.io/npm/dw/@davesheffer/hunch?color=2742ff)](https://www.npmjs.com/package/@davesheffer/hunch)
5
+ [![license](https://img.shields.io/npm/l/@davesheffer/hunch?color=2742ff)](LICENSE)
6
+ [![node](https://img.shields.io/badge/node-%E2%89%A520-2742ff)](https://nodejs.org)
7
+ [![MCP](https://img.shields.io/badge/MCP-native-2742ff)](https://modelcontextprotocol.io)
8
+
3
9
  > Git stores *what* the code is. **Hunch** stores ***why*** it is that way β€” a persistent,
4
10
  > git-native reasoning graph over your codebase, surfaced to Claude Code at reasoning time
5
11
  > so the AI stops re-deriving understanding and stops undoing intentional design.
6
12
 
13
+ ### ⚑ 60-second start
14
+
15
+ ```bash
16
+ npm i -g @davesheffer/hunch
17
+ cd your-repo && hunch init && hunch backfill --since 90d
18
+ hunch why src/some/file.ts # …or just ask Claude Code: "why is X built this way?"
19
+ ```
20
+
21
+ <sub>Works with **Claude Code, Cursor, Copilot & Windsurf** from one shared graph.</sub>
22
+
23
+ ### πŸ“š **[Read the full documentation β†’ hunch-pi.vercel.app/docs](https://hunch-pi.vercel.app/docs)**
24
+
25
+ The docs site is the complete reference β€” setup, every CLI command and MCP tool, the
26
+ guards, troubleshooting. This README is the tour. Jump to:
27
+ [Install](https://hunch-pi.vercel.app/docs#install) Β·
28
+ [MCP setup](https://hunch-pi.vercel.app/docs#mcp) Β·
29
+ [Firmness](https://hunch-pi.vercel.app/docs#firmness) Β·
30
+ [CLI reference](https://hunch-pi.vercel.app/docs#cli) Β·
31
+ [Troubleshooting](https://hunch-pi.vercel.app/docs#doctor)
32
+
7
33
  ## The problem
8
34
 
9
35
  Every AI coding session starts from zero. The model re-reads your code, re-guesses the
@@ -32,385 +58,123 @@ came before. Local-first, no documentation toil, no SaaS.
32
58
  - **Learn**: each commit becomes a structured **Decision** (an ADR); a failing test
33
59
  becomes a **Bug** with a ranked suspect list; recurring or severe bugs are promoted
34
60
  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
61
+ - **Ground**: any MCP assistant reads it through an **MCP server**, an auto-maintained
36
62
  **`CLAUDE.md`**, and **slash commands** β€” every answer cites `provenance`
37
63
  (source + confidence + evidence), so nothing is a blind assertion.
38
64
 
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:
65
+ β†’ Concepts in depth: [the reasoning graph](https://hunch-pi.vercel.app/docs#graph) Β·
66
+ [provenance](https://hunch-pi.vercel.app/docs#provenance) Β·
67
+ [time-travel](https://hunch-pi.vercel.app/docs#time-travel)
64
68
 
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
- > **Prefer `hunch init` (project-local `.mcp.json`) over a global `claude mcp add`.**
89
- > `.mcp.json` is registered by file path, so it's robust. A global `claude mcp add`
90
- > writes to `~/.claude.json` keyed by the raw working-directory string β€” and **on
91
- > Windows** that's a trap: drive letters are case-insensitive (`c:\` and `C:\` are the
92
- > same folder) but Claude Code compares the key case-sensitively, so it can create two
93
- > project blocks for one directory and a session that resolves to the *other* casing
94
- > sees no `hunch_*` tools (registration looked fine, the tools just aren't there). If
95
- > you hit this, run **`hunch doctor`** β€” on Windows it detects the split and heals it
96
- > (merging the MCP servers across both casings, after backing up `~/.claude.json`).
97
- > `hunch init` runs the same heal automatically at the end.
98
-
99
- ### 4. Use it
100
-
101
- ```bash
102
- hunch why src/auth/session.ts # the decisions / bugs / invariants behind a file
103
- hunch doctor # check git, schema version, and synthesis mode
104
- ```
105
-
106
- …and in Claude Code, just ask: *"why is the session module built this way?"*
107
-
108
- ## Two ways to use it
109
-
110
- **Through Claude Code (the point).** Once the MCP server is registered, ask questions
111
- normally and Claude consults Hunch, or invoke the slash commands:
112
-
113
- | Slash command | What it does |
114
- |---|---|
115
- | `/hunch-why <file\|symbol>` | the decisions, invariants, and bug history behind it β€” with citations |
116
- | `/hunch-fix <bug>` | fix a bug grounded in past root causes, blast radius, and constraints |
117
- | `/hunch-fragile` | a fragility report (the riskiest code, with evidence) |
118
-
119
- The MCP tools Claude calls under the hood: `hunch_why`, `hunch_query`,
120
- `hunch_check_constraints`, `hunch_get_dependents` (blast radius), `hunch_blast_radius`
121
- (dependent files + near-violations a change could break indirectly), `hunch_bug_lineage`,
122
- `hunch_context` (surgical minimal slice for a task), `hunch_timeline` (a target's decision
123
- history over time), `hunch_merge_verdict` (a cited BLOCK/WARN/PASS over a diff β€” see below),
124
- and the write-backs `hunch_record_decision`, `hunch_record_correction` (turn a correction into
125
- an enforced invariant β€” see below). `hunch_why` and `hunch_context` take an optional `as_of`
126
- (commit/tag/branch) to **time-travel** the graph to a past state.
127
-
128
- ### Works with any MCP assistant
129
-
130
- The Hunch MCP server is **client-agnostic** β€” one `.hunch/` graph powers every
131
- assistant. `hunch init` scaffolds each tool's MCP config + ambient grounding so
132
- they all consult the same memory:
133
-
134
- | Assistant | MCP config | Grounding file |
135
- |---|---|---|
136
- | Claude Code | `.mcp.json` | `CLAUDE.md` + `/hunch-*` slash commands |
137
- | Cursor | `.cursor/mcp.json` | `.cursor/rules/hunch.mdc` (always-applied) |
138
- | VS Code (Copilot) | `.vscode/mcp.json` | `.github/copilot-instructions.md` |
139
- | Codex CLI | `.codex/config.toml` | `AGENTS.md` |
140
- | Windsurf | `.windsurf/mcp_config.json` | `.windsurf/rules/hunch.md` (always-on) |
141
- | Anything else | β€” | `AGENTS.md` (cross-tool standard) |
142
-
143
- Each writer **merges** into existing files (other MCP servers and your own prose are
144
- preserved) and is idempotent. Opt out with `hunch init --no-providers`.
145
-
146
- **Through the CLI** β€” the same graph, from your terminal:
147
-
148
- | Command | What |
149
- |---|---|
150
- | `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>` |
151
- | `hunch index` | parse repo β†’ symbols / edges / components (deterministic, no LLM) |
152
- | `hunch backfill --since 90d` | replay git history β†’ seed decisions |
153
- | `hunch sync [sha]` | turn a commit into a Decision (run automatically by the hook) |
154
- | `hunch record-bug --test <id> --message <m>` | capture a Bug from a failing test |
155
- | `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) |
156
- | `hunch firmness [off\|advisory\|firm\|strict]` | get/set how firmly the agent hook enforces Hunch before edits (no arg prints the current level) |
157
- | `hunch test [cmd…]` | run the suite (default `npm test`); auto-capture failures as Bugs (suspects + recurrenceβ†’Constraints), mark passing tests' bugs fixed |
158
- | `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 |
159
- | `hunch timeline <path\|symbol>` | the decision history for a target β€” what was believed, its valid-time window, and what superseded it |
160
- | `hunch supersede <old> --by <new>` | mark one decision as replaced by another: closes the old one's valid-time window (invalidate, don't delete) |
161
- | `hunch query "<q>" [--semantic]` | full-text + graph search (`--semantic` blends in local embeddings) |
162
- | `hunch embed` | generate local embeddings for semantic recall (opt-in; needs `@huggingface/transformers`) |
163
- | `hunch context <path\|symbol> [--as-of <ref>]` | minimal relevant slice for a task: invariants β†’ decisions β†’ bugs β†’ blast radius (`--as-of` time-travels) |
164
- | `hunch fragile` | ranked fragility report with evidence |
165
- | `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 |
166
- | `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 |
167
- | `hunch stale [--resync]` | drift: records whose files changed after last verification (`--resync` regenerates stale decisions from their commits) |
168
- | `hunch review [--accept <id>\|--reject <id>]` | curate: triage / promote / drop low-confidence drafts |
169
- | `hunch migrate` | upgrade `.hunch/` records to the current schema version |
170
- | `hunch compact [--apply]` | prune low-value drafts to bound growth (dry-run by default) |
171
- | `hunch doctor` | environment diagnostics (git, auth mode, schema version, counts) |
172
- | `hunch mcp` | start the MCP server over stdio (Claude Code connects here) |
173
-
174
- ## Grounding the agent automatically (firmness)
175
-
176
- Telling an assistant "consult Hunch first" in a prompt is advisory β€” it drifts. `hunch
177
- init` instead installs two **Claude Code agent hooks** (in `.claude/settings.json`) so the
178
- grounding is enforced by the harness, not by the model's memory:
179
-
180
- - **Before every edit** (`PreToolUse` on `Edit`/`Write`/`MultiEdit`) Hunch injects the
181
- relevant slice for the file being touched β€” its decisions, invariants, bug history, and
182
- blast radius β€” straight into the model's context.
183
- - **On every prompt** (`UserPromptSubmit`) it reminds the agent to query Hunch.
184
-
185
- How hard it pushes is one committed knob β€” set it once, it applies to the whole team:
69
+ ## Getting started
186
70
 
187
71
  ```bash
188
- hunch firmness # print the current level
189
- hunch firmness strict # change it (takes effect on the next edit; no restart)
190
- ```
191
-
192
- | Level | Before an edit |
193
- |---|---|
194
- | `off` | nothing (hook is a no-op) |
195
- | `advisory` *(default)* | inject the relevant Hunch slice as context |
196
- | `firm` | advisory **+** explicitly flag invariants in the file's scope |
197
- | `strict` | firm **+** **deny** an edit that hits a *blocking* invariant (directly or via blast radius), feeding the invariant back as the refusal reason |
198
-
199
- Before an edit, the hook also grounds the agent in anything an in-force decision
200
- **deliberately retired** from that file ("don't re-introduce `login` here β€” dec_017 removed
201
- it"). The actual gate is at commit time: `hunch check` runs the **Regression Guard** over
202
- the staged diff and, under `--strict`, fails the commit when a change re-adds a retired
203
- symbol/dependency tied to a blocking invariant (otherwise it warns).
204
-
205
- The hook never breaks your flow: any error or unrecognized input emits nothing and exits
206
- 0, and it stays silent on files Hunch hasn't learned yet. `strict` only bites once you have
207
- **blocking** constraints recorded (`hunch record-constraint … --severity blocking`) β€” with
208
- none, every level degrades to context-only. Opt out of the hooks entirely with `hunch init
209
- --no-agent-hooks`.
210
-
211
- ## Never Twice: corrections become enforced invariants
212
-
213
- The most expensive failure in AI coding is being corrected and then *re-corrected* β€” you
214
- tell the agent "no, never call the pay-per-token API here," it complies once, and next
215
- session it does it again because the feedback was stored as advisory text, not enforced.
216
-
217
- Hunch closes that loop. When you correct the agent, it captures the rule as a **first-class
218
- Constraint** (provenance `human_confirmed`) via the `hunch_record_correction` MCP tool β€” and
219
- from then on the **same pre-edit hook + CI Constraint Guard** hold *every* assistant to it:
220
-
221
- ```text
222
- You: "no β€” never import lodash, we ship our own utils"
223
- Agent: calls hunch_record_correction({ rule: "never import lodash; use src/utils",
224
- scope_hint_file: "src/cart.ts", severity: "blocking" })
225
- β†’ con_… recorded. A later edit that adds `import _ from "lodash"` to that scope is DENIED
226
- (strict firmness) and the PR fails CI β€” in Cursor, Copilot, Windsurf, or Claude Code alike.
227
- ```
228
-
229
- The `UserPromptSubmit` hook nudges the agent to persist a rule whenever your prompt reads
230
- like a correction ("no…", "that's wrong", "never do X"), so capture is one frictionless step
231
- rather than a discipline. Scoping is conservative by default (the file you were in); a
232
- repo-wide (`**`) rule is only blocking when you pass `applies_to_all`, so one correction
233
- can't silently gate the whole tree. Because it's the *same* constraint machinery, a
234
- correction is enforced exactly like a hand-authored invariant β€” see firmness above.
235
-
236
- ## Causal Merge Verdict: does this change re-open a closed bug?
237
-
238
- A diff-only reviewer (CodeRabbit, Greptile) sees *what* changed. It can't see that the line
239
- you're deleting is the fix for an incident, or that the symbol you're re-adding was
240
- deliberately retired. Hunch can β€” because it holds the **why**.
241
-
242
- `hunch_merge_verdict` (MCP tool) and `hunch check` replay a diff against the graph and return
243
- one verdict β€” **BLOCK / WARN / PASS** β€” that *cites the reasoning*, not just the rule:
72
+ npm install -g @davesheffer/hunch # Node β‰₯ 20; puts `hunch` on your PATH
73
+ cd your-repo
74
+ hunch init # scaffold .hunch/, index, install hooks, wire up assistants
75
+ hunch backfill --since 90d # cold start: seed decisions from recent git history
76
+ hunch why src/auth/session.ts # …then ask your assistant: "why is X built this way?"
77
+ ```
78
+
79
+ `hunch init` scaffolds `.hunch/`, indexes the repo, installs the git hooks + merge driver,
80
+ writes `.mcp.json` + slash commands + an auto-maintained `CLAUDE.md`, and wires up **every
81
+ detected assistant** (Claude Code, Cursor, VS Code/Copilot, Windsurf, Codex) to the same
82
+ graph β€” merging idempotently into existing files. **Reload your assistant in the repo**
83
+ afterward to pick up the `hunch_*` tools. Each teammate runs `hunch init` once; the
84
+ `.hunch/` content is shared via git.
85
+
86
+ > Synthesis is billed to **your coding-assistant subscription** (Claude/Codex/Cursor CLI),
87
+ > **never** a pay-per-token API key β€” and falls back to a deterministic heuristic if no CLI
88
+ > is present. Details: [Synthesis & billing](https://hunch-pi.vercel.app/docs#synthesis).
89
+ > On Windows, prefer `hunch init` over a global `claude mcp add`; if tools don't appear,
90
+ > `hunch doctor` heals it ([why](https://hunch-pi.vercel.app/docs#windows)).
91
+
92
+ **Full walkthrough β†’** [Getting started](https://hunch-pi.vercel.app/docs#install) Β·
93
+ [MCP & assistants](https://hunch-pi.vercel.app/docs#mcp) Β·
94
+ [MCP tools](https://hunch-pi.vercel.app/docs#mcp-tools) Β·
95
+ [slash commands](https://hunch-pi.vercel.app/docs#slash) Β·
96
+ [the 22-command CLI](https://hunch-pi.vercel.app/docs#cli)
97
+
98
+ ## Enforcement: memory that holds the line
99
+
100
+ Hunch isn't just recall β€” it's a set of **guards** that stop the AI (and you) from undoing
101
+ intentional design. All ride the same rails: the pre-edit hook, `hunch check`, the
102
+ `hunch_merge_verdict` MCP tool, and the CI Constraint Guard. How hard they push is one
103
+ committed knob β€” [**firmness**](https://hunch-pi.vercel.app/docs#firmness)
104
+ (`off` β†’ `advisory` β†’ `firm` β†’ `strict`).
105
+
106
+ ### Never Twice β€” corrections become enforced invariants
107
+
108
+ You tell the agent "no, never call the pay-per-token API here," it complies once, and next
109
+ session it does it again β€” because the feedback was stored as advisory text. Hunch closes
110
+ that loop: a correction is captured as a first-class **Constraint** (`human_confirmed`) via
111
+ `hunch_record_correction`, and from then on the **same hook + CI guard** hold *every*
112
+ assistant to it. β†’ [docs](https://hunch-pi.vercel.app/docs#never-twice)
113
+
114
+ ### Causal Merge Verdict β€” does this change re-open a closed bug?
115
+
116
+ A diff-only reviewer sees *what* changed; it can't see that the line you're deleting is the
117
+ fix for an incident. Hunch can β€” `hunch_merge_verdict` replays a diff against the graph and
118
+ returns a cited **BLOCK / WARN / PASS**:
244
119
 
245
120
  ```text
246
121
  VERDICT: β›” BLOCK β€” this change breaks a recorded invariant or re-opens a known bug.
247
122
 
248
123
  β›” pay() must verify the session before charging β€” con_pay
249
124
  🧠 why: "Charge must verify the session first" (dec_pay)
250
- 🐞 guards against: Double-charge on unverified session β€” pay() charged without verifying (bug_trunc)
251
- ```
252
-
253
- It's **deterministic** (no LLM): for every invariant *directly* in scope it walks
254
- constraint β†’ `source_decision` β†’ the bug whose root cause spawned it; it flags invariants
255
- reached transitively (blast radius, advisory) and any deliberately-retired code the diff
256
- re-introduces. BLOCK fires only on a direct, high-confidence, non-stale **blocking** invariant
257
- or a blocking-linked regression β€” near-hits stay advisory, so it's safe as a merge gate. Call
258
- it before opening a PR (`{}` checks staged changes; pass `base: "origin/main"` for the range);
259
- the CI Constraint Guard renders the same cited verdict as a PR comment.
260
-
261
- ## Veto: re-introducing a *rejected* approach is blocked
262
-
263
- The Regression Guard catches re-adding code a decision deliberately **retired**. But the most
264
- expensive reversal is re-introducing an approach a decision **rejected** β€” the
265
- `alternatives_rejected` that *never existed in code*, so a diff-only reviewer (and the regression
266
- guard) is blind to it. A fresh session, not knowing, re-adds the very dependency you rejected for
267
- latency last month.
268
-
269
- **Veto** closes that gap. A decision can carry **tripwires** β€” machine-checkable signals (a
270
- forbidden dependency, symbol, or scoped pattern) for an `alternatives_rejected` entry. When a diff
271
- re-introduces one, Hunch blocks it with the receipt:
272
-
273
- ```text
274
- β›” VETO β€” this reverses dec_49916d02c9 ("Read-only layer over committed .hunch/ JSON").
275
- You rejected: "extension queries MCP/API server for data" (adds latency, runtime coupling)
276
- You chose: read directly from committed JSON, no backend dependency.
277
- evidence: +import axios (vscode-extension/src/extension.ts)
125
+ 🐞 guards against: Double-charge on unverified session (bug_…)
278
126
  ```
279
127
 
280
- It rides the **same rails** as everything else: `CheckReport.vetoes` lights up `hunch check`, the CI
281
- guard, and `hunch_merge_verdict` together, and the pre-edit hook denies the live edit (Edit / Write /
282
- MultiEdit) *before* it's staged β€” so the agent self-corrects with no human in the loop. Enforcement
283
- is **deterministic** (a set-intersection over a human-vouched record β€” no model in the block path)
284
- and **progressive**: an auto-drafted tripwire only *warns*; `hunch veto backfill` drafts them and
285
- `hunch review --accept` confirms a decision **and** its tripwires, flipping it from advisory to
286
- blocking in one keypress. Full design + DX: [docs/veto.md](docs/veto.md).
287
-
288
- ## Semantic search (optional)
289
-
290
- By default `hunch query` and the `hunch_query` MCP tool use fast keyword (FTS) search β€”
291
- zero setup, instant, offline. For recall on *paraphrases* (a question that shares no words
292
- with the record it should find), opt into **local embeddings**:
293
-
294
- ```bash
295
- npm i -g @huggingface/transformers # one-time; a local model runtime
296
- hunch embed # embed your records (first run downloads ~90MB)
297
- hunch query --semantic "auth token expiry" # hybrid keyword + semantic recall
298
- ```
128
+ Deterministic (no LLM), and safe as a merge gate β€” it blocks only on a direct,
129
+ high-confidence, non-stale blocking invariant. β†’ [docs](https://hunch-pi.vercel.app/docs#merge-verdict)
299
130
 
300
- > **Install it where `hunch` runs.** The runtime is resolved from `hunch`'s own
301
- > `node_modules`, so match the install scope: a globally-installed `hunch` needs the
302
- > global (`-g`) install above; running from a source checkout needs it in the repo
303
- > (`npm i @huggingface/transformers` there). If `hunch embed` reports the model "present
304
- > but failed to load," the scopes don't match. `hunch doctor` shows the active mode.
131
+ ### Decision Guard (Veto) β€” re-introducing a *rejected* approach is blocked
305
132
 
306
- Embeddings are **local and free** (no API β€” consistent with the subscription-only synthesis
307
- rule) and **opt-in** (the base install stays lean). The long-lived MCP server picks them up
308
- automatically once present. Vectors live in the derived SQLite index and are reconciled by
309
- content hash on every `hunch index`, so they never drift from the JSON source of truth. `hunch
310
- doctor` reports coverage; tune the blend with `HUNCH_RRF_W_FTS` / `HUNCH_RRF_W_SEM` / `HUNCH_RRF_K`.
133
+ The most expensive reversal is re-adding an approach a decision **rejected** (latency, a
134
+ forbidden dependency) β€” code that never existed, so a diff reviewer is blind to it. A
135
+ decision carries machine-checkable **tripwires**; re-introduce one and Hunch blocks it with
136
+ the receipt of what you rejected and why. β†’ [docs](https://hunch-pi.vercel.app/docs#veto)
311
137
 
312
- ## What makes the capture good (not just "changed N files")
313
-
314
- Even with **no LLM**, the write path runs a structured **diff analysis** β€” added /
315
- removed / changed symbols, new and dropped dependencies, and which invariants a change
316
- touches β€” so an auto-captured decision reads like *"introduced `verifySession`,
317
- `revokeSession`; removed `login`; new dep: redis; touches con_004"*, with breaking-change
318
- consequences. With the `claude` CLI present it upgrades to full LLM synthesis; otherwise
319
- it stays useful offline. Either way every record is **advisory and cheap to discard** β€”
320
- `hunch review` lets you promote the good ones to human-confirmed.
138
+ Plus the **Regression Guard** (re-adding deliberately-retired code) and the
139
+ **[CI Constraint Guard](https://hunch-pi.vercel.app/docs#ci)** (`hunch ci` β€” a PR gate that
140
+ comments the affected `con_`/`dec_` ids and fails on a blocking one).
321
141
 
322
142
  ## Working as a team
323
143
 
324
- The `.hunch/` JSON is the **source of truth**: diffable, reviewable in PRs, and synced
325
- for free over `git push` / `pull`. `hunch init` also registers a **git merge driver** so
326
- concurrent edits to the graph merge **by record id** instead of throwing conflict markers
327
- (human-confirmed beats auto, then higher confidence, then recency). The routing lives in a
328
- committed `.gitattributes`; the per-clone driver definition is set up by each teammate's
329
- `hunch init`. The graph is **OS-agnostic**: paths are stored and matched in POSIX form, and
330
- an installed Hunch registers its MCP server by package name (`npx -y @davesheffer/hunch`)
331
- rather than an absolute path β€” so a teammate on Windows, macOS, or Linux shares the same
332
- `.hunch/` and the same committed config without per-machine fixups.
144
+ The `.hunch/` JSON is the **source of truth** β€” diffable, reviewable in PRs, synced for free
145
+ over `git push` / `pull`. `hunch init` registers a git **merge driver** so concurrent edits
146
+ merge **by record id** (human-confirmed beats auto, then confidence, then recency). The graph
147
+ is **OS-agnostic**: paths are stored in POSIX form and an installed Hunch registers its MCP
148
+ server by package name, so Windows / macOS / Linux teammates share one memory without
149
+ per-machine fixups. β†’ [docs](https://hunch-pi.vercel.app/docs#team)
333
150
 
334
151
  ## Continuous learning (CI)
335
152
 
336
- The decision half of the loop is automatic (the post-commit hook). Light up the **bug /
337
- constraint half** by wrapping your test run with `hunch test`:
338
-
339
- ```bash
340
- hunch test # runs `npm test`; capture failures β†’ Bugs, resolve fixed ones
341
- hunch test -- pytest -q # any runner: pass the command after `--`
342
- ```
343
-
344
- It parses TAP and the `node:test` spec reporter, captures each failing test as a **Bug**
345
- (ranked suspects; a recurrence or substantiated high-severity failure auto-promotes a
346
- do-not-break **Constraint**), and marks a previously-open bug **fixed** once its test passes
347
- again. It preserves the runner's exit code, so it's a drop-in CI step:
348
-
349
- ```yaml
350
- # .github/workflows/ci.yml
351
- - run: npm ci
352
- - run: npx hunch test # exits non-zero on failure, just like the suite
353
- - run: | # persist what was learned (optional)
354
- git add .hunch && git commit -m "chore(hunch): capture test run" || true
355
- git push || true
356
- ```
357
-
358
- Repair drift after refactors with **`hunch stale --resync`** (re-synthesizes stale decisions
359
- from their commits via the LLM).
360
-
361
- ### Block a PR that breaks memory (CI Constraint Guard)
362
-
363
- Memory that only *advises* gets ignored. `hunch ci` scaffolds a GitHub Action that turns
364
- Hunch into a **merge gate**: on every pull request it runs `hunch check` over the diff,
365
- posts a sticky comment citing the affected `con_`/`dec_` ids, and **fails the check** when
366
- the PR breaks a *direct, high-confidence, non-stale* blocking invariant, re-adds
367
- deliberately-retired code, or contradicts an in-force decision.
153
+ The decision half of the loop is automatic (the post-commit hook). Light up the bug/constraint
154
+ half by wrapping your test run β€” it captures failures as **Bugs** (recurrences auto-promote
155
+ **Constraints**) and resolves fixed ones, preserving the runner's exit code:
368
156
 
369
157
  ```bash
370
- hunch ci # writes .github/workflows/hunch-guard.yml β€” commit it
158
+ hunch test # runs `npm test`; any runner: hunch test -- pytest -q
371
159
  ```
372
160
 
373
- It reasons over **the diff plus the constraints committed in the same git history**, so the
374
- comment says exactly which decision a change violates ("breaks `con_004` β€” server-side
375
- revocation, from `dec_017`"). Make *Hunch Guard* a required status check in branch protection
376
- to enforce on merge. The hardened strict gate only blocks on high-confidence, non-stale
377
- invariants β€” stale / low-confidence / blast-radius hits stay advisory in the comment β€” so
378
- it's safe to require on a shared repo. Under the hood it's just
379
- `hunch check --base origin/<target> --strict --format markdown`.
380
-
381
- ## Maintenance
161
+ Drop `npx hunch test` into CI, and `hunch ci` to scaffold the PR merge gate.
162
+ β†’ [docs](https://hunch-pi.vercel.app/docs#ci)
382
163
 
383
- - **`hunch doctor`** β€” is git healthy? are you on the subscription path or the offline
384
- heuristic? what schema version is on disk? how many records?
385
- - **`hunch migrate`** β€” after upgrading Hunch, bring old `.hunch/` records up to the
386
- current schema (old records are migrated in memory on every read, so reads never break;
387
- `migrate` persists the upgrade and never drops a record it can't migrate).
388
- - **`hunch compact --apply`** β€” auto-captured drafts accumulate; compaction prunes the
389
- low-value ones (rejected / superseded / stale drafts, resolved low-confidence bugs).
390
- It **never** removes an accepted/human-confirmed decision, an open bug, a constraint, or
391
- any record another record still references. Run without `--apply` first to preview.
164
+ ## Semantic search (optional)
392
165
 
393
- ## Where it's stored
166
+ `hunch query` uses fast keyword search out of the box. For recall on paraphrases, opt into
167
+ **local embeddings** (`npm i -g @huggingface/transformers && hunch embed`) β€” local, free, and
168
+ opt-in. Vectors live in the derived SQLite index and never drift from the JSON source of truth.
394
169
 
395
- ```
396
- .hunch/
397
- β”œβ”€ components/ one JSON file per architecture node (curated, PR-reviewable)
398
- β”œβ”€ decisions/ one JSON file per Decision (ADR)
399
- β”œβ”€ bugs/ one JSON file per Bug
400
- β”œβ”€ constraints/ one JSON file per Constraint (invariant)
401
- β”œβ”€ symbols/index.json the symbol graph (high-cardinality, single file)
402
- β”œβ”€ edges/index.json the dependency graph
403
- β”œβ”€ manifest.json on-disk schema version
404
- └─ hunch.sqlite DERIVED FTS5 + graph index, rebuilt by `hunch index` (gitignored)
405
- ```
406
-
407
- Low-volume entities are one file per record so they read cleanly in a PR; the
408
- high-cardinality symbol/edge graphs are single id-sorted arrays to keep git noise down.
409
- SQLite is a throwaway index rebuilt from the JSON β€” only the JSON is committed.
170
+ ## VS Code
410
171
 
411
- > Note: the on-disk directory is still `.hunch/` (and the MCP tools are still `hunch_*`)
412
- > for backward compatibility with existing graphs. A future release may migrate these to
413
- > `.hunch/` / `hunch_*`.
172
+ A companion **[VS Code extension](vscode-extension/)** (on
173
+ [Open VSX](https://open-vsx.org/extension/davesheffer/hunch-vscode) β€” VS Code / Cursor /
174
+ Windsurf / VSCodium) brings the graph into the editor: a tree of decisions / invariants /
175
+ bugs / bug-lineage / fragility / stale records, CodeLens summaries, hover with bug history,
176
+ invariants in the Problems panel, an interactive component graph, and a status-bar invariant
177
+ counter. It reads the committed `.hunch/` JSON directly; writes delegate to the `hunch` CLI.
414
178
 
415
179
  ## Architecture
416
180
 
@@ -419,51 +183,39 @@ src/
419
183
  β”œβ”€ core/ types (Zod schema), ids, paths, glob, schema migration, atomic file I/O
420
184
  β”œβ”€ store/ JSON source of truth ←→ SQLite/FTS5 derived index; merge driver; compaction
421
185
  β”œβ”€ extractors/ tree-sitter parse, git introspection, the indexer
422
- β”œβ”€ synthesis/ write path: Claude-CLI (subscription) or deterministic fallback
186
+ β”œβ”€ synthesis/ write path: subscription CLI (Claude/Codex/Cursor) or deterministic fallback
423
187
  β”œβ”€ mcp/ MCP stdio server (the hunch_* tools)
424
188
  β”œβ”€ integrations/ post-commit hook, CLAUDE.md writer, .mcp.json + slash commands, merge driver
425
189
  └─ cli/ commander entrypoint
426
190
  ```
427
191
 
428
- ## VS Code
429
-
430
- A companion **[VS Code extension](vscode-extension/)** (on
431
- [Open VSX](https://open-vsx.org/extension/davesheffer/hunch-vscode) β€” works in
432
- VS Code / Cursor / Windsurf / VSCodium) brings the graph into the editor: a tree of
433
- decisions / invariants / bugs / **bug-lineage** / fragility / **stale records**, a
434
- **CodeLens** summary + per-symbol bug/fragility marks, **hover** with bug history,
435
- invariants surfaced in the **Problems panel**, overview-ruler hotspot marks, an
436
- interactive **component graph**, fuzzy **search**, and a status-bar invariant counter.
437
- It reads the committed `.hunch/` JSON directly (no server, no native deps); writes
438
- delegate to the `hunch` CLI.
192
+ Everything lives under `.hunch/` as git-tracked JSON (the source of truth); SQLite is a
193
+ throwaway derived index. β†’ [storage layout](https://hunch-pi.vercel.app/docs#storage) Β·
194
+ [DESIGN.md](DESIGN.md) for the full conceptual model.
439
195
 
440
196
  ## Notable engineering decisions
441
197
 
442
198
  - **Subscription-billed synthesis, never the API.** The write path drives your Claude
443
199
  subscription via the `claude` CLI; `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` are
444
- stripped from the child env to force subscription auth (they outrank it in headless
445
- mode). A deterministic, no-LLM fallback means the loop never hard-requires credentials.
446
- - **Native `tree-sitter` (0.21.1) + `tree-sitter-typescript`, not web-tree-sitter.** The
447
- prebuilt WASM grammars have an ABI incompatible with current `web-tree-sitter`; the
448
- native bindings ship Node-20 prebuilds (no compiler) and a simpler synchronous API.
449
- - **`better-sqlite3` pinned to `12.9.0`** β€” 12.10.x ships no Node-20 prebuild and would
450
- force a source compile; 12.9.0 has the Node-20 (ABI 115) prebuild.
451
- - **Atomic, durable writes.** All `.hunch/` writes go through a temp-file + rename, with a
452
- Windows-safe fallback, so an interrupted write can't truncate the index; `put`/`delete`
453
- refuse to rewrite a corrupt index rather than flatten it.
454
- - **OS-agnostic by construction.** Git emits `/`-separated paths on every platform, so the
455
- graph is the source of truth in POSIX form; any free-form target is canonicalized before
456
- comparison, so `src\auth\x.ts` and `src/auth/x.ts` resolve to the same records. Generated
457
- configs that get committed reference Hunch by package name, never a machine-local path.
200
+ stripped from the child env to force subscription auth. A deterministic, no-LLM fallback
201
+ means the loop never hard-requires credentials.
202
+ - **Native `tree-sitter` (0.21.1)**, not web-tree-sitter β€” the prebuilt WASM grammars have an
203
+ incompatible ABI; the native bindings ship Node-20 prebuilds and a simpler synchronous API.
204
+ - **`better-sqlite3` pinned to `12.9.0`** β€” 12.10.x ships no Node-20 prebuild.
205
+ - **Atomic, durable writes.** All `.hunch/` writes go through temp-file + rename; an
206
+ interrupted write can't truncate the index, and `put`/`delete` refuse to rewrite a corrupt one.
207
+ - **OS-agnostic by construction.** Paths are canonicalized to POSIX before comparison, and
208
+ committed configs reference Hunch by package name, never a machine-local path.
458
209
 
459
210
  ## Develop
460
211
 
461
212
  ```bash
462
- npm run typecheck # tsc --noEmit
463
- npm test # node:test suite (store, graph, parse, indexer, synthesis, migrate, merge, compact)
464
- npm run hunch -- why src/store/hunchStore.ts # run the CLI from source via tsx, no build
213
+ npm run dev -- <args> # run the CLI from source via tsx (no build step)
214
+ npm run typecheck # strict tsc β€” the gate
215
+ npm test # node:test suite
216
+ npm run build # compile to dist/ (the published artifact)
465
217
  ```
466
218
 
467
- See [DESIGN.md](DESIGN.md) for the full spec. PR/CI enforcement now ships as the
468
- **CI Constraint Guard** (`hunch ci`). Still deferred by design: a hosted web dashboard
469
- and multi-repo support.
219
+ Hunch is pure TypeScript ESM, Node β‰₯ 20, licensed **Apache-2.0**. See
220
+ [CONTRIBUTING.md](CONTRIBUTING.md), [DESIGN.md](DESIGN.md), and the full
221
+ [developer docs](https://hunch-pi.vercel.app/docs#develop).
package/dist/cli/index.js CHANGED
@@ -15,11 +15,11 @@
15
15
  */
16
16
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { execFileSync, spawnSync } from "node:child_process";
18
- import { join, relative, dirname } from "node:path";
19
- import { fileURLToPath } from "node:url";
18
+ import { relative } from "node:path";
20
19
  import { Command } from "commander";
21
20
  import { hunchPaths, findRoot, toPosixTarget } from "../core/paths.js";
22
21
  import { looksLikeCorrection, CORRECTION_NUDGE } from "../core/correction.js";
22
+ import { HUNCH_VERSION } from "../core/version.js";
23
23
  import { HunchStore } from "../store/hunchStore.js";
24
24
  import { selectEmbedder } from "../store/embedder.js";
25
25
  import { indexRepo } from "../extractors/indexer.js";
@@ -45,20 +45,6 @@ import { readManifest, writeManifest, SCHEMA_VERSION } from "../core/migrate.js"
45
45
  import { mergeHunchJson } from "../store/merge.js";
46
46
  import { planCompaction } from "../store/compact.js";
47
47
  import { resolveInvocation } from "./invocation.js";
48
- /** The real package version β€” read from package.json (at the package root, two up
49
- * from dist/cli/ in the published tarball, and from src/cli/ in dev) so the CLI
50
- * never drifts from what npm shipped. A hardcoded literal here silently lied
51
- * (every release reported 0.1.0 regardless of the actual installed version). */
52
- const HUNCH_VERSION = (() => {
53
- try {
54
- const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "../../package.json");
55
- const v = JSON.parse(readFileSync(pkgPath, "utf8")).version;
56
- return typeof v === "string" ? v : "0.0.0";
57
- }
58
- catch {
59
- return "0.0.0";
60
- }
61
- })();
62
48
  const program = new Command();
63
49
  program.name("hunch").description("Hunch β€” an Engineering Memory OS: a git-native reasoning graph for your codebase.").version(HUNCH_VERSION);
64
50
  let openStore = null;
@@ -0,0 +1,19 @@
1
+ /** The real package version, read once from package.json at the package root β€”
2
+ * two dirs up from dist/<area>/ in the published tarball, and from src/<area>/ in
3
+ * dev. Shared by the CLI (`--version`) and the MCP server (serverInfo) so neither
4
+ * drifts from what npm shipped; a hardcoded literal silently lies (the MCP server
5
+ * reported 0.1.0 for every release until this). Falls back to "0.0.0" if unreadable. */
6
+ import { readFileSync } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+ import { dirname, join } from "node:path";
9
+ export const HUNCH_VERSION = (() => {
10
+ try {
11
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "../../package.json");
12
+ const v = JSON.parse(readFileSync(pkgPath, "utf8")).version;
13
+ return typeof v === "string" ? v : "0.0.0";
14
+ }
15
+ catch {
16
+ return "0.0.0";
17
+ }
18
+ })();
19
+ //# sourceMappingURL=version.js.map
@@ -17,6 +17,7 @@ import { buildCorrectionConstraint } from "../core/correction.js";
17
17
  import { revParse, asOfDate, revExists, lastChangeDate, rangeFiles, rangeDiff, commitFiles, commitDiff, stagedFiles, stagedDiff } from "../extractors/git.js";
18
18
  import { formatContext } from "../core/format.js";
19
19
  import { renderMarkdown, verdict } from "../core/checkreport.js";
20
+ import { HUNCH_VERSION } from "../core/version.js";
20
21
  const ok = (text) => ({ content: [{ type: "text", text }] });
21
22
  const err = (text) => ({ content: [{ type: "text", text }], isError: true });
22
23
  // Read-side token budgets: every tool result is injected into a Claude Code
@@ -60,7 +61,7 @@ export function buildServer(root) {
60
61
  // the optional model isn't installed). The model then loads lazily on the first
61
62
  // hunch_query and stays warm β€” and hybridSearch degrades to FTS until then.
62
63
  const embedderReady = selectEmbedder();
63
- const server = new McpServer({ name: "hunch", version: "0.1.0" });
64
+ const server = new McpServer({ name: "hunch", version: HUNCH_VERSION });
64
65
  // -- hunch_query ----------------------------------------------------------
65
66
  server.registerTool("hunch_query", {
66
67
  title: "Query Hunch",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
6
6
  "description": "Hunch β€” an Engineering Memory OS: a persistent, git-native reasoning graph over a codebase, exposed to Claude Code via MCP.",