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

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/dist/cli.js CHANGED
@@ -32,7 +32,7 @@ const FULL_USAGE = `Kage — full command reference
32
32
 
33
33
  Usage:
34
34
  kage index --project <dir>
35
- kage scan --project <dir> [--json]
35
+ kage scan --project <dir> [--json] [--scorecard [--out <file>]]
36
36
  kage demo [--project <dir>]
37
37
  kage install [--project <dir>] [--agents a,b] [--no-agents] [--json]
38
38
  kage init --project <dir> [--with-policy]
@@ -71,12 +71,15 @@ Usage:
71
71
  kage slots set --project <dir> --label <label> --content <text> [--description <text>] [--paths a,b] [--tags a,b] [--size-limit <n>] [--unpinned] [--json]
72
72
  kage slots delete --project <dir> --label <label> [--json]
73
73
  kage handoff --project <dir> [--json]
74
+ kage layers --project <dir> [--json]
74
75
  kage lifecycle --project <dir> [--json]
75
76
  kage reverify --project <dir> --packet <id> [--json]
76
77
  kage reconcile --project <dir> [--session <id>] [--json]
77
78
  kage timeline --project <dir> [--days <n>] [--json]
78
79
  kage lineage --project <dir> [--json]
79
80
  kage supersede --project <dir> --packet <old-id> --replacement <new-id> [--reason <text>] [--json]
81
+ kage conflicts --project <dir> [--json]
82
+ kage skills --project <dir> [--dir <path>] [--dry-run] [--json]
80
83
  kage contributors --project <dir> [--json]
81
84
  kage profile --project <dir> [--json]
82
85
  kage xray --project <dir> [--json]
@@ -108,7 +111,8 @@ Usage:
108
111
  kage graph "<query>" --project <dir> [--json]
109
112
  kage graph-registry --project <dir> [--json]
110
113
  kage embeddings build --project <dir> [--model Xenova/all-MiniLM-L6-v2] [--json]
111
- kage recall "<query>" --project <dir> [--json] [--explain] [--embeddings] [--max-context-tokens <n>] [--structural-hops <n>]
114
+ kage recall "<query>" --project <dir> [--json] [--explain] [--embeddings] [--docs] [--max-context-tokens <n>] [--structural-hops <n>]
115
+ kage docs-search "<query>" --project <dir> [--limit <n>] [--json] search this repo's own committed docs (README, docs/**, *.md)
112
116
  kage file-context --project <dir> --path <file> [--json]
113
117
  kage observe --project <dir> --event <json>
114
118
  kage sessions --project <dir> [--json]
@@ -166,6 +170,15 @@ function numberArg(args, name, fallback) {
166
170
  const value = takeArg(args, name);
167
171
  return value ? Number(value) : fallback;
168
172
  }
173
+ function printContradictionWarning(contradictions) {
174
+ if (!contradictions?.length)
175
+ return;
176
+ console.log(`\n⚠ This contradicts ${contradictions.length} existing memor${contradictions.length === 1 ? "y" : "ies"}:`);
177
+ for (const item of contradictions) {
178
+ console.log(` - ${item.packet_id} (${item.title}): ${item.reason}`);
179
+ }
180
+ console.log(" Resolve with kage supersede --packet <old> --replacement <new>, or keep both intentionally.");
181
+ }
169
182
  function firstPositional(args) {
170
183
  return args.find((arg, index) => index > 0 && !arg.startsWith("--") && !args[index - 1]?.startsWith("--"));
171
184
  }
@@ -306,15 +319,30 @@ async function main() {
306
319
  console.log(JSON.stringify(result, null, 2));
307
320
  return;
308
321
  }
322
+ if (args.includes("--scorecard")) {
323
+ // A shareable artifact, not terminal output: write the SVG (or Markdown if
324
+ // --out ends in .md) and echo the Markdown table so it's pasteable now.
325
+ const out = takeArg(args, "--out");
326
+ const asMarkdown = out ? out.toLowerCase().endsWith(".md") : false;
327
+ const outPath = out ?? (0, node_path_1.join)(scanTarget, "kage-scorecard.svg");
328
+ (0, node_fs_1.writeFileSync)(outPath, asMarkdown ? (0, kernel_js_1.truthScorecardMarkdown)(result) : (0, kernel_js_1.truthScorecardSvg)(result), "utf8");
329
+ console.log(`Scorecard written to ${outPath}`);
330
+ console.log("Share it: embed the SVG in a README, screenshot it, or post it.\n");
331
+ console.log((0, kernel_js_1.truthScorecardMarkdown)(result));
332
+ return;
333
+ }
309
334
  console.log(`Kage Truth Report — ${result.project_dir}`);
310
335
  console.log(`Scanned ${result.totals.files_scanned} files, ${result.totals.symbols_scanned} symbols${result.totals.docs_scanned ? `, ${result.totals.docs_scanned} doc file(s)` : ""}\n`);
311
- console.log(` ${result.headline}\n`);
336
+ console.log(result.headline ? ` ${result.headline}\n` : "");
312
337
  const sections = [
313
- { kind: "duplicate_cluster", heading: "DUPLICATE IMPLEMENTATIONS", count: result.totals.duplicate_clusters },
314
- { kind: "ghost_export", heading: "GHOST KNOWLEDGEexported, never called", count: result.totals.ghost_exports },
315
- { kind: "bus_factor", heading: "BUS FACTOR 1 one head holds it all", count: result.totals.bus_factor_files },
316
- { kind: "knowledge_void", heading: "KNOWLEDGE VOIDhigh churn, zero memory", count: result.totals.knowledge_voids },
317
- { kind: "doc_lie", heading: "DOC LIESthe README vs reality", count: result.totals.doc_lies },
338
+ { kind: "knowledge_void", heading: "KNOWLEDGE VOID — high churn, zero memory", clean: "no undocumented hot files", count: result.totals.knowledge_voids },
339
+ { kind: "untested_hot", heading: "UNTESTED HOT PATH depended on, no test covers it", clean: "every hot path has a test", count: result.totals.untested_hot_paths },
340
+ { kind: "complexity_hotspot", heading: "COMPLEXITY HOTSPOTbig file, many dependents", clean: "no oversized hub files", count: result.totals.complexity_hotspots },
341
+ { kind: "debt_marker", heading: "KNOWN DEBTTODO / FIXME / HACK left in code", clean: "no debt markers in code", count: result.totals.debt_markers },
342
+ { kind: "bus_factor", heading: "BUS FACTOR 1 one head holds it all", clean: "no single-owner hot files", count: result.totals.bus_factor_files },
343
+ { kind: "duplicate_cluster", heading: "DUPLICATE IMPLEMENTATIONS", clean: "no duplicate implementations", count: result.totals.duplicate_clusters },
344
+ { kind: "ghost_export", heading: "GHOST KNOWLEDGE — exported, never called", clean: "no dead exports", count: result.totals.ghost_exports },
345
+ { kind: "doc_lie", heading: "DOC LIES — the README vs reality", clean: "docs match the code", count: result.totals.doc_lies },
318
346
  ];
319
347
  for (const section of sections) {
320
348
  const items = result.findings.filter((finding) => finding.kind === section.kind);
@@ -329,6 +357,14 @@ async function main() {
329
357
  }
330
358
  console.log("");
331
359
  }
360
+ // Reframe the categories that came back clean as reassurance, not emptiness.
361
+ // (doc checks only run when docs exist, so skip that line when none scanned.)
362
+ const cleanLabels = sections
363
+ .filter((section) => section.count === 0 && !(section.kind === "doc_lie" && !result.totals.docs_scanned))
364
+ .map((section) => section.clean);
365
+ if (result.findings.length && cleanLabels.length) {
366
+ console.log(`Clean: ${cleanLabels.join(" · ")}\n`);
367
+ }
332
368
  if (!result.findings.length) {
333
369
  const small = result.totals.files_scanned < 30;
334
370
  const noGit = result.warnings.some((warning) => warning.includes("Git history"));
@@ -454,6 +490,11 @@ async function main() {
454
490
  : null;
455
491
  const detected = requested ?? probes.filter((p) => p.paths.some((path) => (0, node_fs_1.existsSync)(path))).map((p) => p.agent);
456
492
  const init = (0, kernel_js_1.initProject)(project, { policy: false });
493
+ // Always write the repo policy (AGENTS.md + CLAUDE.md) — it is what instructs
494
+ // agents to use Kage and it travels with the repo, so teammates who clone are
495
+ // covered even before they wire their own agent. Decoupled from agent detection:
496
+ // a machine where no agent is auto-detected must still commit the policy.
497
+ const policy = (0, kernel_js_1.installAgentPolicy)(project);
457
498
  const wired = [];
458
499
  if (!skipAgents) {
459
500
  for (const agent of detected) {
@@ -475,6 +516,7 @@ async function main() {
475
516
  console.log(`Kage installed in ${init.index.projectDir}\n`);
476
517
  console.log(" Memory .agent_memory/ created — packets are plain files, reviewable in git");
477
518
  console.log(` Indexes ${init.index.indexes.length} built (code graph, recall, structure)`);
519
+ console.log(` Policy AGENTS.md + CLAUDE.md ${policy.created ? "written" : policy.updated ? "updated" : "current"} — commit these so every teammate's agent uses Kage`);
478
520
  if (skipAgents) {
479
521
  console.log(" Agents skipped (--no-agents)");
480
522
  }
