@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 +174 -16
- package/dist/index.js +98 -31
- package/dist/kernel.js +1103 -34
- package/package.json +2 -2
- package/viewer/console.js +69 -2
- package/viewer/index.html +12 -3
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: "
|
|
314
|
-
{ kind: "
|
|
315
|
-
{ kind: "
|
|
316
|
-
{ kind: "
|
|
317
|
-
{ kind: "
|
|
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 HOTSPOT — big file, many dependents", clean: "no oversized hub files", count: result.totals.complexity_hotspots },
|
|
341
|
+
{ kind: "debt_marker", heading: "KNOWN DEBT — TODO / 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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 {
|