@kage-core/kage-graph-mcp 1.1.29 → 1.1.30

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
@@ -38,6 +38,8 @@ Restart your agent once after setup so MCP tools reload.
38
38
  and code explanations
39
39
  - a code graph for files, symbols, imports, calls, routes, tests, and packages,
40
40
  including generic call/test signals and mixed-language framework routes
41
+ - conservative cleanup candidates for unreferenced files, unused exports, and
42
+ internal-looking unused symbols
41
43
  - memory-code links so project knowledge points at the code it affects
42
44
  - decision intelligence for why-memory coverage, stale/weak packets, and
43
45
  important files that still lack linked repo knowledge
package/dist/index.js CHANGED
@@ -221,7 +221,7 @@ function listTools() {
221
221
  },
222
222
  {
223
223
  name: "kage_cleanup_candidates",
224
- description: "Find conservative cleanup candidates from Kage's code graph. Reports unreferenced source files with confidence and reasons; never auto-deletes.",
224
+ description: "Find conservative cleanup candidates from Kage's code graph. Reports unreferenced source files, unused exports, and internal-looking unused symbols with confidence and reasons; never auto-deletes.",
225
225
  inputSchema: {
226
226
  type: "object",
227
227
  properties: {
package/dist/kernel.js CHANGED
@@ -5501,6 +5501,26 @@ function hasRuntimePathReference(projectDir, graph, target) {
5501
5501
  }
5502
5502
  return false;
5503
5503
  }
5504
+ function cleanupSymbolKind(symbol) {
5505
+ return ["function", "method", "class", "constant"].includes(symbol.kind);
5506
+ }
5507
+ function symbolCleanupCandidate(symbol, kind, reasons, score, coveredByTests, git) {
5508
+ return {
5509
+ path: symbol.path,
5510
+ kind,
5511
+ symbol_id: symbol.id,
5512
+ symbol_name: symbol.name,
5513
+ line: symbol.line,
5514
+ confidence: cleanupConfidence(score),
5515
+ score,
5516
+ reasons,
5517
+ inbound_imports: 0,
5518
+ source_inbound_imports: 0,
5519
+ outbound_imports: 0,
5520
+ covered_by_tests: coveredByTests,
5521
+ last_commit_at: git?.last_commit_at ?? null,
5522
+ };
5523
+ }
5504
5524
  function kageCleanupCandidates(projectDir) {
5505
5525
  const graph = readCurrentCodeGraph(projectDir) ?? buildCodeGraph(projectDir);
5506
5526
  const fileByPath = new Map(graph.files.map((file) => [file.path, file]));
@@ -5525,6 +5545,7 @@ function kageCleanupCandidates(projectDir) {
5525
5545
  warnings.push("Git history is unavailable, so cleanup confidence does not use recency.");
5526
5546
  const candidates = [];
5527
5547
  const skippedRuntimeReferences = [];
5548
+ const wholeFileCandidates = new Set();
5528
5549
  for (const file of graph.files) {
5529
5550
  if (file.kind !== "source")
5530
5551
  continue;
@@ -5577,6 +5598,60 @@ function kageCleanupCandidates(projectDir) {
5577
5598
  covered_by_tests: coveredByTests,
5578
5599
  last_commit_at: git?.last_commit_at ?? null,
5579
5600
  });
5601
+ wholeFileCandidates.add(file.path);
5602
+ }
5603
+ const calledSymbols = new Set(graph.calls.map((call) => call.to_symbol));
5604
+ const routeHandlers = new Set(graph.routes.map((route) => route.handler_symbol).filter((value) => Boolean(value)));
5605
+ const coveredSymbolNames = new Set(graph.tests.map((test) => test.covers_symbol?.toLowerCase()).filter((value) => Boolean(value)));
5606
+ const symbolsByPath = new Map();
5607
+ for (const symbol of graph.symbols.filter(cleanupSymbolKind)) {
5608
+ const list = symbolsByPath.get(symbol.path) ?? [];
5609
+ list.push(symbol);
5610
+ symbolsByPath.set(symbol.path, list);
5611
+ }
5612
+ const importedNamesByPath = new Map();
5613
+ for (const edge of graph.imports) {
5614
+ if (!edge.to_path || !edge.imported.length)
5615
+ continue;
5616
+ const names = importedNamesByPath.get(edge.to_path) ?? new Set();
5617
+ for (const name of edge.imported)
5618
+ names.add(name);
5619
+ importedNamesByPath.set(edge.to_path, names);
5620
+ }
5621
+ for (const file of graph.files) {
5622
+ if (file.kind !== "source" || wholeFileCandidates.has(file.path))
5623
+ continue;
5624
+ if (isEntrypointLike(file.path) || routeFiles.has(file.path))
5625
+ continue;
5626
+ const fileSymbols = symbolsByPath.get(file.path) ?? [];
5627
+ if (!fileSymbols.length)
5628
+ continue;
5629
+ const git = hasGit ? gitFileSignal(projectDir, file.path, graphPaths) : null;
5630
+ const coveredByTests = hasTestCoverage(file.path, graph);
5631
+ const importedNames = importedNamesByPath.get(file.path) ?? new Set();
5632
+ const exportedSymbols = fileSymbols.filter((symbol) => symbol.export);
5633
+ const hasMatchedNamedExport = exportedSymbols.some((symbol) => importedNames.has(symbol.name));
5634
+ for (const symbol of fileSymbols) {
5635
+ const symbolReferenced = calledSymbols.has(symbol.id) || routeHandlers.has(symbol.id) || coveredSymbolNames.has(symbol.name.toLowerCase());
5636
+ if (symbolReferenced)
5637
+ continue;
5638
+ if (symbol.export) {
5639
+ if (!hasMatchedNamedExport || importedNames.has(symbol.name))
5640
+ continue;
5641
+ candidates.push(symbolCleanupCandidate(symbol, "unused_export", [
5642
+ `export "${symbol.name}" is not imported by current named import edges`,
5643
+ "symbol is not a known call target, route handler, or covered test target",
5644
+ "file has at least one other exported symbol imported by name",
5645
+ ], 0.62, coveredByTests, git));
5646
+ }
5647
+ else if (/^_[A-Za-z0-9_]+/.test(symbol.name)) {
5648
+ candidates.push(symbolCleanupCandidate(symbol, "unused_internal_symbol", [
5649
+ `internal-looking symbol "${symbol.name}" has no known call edge`,
5650
+ "symbol is not a route handler or covered test target",
5651
+ "candidate is review input only; dynamic references may exist",
5652
+ ], 0.5, coveredByTests, git));
5653
+ }
5654
+ }
5580
5655
  }
5581
5656
  candidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
5582
5657
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "1.1.29",
3
+ "version": "1.1.30",
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": [