@davesheffer/hunch 1.22.0 → 1.22.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 +162 -226
- package/dist/extractors/git.js +4 -9
- package/dist/extractors/repoSource.js +132 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
# Hunch
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Your repo remembers why — and teaches every coding agent how the project works.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@davesheffer/hunch)
|
|
6
6
|
[](https://github.com/davesheffer/hunch)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Every new AI coding session can read your code. It cannot automatically see why your team chose
|
|
10
|
+
this design, which alternative already failed, what an odd-looking line protects, or how this
|
|
11
|
+
repository expects work to be explained and reviewed.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
relevant reasons. After the change, it can check whether the work conflicts with a rule your team
|
|
16
|
-
explicitly trusts.
|
|
13
|
+
That is how settled decisions get reopened, fixed bugs return, and technically plausible changes
|
|
14
|
+
arrive feeling foreign to the project.
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
**Hunch is evidence-backed project intelligence for the AI coding tools you already use.** It gives
|
|
17
|
+
Claude, Codex, Cursor, Copilot, Windsurf, Antigravity, and other MCP clients the same durable
|
|
18
|
+
understanding of your codebase:
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
- why the code is shaped this way;
|
|
21
|
+
- how the repository communicates, reviews, and builds;
|
|
22
|
+
- what depends on the code about to change; and
|
|
23
|
+
- which trusted decisions, fixes, and architectural boundaries the result must preserve.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
For precise rules your team has explicitly trusted, the promise is **Never Twice**: an agent may
|
|
26
|
+
propose a different direction, but it cannot quietly re-make a decided decision or re-introduce a
|
|
27
|
+
fixed failure without Hunch surfacing the conflict and its evidence.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
turn on strict checks for a precise rule.
|
|
29
|
+
Memory starts advisory. Nothing blocks until a human deliberately trusts a precise rule and opts
|
|
30
|
+
into strict enforcement.
|
|
31
31
|
|
|
32
32
|
## Start in five minutes
|
|
33
33
|
|
|
34
|
-
Requires Node 22.13+ and a
|
|
34
|
+
Requires Node 22.13+ and a Git repository.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npm i -g @davesheffer/hunch
|
|
@@ -44,254 +44,190 @@ Reload your coding assistant, then ask a normal question:
|
|
|
44
44
|
|
|
45
45
|
> Why is this built this way?
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
one exact ADR at a time. Silence never grants authority, and changed ADR text is asked again.
|
|
71
|
-
|
|
72
|
-
The source of truth is readable JSON in `.hunch/`. A local SQLite index makes retrieval fast but
|
|
73
|
-
is always rebuildable.
|
|
74
|
-
|
|
75
|
-
## What improved in v1.19
|
|
76
|
-
|
|
77
|
-
Hunch now gives developers a better, shorter list of code to inspect when they describe a problem.
|
|
78
|
-
In a 12-problem test on unfamiliar code, it found the changed piece of code in 6 cases instead of 3
|
|
79
|
-
and found the correct file in 10 cases instead of 8. In a separate test, it kept the same five
|
|
80
|
-
successful finds while reducing the average number of named code items to inspect—such as functions
|
|
81
|
-
or classes—from 18.9 to 11.
|
|
82
|
-
|
|
83
|
-
| What the test measured | Before | v1.19 | Result |
|
|
84
|
-
| --- | ---: | ---: | ---: |
|
|
85
|
-
| Problems where Hunch found the changed code | 3/12 | 6/12 | 2× as many in this test |
|
|
86
|
-
| Problems where Hunch found the correct file | 8/12 | 10/12 | 2 more correct files |
|
|
87
|
-
| Pieces of code inspected for the same five finds | 18.9 average | 11 average | 41.9% less to inspect |
|
|
88
|
-
|
|
89
|
-
These are small, controlled tests—not a promise that Hunch is twice as accurate everywhere. Hunch
|
|
90
|
-
also refuses to pretend it knows the exact fix when the evidence only shows where to investigate.
|
|
91
|
-
The detailed receipts live in [`bench/external/results`](bench/external/results).
|
|
92
|
-
|
|
93
|
-
See the public [roadmap](ROADMAP.md) for what is next and what is deliberately out of scope.
|
|
94
|
-
|
|
95
|
-
## Common tasks
|
|
96
|
-
|
|
97
|
-
Most memory work happens automatically after commits. These commands cover the common manual paths:
|
|
98
|
-
|
|
99
|
-
| Command | Use it for |
|
|
100
|
-
| --- | --- |
|
|
101
|
-
| `hunch why <file>` | Explain why a file is built this way and what could be affected by changing it |
|
|
102
|
-
| `hunch query "<question>"` | Search project memory |
|
|
103
|
-
| `hunch context "<task>" --profile reviewer` | Get a bounded builder, reviewer, or architect view without changing enforcement |
|
|
104
|
-
| `hunch change-id <base> [head]` | Bind a branch and its exact squash merge to the same content-based change ID |
|
|
105
|
-
| `hunch check --working` | Check current changes against the decisions and rules your team trusts |
|
|
106
|
-
| `hunch log` | See what Hunch remembered and undo a memory change if needed |
|
|
107
|
-
| `hunch escalations` | See the rare questions that need a human answer |
|
|
108
|
-
| `hunch review` | Answer the current imported-ADR approve/decline question from the terminal |
|
|
109
|
-
| `hunch doctor` | Diagnose setup problems |
|
|
47
|
+
`hunch init` indexes the repository, installs local lifecycle hooks, and connects supported
|
|
48
|
+
assistants without replacing their existing configuration. The next session receives the relevant
|
|
49
|
+
story with its sources, not a giant transcript or a generic prompt wall.
|
|
50
|
+
|
|
51
|
+
## One evidence loop, not another model
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
Git history + ADRs + corrections + tests + repository conventions
|
|
55
|
+
│
|
|
56
|
+
▼
|
|
57
|
+
Hunch's evidence graph
|
|
58
|
+
/ │ \
|
|
59
|
+
engineering memory Project DNA reviewed landscape
|
|
60
|
+
\ │ /
|
|
61
|
+
▼
|
|
62
|
+
role-shaped, budgeted context delivery
|
|
63
|
+
│
|
|
64
|
+
▼
|
|
65
|
+
Claude / Codex / Cursor / any MCP agent
|
|
66
|
+
│
|
|
67
|
+
▼
|
|
68
|
+
deterministic change receipt
|
|
69
|
+
```
|
|
110
70
|
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
Hunch is not an agent, orchestrator, or hosted knowledge service. It is the durable reasoning and
|
|
72
|
+
validation layer behind the tools that write the code.
|
|
113
73
|
|
|
114
|
-
|
|
115
|
-
show uncertainty instead of claiming to know the exact fix.
|
|
74
|
+
## What Hunch understands
|
|
116
75
|
|
|
117
|
-
|
|
|
76
|
+
| Layer | What it adds |
|
|
118
77
|
| --- | --- |
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
claim that behaviorally influential code owns the correction. Use `--json` for the machine-readable
|
|
126
|
-
map; MCP clients can submit the same receipt through `hunch_evidence_map`.
|
|
127
|
-
|
|
128
|
-
`hunch shortlist --evidence` attaches authenticated observations to the relevant candidates but does
|
|
129
|
-
not reorder them. Three fresh transfer experiments failed to prove that execution or intervention
|
|
130
|
-
influence identifies the correction owner, so the production path converts that result into a hard
|
|
131
|
-
safeguard: no candidate is promoted or displaced by evidence. JSON output still includes a
|
|
132
|
-
deterministic receipt and the explicit `exact_owner_enabled: false` policy.
|
|
133
|
-
|
|
134
|
-
Every shortlist also preserves its flat top five and adds a deterministic hierarchical inspection
|
|
135
|
-
view anchored to those files: at most five files, two semantic declaration families per file, and
|
|
136
|
-
three declarations per family. On a preregistered 12-case fresh transfer, the preserved union found
|
|
137
|
-
6/12 changed declarations versus 3/12 for the flat top five (**+25 percentage points**, three
|
|
138
|
-
rescues), while correct-file coverage improved from 8/12 to 10/12. The view averaged 18.8 unique
|
|
139
|
-
declarations and never exceeded 24. This promotes the clusters as a supplemental diagnostic, not as
|
|
140
|
-
a top-five accuracy claim; exact-owner output and per-case confidence remain disabled. JSON output
|
|
141
|
-
includes the deterministic cluster receipt and the transfer calibration.
|
|
142
|
-
|
|
143
|
-
The default output also turns those clusters into a progressive inspection queue. It preserves the
|
|
144
|
-
flat shortlist, adds only the strongest members of already-selected semantic families, stops at ten
|
|
145
|
-
when the behavior is explained, and permits one final fallback declaration before reporting
|
|
146
|
-
uncertainty. Development replay retained all 21/36 combined hits from the full cluster view while
|
|
147
|
-
reducing the hard inspection ceiling to 11 from an average of 19.8 declarations (44% less). On a
|
|
148
|
-
separate preregistered 12-case ArkType transfer it retained all 5 full-cluster hits with zero losses
|
|
149
|
-
and reduced mean inspection from 18.9 declarations to 11 (41.9% less). It found no additional fresh
|
|
150
|
-
hit, so the queue is retained as an efficiency advisory rather than promoted as an accuracy gain.
|
|
151
|
-
|
|
152
|
-
Follow-up optimization attempts stay out of production. Replacing cluster slots with same-file
|
|
153
|
-
declarations produced four development rescues but also three losses. Appending two same-file slots
|
|
154
|
-
removed those development losses, but a second blind 12-case ArkType transfer produced 3/12 hits for
|
|
155
|
-
both the existing and expanded plans, with zero rescues. Product-source filtering lost one prior hit,
|
|
156
|
-
one-hop relationship expansion added none, and evidence/causal rerankers also failed their frozen
|
|
157
|
-
transfer gates. The receipts remain in `bench/external/results`; rejected mechanisms cannot silently
|
|
158
|
-
change the production ordering.
|
|
78
|
+
| **Engineering Memory** | Decisions, rejected alternatives, corrections, bug lineage, findings, and the rationale a future session would otherwise miss. |
|
|
79
|
+
| **Code Graph** | Symbols, calls, imports, dependencies, components, blast radius, and architectural reachability across TypeScript, JavaScript, Python, Go, PHP, YAML, and Helm. Memory itself works with any language. |
|
|
80
|
+
| **Project DNA** | Revision-specific, evidence-backed observations about how a repository communicates and works: vocabulary, contribution habits, review expectations, engineering conventions, and culture. |
|
|
81
|
+
| **Engineering Landscape** | Human-reviewed links from product and capability to system, repository, service, interface, data, delivery resources, runbooks, ownership, dashboards, and SLOs. |
|
|
82
|
+
| **Validated Delivery** | The smallest relevant evidence for the current builder, reviewer, or architect, with provenance, currentness, omissions, authority, and a content-addressed receipt. |
|
|
83
|
+
| **Change Gate + Constitution** | Deterministic checks for trusted constraints and architectural intent. Policies are compiled, proved, inspected, and explicitly activated by a human—never promoted by an agent in the background. |
|
|
159
84
|
|
|
160
|
-
|
|
85
|
+
Readable JSON in `.hunch/` is the source of truth. SQLite is a fast, rebuildable projection. Git
|
|
86
|
+
keeps the memory portable, reviewable, and reversible.
|
|
161
87
|
|
|
162
|
-
|
|
163
|
-
deterministic and opt-in:
|
|
88
|
+
## Project DNA: help the agent work like it belongs here
|
|
164
89
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
hunch check --staged --strict
|
|
168
|
-
```
|
|
90
|
+
Project DNA is Hunch's evidence-bound model of **how a repository communicates and works**. It is
|
|
91
|
+
not a persona, does not impersonate a maintainer, and does not turn frequent behavior into policy.
|
|
169
92
|
|
|
170
|
-
|
|
93
|
+
The deterministic baseline reads an exact Git revision, bounded commit history, and committed
|
|
94
|
+
convention files. The current release can also accept bounded, caller-authorized pull-request and
|
|
95
|
+
review evidence. Every evidence batch is validated and sealed; raw collaboration text does not
|
|
96
|
+
enter the profile.
|
|
171
97
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
98
|
+
Each trait keeps its category, confidence, freshness, repository revision, and evidence hash. Hunch
|
|
99
|
+
can then include only the relevant DNA in normal context, explain how well a commit, PR, issue, or
|
|
100
|
+
message matches repository conventions, and show how the profile changed between two revisions.
|
|
175
101
|
|
|
176
102
|
```bash
|
|
177
|
-
|
|
178
|
-
hunch
|
|
179
|
-
|
|
180
|
-
git commit -m "chore: connect shared Hunch memory"
|
|
181
|
-
git push
|
|
103
|
+
hunch dna inspect
|
|
104
|
+
hunch dna context
|
|
105
|
+
hunch dna diff <older-ref> <newer-ref>
|
|
182
106
|
```
|
|
183
107
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
repo, add `--migrate`, review the reported untrack/ignore changes, and follow the commit instructions
|
|
187
|
-
printed by Hunch. Omit `--migrate` for a new setup.
|
|
108
|
+
DNA may shape orientation, terminology, and advisory Project Match checks. It cannot create or
|
|
109
|
+
override a decision, constraint, finding, conformance rule, policy, or permission.
|
|
188
110
|
|
|
189
|
-
|
|
111
|
+
Read the [Project DNA contract](docs/project-dna.md) and the broader
|
|
112
|
+
[Project DNA vision](docs/project-dna-engine.md).
|
|
190
113
|
|
|
191
|
-
|
|
192
|
-
npm i -g @davesheffer/hunch@1.22.0
|
|
193
|
-
git pull
|
|
194
|
-
hunch init
|
|
195
|
-
hunch doctor
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
`hunch init` validates and connects an ignored local clone of the memory repo. Memory-reading and
|
|
199
|
-
writing CLI operations attempt a bounded refresh at startup; connected MCP sessions check for new
|
|
200
|
-
team memory at each tool-request boundary and rebuild their local index only when the JSON changed.
|
|
201
|
-
New captures route to that repo and are committed and synchronized automatically by default. If a
|
|
202
|
-
push cannot complete, a later capture or `hunch shared --sync` retries it.
|
|
203
|
-
|
|
204
|
-
The committed `.hunch/team.json` contains only the credential-free memory-repo locator and canonical
|
|
205
|
-
branch. The ignored `.hunch/local.json` contains local paths and preferences, not credentials;
|
|
206
|
-
authentication stays in SSH or the normal Git credential helper. Shared memory records,
|
|
207
|
-
`.hunch/local.json`, and `.hunch-private/` stay out of code history. Use
|
|
208
|
-
`hunch check --base origin/main --strict --public-only --format markdown` for output that may be
|
|
209
|
-
posted publicly; omit `--public-only` for an internal check that should enforce team memory.
|
|
210
|
-
`HUNCH_PRIVATE_DIR` remains an explicit process-level override for CI and portability. When it
|
|
211
|
-
redirects a repo away from `.hunch/local.json` or bypasses an advertised team store, CLI and MCP
|
|
212
|
-
startup warn on stderr and `hunch doctor` labels the effective source.
|
|
213
|
-
|
|
214
|
-
For a correction that Hunch can express as a deterministic policy, create and inspect its
|
|
215
|
-
proof-backed proposal:
|
|
114
|
+
## Day-to-day
|
|
216
115
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
hunch policy card pol_...
|
|
220
|
-
```
|
|
116
|
+
Most capture happens around normal commits and test failures. These commands cover the common
|
|
117
|
+
manual paths:
|
|
221
118
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
119
|
+
| Command | Use it for |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `hunch context "<task>" --profile builder` | Get a bounded builder, reviewer, or architect brief before work starts |
|
|
122
|
+
| `hunch why <file-or-symbol>` | See the decisions, bugs, constraints, and blast radius behind code |
|
|
123
|
+
| `hunch structure [target]` | Inspect the indexed repository shape without repeated search rounds |
|
|
124
|
+
| `hunch findings [scope]` | Inherit known-but-unfixed gaps instead of rediscovering them |
|
|
125
|
+
| `hunch check --working` | Review the current tree against trusted project rules |
|
|
126
|
+
| `hunch conform` | Prove the code still satisfies recorded architectural intent |
|
|
127
|
+
| `hunch impact origin/main` | See the dependency and memory surface of a branch |
|
|
128
|
+
| `hunch compare branch-a branch-b` | Rank candidate changes by the fewest invariant and decision conflicts |
|
|
129
|
+
| `hunch landscape review` | Inspect a hash-bound repository landscape without writing authority |
|
|
130
|
+
| `hunch now` | See recent memory and the live decision-backed roadmap |
|
|
131
|
+
| `hunch escalations` | See the rare questions that genuinely require a human answer |
|
|
132
|
+
| `hunch doctor` | Diagnose setup, provider, index, or overlay problems |
|
|
133
|
+
|
|
134
|
+
When you are ready for deterministic enforcement:
|
|
227
135
|
|
|
228
136
|
```bash
|
|
229
|
-
hunch firmness
|
|
230
|
-
hunch
|
|
231
|
-
# Later, publish any pending local memory explicitly:
|
|
232
|
-
hunch shared --sync
|
|
137
|
+
hunch firmness strict
|
|
138
|
+
hunch check --staged --strict
|
|
233
139
|
```
|
|
234
140
|
|
|
235
|
-
|
|
236
|
-
but stops automatic memory commits and pushes. As a team-coordinated rollback, revert the setup
|
|
237
|
-
commit to stop discovery after teammates pull the revert. Existing machines retain their ignored
|
|
238
|
-
local overlay until they are deliberately disconnected; do not delete the memory repo as part of a
|
|
239
|
-
rollback. For this rollout, reinstall the previous published package with
|
|
240
|
-
`npm i -g @davesheffer/hunch@1.20.3`; the release receipt resolves and records the verified rollback
|
|
241
|
-
target from the npm registry instead of trusting Git tags. Pause enforcement first as shown above,
|
|
242
|
-
and keep every team client on the same release before resuming Matrix policy workflows.
|
|
141
|
+
That is the one moment of teeth. Captured memory cannot silently hard-block on its own.
|
|
243
142
|
|
|
244
|
-
##
|
|
143
|
+
## What changed after v1.19
|
|
245
144
|
|
|
246
|
-
|
|
145
|
+
The v1.19 correction-search benchmark is still useful evidence, but it no longer describes the
|
|
146
|
+
whole product.
|
|
247
147
|
|
|
248
|
-
-
|
|
249
|
-
|
|
250
|
-
|
|
148
|
+
- **v1.20 — one validated path from reason to result.** Role-shaped context, reviewed Engineering
|
|
149
|
+
Landscape fragments, exact change identity, PHP graph support, and hash-bound ADR review moved
|
|
150
|
+
source, provenance, currentness, omissions, and human authority through one delivery contract.
|
|
151
|
+
- **v1.21 — Project DNA.** Hunch gained deterministic, revision-specific repository profiles,
|
|
152
|
+
bounded DNA context delivery, explainable Project Match checks, and auditable profile deltas.
|
|
153
|
+
- **v1.22 — authorized collaboration evidence.** Hosts can contribute bounded PR and review
|
|
154
|
+
evidence to Project DNA through a typed, sealed contract without storing raw collaboration text
|
|
155
|
+
or changing policy authority.
|
|
251
156
|
|
|
252
|
-
|
|
157
|
+
See the [changelog](CHANGELOG.md) for the release-by-release detail and the
|
|
158
|
+
[roadmap](ROADMAP.md) for what is next and deliberately out of scope.
|
|
253
159
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
```
|
|
160
|
+
<details>
|
|
161
|
+
<summary><strong>The scoped v1.19 benchmark</strong></summary>
|
|
257
162
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
163
|
+
On a preregistered 12-problem transfer, the supplemental inspection view found the changed
|
|
164
|
+
declaration in 6 cases instead of 3 and the correct file in 10 cases instead of 8. On a separate
|
|
165
|
+
12-case transfer, its progressive queue retained the same five successful finds while reducing the
|
|
166
|
+
average declarations to inspect from 18.9 to 11 (41.9% less).
|
|
261
167
|
|
|
262
|
-
|
|
168
|
+
These are bounded diagnostic results, not a claim that Hunch is universally twice as accurate.
|
|
169
|
+
Failed evidence and causal rerankers remain disabled; evidence can annotate the shortlist but does
|
|
170
|
+
not reorder it or claim an exact correction owner. The detailed receipts live in
|
|
171
|
+
[`bench/external/results`](bench/external/results).
|
|
263
172
|
|
|
264
|
-
|
|
265
|
-
is not tied to one editor or model provider.
|
|
173
|
+
</details>
|
|
266
174
|
|
|
267
|
-
|
|
175
|
+
## Share one living memory with your team
|
|
176
|
+
|
|
177
|
+
Hunch can keep a team's memory in a dedicated private Git repository, separate from the code. Hunch
|
|
178
|
+
does not host that repository. Give teammates and CI normal Git access, keep credentials in SSH or
|
|
179
|
+
the Git credential helper, and have one maintainer connect it:
|
|
268
180
|
|
|
269
181
|
```bash
|
|
270
|
-
|
|
182
|
+
npm i -g @davesheffer/hunch@1.22.2
|
|
183
|
+
hunch shared --repo git@github.com:acme/project-hunch-memory.git
|
|
184
|
+
git add .gitignore .hunch/team.json
|
|
185
|
+
git commit -m "chore: connect shared Hunch memory"
|
|
186
|
+
git push
|
|
271
187
|
```
|
|
272
188
|
|
|
273
|
-
|
|
189
|
+
Teammates then install the same version and run:
|
|
274
190
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
191
|
+
```bash
|
|
192
|
+
npm i -g @davesheffer/hunch@1.22.2
|
|
193
|
+
git pull
|
|
194
|
+
hunch init
|
|
195
|
+
hunch doctor
|
|
196
|
+
```
|
|
281
197
|
|
|
282
|
-
The
|
|
283
|
-
|
|
284
|
-
|
|
198
|
+
The committed pointer contains a credential-free repository locator and branch. The local clone,
|
|
199
|
+
paths, preferences, and private overlays stay ignored. MCP sessions refresh shared memory at tool
|
|
200
|
+
boundaries, and failed pushes are retried by a later capture or `hunch shared --sync`.
|
|
201
|
+
|
|
202
|
+
Use `hunch firmness off` to pause hook enforcement without deleting history. Use
|
|
203
|
+
`hunch shared --repo <url> --no-auto-commit` when captures should remain local until an explicit
|
|
204
|
+
`hunch shared --sync`.
|
|
205
|
+
|
|
206
|
+
## Trust boundaries that stay visible
|
|
207
|
+
|
|
208
|
+
- **Local-first.** Hunch has no hosted memory service or telemetry. The graph travels with Git and
|
|
209
|
+
speaks MCP instead of belonging to one editor or model provider.
|
|
210
|
+
- **Private when needed.** `hunch private --repo <url>` keeps sensitive reasoning in a separate
|
|
211
|
+
overlay. Local tools see the union; public CI and documentation remain public-only.
|
|
212
|
+
- **Human authority.** Observations, generated drafts, imported ADRs, discovered landscape records,
|
|
213
|
+
and proved policy candidates do not silently become trusted truth.
|
|
214
|
+
- **Deterministic core.** Indexing, retrieval receipts, currentness, conformance, checks, Project
|
|
215
|
+
DNA discovery, and policy evaluation do not require a model.
|
|
216
|
+
- **No surprise synthesis bill.** Optional drafting can use a selected Claude Code, Codex, or
|
|
217
|
+
Cursor subscription CLI, a local OpenAI-compatible endpoint, or the deterministic fallback.
|
|
218
|
+
Public remote endpoints require explicit `HUNCH_SYNTH_ALLOW_METERED=1` opt-in.
|
|
219
|
+
- **Traceable releases.** npm packages and the VS Code extension are content-addressed, verified
|
|
220
|
+
against their public registries, and tied back to exact source tags.
|
|
285
221
|
|
|
286
222
|
## Learn more
|
|
287
223
|
|
|
288
224
|
- [Full documentation](https://hunch-pi.vercel.app/docs)
|
|
289
225
|
- [Copy-paste cookbook](https://hunch-pi.vercel.app/cookbook)
|
|
290
|
-
- [
|
|
291
|
-
- [
|
|
226
|
+
- [Project DNA](docs/project-dna.md)
|
|
227
|
+
- [Engineering Landscape Graph](docs/engineering-landscape.md)
|
|
228
|
+
- [Hunch roadmap](ROADMAP.md)
|
|
229
|
+
- [VS Code extension](vscode-extension/README.md)
|
|
292
230
|
- [Architecture benchmark](bench/architectural-conformance.md)
|
|
293
|
-
- [
|
|
294
|
-
- [ORC outcome/experience protocol](docs/outcome-experience-protocol.md)
|
|
295
|
-
- [Competitive landscape (dated; re-verify before quoting)](docs/competitive-landscape.md)
|
|
231
|
+
- [Contributing](CONTRIBUTING.md)
|
|
296
232
|
|
|
297
233
|
Apache-2.0
|
package/dist/extractors/git.js
CHANGED
|
@@ -707,17 +707,12 @@ export function commitAndPushHunch(hunchDir, message, opts) {
|
|
|
707
707
|
if (!contractReady(opts.remote)
|
|
708
708
|
|| mergeRemote(hunchDir, env, CAPTURE_REMOTE_TIMEOUT_MS, opts.remote) === "failed")
|
|
709
709
|
return "committed";
|
|
710
|
-
// Hooks are disabled in the merge seam, but another process can still
|
|
711
|
-
// rewrite Git configuration. Check once after merge and once at the
|
|
712
|
-
// actual push seam; either refusal leaves the private commit local.
|
|
713
|
-
if (unsafeOverlayPublication(hunchDir, opts.protectedRepoRoot) || !contractReady(opts.remote)) {
|
|
714
|
-
console.error(`hunch: private memory was committed locally, but the overlay publication boundary changed during sync. Nothing was pushed.`);
|
|
715
|
-
return "committed";
|
|
716
|
-
}
|
|
717
710
|
// Push tracked (not via run): a no-upstream/offline/rejected push must report
|
|
718
711
|
// "committed", not overclaim "pushed" — the next flush's merge+push retries.
|
|
719
|
-
|
|
720
|
-
|
|
712
|
+
// pushWithOneRemoteAdvanceRetry performs the publication and remote-contract
|
|
713
|
+
// proof immediately before its push seam. Repeating the same expensive proof
|
|
714
|
+
// twice here adds no intervening mutation boundary and materially slows large
|
|
715
|
+
// graph refreshes on process-spawn-sensitive platforms such as Windows.
|
|
721
716
|
if (pushWithOneRemoteAdvanceRetry(hunchDir, env, opts.protectedRepoRoot, CAPTURE_REMOTE_TIMEOUT_MS, opts.remote))
|
|
722
717
|
return "pushed";
|
|
723
718
|
}
|
|
@@ -10,6 +10,8 @@ import { languageFor } from "./languages.js";
|
|
|
10
10
|
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".hunch", "coverage", ".next", "out"]);
|
|
11
11
|
const ORDINARY_BLOB_MODES = new Set(["100644", "100755"]);
|
|
12
12
|
const GIT_MAX_LISTING_BYTES = 64 * 1024 * 1024;
|
|
13
|
+
const GIT_MAX_BLOB_BATCH_BYTES = 64 * 1024 * 1024;
|
|
14
|
+
const GIT_MAX_BLOB_BATCH_ITEMS = 128;
|
|
13
15
|
function rawContentHash(bytes) {
|
|
14
16
|
return `sha1:${createHash("sha1").update(bytes).digest("hex")}`;
|
|
15
17
|
}
|
|
@@ -52,6 +54,16 @@ function gitBuffer(root, args, maxBuffer = GIT_MAX_LISTING_BYTES, preserveInvoca
|
|
|
52
54
|
timeout: 15_000,
|
|
53
55
|
});
|
|
54
56
|
}
|
|
57
|
+
function gitBufferInput(root, args, input, maxBuffer) {
|
|
58
|
+
return execFileSync("git", ["-C", root, ...args], {
|
|
59
|
+
encoding: "buffer",
|
|
60
|
+
env: gitEnv(),
|
|
61
|
+
input: Buffer.from(input, "ascii"),
|
|
62
|
+
maxBuffer,
|
|
63
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
64
|
+
timeout: 15_000,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
55
67
|
function gitText(root, args) {
|
|
56
68
|
return gitBuffer(root, args, 1024 * 1024).toString("utf8").trim();
|
|
57
69
|
}
|
|
@@ -171,6 +183,122 @@ function gitBlobEntry(root, path, mode, oid) {
|
|
|
171
183
|
},
|
|
172
184
|
};
|
|
173
185
|
}
|
|
186
|
+
/** Resolve blob type/size for a complete immutable tree in one Git process.
|
|
187
|
+
* Oversized blobs are rejected before hydration, preserving the source byte
|
|
188
|
+
* limit without paying one `cat-file -t` and `cat-file -s` spawn per path. */
|
|
189
|
+
function checkedGitBlobs(root, candidates) {
|
|
190
|
+
if (candidates.length === 0)
|
|
191
|
+
return { readable: [], rejected: [] };
|
|
192
|
+
const raw = gitBufferInput(root, ["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], `${candidates.map((candidate) => candidate.oid).join("\n")}\n`, GIT_MAX_LISTING_BYTES).toString("ascii");
|
|
193
|
+
const lines = raw.endsWith("\n") ? raw.slice(0, -1).split("\n") : raw.split("\n");
|
|
194
|
+
if (lines.length !== candidates.length)
|
|
195
|
+
throw new Error("Git blob batch check returned an incomplete result");
|
|
196
|
+
const readable = [];
|
|
197
|
+
const rejected = [];
|
|
198
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
199
|
+
const line = lines[index] ?? "";
|
|
200
|
+
const match = line.match(/^([0-9a-f]{40,64}) ([a-z]+) ([0-9]+)$/i);
|
|
201
|
+
const size = match ? Number(match[3]) : Number.NaN;
|
|
202
|
+
if (!match || match[1].toLowerCase() !== candidate.oid || match[2] !== "blob"
|
|
203
|
+
|| !Number.isSafeInteger(size) || size < 0) {
|
|
204
|
+
rejected.push(issueEntry(candidate.path, candidate.mode, "read_failed", `${candidate.path} does not resolve to an ordinary blob`));
|
|
205
|
+
}
|
|
206
|
+
else if (size > MAX_REPO_SOURCE_FILE_BYTES) {
|
|
207
|
+
rejected.push(issueEntry(candidate.path, candidate.mode, "oversized", `${candidate.path} exceeds the ${MAX_REPO_SOURCE_FILE_BYTES}-byte source limit`));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
readable.push({ ...candidate, size });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { readable, rejected };
|
|
214
|
+
}
|
|
215
|
+
function readGitBlobBatch(root, batch) {
|
|
216
|
+
try {
|
|
217
|
+
const expectedBytes = batch.reduce((total, candidate) => total + candidate.size + 160, 0);
|
|
218
|
+
const raw = gitBufferInput(root, ["cat-file", "--batch"], `${batch.map((candidate) => candidate.oid).join("\n")}\n`, Math.max(1024 * 1024, expectedBytes));
|
|
219
|
+
const results = [];
|
|
220
|
+
let cursor = 0;
|
|
221
|
+
for (const candidate of batch) {
|
|
222
|
+
const headerEnd = raw.indexOf(0x0a, cursor);
|
|
223
|
+
if (headerEnd < 0)
|
|
224
|
+
throw new Error("Git blob batch omitted an object header");
|
|
225
|
+
const header = raw.subarray(cursor, headerEnd).toString("ascii");
|
|
226
|
+
const match = header.match(/^([0-9a-f]{40,64}) blob ([0-9]+)$/i);
|
|
227
|
+
const size = match ? Number(match[2]) : Number.NaN;
|
|
228
|
+
if (!match || match[1].toLowerCase() !== candidate.oid || size !== candidate.size) {
|
|
229
|
+
throw new Error("Git blob batch returned a different object than requested");
|
|
230
|
+
}
|
|
231
|
+
const contentStart = headerEnd + 1;
|
|
232
|
+
const contentEnd = contentStart + size;
|
|
233
|
+
if (contentEnd >= raw.length || raw[contentEnd] !== 0x0a) {
|
|
234
|
+
throw new Error("Git blob batch returned a truncated object");
|
|
235
|
+
}
|
|
236
|
+
results.push(decodedSource(candidate.path, candidate.mode, raw.subarray(contentStart, contentEnd)));
|
|
237
|
+
cursor = contentEnd + 1;
|
|
238
|
+
}
|
|
239
|
+
if (cursor !== raw.length)
|
|
240
|
+
throw new Error("Git blob batch returned trailing bytes");
|
|
241
|
+
return results;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return batch.map((candidate) => ({
|
|
245
|
+
source: null,
|
|
246
|
+
mode: candidate.mode,
|
|
247
|
+
issue: {
|
|
248
|
+
path: candidate.path,
|
|
249
|
+
code: "read_failed",
|
|
250
|
+
detail: `${candidate.path} blob could not be read`,
|
|
251
|
+
},
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Hydrate exact-tree blobs through bounded `git cat-file --batch` groups.
|
|
256
|
+
* Normal scan consumers read each entry once; a repeated read retains the old
|
|
257
|
+
* entry contract by falling back to the single-blob path. Completed batches
|
|
258
|
+
* release their decoded payloads so a large repo never accumulates every source
|
|
259
|
+
* body in memory at once. */
|
|
260
|
+
function batchedGitBlobEntries(root, candidates) {
|
|
261
|
+
const groups = [];
|
|
262
|
+
let group = [];
|
|
263
|
+
let groupBytes = 0;
|
|
264
|
+
for (const candidate of candidates) {
|
|
265
|
+
if (group.length > 0 && (group.length >= GIT_MAX_BLOB_BATCH_ITEMS
|
|
266
|
+
|| groupBytes + candidate.size > GIT_MAX_BLOB_BATCH_BYTES)) {
|
|
267
|
+
groups.push(group);
|
|
268
|
+
group = [];
|
|
269
|
+
groupBytes = 0;
|
|
270
|
+
}
|
|
271
|
+
group.push(candidate);
|
|
272
|
+
groupBytes += candidate.size;
|
|
273
|
+
}
|
|
274
|
+
if (group.length > 0)
|
|
275
|
+
groups.push(group);
|
|
276
|
+
return groups.flatMap((batch) => {
|
|
277
|
+
let loaded = null;
|
|
278
|
+
let unread = batch.length;
|
|
279
|
+
return batch.map((candidate, index) => {
|
|
280
|
+
let firstRead = true;
|
|
281
|
+
return {
|
|
282
|
+
path: candidate.path,
|
|
283
|
+
mode: candidate.mode,
|
|
284
|
+
read: () => {
|
|
285
|
+
if (!firstRead)
|
|
286
|
+
return gitBlobEntry(root, candidate.path, candidate.mode, candidate.oid).read();
|
|
287
|
+
firstRead = false;
|
|
288
|
+
loaded ??= readGitBlobBatch(root, batch);
|
|
289
|
+
const result = loaded[index];
|
|
290
|
+
if (!result)
|
|
291
|
+
throw new Error(`Git blob batch lost ${candidate.path}`);
|
|
292
|
+
loaded[index] = undefined;
|
|
293
|
+
unread--;
|
|
294
|
+
if (unread === 0)
|
|
295
|
+
loaded = null;
|
|
296
|
+
return result;
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
174
302
|
function indexRows(root, preserveInvocationIndex = false) {
|
|
175
303
|
const raw = gitBuffer(root, ["ls-files", "--cached", "--stage", "-z"], GIT_MAX_LISTING_BYTES, preserveInvocationIndex);
|
|
176
304
|
const rows = [];
|
|
@@ -253,6 +381,7 @@ function treeInventory(root, kind, ref) {
|
|
|
253
381
|
const revision = exactCommit(root, ref);
|
|
254
382
|
const raw = gitBuffer(root, ["ls-tree", "--full-tree", "-r", "-z", revision]);
|
|
255
383
|
const entries = [];
|
|
384
|
+
const blobCandidates = [];
|
|
256
385
|
for (const record of nulRecords(raw)) {
|
|
257
386
|
const tab = record.indexOf(0x09);
|
|
258
387
|
if (tab < 0)
|
|
@@ -284,9 +413,11 @@ function treeInventory(root, kind, ref) {
|
|
|
284
413
|
entries.push(issueEntry(path, mode, "unsafe_mode", `${path} uses unsupported Git ${type} mode ${mode}`));
|
|
285
414
|
}
|
|
286
415
|
else {
|
|
287
|
-
|
|
416
|
+
blobCandidates.push({ path, mode, oid });
|
|
288
417
|
}
|
|
289
418
|
}
|
|
419
|
+
const checked = checkedGitBlobs(root, blobCandidates);
|
|
420
|
+
entries.push(...checked.rejected, ...batchedGitBlobEntries(root, checked.readable));
|
|
290
421
|
entries.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
291
422
|
return { identity: { kind, revision }, entries };
|
|
292
423
|
}
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://hunch-pi.vercel.app",
|
|
10
|
-
"version": "1.22.
|
|
10
|
+
"version": "1.22.2",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@davesheffer/hunch",
|
|
16
|
-
"version": "1.22.
|
|
16
|
+
"version": "1.22.2",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|