@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 +132 -380
- package/dist/cli/index.js +2 -16
- package/dist/core/version.js +19 -0
- package/dist/mcp/server.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
# π§ Hunch β Engineering Memory OS
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@davesheffer/hunch)
|
|
4
|
+
[](https://www.npmjs.com/package/@davesheffer/hunch)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](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**:
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
337
|
-
|
|
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
|
|
158
|
+
hunch test # runs `npm test`; any runner: hunch test -- pytest -q
|
|
371
159
|
```
|
|
372
160
|
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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:
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
|
445
|
-
|
|
446
|
-
- **Native `tree-sitter` (0.21.1)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
-
|
|
450
|
-
|
|
451
|
-
- **
|
|
452
|
-
|
|
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
|
|
463
|
-
npm
|
|
464
|
-
npm
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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 {
|
|
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
|
package/dist/mcp/server.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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.",
|