@@ -489,13 +531,27 @@ async function main() {
489
531
  console.log(` Agents ${w.agent} ✗ ${w.error ?? "print-only; run kage setup " + w.agent + " --project . --write"}`);
490
532
  }
491
533
  }
492
- console.log("\nNext:");
493
- console.log(" restart your agent memory now recalls automatically at session start");
494
- console.log(" kage scan --project . 60-second Truth Report on this repo");
495
- console.log(" kage viewer --project . local dashboard (gains, packets, graph)");
496
- console.log("\nVersion control: commit .agent_memory/packets/, ignore .agent_memory/indexes/ and reports/.");
497
- console.log("Enable the packet merge driver once per clone:");
498
- console.log(` ${kernel_js_1.PACKET_MERGE_DRIVER_CONFIG}`);
534
+ // Do the version-control housekeeping instead of telling the user to. Both are
535
+ // idempotent and safe to skip if this isn't a git repo.
536
+ let vcDone = false;
537
+ try {
538
+ const gitignorePath = (0, node_path_1.join)(project, ".gitignore");
539
+ const want = [".agent_memory/indexes/", ".agent_memory/reports/"];
540
+ const current = (0, node_fs_1.existsSync)(gitignorePath) ? (0, node_fs_1.readFileSync)(gitignorePath, "utf8") : "";
541
+ const missing = want.filter((line) => !current.split("\n").some((l) => l.trim() === line));
542
+ if (missing.length) {
543
+ const prefix = current && !current.endsWith("\n") ? "\n" : "";
544
+ (0, node_fs_1.writeFileSync)(gitignorePath, `${current}${prefix}${current ? "" : "# Kage: regenerated, not committed\n"}${missing.join("\n")}\n`, "utf8");
545
+ }
546
+ (0, node_child_process_1.execFileSync)("git", ["-C", project, "config", "merge.kage-packet.driver", "npx -y @kage-core/kage-graph-mcp merge-packet %A %O %B"], { stdio: "ignore" });
547
+ vcDone = true;
548
+ }
549
+ catch { /* not a git repo or no git — fall back to a hint */ }
550
+ console.log(` Git ${vcDone ? ".gitignore + packet merge driver configured" : "skipped (not a git repo)"}`);
551
+ console.log("\nNext: restart your agent — Kage then recalls automatically every session.");
552
+ console.log(" kage scan a 60-second Truth Report on this repo");
553
+ if (!vcDone)
554
+ console.log(`\nWhen this becomes a git repo, run once: ${kernel_js_1.PACKET_MERGE_DRIVER_CONFIG}`);
499
555
  if (!init.validation.ok)
