@kage-core/kage-graph-mcp 1.1.4 → 1.1.5
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 +8 -0
- package/dist/cli.js +41 -0
- package/dist/index.js +18 -0
- package/dist/kernel.js +105 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ kage recall "how do I run tests" --project /path/to/repo
|
|
|
38
38
|
kage recall "how do I run tests" --project /path/to/repo --explain --json
|
|
39
39
|
kage quality --project /path/to/repo
|
|
40
40
|
kage benchmark --project /path/to/repo
|
|
41
|
+
kage benchmark --project /path/to/repo --compare --task "how do I run tests"
|
|
41
42
|
kage viewer --project /path/to/repo
|
|
42
43
|
kage daemon start --project /path/to/repo
|
|
43
44
|
kage observe --project /path/to/repo --event '{"type":"command_result","session_id":"s1","command":"npm test","exit_code":0}'
|
|
@@ -125,6 +126,12 @@ and parser coverage, code graph counts, evidence coverage, approved vs pending
|
|
|
125
126
|
memory, validation status, estimated tokens saved per recall, duplicate
|
|
126
127
|
candidates, average memory quality, and a readiness score.
|
|
127
128
|
|
|
129
|
+
Use `kage benchmark --compare --task "<task>" --project <repo>` or
|
|
130
|
+
`kage_benchmark_compare` to compare the same task on the same repo with and
|
|
131
|
+
without Kage. It estimates manual full-file rediscovery tokens/steps, compares
|
|
132
|
+
them to compact Kage recall plus code graph context, and returns evidence plus
|
|
133
|
+
caveats for honest marketing proof.
|
|
134
|
+
|
|
128
135
|
Use `kage refresh --project <repo>` or the `kage_refresh` MCP tool after
|
|
129
136
|
meaningful file changes. Refresh rebuilds indexes, code graph, memory graph,
|
|
130
137
|
metrics, and stale-memory metadata. Memory is marked stale when status or
|
|
@@ -204,6 +211,7 @@ Local repo tools:
|
|
|
204
211
|
- `kage_pr_check`
|
|
205
212
|
- `kage_quality`
|
|
206
213
|
- `kage_benchmark`
|
|
214
|
+
- `kage_benchmark_compare`
|
|
207
215
|
- `kage_setup_agent`
|
|
208
216
|
- `kage_graph`
|
|
209
217
|
- `kage_graph_visual`
|
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,7 @@ Usage:
|
|
|
31
31
|
kage metrics --project <dir> [--json]
|
|
32
32
|
kage quality --project <dir> [--json]
|
|
33
33
|
kage benchmark --project <dir> [--json]
|
|
34
|
+
kage benchmark --project <dir> --compare --task <task> [--json]
|
|
34
35
|
kage code-graph --project <dir> [--json]
|
|
35
36
|
kage code-graph "<query>" --project <dir> [--json]
|
|
36
37
|
kage graph --project <dir> [--json]
|
|
@@ -533,6 +534,46 @@ async function main() {
|
|
|
533
534
|
return;
|
|
534
535
|
}
|
|
535
536
|
if (command === "benchmark") {
|
|
537
|
+
if (args.includes("--compare")) {
|
|
538
|
+
const result = (0, kernel_js_1.benchmarkTaskComparison)(projectArg(args), takeArg(args, "--task") ?? firstPositional(args) ?? "how do I run tests");
|
|
539
|
+
if (args.includes("--json")) {
|
|
540
|
+
console.log(JSON.stringify(result, null, 2));
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
console.log(`Kage A/B Benchmark: ${result.project_dir}`);
|
|
544
|
+
console.log(`Task: ${result.task}`);
|
|
545
|
+
console.log("");
|
|
546
|
+
console.log("Without Kage:");
|
|
547
|
+
console.log(` Files examined: ${result.baseline_without_kage.files_examined}`);
|
|
548
|
+
console.log(` Full-file tokens: ${result.baseline_without_kage.full_file_tokens}`);
|
|
549
|
+
console.log(` Steps: ${result.baseline_without_kage.steps}`);
|
|
550
|
+
console.log(` Estimated time: ${result.baseline_without_kage.estimated_time_seconds}s`);
|
|
551
|
+
console.log("");
|
|
552
|
+
console.log("With Kage:");
|
|
553
|
+
console.log(` Memory packets: ${result.with_kage.memory_packets_used}`);
|
|
554
|
+
console.log(` Code facts: ${result.with_kage.code_files_returned + result.with_kage.code_symbols_returned + result.with_kage.code_routes_returned + result.with_kage.code_tests_returned}`);
|
|
555
|
+
console.log(` Context tokens: ${result.with_kage.context_tokens}`);
|
|
556
|
+
console.log(` Steps: ${result.with_kage.steps}`);
|
|
557
|
+
console.log(` Estimated time: ${result.with_kage.estimated_time_seconds}s`);
|
|
558
|
+
console.log("");
|
|
559
|
+
console.log("Delta:");
|
|
560
|
+
console.log(` Estimated tokens saved: ${result.delta.estimated_tokens_saved}`);
|
|
561
|
+
console.log(` Context reduction: ${result.delta.context_reduction_percent}%`);
|
|
562
|
+
console.log(` Rediscovery steps saved: ${result.delta.rediscovery_steps_saved}`);
|
|
563
|
+
console.log(` Estimated time saved: ${result.delta.estimated_time_saved_seconds}s`);
|
|
564
|
+
console.log(` Full-file reads avoided: ${result.delta.full_file_reads_avoided}`);
|
|
565
|
+
console.log(` Recall hit: ${result.delta.recall_hit ? "yes" : "no"}`);
|
|
566
|
+
console.log(` Code graph hit: ${result.delta.code_graph_hit ? "yes" : "no"}`);
|
|
567
|
+
console.log("");
|
|
568
|
+
console.log("Baseline files:");
|
|
569
|
+
for (const file of result.evidence.baseline_files.slice(0, 8))
|
|
570
|
+
console.log(` - ${file.path} (${file.tokens} tokens): ${file.why}`);
|
|
571
|
+
console.log("");
|
|
572
|
+
console.log("Kage memory:");
|
|
573
|
+
for (const packet of result.evidence.kage_memory.slice(0, 5))
|
|
574
|
+
console.log(` - ${packet.title} (${packet.type}, score ${packet.score})`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
536
577
|
const result = (0, kernel_js_1.benchmarkProject)(projectArg(args));
|
|
537
578
|
if (args.includes("--json")) {
|
|
538
579
|
console.log(JSON.stringify(result, null, 2));
|
package/dist/index.js
CHANGED
|
@@ -209,6 +209,18 @@ function listTools() {
|
|
|
209
209
|
required: ["project_dir"],
|
|
210
210
|
},
|
|
211
211
|
},
|
|
212
|
+
{
|
|
213
|
+
name: "kage_benchmark_compare",
|
|
214
|
+
description: "Compare the same task on the same repo with and without Kage. Reports estimated baseline discovery tokens/steps versus Kage recall/code-graph context, with evidence and caveats.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
project_dir: { type: "string" },
|
|
219
|
+
task: { type: "string" },
|
|
220
|
+
},
|
|
221
|
+
required: ["project_dir", "task"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
212
224
|
{
|
|
213
225
|
name: "kage_setup_agent",
|
|
214
226
|
description: "Generate MCP/setup instructions for Codex, Claude Code, Cursor, Windsurf, Gemini CLI, OpenCode, Cline, Goose, Roo Code, Kilo Code, Claude Desktop, Aider, or generic MCP.",
|
|
@@ -661,6 +673,12 @@ async function callTool(name, args) {
|
|
|
661
673
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
662
674
|
};
|
|
663
675
|
}
|
|
676
|
+
if (name === "kage_benchmark_compare") {
|
|
677
|
+
const result = (0, kernel_js_1.benchmarkTaskComparison)(String(args?.project_dir ?? ""), String(args?.task ?? ""));
|
|
678
|
+
return {
|
|
679
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
664
682
|
if (name === "kage_setup_agent") {
|
|
665
683
|
const result = (0, kernel_js_1.setupAgent)(String(args?.agent ?? ""), String(args?.project_dir ?? ""), { write: Boolean(args?.write) });
|
|
666
684
|
return {
|
package/dist/kernel.js
CHANGED
|
@@ -75,6 +75,7 @@ exports.graphMermaid = graphMermaid;
|
|
|
75
75
|
exports.kageMetrics = kageMetrics;
|
|
76
76
|
exports.qualityReport = qualityReport;
|
|
77
77
|
exports.benchmarkProject = benchmarkProject;
|
|
78
|
+
exports.benchmarkTaskComparison = benchmarkTaskComparison;
|
|
78
79
|
exports.learn = learn;
|
|
79
80
|
exports.capture = capture;
|
|
80
81
|
exports.createPublicCandidate = createPublicCandidate;
|
|
@@ -3064,6 +3065,110 @@ function benchmarkProject(projectDir) {
|
|
|
3064
3065
|
},
|
|
3065
3066
|
};
|
|
3066
3067
|
}
|
|
3068
|
+
function baselineDiscoveryFiles(projectDir, task) {
|
|
3069
|
+
const terms = tokenize(task);
|
|
3070
|
+
const graph = buildCodeGraph(projectDir);
|
|
3071
|
+
const candidatePaths = unique([
|
|
3072
|
+
"README.md",
|
|
3073
|
+
"AGENTS.md",
|
|
3074
|
+
"CLAUDE.md",
|
|
3075
|
+
"package.json",
|
|
3076
|
+
...graph.files.map((file) => file.path),
|
|
3077
|
+
]).filter((path) => path && !shouldSkipRepoMemoryPath(path));
|
|
3078
|
+
return candidatePaths
|
|
3079
|
+
.map((path) => {
|
|
3080
|
+
const absolute = (0, node_path_1.join)(projectDir, path);
|
|
3081
|
+
if (!(0, node_fs_1.existsSync)(absolute))
|
|
3082
|
+
return null;
|
|
3083
|
+
const stats = (0, node_fs_1.statSync)(absolute);
|
|
3084
|
+
if (!stats.isFile() || stats.size > 240_000)
|
|
3085
|
+
return null;
|
|
3086
|
+
const text = (0, node_fs_1.readFileSync)(absolute, "utf8");
|
|
3087
|
+
const score = scoreText(terms, `${path}\n${text.slice(0, 8000)}`, [path]);
|
|
3088
|
+
const alwaysUseful = ["README.md", "AGENTS.md", "CLAUDE.md", "package.json"].includes(path);
|
|
3089
|
+
if (score <= 0 && !alwaysUseful)
|
|
3090
|
+
return null;
|
|
3091
|
+
return {
|
|
3092
|
+
path,
|
|
3093
|
+
tokens: Math.max(1, Math.ceil(stats.size / 4)),
|
|
3094
|
+
why: score > 0 ? "task terms matched path or file content" : "standard repo orientation file",
|
|
3095
|
+
score: score + (alwaysUseful ? 1 : 0),
|
|
3096
|
+
};
|
|
3097
|
+
})
|
|
3098
|
+
.filter((entry) => Boolean(entry))
|
|
3099
|
+
.sort((a, b) => b.score - a.score || b.tokens - a.tokens || a.path.localeCompare(b.path))
|
|
3100
|
+
.slice(0, 10);
|
|
3101
|
+
}
|
|
3102
|
+
function benchmarkTaskComparison(projectDir, task) {
|
|
3103
|
+
ensureMemoryDirs(projectDir);
|
|
3104
|
+
const query = task.trim() || "how do I run tests";
|
|
3105
|
+
const baselineFiles = baselineDiscoveryFiles(projectDir, query);
|
|
3106
|
+
const baselineTokens = baselineFiles.reduce((sum, file) => sum + file.tokens, 0);
|
|
3107
|
+
const recallResult = recall(projectDir, query, 5, true);
|
|
3108
|
+
const codeResult = queryCodeGraph(projectDir, query, 10);
|
|
3109
|
+
const kageContext = `${recallResult.context_block}\n\n${codeResult.context_block}`;
|
|
3110
|
+
const kageTokens = estimateTokens(kageContext);
|
|
3111
|
+
const codeFactLines = [
|
|
3112
|
+
...codeResult.routes.map((route) => `[route] ${route.method} ${route.path} in ${route.file_path}:${route.line}`),
|
|
3113
|
+
...codeResult.symbols.map((symbol) => `[symbol] ${symbol.kind} ${symbol.name} in ${symbol.path}:${symbol.line}`),
|
|
3114
|
+
...codeResult.tests.map((test) => `[test] ${test.title} in ${test.test_path}:${test.line}`),
|
|
3115
|
+
...codeResult.files.slice(0, 5).map((file) => `[file] ${file.path} (${file.kind}, ${file.language}, ${file.parser})`),
|
|
3116
|
+
];
|
|
3117
|
+
const baselineSteps = Math.max(3, baselineFiles.length + 2);
|
|
3118
|
+
const kageSteps = 3;
|
|
3119
|
+
const tokensSaved = Math.max(0, baselineTokens - kageTokens);
|
|
3120
|
+
const contextReduction = baselineTokens > 0 ? percent(tokensSaved, baselineTokens) : 0;
|
|
3121
|
+
const timeSaved = Math.max(0, baselineSteps * 45 - kageSteps * 12);
|
|
3122
|
+
return {
|
|
3123
|
+
schema_version: 1,
|
|
3124
|
+
project_dir: projectDir,
|
|
3125
|
+
task: query,
|
|
3126
|
+
generated_at: nowIso(),
|
|
3127
|
+
baseline_without_kage: {
|
|
3128
|
+
strategy: "manual_repo_discovery_estimate",
|
|
3129
|
+
files_examined: baselineFiles.length,
|
|
3130
|
+
full_file_tokens: baselineTokens,
|
|
3131
|
+
steps: baselineSteps,
|
|
3132
|
+
estimated_time_seconds: baselineSteps * 45,
|
|
3133
|
+
},
|
|
3134
|
+
with_kage: {
|
|
3135
|
+
strategy: "recall_plus_code_graph",
|
|
3136
|
+
recall_results: recallResult.results.length,
|
|
3137
|
+
memory_packets_used: recallResult.results.length,
|
|
3138
|
+
code_files_returned: codeResult.files.length,
|
|
3139
|
+
code_symbols_returned: codeResult.symbols.length,
|
|
3140
|
+
code_routes_returned: codeResult.routes.length,
|
|
3141
|
+
code_tests_returned: codeResult.tests.length,
|
|
3142
|
+
context_tokens: kageTokens,
|
|
3143
|
+
steps: kageSteps,
|
|
3144
|
+
estimated_time_seconds: kageSteps * 12,
|
|
3145
|
+
},
|
|
3146
|
+
delta: {
|
|
3147
|
+
estimated_tokens_saved: tokensSaved,
|
|
3148
|
+
context_reduction_percent: contextReduction,
|
|
3149
|
+
rediscovery_steps_saved: Math.max(0, baselineSteps - kageSteps),
|
|
3150
|
+
estimated_time_saved_seconds: timeSaved,
|
|
3151
|
+
full_file_reads_avoided: Math.max(0, baselineFiles.length - codeResult.files.length),
|
|
3152
|
+
recall_hit: recallResult.results.length > 0,
|
|
3153
|
+
code_graph_hit: codeFactLines.length > 0,
|
|
3154
|
+
},
|
|
3155
|
+
evidence: {
|
|
3156
|
+
baseline_files: baselineFiles.map(({ path, tokens, why }) => ({ path, tokens, why })),
|
|
3157
|
+
kage_memory: recallResult.results.map((entry) => ({
|
|
3158
|
+
id: entry.packet.id,
|
|
3159
|
+
title: entry.packet.title,
|
|
3160
|
+
type: entry.packet.type,
|
|
3161
|
+
score: entry.score,
|
|
3162
|
+
})),
|
|
3163
|
+
kage_code_facts: codeFactLines.slice(0, 12),
|
|
3164
|
+
},
|
|
3165
|
+
caveats: [
|
|
3166
|
+
"Baseline is a deterministic manual-discovery estimate, not a live human or agent timing trace.",
|
|
3167
|
+
"Token savings estimate full-file reads avoided versus compact Kage recall/code-graph context.",
|
|
3168
|
+
"Use this for relative proof on the same repo/task, not cross-repo absolute claims.",
|
|
3169
|
+
],
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3067
3172
|
function kageMetricsShallow(projectDir) {
|
|
3068
3173
|
const codeGraph = buildCodeGraph(projectDir);
|
|
3069
3174
|
const knowledgeGraph = buildKnowledgeGraph(projectDir);
|