@kage-core/kage-graph-mcp 2.2.5 → 2.2.7

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.
Files changed (2) hide show
  1. package/dist/kernel.js +36 -9
  2. package/package.json +1 -1
package/dist/kernel.js CHANGED
@@ -8089,9 +8089,15 @@ function kageRisk(projectDir, targets = [], changedFiles = []) {
8089
8089
  const graph = readCurrentCodeGraph(projectDir) ?? buildCodeGraph(projectDir);
8090
8090
  const graphPaths = new Set(graph.files.map((file) => file.path));
8091
8091
  const dependents = codeDependents(graph);
8092
- const resolvedTargets = unique((targets.length ? targets : changedFiles.length ? changedFiles : gitChangedFiles(projectDir))
8092
+ const explicitTargets = targets.length > 0;
8093
+ const resolvedTargets = unique((explicitTargets ? targets : changedFiles.length ? changedFiles : gitChangedFiles(projectDir))
8093
8094
  .map((path) => gitPathToProjectRelative(projectDir, path) ?? path)
8094
- .filter((path) => path && !isNoisePath(path)));
8095
+ .filter((path) => path && !isNoisePath(path))
8096
+ // Risk is a CODE assessment. When inferring targets from the working tree,
8097
+ // keep only files the code graph actually knows about — memory packets,
8098
+ // dotfiles, and docs have no dependents/hotspot signal and are pure noise
8099
+ // here. Explicitly-named targets are always honored.
8100
+ .filter((path) => explicitTargets || graphPaths.has(path)));
8095
8101
  const warnings = [];
8096
8102
  if (!gitHead(projectDir))
8097
8103
  warnings.push("Git history is unavailable, so churn, ownership, and co-change signals may be empty.");
@@ -8533,6 +8539,12 @@ const TRUTH_COMMON_SYMBOL_NAMES = new Set([
8533
8539
  "build", "parse", "load", "save", "update", "delete", "remove", "test", "validate",
8534
8540
  "format", "process", "next", "send", "write", "read", "config", "helper", "util",
8535
8541
  ]);
8542
+ // Convention-named local closures that collide across unrelated code by idiom,
8543
+ // not duplication. Kept separate from common-names so the intent is clear.
8544
+ const TRUTH_DUPLICATE_NAME_DENYLIST = new Set([
8545
+ "decorator", "wrapper", "inner", "callback", "wrapped", "fn", "cb", "noop",
8546
+ "predicate", "comparator", "getter", "setter", "factory", "visit",
8547
+ ]);
8536
8548
  function truthExcludedPath(path) {
8537
8549
  return /(^|\/)(tests?|__tests__|specs?|examples?|fixtures?|benchmarks?|mocks?|__mocks__|vendor|node_modules|dist|build)\//i.test(path)
8538
8550
  || /\.(test|spec)\.[^.]+$/i.test(path)
@@ -8619,7 +8631,14 @@ function truthReport(projectDir) {
8619
8631
  // 1a. Duplicate implementations: same-name symbols spread across directories.
8620
8632
  const symbolsByName = new Map();
8621
8633
  for (const symbol of graph.symbols) {
8622
- if (!["function", "class", "method"].includes(symbol.kind))
8634
+ // Methods are excluded: same-named methods on different classes (__init__,
8635
+ // to_dict, validate, ...) are normal polymorphism, not duplication. Only
8636
+ // top-level functions and classes can be genuine parallel implementations.
8637
+ if (!["function", "class"].includes(symbol.kind))
8638
+ continue;
8639
+ // Dunders and convention-named local closures (decorator/wrapper/inner/...)
8640
+ // collide by language idiom across unrelated code; never real duplicates.
8641
+ if (/^__.*__$/.test(symbol.name) || TRUTH_DUPLICATE_NAME_DENYLIST.has(symbol.name.toLowerCase()))
8623
8642
  continue;
8624
8643
  if (symbol.name.length < 4 || TRUTH_COMMON_SYMBOL_NAMES.has(symbol.name.toLowerCase()))
8625
8644
  continue;
@@ -8641,14 +8660,16 @@ function truthReport(projectDir) {
8641
8660
  signatureCounts.set(normalized, (signatureCounts.get(normalized) ?? 0) + 1);
8642
8661
  }
8643
8662
  const signatureMatch = [...signatureCounts.values()].some((count) => count >= 2);
8663
+ // A same-name-only collision across dirs is weak signal and the source of
8664
+ // most false positives. Require a matching signature to report it at all.
8665
+ if (!signatureMatch)
8666
+ continue;
8644
8667
  const newestEpoch = Math.max(0, ...members.map((member) => fileNewestEpoch.get(member.path) ?? 0));
8645
8668
  const recent = hasGit && newestEpoch > aiEraCutoff;
8646
8669
  duplicateFindings.push({
8647
8670
  kind: "duplicate_cluster",
8648
- title: `${members[0].name} — ${paths.size} implementations across ${dirs.size} directories${recent ? " [recent, likely AI-era]" : ""}`,
8649
- detail: signatureMatch
8650
- ? "Same name AND near-identical signature: almost certainly parallel implementations of the same idea."
8651
- : "Same name in unrelated directories: agents and humans may be solving the same problem twice.",
8671
+ title: `${members[0].name} — ${paths.size} implementations across ${dirs.size} directories${recent ? " [recently changed]" : ""}`,
8672
+ detail: "Same name and near-identical signature in unrelated directories — likely parallel implementations of the same idea, worth a look.",
8652
8673
  evidence: members.slice(0, 5).map((member) => `${member.path}:${member.line} ${member.kind} ${member.signature.slice(0, 80)}`),
8653
8674
  surprise: Math.min(100, 45 + paths.size * 8 + (signatureMatch ? 15 : 0) + (recent ? 20 : 0)),
8654
8675
  });
@@ -9189,9 +9210,15 @@ function renderClaudeMemAuditReceipt(report) {
9189
9210
  function kageReviewerSuggestions(projectDir, targets = [], changedFiles = []) {
9190
9211
  const graph = readCurrentCodeGraph(projectDir) ?? buildCodeGraph(projectDir);
9191
9212
  const graphPaths = new Set(graph.files.map((file) => file.path));
9192
- const resolvedTargets = unique((targets.length ? targets : changedFiles.length ? changedFiles : gitChangedFiles(projectDir))
9213
+ const explicitTargets = targets.length > 0;
9214
+ const resolvedTargets = unique((explicitTargets ? targets : changedFiles.length ? changedFiles : gitChangedFiles(projectDir))
9193
9215
  .map((path) => gitPathToProjectRelative(projectDir, path) ?? path)
9194
- .filter((path) => path && !isNoisePath(path)));
9216
+ .filter((path) => path && !isNoisePath(path))
9217
+ // Risk is a CODE assessment. When inferring targets from the working tree,
9218
+ // keep only files the code graph actually knows about — memory packets,
9219
+ // dotfiles, and docs have no dependents/hotspot signal and are pure noise
9220
+ // here. Explicitly-named targets are always honored.
9221
+ .filter((path) => explicitTargets || graphPaths.has(path)));
9195
9222
  const warnings = [];
9196
9223
  if (!gitHead(projectDir))
9197
9224
  warnings.push("Git history is unavailable, so reviewer suggestions cannot be computed.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "Local-first repo memory, code graph, and recall MCP server for coding agents",
5
5
  "main": "dist/index.js",
6
6
  "files": [