@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 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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Local-first repo memory, code graph, and recall MCP server for coding agents",
5
5
  "main": "dist/index.js",
6
6
  "files": [