@davesheffer/hunch 0.4.0 → 0.5.0
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 +2 -1
- package/dist/cli/index.js +9 -5
- package/dist/mcp/server.js +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,7 +98,8 @@ normally and Claude consults Hunch, or invoke the slash commands:
|
|
|
98
98
|
| `/hunch-fragile` | a fragility report (the riskiest code, with evidence) |
|
|
99
99
|
|
|
100
100
|
The MCP tools Claude calls under the hood: `hunch_why`, `hunch_query`,
|
|
101
|
-
`hunch_check_constraints`, `hunch_get_dependents` (blast radius), `
|
|
101
|
+
`hunch_check_constraints`, `hunch_get_dependents` (blast radius), `hunch_blast_radius`
|
|
102
|
+
(dependent files + near-violations a change could break indirectly), `hunch_bug_lineage`,
|
|
102
103
|
`hunch_context` (surgical minimal slice for a task), `hunch_record_decision` (write-back).
|
|
103
104
|
|
|
104
105
|
**Through the CLI** — the same graph, from your terminal:
|
package/dist/cli/index.js
CHANGED
|
@@ -47,8 +47,8 @@ program
|
|
|
47
47
|
.command("init")
|
|
48
48
|
.description("Scaffold .hunch/, index the repo, install the git hook, and wire up Claude Code.")
|
|
49
49
|
.option("--no-index", "skip the initial repo index")
|
|
50
|
-
.option("--enforce", "install
|
|
51
|
-
.option("--enforce-strict", "
|
|
50
|
+
.option("--no-enforce", "do not install the advisory pre-commit constraint guard")
|
|
51
|
+
.option("--enforce-strict", "make the pre-commit guard FAIL the commit on a blocking invariant (direct or near)")
|
|
52
52
|
.action((opts) => {
|
|
53
53
|
const root = findRoot();
|
|
54
54
|
const paths = hunchPaths(root);
|
|
@@ -70,9 +70,13 @@ program
|
|
|
70
70
|
console.log(` ✓ post-commit hook ${h.action} (learning loop)`);
|
|
71
71
|
const m = installMergeDriver(root, inv.shell);
|
|
72
72
|
console.log(` ✓ team merge driver ${m.action}`);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// Auto-install the pre-commit guard by default (advisory: flags invariants
|
|
74
|
+
// touched directly OR via blast radius, never blocks). Opt out with
|
|
75
|
+
// --no-enforce; --enforce-strict makes blocking near/direct hits fail the commit.
|
|
76
|
+
if (opts.enforce !== false || opts.enforceStrict) {
|
|
77
|
+
const strict = !!opts.enforceStrict;
|
|
78
|
+
const p = installPreCommitHook(root, inv.shell, strict);
|
|
79
|
+
console.log(` ✓ pre-commit constraint guard ${p.action} (${strict ? "strict — blocks on blocking invariants, direct or near" : "advisory — flags invariants in scope or blast radius"})`);
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
else {
|
package/dist/mcp/server.js
CHANGED
|
@@ -38,6 +38,12 @@ function resolveSymbols(store, target) {
|
|
|
38
38
|
return byName;
|
|
39
39
|
return syms.filter((s) => s.file === target || s.file.endsWith(target));
|
|
40
40
|
}
|
|
41
|
+
/** Resolve a target to canonical indexed file path(s) (for file-granular blast
|
|
42
|
+
* radius). Falls back to the literal target so direct-scope checks still run. */
|
|
43
|
+
function resolveFiles(store, target) {
|
|
44
|
+
const files = new Set(resolveSymbols(store, target).map((s) => s.file));
|
|
45
|
+
return files.size ? [...files] : [target];
|
|
46
|
+
}
|
|
41
47
|
export function buildServer(root) {
|
|
42
48
|
const store = new HunchStore(hunchPaths(root));
|
|
43
49
|
// Ensure the SQLite index reflects the JSON source of truth on startup.
|
|
@@ -143,6 +149,35 @@ export function buildServer(root) {
|
|
|
143
149
|
const lines = deps.slice(0, DEP_CAP).map((d) => ` • [depth ${d.depth}] ${d.via} (${d.id})`);
|
|
144
150
|
return ok(`Blast radius of "${symbol}" — ${deps.length} dependent(s):\n${lines.join("\n")}${more(deps.length, DEP_CAP, "closest shown first")}`);
|
|
145
151
|
});
|
|
152
|
+
// -- hunch_blast_radius (dependents + near-violations) --------------------
|
|
153
|
+
server.registerTool("hunch_blast_radius", {
|
|
154
|
+
title: "Blast radius + near-violations for a file",
|
|
155
|
+
description: "Given a file you're about to change, return its dependency blast radius (files whose code depends on it) AND any invariants reached THROUGH that radius — 'near-violations' you could break indirectly without touching their own scope. Call before editing a widely-depended-on file. Mirrors `hunch check --blast`.",
|
|
156
|
+
inputSchema: { target: z.string().describe("A file path (e.g. src/auth/jwt.ts) or symbol.") },
|
|
157
|
+
}, async ({ target }) => {
|
|
158
|
+
const parts = [];
|
|
159
|
+
for (const file of resolveFiles(store, target)) {
|
|
160
|
+
const blast = store.blastRadiusFiles(file);
|
|
161
|
+
const directIds = new Set(store.checkConstraints(file).map((c) => c.id));
|
|
162
|
+
const near = new Map();
|
|
163
|
+
for (const b of blast) {
|
|
164
|
+
for (const c of store.checkConstraints(b.file)) {
|
|
165
|
+
if (directIds.has(c.id) || near.has(c.id))
|
|
166
|
+
continue;
|
|
167
|
+
near.set(c.id, { c, via: `${b.file} (${b.via}, depth ${b.depth})` });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const blastBody = blast.length
|
|
171
|
+
? `:\n${blast.slice(0, DEP_CAP).map((b) => ` • [depth ${b.depth}] ${b.file} (via ${b.via})`).join("\n")}${more(blast.length, DEP_CAP, "closest first")}`
|
|
172
|
+
: "";
|
|
173
|
+
const nearArr = [...near.values()];
|
|
174
|
+
const nearBody = nearArr.length
|
|
175
|
+
? `\n NEAR-VIOLATIONS (invariants reachable via this radius — review before editing):\n${nearArr.map((n) => ` ⚠ ${n.c.id} [${n.c.severity}] ${n.c.statement}\n via ${n.via}`).join("\n")}`
|
|
176
|
+
: "\n No invariants in the blast radius.";
|
|
177
|
+
parts.push(`${file} → ${blast.length} dependent file(s)${blastBody}${nearBody}`);
|
|
178
|
+
}
|
|
179
|
+
return ok(`Blast radius for "${target}":\n\n${parts.join("\n\n")}`);
|
|
180
|
+
});
|
|
146
181
|
// -- hunch_context (surgical retrieval) -----------------------------------
|
|
147
182
|
server.registerTool("hunch_context", {
|
|
148
183
|
title: "Assemble the minimal relevant Hunch slice for a task",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davesheffer/hunch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
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.",
|