500
556
  process.exit(2);
501
557
  return;
@@ -1294,7 +1350,13 @@ async function main() {
1294
1350
  const plural = (count, singular, pluralForm) => (count === 1 ? singular : pluralForm);
1295
1351
  const week = summary.last_7d;
1296
1352
  if (!summary.all_time.recalls && !summary.all_time.stale_withheld && !summary.all_time.stale_caught && !summary.all_time.caller_answers) {
1297
- console.log("No value events recorded yet. Run kage recall (or let your agent call kage_context) and check back.");
1353
+ console.log("No value events recorded yet this ledger fills up as your agent works.");
1354
+ console.log("Every recall logs the tokens it saved (by not re-reading cited files) and every");
1355
+ console.log("stale memory it withheld. Come back after a session and you'll see a receipt here.\n");
1356
+ console.log("Start now:");
1357
+ console.log(" kage scan --project . a 60-second Truth Report on this repo");
1358
+ console.log(" kage scan --project . --scorecard a shareable scorecard you can post");
1359
+ console.log(" then just work — your agent captures and recalls, verified against this code.");
1298
1360
  return;
1299
1361
  }
1300
1362
  console.log(`This week Kage saved you ~${(0, kernel_js_1.formatTokenCount)(week.tokens_saved)} tokens (~$${week.estimated_dollars.toFixed(2)}), ` +
@@ -1363,6 +1425,21 @@ async function main() {
1363
1425
  }
1364
1426
  return;
1365
1427
  }
1428
+ if (command === "layers" || command === "memory-layers") {
1429
+ const result = (0, kernel_js_1.kageLayers)(projectArg(args));
1430
+ if (args.includes("--json")) {
1431
+ console.log(JSON.stringify(result, null, 2));
1432
+ return;
1433
+ }
1434
+ console.log("Kage memory layers");
1435
+ for (const layer of result.layers) {
1436
+ console.log(` ${layer.layer} ${layer.label} (${layer.count})`);
1437
+ console.log(` ${layer.description}`);
1438
+ for (const ex of layer.examples)
1439
+ console.log(` · ${ex}`);
1440
+ }
1441
+ return;
1442
+ }
1366
1443
  if (command === "lifecycle" || command === "memory-lifecycle") {
1367
1444
  const result = (0, kernel_js_1.kageMemoryLifecycle)(projectArg(args));
1368
1445
  if (args.includes("--json")) {
@@ -1402,6 +1479,36 @@ async function main() {
1402
1479
  process.exit(2);
1403
1480
  return;
1404
1481
  }
1482
+ if (command === "skills" || command === "skills-build") {
1483
+ const result = (0, kernel_js_1.generateSkills)(projectArg(args), {
1484
+ dryRun: args.includes("--dry-run"),
1485
+ dir: takeArg(args, "--dir"),
1486
+ });
1487
+ if (args.includes("--json")) {
1488
+ console.log(JSON.stringify(result, null, 2));
1489
+ }
1490
+ else {
1491
+ const verb = result.dry_run ? "Would generate" : "Generated";
1492
+ console.log(`Kage skills: ${verb} ${result.generated.length} skill file(s) in ${result.dir}/ from verified memory`);
1493
+ for (const skill of result.generated)
1494
+ console.log(` ${result.dry_run ? "·" : "✓"} ${skill.path} (${skill.type})`);
1495
+ if (result.skipped.length)
1496
+ console.log(` skipped ${result.skipped.length} packet(s) not skill-worthy or not grounded`);
1497
+ if (result.errors.length)
1498
+ console.log(` errors: ${result.errors.join("; ")}`);
1499
+ if (!result.dry_run && result.generated.length) {
1500
+ if (result.git_ignored) {
1501
+ console.log(`\n⚠ ${result.dir}/ is git-ignored, so these skills won't reach your team. Un-ignore it (or run with --dir <tracked-path>) and commit them.`);
1502
+ }
1503
+ else {
1504
+ console.log(`\nThese are plain files in ${result.dir}/ — commit them so every teammate's agent loads the same skills.`);
1505
+ }
1506
+ }
1507
+ }
1508
+ if (!result.ok)
1509
+ process.exit(2);
1510
+ return;
1511
+ }
1405
1512
  if (command === "reconcile" || command === "memory-reconcile" || command === "memory-reconciliation") {
1406
1513
  const result = (0, kernel_js_1.kageMemoryReconciliation)(projectArg(args), {
1407
1514
  sessionId: takeArg(args, "--session"),
@@ -1604,6 +1711,27 @@ async function main() {
1604
1711
  console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
1605
1712
  return;
1606
1713
  }
1714
+ if (command === "conflicts") {
1715
+ const result = (0, kernel_js_1.kageConflicts)(projectArg(args));
1716
+ if (args.includes("--json")) {
1717
+ console.log(JSON.stringify(result, null, 2));
1718
+ return;
1719
+ }
1720
+ if (!result.count) {
1721
+ console.log("Memory conflicts: none — no contradicting packet pairs found.");
1722
+ return;
1723
+ }
1724
+ console.log(`Memory conflicts: ${result.count} contradicting packet pair${result.count === 1 ? "" : "s"}`);
1725
+ for (const pair of result.pairs) {
1726
+ console.log(`\n ⚠ ${pair.a.title} <--> ${pair.b.title}`);
1727
+ console.log(` ${pair.a.id}`);
1728
+ console.log(` ${pair.b.id}`);
1729
+ console.log(` shared paths: ${pair.shared_paths.join(", ")}`);
1730
+ console.log(` ${pair.reason}`);
1731
+ }
1732
+ console.log("\nResolve each with: kage supersede --packet <old> --replacement <new>");
1733
+ return;
1734
+ }
1607
1735
  if (command === "module-health") {
1608
1736
  const result = (0, kernel_js_1.kageModuleHealth)(projectArg(args));
1609
1737
  if (args.includes("--json")) {
@@ -1918,6 +2046,7 @@ async function main() {
1918
2046
  graphNodes: listArg(takeArg(args, "--graph-nodes")),
1919
2047
  allowMissingPaths: args.includes("--allow-missing-paths"),
1920
2048
  strictCitations: true,
2049
+ strictContradictions: args.includes("--strict-contradictions"),
1921
2050
  discoveryTokens: args.includes("--discovery-tokens") ? numberArg(args, "--discovery-tokens", 0) : undefined,
1922
2051
  });
1923
2052
  if (!result.ok) {
@@ -1925,6 +2054,7 @@ async function main() {
1925
2054
  process.exit(2);
1926
2055
  }
1927
2056
  console.log(`Captured ${personal ? "personal" : "session"} learning: ${result.path}`);
2057
+ printContradictionWarning(result.contradictions);
1928
2058
  if (result.warnings?.length)
1929
2059
  console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
1930
2060
  console.log(personal
@@ -2003,6 +2133,11 @@ async function main() {
2003
2133
  const result = args.includes("--embeddings")
2004
2134
  ? await (0, kernel_js_1.recallWithEmbeddings)(projectArg(args), query, 5, args.includes("--explain"))
2005
2135
  : (0, kernel_js_1.recall)(projectArg(args), query, 5, args.includes("--explain"), { maxContextTokens, structuralHops });
2136
+ if (args.includes("--docs")) {
2137
+ const docsSection = (0, kernel_js_1.docsRecallSection)(projectArg(args), query, 3);
2138
+ if (docsSection)
2139
+ result.context_block = `${result.context_block}\n\n${docsSection}`;
2140
+ }
2006
2141
  if (args.includes("--json"))
2007
2142
  console.log(JSON.stringify(result, null, 2));
2008
2143
  else {
@@ -2034,6 +2169,27 @@ async function main() {
2034
2169
  console.log(`Packets: ${result.packet_count}`);
2035
2170
  return;
2036
2171
  }
2172
+ if (command === "docs-search") {
2173
+ const query = firstPositional(args);
2174
+ if (!query)
2175
+ usage();
2176
+ const limit = numberArg(args, "--limit", 5);
2177
+ const hits = (0, kernel_js_1.searchDocs)(projectArg(args), query, limit);
2178
+ if (args.includes("--json")) {
2179
+ console.log(JSON.stringify({ query, source: "repo-docs", hits }, null, 2));
2180
+ return;
2181
+ }
2182
+ if (!hits.length) {
2183
+ console.log("No matching docs found in this repo's own committed documentation.");
2184
+ return;
2185
+ }
2186
+ console.log(`Docs search (this repo's own committed documentation) — "${query}":`);
2187
+ for (const hit of hits) {
2188
+ console.log(`\n${hit.doc_path}:${hit.line} [${hit.score}] ${hit.heading}`);
2189
+ console.log(` ${hit.snippet}`);
2190
+ }
2191
+ return;
2192
+ }
2037
2193
  if (command === "observe") {
2038
2194
  const event = takeArg(args, "--event");
2039
2195
  if (!event)
@@ -2173,6 +2329,7 @@ async function main() {
2173
2329
  graphNodes: listArg(takeArg(args, "--graph-nodes")),
2174
2330
  allowMissingPaths: args.includes("--allow-missing-paths"),
2175
2331
  strictCitations: true,
2332
+ strictContradictions: args.includes("--strict-contradictions"),
2176
2333
  };
2177
2334
  const result = (0, kernel_js_1.capture)(input);
2178
2335
  if (!result.ok) {
@@ -2180,6 +2337,7 @@ async function main() {
2180
2337
  process.exit(2);
2181
2338
  }
2182
2339
  console.log(`Captured repo-local packet: ${result.path}`);
2340
+ printContradictionWarning(result.contradictions);
2183
2341
  if (result.warnings?.length)
2184
2342
  console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
2185
2343
  console.log("Repo-local memory is written immediately. Promotion to org/global still requires explicit review.");
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.CORE_TOOLS = void 0;
4
5
  exports.listTools = listTools;
5
6
  exports.callTool = callTool;
6
7
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
@@ -84,14 +85,35 @@ const KAGE_WORKFLOW_TEXT = "Kage memory workflow (this tool performs no action;
84
85
  "4) After meaningful file changes, call kage_refresh so indexes, graphs, and stale-memory checks stay current. " +
85
86
  "5) Before finishing a branch, call kage_pr_summarize then kage_pr_check. " +
86
87
  "Recall receipts show estimated tokens saved versus rediscovery; report memory quality with kage_feedback (helpful/wrong/stale).";
88
+ // Agent-facing core: the verbs an agent actually uses in the loop (recall,
89
+ // capture, stay-honest, refresh, codify). Everything else is operator/diagnostic
90
+ // and must not bloat the model's default tool list — it stays reachable in full
91
+ // mode (KAGE_TOOLS=full) or via the CLI. Keeping the default small enough that
92
+ // the client always-loads it removes the per-call ToolSearch round-trip.
93
+ exports.CORE_TOOLS = new Set([
94
+ "kage_context",
95
+ "kage_learn",
96
+ "kage_supersede",
97
+ "kage_feedback",
98
+ "kage_pr_check",
99
+ "kage_refresh",
100
+ "kage_skills",
101
+ // Promoted after the agent-trajectory eval showed a real agent reaches for
102
+ // these on natural tasks (risk before a change, listing decisions, tracing a
103
+ // dependency path, searching the repo's own docs).
104
+ "kage_risk",
105
+ "kage_decisions",
106
+ "kage_dependency_path",
107
+ "kage_docs_search",
108
+ ]);
87
109
  function listTools() {
88
- return [
110
+ const all = [
89
111
  {
90
112
  // Combined entry-point tool: validate + recall + code_graph + graph in one call.
91
113
  // Agents should load this schema first (one ToolSearch) instead of loading four
92
114
  // separate deferred schemas. Cuts session start from 4 schema loads to 1.
93
115
  name: "kage_context",
94
- description: "Primary kage entry point. Validates memory health, recalls relevant packets, and queries both the code graph and knowledge graph — all in one call. Call this at the start of every task instead of calling kage_validate, kage_recall, kage_code_graph, and kage_graph separately.",
116
+ description: "Primary kage entry point. Validates memory health, recalls relevant packets, and queries both the code graph and knowledge graph — all in one call. Call this at the start of every task; it answers caller/usage questions from the code graph too, so you rarely need a separate graph tool.",
95
117
  inputSchema: {
96
118
  type: "object",
97
119
  properties: {
@@ -160,6 +182,7 @@ function listTools() {
160
182
  limit: { type: "number" },
161
183
  explain: { type: "boolean" },
162
184
  embeddings: { type: "boolean" },
185
+ docs: { type: "boolean", description: "If true, append a Docs section (<=3 hits) drawn from this repo's own committed documentation." },
163
186
  json: { type: "boolean" },
164
187
  max_context_tokens: { type: "number" },
165
188
  structural_hops: { type: "number", description: "If >0, append a bounded N-hop code-graph blast radius seeded from the recalled memory's files." },
@@ -192,20 +215,6 @@ function listTools() {
192
215
  required: ["project_dir"],
193
216
  },
194
217
  },
195
- {
196
- name: "kage_code_graph",
197
- description: "Query the source-derived codebase graph: files, symbols, imports, calls, routes, tests, package scripts. This is generated from code, not learned memory.",
198
- inputSchema: {
199
- type: "object",
200
- properties: {
201
- project_dir: { type: "string" },
202
- query: { type: "string" },
203
- limit: { type: "number" },
204
- json: { type: "boolean" },
205
- },
206
- required: ["project_dir"],
207
- },
208
- },
209
218
  {
210
219
  name: "kage_risk",
211
220
  description: "Assess modification risk for files using Kage's code graph plus local git history: dependents, impact surface, churn, ownership, co-change partners, and test gaps. Use before editing hotspot or shared files.",
@@ -374,6 +383,19 @@ function listTools() {
374
383
  required: ["project_dir"],
375
384
  },
376
385
  },
386
+ {
387
+ name: "kage_docs_search",
388
+ description: "Search this repo's OWN committed documentation (README, docs/**, *.md, common doc dirs — including any framework/API docs checked into the repo). BM25 over heading-anchored chunks from .agent_memory/indexes/docs-index.json. Returns ranked doc hits with doc_path, heading, line, and snippet. This indexes only files on disk in the project, never the internet.",
389
+ inputSchema: {
390
+ type: "object",
391
+ properties: {
392
+ query: { type: "string" },
393
+ project_dir: { type: "string" },
394
+ limit: { type: "number" },
395
+ },
396
+ required: ["query", "project_dir"],
397
+ },
398
+ },
377
399
  {
378
400
  name: "kage_metrics",
379
401
  description: "Return concise Kage adoption and quality metrics: code graph counts, language/parser coverage, memory graph evidence coverage, pending/approved packets, validation state, and readiness score.",
@@ -602,6 +624,30 @@ function listTools() {
602
624
  required: ["project_dir", "packet_id", "replacement_packet_id"],
603
625
  },
604
626
  },
627
+ {
628
+ name: "kage_conflicts",
629
+ description: "List repo-local memory packet pairs that contradict each other (same cited path, same subject, opposing claim). Resolve each with kage_supersede, or keep both intentionally.",
630
+ inputSchema: {
631
+ type: "object",
632
+ properties: {
633
+ project_dir: { type: "string" },
634
+ },
635
+ required: ["project_dir"],
636
+ },
637
+ },
638
+ {
639
+ name: "kage_skills",
640
+ description: "Codify durable, verified repo memory (runbooks, workflows, actionable decisions) into git-native SKILL.md files under .claude/skills/ that every teammate's agent auto-loads. Only grounded, non-stale packets become skills. Pass dry_run to preview without writing. dir overrides the output directory.",
641
+ inputSchema: {
642
+ type: "object",
643
+ properties: {
644
+ project_dir: { type: "string" },
645
+ dry_run: { type: "boolean" },
646
+ dir: { type: "string" },
647
+ },
648
+ required: ["project_dir"],
649
+ },
650
+ },
605
651
  {
606
652
  name: "kage_benchmark",
607
653
  description: "Return Kage proof metrics, or set mode=memory_quality / memory_scale for synthetic memory retrieval benchmarks.",
@@ -893,6 +939,9 @@ function listTools() {
893
939
  },
894
940
  },
895
941
  ];
942
+ if (process.env.KAGE_TOOLS === "full" || process.env.KAGE_ALL_TOOLS === "1")
943
+ return all;
944
+ return all.filter((tool) => exports.CORE_TOOLS.has(tool.name));
896
945
  }
897
946
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
898
947
  tools: listTools(),
@@ -1052,8 +1101,25 @@ async function callTool(name, args) {
1052
1101
  const result = args?.embeddings
1053
1102
  ? await (0, kernel_js_1.recallWithEmbeddings)(String(args?.project_dir ?? ""), String(args?.query ?? ""), Number(args?.limit ?? 5), Boolean(args?.explain))
1054
1103
  : (0, kernel_js_1.recall)(String(args?.project_dir ?? ""), String(args?.query ?? ""), Number(args?.limit ?? 5), Boolean(args?.explain), { maxContextTokens, structuralHops });
1104
+ if (args?.docs) {
1105
+ const docsSection = (0, kernel_js_1.docsRecallSection)(String(args?.project_dir ?? ""), String(args?.query ?? ""), 3);
1106
+ if (docsSection)
1107
+ result.context_block = `${result.context_block}\n\n${docsSection}`;
1108
+ }
1109
+ // Visible receipt: in text mode, surface what this recall saved so the agent
1110
+ // can relay it. Value is otherwise invisible; an unseen win is a churned user.
1111
+ const receipt = result.value_receipt;
1112
+ const gainsLine = receipt && (receipt.tokens_saved > 0 || receipt.stale_withheld > 0)
1113
+ ? `\n\nGains: ~${(0, kernel_js_1.formatTokenCount)(receipt.tokens_saved)} tokens saved by this recall${receipt.stale_withheld ? ` · stale memories withheld: ${receipt.stale_withheld}` : ""}`
1114
+ : "";
1055
1115
  return {
1056
- content: [{ type: "text", text: args?.json || args?.explain ? JSON.stringify(result, null, 2) : result.context_block }],
1116
+ content: [{ type: "text", text: args?.json || args?.explain ? JSON.stringify(result, null, 2) : `${result.context_block}${gainsLine}` }],
1117
+ };
1118
+ }
1119
+ if (name === "kage_docs_search") {
1120
+ const hits = (0, kernel_js_1.searchDocs)(String(args?.project_dir ?? ""), String(args?.query ?? ""), Number(args?.limit ?? 5));
1121
+ return {
1122
+ content: [{ type: "text", text: JSON.stringify({ query: String(args?.query ?? ""), source: "repo-docs", hits }, null, 2) }],
1057
1123
  };
1058
1124
  }
1059
1125
  if (name === "kage_graph") {
@@ -1069,20 +1135,6 @@ async function callTool(name, args) {
1069
1135
  isError: !result.ok,
1070
1136
  };
1071
1137
  }
1072
- if (name === "kage_code_graph") {
1073
- const projectDir = String(args?.project_dir ?? "");
1074
- const query = typeof args?.query === "string" ? args.query : "";
1075
- if (query) {
1076
- const result = (0, kernel_js_1.queryCodeGraph)(projectDir, query, Number(args?.limit ?? 10));
1077
- return {
1078
- content: [{ type: "text", text: args?.json ? JSON.stringify(result, null, 2) : result.context_block }],
1079
- };
1080
- }
1081
- const result = (0, kernel_js_1.buildCodeGraph)(projectDir);
1082
- return {
1083
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1084
- };
1085
- }
1086
1138
  if (name === "kage_risk") {
1087
1139
  const result = (0, kernel_js_1.kageRisk)(String(args?.project_dir ?? ""), arrayArg(args?.targets), arrayArg(args?.changed_files));
1088
1140
  return {
@@ -1224,6 +1276,21 @@ async function callTool(name, args) {
1224
1276
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1225
1277
  };
1226
1278
  }
1279
+ if (name === "kage_conflicts") {
1280
+ const result = (0, kernel_js_1.kageConflicts)(String(args?.project_dir ?? ""));
1281
+ return {
1282
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1283
+ };
1284
+ }
1285
+ if (name === "kage_skills") {
1286
+ const result = (0, kernel_js_1.generateSkills)(String(args?.project_dir ?? ""), {
1287
+ dryRun: args?.dry_run === true,
1288
+ dir: typeof args?.dir === "string" ? args.dir : undefined,
1289
+ });
1290
+ return {
1291
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1292
+ };
1293
+ }
1227
1294
  if (name === "kage_module_health") {
1228
1295
  const result = (0, kernel_js_1.kageModuleHealth)(String(args?.project_dir ?? ""));
1229
1296
  return {