@kage-core/kage-graph-mcp 1.1.24 → 1.1.26

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
@@ -36,10 +36,18 @@ Restart your agent once after setup so MCP tools reload.
36
36
 
37
37
  - repo-local memory for decisions, runbooks, bug fixes, gotchas, conventions,
38
38
  and code explanations
39
- - a code graph for files, symbols, imports, calls, routes, tests, and packages
39
+ - a code graph for files, symbols, imports, calls, routes, tests, and packages,
40
+ including generic call/test signals for non-TypeScript repos
40
41
  - memory-code links so project knowledge points at the code it affects
42
+ - decision intelligence for why-memory coverage, stale/weak packets, and
43
+ important files that still lack linked repo knowledge
44
+ - lightweight workspace recall across sibling repos, including package and
45
+ route-contract links when existing code graphs expose them
46
+ - local git intelligence for risk, reviewers, contributor profiles, co-change
47
+ warnings, ownership silos, and module health
41
48
  - `AGENTS.md` bootstrap instructions so agents recall context automatically
42
- - a local viewer for memory, code graph, metrics, evidence, and review state
49
+ - a local viewer for memory, code graph, decision memory, risk, module health,
50
+ workspace reports, metrics, evidence, and review state
43
51
  - review and validation commands for stale or risky memory
44
52
 
45
53
  No hosted service, external database, or API key is required.
@@ -52,6 +60,16 @@ kage init --project .
52
60
  kage recall "how do I run tests" --project .
53
61
  kage recall "how do I run tests" --project . --json --explain
54
62
  kage code-graph "auth routes tests" --project .
63
+ kage cleanup-candidates --project . --json
64
+ kage dependency-path --project . --from src/app.ts --to src/auth.ts --json
65
+ kage module-health --project . --json
66
+ kage graph-insights --project . --json
67
+ kage workspace --project .. --json
68
+ kage workspace recall "auth header contract" --project .. --json
69
+ kage contributors --project . --json
70
+ kage decisions --project . --json
71
+ kage reviewers --project . --changed-files src/auth.ts,src/session.ts --json
72
+ kage risk --project . --targets src/auth.ts --json
55
73
  kage learn --project . --learning "Use npm test after parser changes."
56
74
  kage refresh --project .
57
75
  kage pr check --project .
@@ -61,6 +79,10 @@ kage inbox --project . --json
61
79
  kage viewer --project .
62
80
  ```
63
81
 
82
+ MCP agents should start with `kage_context`. When the query or target list
83
+ mentions file paths, it includes risk and dependency-path context alongside
84
+ memory recall.
85
+
64
86
  For stale or wrong memory:
65
87
 
66
88
  ```bash
@@ -99,6 +121,7 @@ Kage keeps learned memory separate from generated code facts.
99
121
  | Structural map | `.agent_memory/structural/` | files, symbols, imports |
100
122
  | Code graph | `.agent_memory/code_graph/` | source-derived code facts |
101
123
  | Metrics | `.agent_memory/metrics.json` | readiness, quality, coverage |
124
+ | Reports | `.agent_memory/reports/` | risk, contributors, decisions, module health, graph insights, workspace, quality, benchmark |
102
125
 
103
126
  Repo-local packets are git-visible and reviewable. Generated indexes and graphs
104
127
  are rebuildable.
@@ -114,6 +137,17 @@ Kage is optimized so repeat work scales with changed files, not the whole repo:
114
137
  - huge files are represented safely instead of deeply expanded
115
138
  - recall builds lookup maps once per query instead of repeatedly scanning graph
116
139
  edges for every memory packet
140
+ - local risk reports include hidden co-change warnings and ownership-silo
141
+ signals from git history
142
+ - contributor reports show commits, recent activity, touched files/modules,
143
+ primary ownership, ownership silos, hotspot ownership, and commit category mix
144
+ - decision reports show why-memory coverage, weak/stale decision packets, and
145
+ high-signal source paths with no linked decision memory
146
+ - graph insights include language parser coverage and edge mix so large-repo
147
+ index gaps are visible instead of hidden
148
+ - the generic non-TypeScript indexer extracts bounded call edges and test
149
+ function coverage, while SCIP/LSP/LSIF/tree-sitter artifacts override it when
150
+ available
117
151
 
