@davesheffer/hunch 1.12.1 → 1.13.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.
@@ -19,7 +19,7 @@ import { RESET_SQL, embedHash } from "./schema.js";
19
19
  import { selectEmbedder } from "./embedder.js";
20
20
  import { JsonStore } from "./jsonStore.js";
21
21
  import { gitCommonDir, gitWorktreeRoot, sameGitPublication } from "../extractors/git.js";
22
- import { pathMatchesGlob } from "../core/glob.js";
22
+ import { pathMatchesGlob, pathsRelated } from "../core/glob.js";
23
23
  import { currentForTopic, isInForce } from "../core/topics.js";
24
24
  import { edgeId } from "../core/ids.js";
25
25
  import { isStrictBlocker, isVetoBlocker } from "../core/strictgate.js";
@@ -819,14 +819,14 @@ export class HunchStore {
819
819
  const symbols = this.recs("symbols");
820
820
  const components = this.recs("components");
821
821
  const asOf = opts.asOf;
822
- // pathRelated, not bare endsWith: "scenario.ts".endsWith("io.ts") is true,
822
+ // pathsRelated, not bare endsWith: "scenario.ts".endsWith("io.ts") is true,
823
823
  // so an unanchored suffix pulled unrelated files' records into why()/the
824
824
  // pre-edit grounding block (issue #32). Segment-anchored matching only.
825
- const matchedSymbols = symbols.filter((s) => s.file === target || s.name === target || s.id === target || pathRelated(s.file, target));
825
+ const matchedSymbols = symbols.filter((s) => s.file === target || s.name === target || s.id === target || pathsRelated(s.file, target));
826
826
  const symIds = new Set(matchedSymbols.map((s) => s.id));
827
827
  const fileSet = new Set(matchedSymbols.map((s) => s.file));
828
828
  const isPath = target.includes("/") || target.includes(".");
829
- const fileMatch = (files) => files.some((f) => f === target || (isPath && pathRelated(f, target)) || fileSet.has(f));
829
+ const fileMatch = (files) => files.some((f) => f === target || (isPath && pathsRelated(f, target)) || fileSet.has(f));
830
830
  return {
831
831
  target,
832
832
  decisions: decisions.filter((d) => (fileMatch(d.related_files) || d.related_components.some((c) => components.find((x) => x.id === c && fileMatch(x.paths))))
@@ -1057,7 +1057,7 @@ export class HunchStore {
1057
1057
  const live = (f) => f.triage === "open" || f.triage === "accepted-risk" || f.triage === "scheduled";
1058
1058
  return this.recs("findings")
1059
1059
  .filter(live)
1060
- .filter((f) => f.affected_files.some((af) => pathMatchesGlob(t, af) || pathMatchesGlob(af, t) || pathRelated(toPosixTarget(af), t))
1060
+ .filter((f) => f.affected_files.some((af) => pathMatchesGlob(t, af) || pathMatchesGlob(af, t) || pathsRelated(toPosixTarget(af), t))
1061
1061
  || f.affected_symbols.some((s) => s === scope))
1062
1062
  .sort((a, b) => (SEV_FINDING[b.severity] ?? 0) - (SEV_FINDING[a.severity] ?? 0) || a.id.localeCompare(b.id));
1063
1063
  }
@@ -1294,7 +1294,7 @@ export class HunchStore {
1294
1294
  const addedDeps = new Set(added.deps);
1295
1295
  if (!addedSyms.size && !addedDeps.size)
1296
1296
  return [];
1297
- const fileRelevant = (related) => related.some((f) => files.some((x) => pathRelated(x, f)));
1297
+ const fileRelevant = (related) => related.some((f) => files.some((x) => pathsRelated(x, f)));
1298
1298
  const decisions = this.recs("decisions");
1299
1299
  // decisions tied to an active blocking constraint via source_decision
1300
1300
  const blockingDec = new Set(this.recs("constraints")
@@ -1416,7 +1416,7 @@ export class HunchStore {
1416
1416
  continue;
1417
1417
  if (!d.retired.symbols.length && !d.retired.deps.length)
1418
1418
  continue;
1419
- if (!d.related_files.some((f) => pathRelated(f, file)))
1419
+ if (!d.related_files.some((f) => pathsRelated(f, file)))
1420
1420
  continue;
1421
1421
  out.push({ decision: d.id, title: d.title, symbols: d.retired.symbols, deps: d.retired.deps });
1422
1422
  }
@@ -1558,9 +1558,6 @@ const SEV_FINDING = { critical: 4, high: 3, medium: 2, low: 1 };
1558
1558
  /** Do two repo paths refer to the same file? Exact match, or one is a trailing
1559
1559
  * path-SEGMENT suffix of the other (e.g. "x.ts" vs "src/x.ts") — anchored at a
1560
1560
  * "/" boundary so "re.ts" never matches "store.ts" (the bare-endsWith hazard). */
1561
- function pathRelated(a, b) {
1562
- return a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
1563
- }
1564
1561
  function inWindow(valid_from, valid_to, asOf) {
1565
1562
  if (!asOf)
1566
1563
  return true;
@@ -201,8 +201,16 @@ export async function syncCommit(store, root, sha, opts = {}) {
201
201
  },
202
202
  date: meta.date, // the commit date
203
203
  };
204
- // Route to the record's ONE home: the overlay when asked (--private) or in unified
205
- // ("shared") mode; else the public store. Same contract as every other capture path.
204
+ // Route to the resolved home: the overlay when asked (--private / opts.home) or in
205
+ // unified ("shared") mode; else the public store.
206
+ //
207
+ // DELIBERATELY home-scoped, NOT putCapture. Synthesis keeps the public and private
208
+ // spines separate: a public failure must never reach through and rewrite a same-id
209
+ // private record, which putCapture's cross-home collision guard would either throw on
210
+ // or (via putWhereItLives) silently redirect. test/private-capture.test.ts pins that
211
+ // behaviour — "public post-promotion rewrite cannot overwrite the private collision".
212
+ // An earlier comment here claimed the "same contract as every other capture path",
213
+ // which read as an accidental bypass and invited exactly that wrong fix.
206
214
  if (home === "private")
207
215
  store.putPrivate("decisions", decision);
208
216
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.12.1",
3
+ "version": "1.13.0",
4
4
  "mcpName": "io.github.davesheffer/hunch",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
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.12.1",
10
+ "version": "1.13.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.12.1",
16
+ "version": "1.13.0",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {