@kage-core/kage-graph-mcp 2.3.0 → 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 +95 -15
- package/dist/index.js +56 -31
- package/dist/kernel.js +621 -45
- 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]
|
|
@@ -79,6 +79,7 @@ Usage:
|
|
|
79
79
|
kage lineage --project <dir> [--json]
|
|
80
80
|
kage supersede --project <dir> --packet <old-id> --replacement <new-id> [--reason <text>] [--json]
|
|
81
81
|
kage conflicts --project <dir> [--json]
|
|
82
|
+
kage skills --project <dir> [--dir <path>] [--dry-run] [--json]
|
|
82
83
|
kage contributors --project <dir> [--json]
|
|
83
84
|
kage profile --project <dir> [--json]
|
|
84
85
|
kage xray --project <dir> [--json]
|
|
@@ -318,15 +319,30 @@ async function main() {
|
|
|
318
319
|
console.log(JSON.stringify(result, null, 2));
|
|
319
320
|
return;
|
|
320
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
|
+
}
|
|
321
334
|
console.log(`Kage Truth Report — ${result.project_dir}`);
|
|
322
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`);
|
|
323
|
-
console.log(` ${result.headline}\n`);
|
|
336
|
+
console.log(result.headline ? ` ${result.headline}\n` : "");
|
|
324
337
|
const sections = [
|
|
325
|
-
{ kind: "
|
|
326
|
-
{ kind: "
|
|
327
|
-
{ kind: "
|
|
328
|
-
{ kind: "
|
|
329
|
-
{ 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 },
|
|
330
346
|
];
|
|
331
347
|
for (const section of sections) {
|
|
332
348
|
const items = result.findings.filter((finding) => finding.kind === section.kind);
|
|
@@ -341,6 +357,14 @@ async function main() {
|
|
|
341
357
|
}
|
|
342
358
|
console.log("");
|
|
343
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
|
+
}
|
|
344
368
|
if (!result.findings.length) {
|
|
345
369
|
const small = result.totals.files_scanned < 30;
|
|
346
370
|
const noGit = result.warnings.some((warning) => warning.includes("Git history"));
|
|
@@ -466,6 +490,11 @@ async function main() {
|
|
|
466
490
|
: null;
|
|
467
491
|
const detected = requested ?? probes.filter((p) => p.paths.some((path) => (0, node_fs_1.existsSync)(path))).map((p) => p.agent);
|
|
468
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);
|
|
469
498
|
const wired = [];
|
|
470
499
|
if (!skipAgents) {
|
|
471
500
|
for (const agent of detected) {
|
|
@@ -487,6 +516,7 @@ async function main() {
|
|
|
487
516
|
console.log(`Kage installed in ${init.index.projectDir}\n`);
|
|
488
517
|
console.log(" Memory .agent_memory/ created — packets are plain files, reviewable in git");
|
|
489
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`);
|
|
490
520
|
if (skipAgents) {
|
|
491
521
|
console.log(" Agents skipped (--no-agents)");
|
|
492
522
|
}
|
|
@@ -501,13 +531,27 @@ async function main() {
|
|
|
501
531
|
console.log(` Agents ${w.agent} ✗ ${w.error ?? "print-only; run kage setup " + w.agent + " --project . --write"}`);
|
|
502
532
|
}
|
|
503
533
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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}`);
|
|
511
555
|
if (!init.validation.ok)
|
|
512
556
|
process.exit(2);
|
|
513
557
|
return;
|
|
@@ -1306,7 +1350,13 @@ async function main() {
|
|
|
1306
1350
|
const plural = (count, singular, pluralForm) => (count === 1 ? singular : pluralForm);
|
|
1307
1351
|
const week = summary.last_7d;
|
|
1308
1352
|
if (!summary.all_time.recalls && !summary.all_time.stale_withheld && !summary.all_time.stale_caught && !summary.all_time.caller_answers) {
|
|
1309
|
-
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.");
|
|
1310
1360
|
return;
|
|
1311
1361
|
}
|
|
1312
1362
|
console.log(`This week Kage saved you ~${(0, kernel_js_1.formatTokenCount)(week.tokens_saved)} tokens (~$${week.estimated_dollars.toFixed(2)}), ` +
|
|
@@ -1429,6 +1479,36 @@ async function main() {
|
|
|
1429
1479
|
process.exit(2);
|
|
1430
1480
|
return;
|
|
1431
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
|
+
}
|
|
1432
1512
|
if (command === "reconcile" || command === "memory-reconcile" || command === "memory-reconciliation") {
|
|
1433
1513
|
const result = (0, kernel_js_1.kageMemoryReconciliation)(projectArg(args), {
|
|
1434
1514
|
sessionId: takeArg(args, "--session"),
|
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: {
|
|
@@ -193,20 +215,6 @@ function listTools() {
|
|
|
193
215
|
required: ["project_dir"],
|
|
194
216
|
},
|
|
195
217
|
},
|
|
196
|
-
{
|
|
197
|
-
name: "kage_code_graph",
|
|
198
|
-
description: "Query the source-derived codebase graph: files, symbols, imports, calls, routes, tests, package scripts. This is generated from code, not learned memory.",
|
|
199
|
-
inputSchema: {
|
|
200
|
-
type: "object",
|
|
201
|
-
properties: {
|
|
202
|
-
project_dir: { type: "string" },
|
|
203
|
-
query: { type: "string" },
|
|
204
|
-
limit: { type: "number" },
|
|
205
|
-
json: { type: "boolean" },
|
|
206
|
-
},
|
|
207
|
-
required: ["project_dir"],
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
218
|
{
|
|
211
219
|
name: "kage_risk",
|
|
212
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.",
|
|
@@ -627,6 +635,19 @@ function listTools() {
|
|
|
627
635
|
required: ["project_dir"],
|
|
628
636
|
},
|
|
629
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
|
+
},
|
|
630
651
|
{
|
|
631
652
|
name: "kage_benchmark",
|
|
632
653
|
description: "Return Kage proof metrics, or set mode=memory_quality / memory_scale for synthetic memory retrieval benchmarks.",
|
|
@@ -918,6 +939,9 @@ function listTools() {
|
|
|
918
939
|
},
|
|
919
940
|
},
|
|
920
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));
|
|
921
945
|
}
|
|
922
946
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
923
947
|
tools: listTools(),
|
|
@@ -1082,8 +1106,14 @@ async function callTool(name, args) {
|
|
|
1082
1106
|
if (docsSection)
|
|
1083
1107
|
result.context_block = `${result.context_block}\n\n${docsSection}`;
|
|
1084
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
|
+
: "";
|
|
1085
1115
|
return {
|
|
1086
|
-
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}` }],
|
|
1087
1117
|
};
|
|
1088
1118
|
}
|
|
1089
1119
|
if (name === "kage_docs_search") {
|
|
@@ -1105,20 +1135,6 @@ async function callTool(name, args) {
|
|
|
1105
1135
|
isError: !result.ok,
|
|
1106
1136
|
};
|
|
1107
1137
|
}
|
|
1108
|
-
if (name === "kage_code_graph") {
|
|
1109
|
-
const projectDir = String(args?.project_dir ?? "");
|
|
1110
|
-
const query = typeof args?.query === "string" ? args.query : "";
|
|
1111
|
-
if (query) {
|
|
1112
|
-
const result = (0, kernel_js_1.queryCodeGraph)(projectDir, query, Number(args?.limit ?? 10));
|
|
1113
|
-
return {
|
|
1114
|
-
content: [{ type: "text", text: args?.json ? JSON.stringify(result, null, 2) : result.context_block }],
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
const result = (0, kernel_js_1.buildCodeGraph)(projectDir);
|
|
1118
|
-
return {
|
|
1119
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
1138
|
if (name === "kage_risk") {
|
|
1123
1139
|
const result = (0, kernel_js_1.kageRisk)(String(args?.project_dir ?? ""), arrayArg(args?.targets), arrayArg(args?.changed_files));
|
|
1124
1140
|
return {
|
|
@@ -1266,6 +1282,15 @@ async function callTool(name, args) {
|
|
|
1266
1282
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1267
1283
|
};
|
|
1268
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
|
+
}
|
|
1269
1294
|
if (name === "kage_module_health") {
|
|
1270
1295
|
const result = (0, kernel_js_1.kageModuleHealth)(String(args?.project_dir ?? ""));
|
|
1271
1296
|
return {
|