118
152
  ## Viewer
119
153
 
@@ -123,6 +157,11 @@ Open a local viewer for the current repo:
123
157
  kage viewer --project .
124
158
  ```
125
159
 
160
+ The local viewer loads graph artifacts plus `.agent_memory/reports/*.json` and
161
+ shows a repo-intelligence cockpit for memory-code links, decision memory, risk,
162
+ contributors, module health, graph insights, workspace coverage, quality, and
163
+ benchmark proof.
164
+
126
165
  Hosted demo:
127
166
 
128
167
  ```text
package/dist/cli.js CHANGED
@@ -31,6 +31,12 @@ Usage:
31
31
  kage upgrade [--dry-run]
32
32
  kage branch --project <dir> [--json]
33
33
  kage metrics --project <dir> [--json]
34
+ kage contributors --project <dir> [--json]
35
+ kage decisions --project <dir> [--json]
36
+ kage module-health --project <dir> [--json]
37
+ kage graph-insights --project <dir> [--json]
38
+ kage workspace --project <workspace-dir> [--json]
39
+ kage workspace recall "<query>" --project <workspace-dir> [--json]
34
40
  kage audit --project <dir> [--json]
35
41
  kage inbox --project <dir> [--json]
36
42
  kage quality --project <dir> [--json]
@@ -38,6 +44,10 @@ Usage:
38
44
  kage benchmark --project <dir> --compare --task <task> [--json]
39
45
  kage code-graph --project <dir> [--json]
40
46
  kage code-graph "<query>" --project <dir> [--json]
47
+ kage cleanup-candidates --project <dir> [--json]
48
+ kage dependency-path --project <dir> --from <path> --to <path> [--json]
49
+ kage reviewers --project <dir> [--targets a,b] [--changed-files a,b] [--json]
50
+ kage risk --project <dir> [--targets a,b] [--changed-files a,b] [--json]
41
51
  kage code-index --project <dir> [--json]
42
52
  kage structural-index --project <dir> [--json]
43
53
  kage graph --project <dir> [--json]
@@ -506,6 +516,123 @@ async function main() {
506
516
  console.log(`- ${symbol.kind} ${symbol.name} (${symbol.path}:${symbol.line})`);
507
517
  return;
508
518
  }
519
+ if (command === "risk") {
520
+ const result = (0, kernel_js_1.kageRisk)(projectArg(args), listArg(takeArg(args, "--targets")), listArg(takeArg(args, "--changed-files")));
521
+ if (args.includes("--json")) {
522
+ console.log(JSON.stringify(result, null, 2));
523
+ return;
524
+ }
525
+ console.log("Kage risk assessment");
526
+ for (const item of Object.values(result.targets)) {
527
+ console.log(`- ${item.risk_summary}`);
528
+ if (item.dependents.length)
529
+ console.log(` Dependents: ${item.dependents.slice(0, 5).join(", ")}`);
530
+ if (item.git.co_change_partners.length)
531
+ console.log(` Co-change: ${item.git.co_change_partners.map((p) => `${p.file_path} (${p.count})`).join(", ")}`);
532
+ }
533
+ if (result.global_hotspots.length) {
534
+ console.log("Global hotspots:");
535
+ for (const hotspot of result.global_hotspots)
536
+ console.log(`- ${hotspot.file_path}: ${hotspot.commit_count_90d} commits in 90d`);
537
+ }
538
+ if (result.warnings.length)
539
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
540
+ return;
541
+ }
542
+ if (command === "dependency-path") {
543
+ const from = takeArg(args, "--from");
544
+ const to = takeArg(args, "--to");
545
+ if (!from || !to)
546
+ usage();
547
+ const result = (0, kernel_js_1.kageDependencyPath)(projectArg(args), from, to);
548
+ if (args.includes("--json")) {
549
+ console.log(JSON.stringify(result, null, 2));
550
+ return;
551
+ }
552
+ console.log(result.summary);
553
+ if (result.path.length)
554
+ console.log(`Path: ${result.path.join(" -> ")}`);
555
+ for (const edge of result.edges) {
556
+ console.log(`- ${edge.from_path}:${edge.line} ${edge.kind} ${edge.specifier} -> ${edge.to_path}`);
557
+ }
558
+ if (result.warnings.length)
559
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
560
+ return;
561
+ }
562
+ if (command === "cleanup-candidates") {
563
+ const result = (0, kernel_js_1.kageCleanupCandidates)(projectArg(args));
564
+ if (args.includes("--json")) {
565
+ console.log(JSON.stringify(result, null, 2));
566
+ return;
567
+ }
568
+ console.log(`Kage cleanup candidates: ${result.summary}`);
569
+ for (const candidate of result.candidates.slice(0, 20)) {
570
+ console.log(`- ${candidate.path}: ${candidate.confidence} (${Math.round(candidate.score * 100)}%)`);
571
+ console.log(` ${candidate.reasons.join("; ")}`);
572
+ }
573
+ if (result.warnings.length)
574
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
575
+ return;
576
+ }
577
+ if (command === "reviewers") {
578
+ const result = (0, kernel_js_1.kageReviewerSuggestions)(projectArg(args), listArg(takeArg(args, "--targets")), listArg(takeArg(args, "--changed-files")));
579
+ if (args.includes("--json")) {
580
+ console.log(JSON.stringify(result, null, 2));
581
+ return;
582
+ }
583
+ console.log(`Kage reviewer suggestions: ${result.summary}`);
584
+ for (const suggestion of result.suggestions) {
585
+ console.log(`- ${suggestion.reviewer}: ${Math.round(suggestion.score)}%`);
586
+ console.log(` ${suggestion.reasons.slice(0, 3).join("; ")}`);
587
+ }
588
+ if (result.warnings.length)
589
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
590
+ return;
591
+ }
592
+ if (command === "contributors") {
593
+ const result = (0, kernel_js_1.kageContributors)(projectArg(args));
594
+ if (args.includes("--json")) {
595
+ console.log(JSON.stringify(result, null, 2));
596
+ return;
597
+ }
598
+ console.log(`Kage contributors: ${result.summary}`);
599
+ for (const profile of result.contributors.slice(0, 10)) {
600
+ console.log(`- ${profile.contributor}: ${profile.commits_total} commits, ${profile.commits_90d} in 90d, ${profile.primary_owned_files} owned files`);
601
+ if (profile.files_touched.length)
602
+ console.log(` Top files: ${profile.files_touched.slice(0, 3).map((file) => `${file.path} (${file.commits})`).join(", ")}`);
603
+ if (profile.silo_files.length)
604
+ console.log(` Silo files: ${profile.silo_files.slice(0, 3).map((file) => `${file.path} (${Math.round(file.ownership_pct * 100)}%)`).join(", ")}`);
605
+ }
606
+ if (result.warnings.length)
607
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
608
+ return;
609
+ }
610
+ if (command === "decisions") {
611
+ const result = (0, kernel_js_1.kageDecisionIntelligence)(projectArg(args));
612
+ if (args.includes("--json")) {
613
+ console.log(JSON.stringify(result, null, 2));
614
+ return;
615
+ }
616
+ console.log(`Kage decision intelligence: ${result.summary}`);
617
+ if (result.top_decisions.length) {
618
+ console.log("Top why-memory:");
619
+ for (const item of result.top_decisions.slice(0, 10)) {
620
+ console.log(`- ${item.title} (${item.type})`);
621
+ if (item.paths.length)
622
+ console.log(` Paths: ${item.paths.slice(0, 3).join(", ")}`);
623
+ if (item.why)
624
+ console.log(` Why: ${item.why}`);
625
+ }
626
+ }
627
+ if (result.coverage_gaps.length) {
628
+ console.log("Uncovered important paths:");
629
+ for (const gap of result.coverage_gaps.slice(0, 10))
630
+ console.log(`- ${gap.path}: ${gap.reason}`);
631
+ }
632
+ if (result.warnings.length)
633
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
634
+ return;
635
+ }
509
636
  if (command === "code-index") {
510
637
  const result = (0, kernel_js_1.writeCodeIndex)(projectArg(args));
511
638
  if (args.includes("--json")) {
@@ -616,6 +743,82 @@ async function main() {
616
743
  }
617
744
  return;
618
745
  }
746
+ if (command === "module-health") {
747
+ const result = (0, kernel_js_1.kageModuleHealth)(projectArg(args));
748
+ if (args.includes("--json")) {
749
+ console.log(JSON.stringify(result, null, 2));
750
+ return;
751
+ }
752
+ console.log(`Kage module health: ${result.summary}`);
753
+ for (const item of result.modules.slice(0, 20)) {
754
+ console.log(`- ${item.module}: ${item.grade} (${item.score}) - ${item.reasons.join("; ")}`);
755
+ }
756
+ if (result.warnings.length)
757
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
758
+ return;
759
+ }
760
+ if (command === "graph-insights") {
761
+ const result = (0, kernel_js_1.kageGraphInsights)(projectArg(args));
762
+ if (args.includes("--json")) {
763
+ console.log(JSON.stringify(result, null, 2));
764
+ return;
765
+ }
766
+ console.log(`Kage graph insights: ${result.summary}`);
767
+ if (result.central_files.length) {
768
+ console.log("Central files:");
769
+ for (const file of result.central_files.slice(0, 10))
770
+ console.log(`- ${file.path}: pagerank ${file.pagerank}, ${file.dependents} dependent(s)`);
771
+ }
772
+ if (result.dependency_cycles.length) {
773
+ console.log("Dependency cycles:");
774
+ for (const cycle of result.dependency_cycles.slice(0, 5))
775
+ console.log(`- ${cycle.files.join(" -> ")}`);
776
+ }
777
+ if (result.entry_flows.length) {
778
+ console.log("Entry flows:");
779
+ for (const flow of result.entry_flows.slice(0, 5))
780
+ console.log(`- ${flow.path.join(" -> ")}`);
781
+ }
782
+ return;
783
+ }
784
+ if (command === "workspace") {
785
+ const subcommand = args[1];
786
+ if (subcommand === "recall") {
787
+ const query = args.find((arg, index) => index > 1 && !arg.startsWith("--") && !args[index - 1]?.startsWith("--"));
788
+ if (!query)
789
+ usage();
790
+ const result = (0, kernel_js_1.kageWorkspaceRecall)(projectArg(args), query, numberArg(args, "--limit", 8));
791
+ if (args.includes("--json")) {
792
+ console.log(JSON.stringify(result, null, 2));
793
+ return;
794
+ }
795
+ console.log(result.context_block);
796
+ if (result.warnings.length)
797
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
798
+ return;
799
+ }
800
+ const result = (0, kernel_js_1.kageWorkspace)(projectArg(args));
801
+ if (args.includes("--json")) {
802
+ console.log(JSON.stringify(result, null, 2));
803
+ return;
804
+ }
805
+ console.log(`Kage workspace: ${result.summary}`);
806
+ for (const repo of result.repos) {
807
+ const deps = repo.dependencies_on_workspace_repos.length
808
+ ? ` depends on ${repo.dependencies_on_workspace_repos.map((dep) => dep.alias).join(", ")}`
809
+ : "";
810
+ console.log(`- ${repo.alias}: ${repo.path} (${repo.approved_packets} packets, ${repo.code_files} files)${deps}`);
811
+ }
812
+ if (result.route_contracts.length) {
813
+ console.log("Route contracts:");
814
+ for (const contract of result.route_contracts.slice(0, 10)) {
815
+ console.log(`- ${contract.provider_repo} ${contract.method} ${contract.path} -> ${contract.consumer_repo}/${contract.consumer_file}`);
816
+ }
817
+ }
818
+ if (result.warnings.length)
819
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
820
+ return;
821
+ }
619
822
  if (command === "audit") {
620
823
  const result = (0, kernel_js_1.auditProject)(projectArg(args));
621
824
  if (args.includes("--json")) {
package/dist/daemon.js CHANGED
@@ -240,17 +240,35 @@ async function startViewer(projectDir, options = {}) {
240
240
  const inboxPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "inbox.json");
241
241
  const reviewPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "review", "memory-review.md");
242
242
  const pendingDir = (0, node_path_1.join)(projectRoot, ".agent_memory", "pending");
243
+ const reportsDir = (0, node_path_1.join)(projectRoot, ".agent_memory", "reports");
244
+ const qualityPath = (0, node_path_1.join)(reportsDir, "quality.json");
245
+ const benchmarkPath = (0, node_path_1.join)(reportsDir, "benchmark.json");
246
+ const contributorsPath = (0, node_path_1.join)(reportsDir, "contributors.json");
247
+ const decisionsPath = (0, node_path_1.join)(reportsDir, "decisions.json");
248
+ const riskPath = (0, node_path_1.join)(reportsDir, "risk.json");
249
+ const moduleHealthPath = (0, node_path_1.join)(reportsDir, "module-health.json");
250
+ const graphInsightsPath = (0, node_path_1.join)(reportsDir, "graph-insights.json");
251
+ const workspacePath = (0, node_path_1.join)(reportsDir, "workspace.json");
243
252
  // Pre-generate lightweight JSON reports so the viewer can load them directly.
244
253
  try {
254
+ (0, node_fs_1.mkdirSync)(reportsDir, { recursive: true });
245
255
  const metrics = (0, kernel_js_1.kageMetrics)(projectDir);
246
256
  (0, node_fs_1.writeFileSync)(metricsPath, JSON.stringify(metrics, null, 2));
247
257
  const inbox = (0, kernel_js_1.memoryInbox)(projectDir);
248
258
  (0, node_fs_1.writeFileSync)(inboxPath, JSON.stringify(inbox, null, 2));
259
+ (0, node_fs_1.writeFileSync)(qualityPath, JSON.stringify((0, kernel_js_1.qualityReport)(projectDir), null, 2));
260
+ (0, node_fs_1.writeFileSync)(benchmarkPath, JSON.stringify((0, kernel_js_1.benchmarkProject)(projectDir), null, 2));
261
+ (0, node_fs_1.writeFileSync)(contributorsPath, JSON.stringify((0, kernel_js_1.kageContributors)(projectDir), null, 2));
262
+ (0, node_fs_1.writeFileSync)(decisionsPath, JSON.stringify((0, kernel_js_1.kageDecisionIntelligence)(projectDir), null, 2));
263
+ (0, node_fs_1.writeFileSync)(riskPath, JSON.stringify((0, kernel_js_1.kageRisk)(projectDir), null, 2));
264
+ (0, node_fs_1.writeFileSync)(moduleHealthPath, JSON.stringify((0, kernel_js_1.kageModuleHealth)(projectDir), null, 2));
265
+ (0, node_fs_1.writeFileSync)(graphInsightsPath, JSON.stringify((0, kernel_js_1.kageGraphInsights)(projectDir), null, 2));
266
+ (0, node_fs_1.writeFileSync)(workspacePath, JSON.stringify((0, kernel_js_1.kageWorkspace)(projectDir), null, 2));
249
267
  }
250
268
  catch {
251
269
  // non-fatal: viewer will show 404 for reports if generation fails
252
270
  }
253
- const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&inbox=${encodeURIComponent(inboxPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}&view=code`;
271
+ const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&inbox=${encodeURIComponent(inboxPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}&quality=${encodeURIComponent(qualityPath)}&benchmark=${encodeURIComponent(benchmarkPath)}&contributors=${encodeURIComponent(contributorsPath)}&decisions=${encodeURIComponent(decisionsPath)}&risk=${encodeURIComponent(riskPath)}&moduleHealth=${encodeURIComponent(moduleHealthPath)}&graphInsights=${encodeURIComponent(graphInsightsPath)}&workspace=${encodeURIComponent(workspacePath)}&view=code`;
254
272
  const server = (0, node_http_1.createServer)((req, res) => {
255
273
  const requestUrl = new URL(req.url ?? "/", `http://${host}:${port}`);
256
274
  let filePath = null;
package/dist/index.js CHANGED
@@ -55,6 +55,25 @@ function arrayArg(value) {
55
55
  return value.split(",").map((item) => item.trim()).filter(Boolean);
56
56
  return [];
57
57
  }
58
+ function filePathHints(query) {
59
+ const matches = query.match(/[A-Za-z0-9_./@-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|kts|rb|php|cs|c|h|cc|cpp|hpp|swift|json|md)\b/g) ?? [];
60
+ return [...new Set(matches.map((match) => match.replace(/^\.\//, "")).filter((match) => !/^https?:\/\//.test(match)))];
61
+ }
62
+ function wantsDependencyPath(query) {
63
+ return /\b(connect|connected|dependency|depend|depends|path|impact|flow|trace)\b/i.test(query);
64
+ }
65
+ function riskContextBlock(result) {
66
+ const targets = Object.values(result.targets);
67
+ if (!targets.length)
68
+ return "";
69
+ const lines = targets.slice(0, 5).map((item) => {
70
+ const coChange = item.git.co_change_partners.length
71
+ ? ` Co-change: ${item.git.co_change_partners.slice(0, 3).map((partner) => `${partner.file_path} (${partner.count})`).join(", ")}.`
72
+ : "";
73
+ return `- ${item.risk_summary}${coChange}`;
74
+ });
75
+ return `\n## Risk Signals\n${lines.join("\n")}`;
76
+ }
58
77
  const server = new index_js_1.Server({ name: "kage-graph", version: "1.1.7" }, { capabilities: { tools: {} } });
59
78
  function listTools() {
60
79
  return [
@@ -70,6 +89,8 @@ function listTools() {
70
89
  project_dir: { type: "string", description: "Absolute path to the project root" },
71
90
  query: { type: "string", description: "The task or question — used for both memory recall and code graph search" },
72
91
  limit: { type: "number", description: "Max memory packets to return (default 5)" },
92
+ targets: { type: "array", items: { type: "string" }, description: "Optional files the agent may edit or explain; used for risk context" },
93
+ changed_files: { type: "array", items: { type: "string" }, description: "Optional changed files for pre-edit or PR risk context" },
73
94
  },
74
95
  required: ["project_dir", "query"],
75
96
  },
@@ -172,6 +193,78 @@ function listTools() {
172
193
  required: ["project_dir"],
173
194
  },
174
195
  },
196
+ {
197
+ name: "kage_risk",
198
+ 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.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ project_dir: { type: "string" },
203
+ targets: { type: "array", items: { type: "string" }, description: "File paths to assess" },
204
+ changed_files: { type: "array", items: { type: "string" }, description: "Optional PR/branch changed files. If targets is omitted, these are assessed." },
205
+ },
206
+ required: ["project_dir"],
207
+ },
208
+ },
209
+ {
210
+ name: "kage_dependency_path",
211
+ description: "Find how two files are connected in Kage's source-derived code graph. Reports direct dependency direction, reverse impact direction, or undirected graph connection.",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ project_dir: { type: "string" },
216
+ from: { type: "string", description: "Source file path or unique suffix" },
217
+ to: { type: "string", description: "Target file path or unique suffix" },
218
+ },
219
+ required: ["project_dir", "from", "to"],
220
+ },
221
+ },
222
+ {
223
+ name: "kage_cleanup_candidates",
224
+ description: "Find conservative cleanup candidates from Kage's code graph. Reports unreferenced source files with confidence and reasons; never auto-deletes.",
225
+ inputSchema: {
226
+ type: "object",
227
+ properties: {
228
+ project_dir: { type: "string" },
229
+ },
230
+ required: ["project_dir"],
231
+ },
232
+ },
233
+ {
234
+ name: "kage_reviewers",
235
+ description: "Suggest reviewers for target or changed files from local git authorship, recent edits, and code-graph co-change ownership. Does not contact GitHub or external services.",
236
+ inputSchema: {
237
+ type: "object",
238
+ properties: {
239
+ project_dir: { type: "string" },
240
+ targets: { type: "array", items: { type: "string" }, description: "File paths to review" },
241
+ changed_files: { type: "array", items: { type: "string" }, description: "Optional PR/branch changed files. If targets is omitted, these are used." },
242
+ },
243
+ required: ["project_dir"],
244
+ },
245
+ },
246
+ {
247
+ name: "kage_contributors",
248
+ description: "Build local contributor profiles from git history: commits, recent activity, touched files, modules, ownership silos, hotspot ownership, and commit category mix.",
249
+ inputSchema: {
250
+ type: "object",
251
+ properties: {
252
+ project_dir: { type: "string" },
253
+ },
254
+ required: ["project_dir"],
255
+ },
256
+ },
257
+ {
258
+ name: "kage_decisions",
259
+ description: "Summarize Kage why-memory for a repo: decisions, gotchas, runbooks, conventions, code explanations, path coverage, weak/stale memory, and important code paths that still lack decision memory.",
260
+ inputSchema: {
261
+ type: "object",
262
+ properties: {
263
+ project_dir: { type: "string" },
264
+ },
265
+ required: ["project_dir"],
266
+ },
267
+ },
175
268
  {
176
269
  name: "kage_code_index",
177
270
  description: "Write external code index artifacts consumed by the code graph. Prefers SCIP when scip-typescript and scip are installed, then falls back to the built-in LSP-compatible symbol index.",
@@ -205,6 +298,52 @@ function listTools() {
205
298
  required: ["project_dir"],
206
299
  },
207
300
  },
301
+ {
302
+ name: "kage_module_health",
303
+ description: "Return local module health scorecards from Kage's code graph, test signals, cleanup candidates, git churn, and ownership concentration.",
304
+ inputSchema: {
305
+ type: "object",
306
+ properties: {
307
+ project_dir: { type: "string" },
308
+ },
309
+ required: ["project_dir"],
310
+ },
311
+ },
312
+ {
313
+ name: "kage_graph_insights",
314
+ description: "Return deterministic code graph intelligence: central files, dependency cycles, import communities, and short entry flows. Use to orient agents before broad architectural edits.",
315
+ inputSchema: {
316
+ type: "object",
317
+ properties: {
318
+ project_dir: { type: "string", description: "Absolute path to the project root" },
319
+ },
320
+ required: ["project_dir"],
321
+ },
322
+ },
323
+ {
324
+ name: "kage_workspace",
325
+ description: "Summarize a local multi-repo workspace: discovered git repos, Kage memory coverage, code graph counts, and package dependencies between repos. Use when a task spans multiple sibling repos.",
326
+ inputSchema: {
327
+ type: "object",
328
+ properties: {
329
+ project_dir: { type: "string", description: "Workspace root directory to scan for git repos" },
330
+ },
331
+ required: ["project_dir"],
332
+ },
333
+ },
334
+ {
335
+ name: "kage_workspace_recall",
336
+ description: "Recall Kage memory across every indexed repo in a local workspace and rank the combined hits. Use for cross-repo teammate knowledge and shared context.",
337
+ inputSchema: {
338
+ type: "object",
339
+ properties: {
340
+ project_dir: { type: "string", description: "Workspace root directory to scan for git repos" },
341
+ query: { type: "string", description: "Question or task to recall across repos" },
342
+ limit: { type: "number", description: "Max combined hits to return (default 8)" },
343
+ },
344
+ required: ["project_dir", "query"],
345
+ },
346
+ },
208
347
  {
209
348
  name: "kage_audit",
210
349
  description: "Audit whether repo memory and code intelligence are trustworthy: validation, memory inbox, structured context coverage, code graph precision, graph links, and concrete recommendations.",
@@ -694,9 +833,18 @@ async function callTool(name, args) {
694
833
  const recallResult = (0, kernel_js_1.recall)(projectDir, query, limit, false);
695
834
  // graph facts on top of recall
696
835
  const graphResult = (0, kernel_js_1.queryGraph)(projectDir, query, 5);
836
+ const explicitTargets = [...arrayArg(args?.targets), ...filePathHints(query)];
837
+ const changedFiles = arrayArg(args?.changed_files);
838
+ const riskResult = explicitTargets.length || changedFiles.length ? (0, kernel_js_1.kageRisk)(projectDir, explicitTargets, changedFiles) : null;
839
+ const pathHints = filePathHints(query);
840
+ const dependencyResult = wantsDependencyPath(query) && pathHints.length >= 2
841
+ ? (0, kernel_js_1.kageDependencyPath)(projectDir, pathHints[0], pathHints[1])
842
+ : null;
697
843
  const sections = [
698
844
  recallResult.context_block,
699
845
  graphResult.context_block ? `\n## Graph Facts\n${graphResult.context_block}` : "",
846
+ riskResult ? riskContextBlock(riskResult) : "",
847
+ dependencyResult ? `\n## Dependency Path\n${dependencyResult.summary}${dependencyResult.path.length ? `\nPath: ${dependencyResult.path.join(" -> ")}` : ""}` : "",
700
848
  `\n_${validationText}_`,
701
849
  ].filter(Boolean).join("");
702
850
  return {
@@ -736,6 +884,42 @@ async function callTool(name, args) {
736
884
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
737
885
  };
738
886
  }
887
+ if (name === "kage_risk") {
888
+ const result = (0, kernel_js_1.kageRisk)(String(args?.project_dir ?? ""), arrayArg(args?.targets), arrayArg(args?.changed_files));
889
+ return {
890
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
891
+ };
892
+ }
893
+ if (name === "kage_dependency_path") {
894
+ const result = (0, kernel_js_1.kageDependencyPath)(String(args?.project_dir ?? ""), String(args?.from ?? ""), String(args?.to ?? ""));
895
+ return {
896
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
897
+ };
898
+ }
899
+ if (name === "kage_cleanup_candidates") {
900
+ const result = (0, kernel_js_1.kageCleanupCandidates)(String(args?.project_dir ?? ""));
901
+ return {
902
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
903
+ };
904
+ }
905
+ if (name === "kage_reviewers") {
906
+ const result = (0, kernel_js_1.kageReviewerSuggestions)(String(args?.project_dir ?? ""), arrayArg(args?.targets), arrayArg(args?.changed_files));
907
+ return {
908
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
909
+ };
910
+ }
911
+ if (name === "kage_contributors") {
912
+ const result = (0, kernel_js_1.kageContributors)(String(args?.project_dir ?? ""));
913
+ return {
914
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
915
+ };
916
+ }
917
+ if (name === "kage_decisions") {
918
+ const result = (0, kernel_js_1.kageDecisionIntelligence)(String(args?.project_dir ?? ""));
919
+ return {
920
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
921
+ };
922
+ }
739
923
  if (name === "kage_code_index") {
740
924
  const result = (0, kernel_js_1.writeCodeIndex)(String(args?.project_dir ?? ""));
741
925
  return {
@@ -755,6 +939,30 @@ async function callTool(name, args) {
755
939
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
756
940
  };
757
941
  }
942
+ if (name === "kage_module_health") {
943
+ const result = (0, kernel_js_1.kageModuleHealth)(String(args?.project_dir ?? ""));
944
+ return {
945
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
946
+ };
947
+ }
948
+ if (name === "kage_graph_insights") {
949
+ const result = (0, kernel_js_1.kageGraphInsights)(String(args?.project_dir ?? ""));
950
+ return {
951
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
952
+ };
953
+ }
954
+ if (name === "kage_workspace") {
955
+ const result = (0, kernel_js_1.kageWorkspace)(String(args?.project_dir ?? ""));
956
+ return {
957
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
958
+ };
959
+ }
960
+ if (name === "kage_workspace_recall") {
961
+ const result = (0, kernel_js_1.kageWorkspaceRecall)(String(args?.project_dir ?? ""), String(args?.query ?? ""), Number(args?.limit ?? 8));
962
+ return {
963
+ content: [{ type: "text", text: args?.json ? JSON.stringify(result, null, 2) : result.context_block }],
964
+ };
965
+ }
758
966
  if (name === "kage_audit") {
759
967
  const result = (0, kernel_js_1.auditProject)(String(args?.project_dir ?? ""));
760
968
  return {