@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 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), `hunch_bug_lineage`,
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 an advisory pre-commit constraint guard")
51
- .option("--enforce-strict", "install a pre-commit guard that FAILS the commit on a blocking invariant")
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
- if (opts.enforce || opts.enforceStrict) {
74
- const p = installPreCommitHook(root, inv.shell, !!opts.enforceStrict);
75
- console.log(` ✓ pre-commit constraint guard ${p.action} (${opts.enforceStrict ? "strict blocks on blocking invariants" : "advisory"})`);
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 {
@@ -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.4.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.",