@kage-core/kage-graph-mcp 1.1.25 → 1.1.27

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,8 +60,19 @@ 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 .
75
+ kage hook install --project .
57
76
  kage pr check --project .
58
77
  kage metrics --project . --json
59
78
  kage audit --project . --json
@@ -61,6 +80,10 @@ kage inbox --project . --json
61
80
  kage viewer --project .
62
81
  ```
63
82
 
83
+ MCP agents should start with `kage_context`. When the query or target list
84
+ mentions file paths, it includes risk and dependency-path context alongside
85
+ memory recall.
86
+
64
87
  For stale or wrong memory:
65
88
 
66
89
  ```bash
@@ -99,6 +122,7 @@ Kage keeps learned memory separate from generated code facts.
99
122
  | Structural map | `.agent_memory/structural/` | files, symbols, imports |
100
123
  | Code graph | `.agent_memory/code_graph/` | source-derived code facts |
101
124
  | Metrics | `.agent_memory/metrics.json` | readiness, quality, coverage |
125
+ | Reports | `.agent_memory/reports/` | risk, contributors, decisions, module health, graph insights, workspace, quality, benchmark |
102
126
 
103
127
  Repo-local packets are git-visible and reviewable. Generated indexes and graphs
104
128
  are rebuildable.
@@ -110,10 +134,22 @@ Kage is optimized so repeat work scales with changed files, not the whole repo:
110
134
  - read-only recall reuses fresh graph artifacts
111
135
  - unchanged structural file facts are reused
112
136
  - generated graphs are compact and avoid duplicated structural JSON
137
+ - optional git `post-commit` hooks keep repo memory and branch summaries current
113
138
  - generated/vendor/cache paths are ignored
114
139
  - huge files are represented safely instead of deeply expanded
115
140
  - recall builds lookup maps once per query instead of repeatedly scanning graph
116
141
  edges for every memory packet
142
+ - local risk reports include hidden co-change warnings and ownership-silo
143
+ signals from git history
144
+ - contributor reports show commits, recent activity, touched files/modules,
145
+ primary ownership, ownership silos, hotspot ownership, and commit category mix
146
+ - decision reports show why-memory coverage, weak/stale decision packets, and
147
+ high-signal source paths with no linked decision memory
148
+ - graph insights include language parser coverage and edge mix so large-repo
149
+ index gaps are visible instead of hidden
150
+ - the generic non-TypeScript indexer extracts bounded call edges and test
151
+ function coverage, while SCIP/LSP/LSIF/tree-sitter artifacts override it when
152
+ available
117
153
 
118
154
  ## Viewer
119
155
 
@@ -123,6 +159,11 @@ Open a local viewer for the current repo:
123
159
  kage viewer --project .
124
160
  ```
125
161
 
162
+ The local viewer loads graph artifacts plus `.agent_memory/reports/*.json` and
163
+ shows a repo-intelligence cockpit for memory-code links, decision memory, risk,
164
+ contributors, module health, graph insights, workspace coverage, quality, and
165
+ benchmark proof.
166
+
126
167
  Hosted demo:
127
168
 
128
169
  ```text
package/dist/cli.js CHANGED
@@ -24,6 +24,9 @@ Usage:
24
24
  kage daemon status --project <dir> [--json]
25
25
  kage daemon doctor --project <dir> [--json]
26
26
  kage viewer --project <dir> [--port 3113]
27
+ kage hook install --project <dir> [--json]
28
+ kage hook status --project <dir> [--json]
29
+ kage hook uninstall --project <dir> [--json]
27
30
  kage refresh --project <dir> [--full] [--json]
28
31
  kage gc --project <dir> [--dry-run] [--force] [--json]
29
32
  kage pr summarize --project <dir> [--json]
@@ -31,6 +34,12 @@ Usage:
31
34
  kage upgrade [--dry-run]
32
35
  kage branch --project <dir> [--json]
33
36
  kage metrics --project <dir> [--json]
37
+ kage contributors --project <dir> [--json]
38
+ kage decisions --project <dir> [--json]
39
+ kage module-health --project <dir> [--json]
40
+ kage graph-insights --project <dir> [--json]
41
+ kage workspace --project <workspace-dir> [--json]
42
+ kage workspace recall "<query>" --project <workspace-dir> [--json]
34
43
  kage audit --project <dir> [--json]
35
44
  kage inbox --project <dir> [--json]
36
45
  kage quality --project <dir> [--json]
@@ -38,6 +47,10 @@ Usage:
38
47
  kage benchmark --project <dir> --compare --task <task> [--json]
39
48
  kage code-graph --project <dir> [--json]
40
49
  kage code-graph "<query>" --project <dir> [--json]
50
+ kage cleanup-candidates --project <dir> [--json]
51
+ kage dependency-path --project <dir> --from <path> --to <path> [--json]
52
+ kage reviewers --project <dir> [--targets a,b] [--changed-files a,b] [--json]
53
+ kage risk --project <dir> [--targets a,b] [--changed-files a,b] [--json]
41
54
  kage code-index --project <dir> [--json]
42
55
  kage structural-index --project <dir> [--json]
43
56
  kage graph --project <dir> [--json]
@@ -309,6 +322,37 @@ async function main() {
309
322
  await (0, daemon_js_1.startViewer)(projectArg(args), { port: numberArg(args, "--port", 3113) });
310
323
  return;
311
324
  }
325
+ if (command === "hook") {
326
+ const action = args[1];
327
+ const projectDir = projectArg(args);
328
+ const result = action === "install"
329
+ ? (0, kernel_js_1.kageHookInstall)(projectDir)
330
+ : action === "status"
331
+ ? (0, kernel_js_1.kageHookStatus)(projectDir)
332
+ : action === "uninstall"
333
+ ? (0, kernel_js_1.kageHookUninstall)(projectDir)
334
+ : null;
335
+ if (!result)
336
+ usage();
337
+ if (args.includes("--json")) {
338
+ console.log(JSON.stringify(result, null, 2));
339
+ if (!result.ok)
340
+ process.exit(2);
341
+ return;
342
+ }
343
+ console.log(result.message);
344
+ if (result.hook_path)
345
+ console.log(`Hook: ${result.hook_path}`);
346
+ console.log(`Installed: ${result.installed ? "yes" : "no"}`);
347
+ console.log(`Changed: ${result.changed ? "yes" : "no"}`);
348
+ if (result.warnings.length)
349
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
350
+ if (result.errors.length)
351
+ console.log(`Errors:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
352
+ if (!result.ok)
353
+ process.exit(2);
354
+ return;
355
+ }
312
356
  if (command === "gc") {
313
357
  const project = projectArg(args);
314
358
  const dryRun = args.includes("--dry-run");
@@ -506,6 +550,123 @@ async function main() {
506
550
  console.log(`- ${symbol.kind} ${symbol.name} (${symbol.path}:${symbol.line})`);
507
551
  return;
508
552
  }
553
+ if (command === "risk") {
554
+ const result = (0, kernel_js_1.kageRisk)(projectArg(args), listArg(takeArg(args, "--targets")), listArg(takeArg(args, "--changed-files")));
555
+ if (args.includes("--json")) {
556
+ console.log(JSON.stringify(result, null, 2));
557
+ return;
558
+ }
559
+ console.log("Kage risk assessment");
560
+ for (const item of Object.values(result.targets)) {
561
+ console.log(`- ${item.risk_summary}`);
562
+ if (item.dependents.length)
563
+ console.log(` Dependents: ${item.dependents.slice(0, 5).join(", ")}`);
564
+ if (item.git.co_change_partners.length)
565
+ console.log(` Co-change: ${item.git.co_change_partners.map((p) => `${p.file_path} (${p.count})`).join(", ")}`);
566
+ }
567
+ if (result.global_hotspots.length) {
568
+ console.log("Global hotspots:");
569
+ for (const hotspot of result.global_hotspots)
570
+ console.log(`- ${hotspot.file_path}: ${hotspot.commit_count_90d} commits in 90d`);
571
+ }
572
+ if (result.warnings.length)
573
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
574
+ return;
575
+ }
576
+ if (command === "dependency-path") {
577
+ const from = takeArg(args, "--from");
578
+ const to = takeArg(args, "--to");
579
+ if (!from || !to)
580
+ usage();
581
+ const result = (0, kernel_js_1.kageDependencyPath)(projectArg(args), from, to);
582
+ if (args.includes("--json")) {
583
+ console.log(JSON.stringify(result, null, 2));
584
+ return;
585
+ }
586
+ console.log(result.summary);
587
+ if (result.path.length)
588
+ console.log(`Path: ${result.path.join(" -> ")}`);
589
+ for (const edge of result.edges) {
590
+ console.log(`- ${edge.from_path}:${edge.line} ${edge.kind} ${edge.specifier} -> ${edge.to_path}`);
591
+ }
592
+ if (result.warnings.length)
593
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
594
+ return;
595
+ }
596
+ if (command === "cleanup-candidates") {
597
+ const result = (0, kernel_js_1.kageCleanupCandidates)(projectArg(args));
598
+ if (args.includes("--json")) {
599
+ console.log(JSON.stringify(result, null, 2));
600
+ return;
601
+ }
602
+ console.log(`Kage cleanup candidates: ${result.summary}`);
603
+ for (const candidate of result.candidates.slice(0, 20)) {
604
+ console.log(`- ${candidate.path}: ${candidate.confidence} (${Math.round(candidate.score * 100)}%)`);
605
+ console.log(` ${candidate.reasons.join("; ")}`);
606
+ }
607
+ if (result.warnings.length)
608
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
609
+ return;
610
+ }
611
+ if (command === "reviewers") {
612
+ const result = (0, kernel_js_1.kageReviewerSuggestions)(projectArg(args), listArg(takeArg(args, "--targets")), listArg(takeArg(args, "--changed-files")));
613
+ if (args.includes("--json")) {
614
+ console.log(JSON.stringify(result, null, 2));
615
+ return;
616
+ }
617
+ console.log(`Kage reviewer suggestions: ${result.summary}`);
618
+ for (const suggestion of result.suggestions) {
619
+ console.log(`- ${suggestion.reviewer}: ${Math.round(suggestion.score)}%`);
620
+ console.log(` ${suggestion.reasons.slice(0, 3).join("; ")}`);
621
+ }
622
+ if (result.warnings.length)
623
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
624
+ return;
625
+ }
626
+ if (command === "contributors") {
627
+ const result = (0, kernel_js_1.kageContributors)(projectArg(args));
628
+ if (args.includes("--json")) {
629
+ console.log(JSON.stringify(result, null, 2));
630
+ return;
631
+ }
632
+ console.log(`Kage contributors: ${result.summary}`);
633
+ for (const profile of result.contributors.slice(0, 10)) {
634
+ console.log(`- ${profile.contributor}: ${profile.commits_total} commits, ${profile.commits_90d} in 90d, ${profile.primary_owned_files} owned files`);
635
+ if (profile.files_touched.length)
636
+ console.log(` Top files: ${profile.files_touched.slice(0, 3).map((file) => `${file.path} (${file.commits})`).join(", ")}`);
637
+ if (profile.silo_files.length)
638
+ console.log(` Silo files: ${profile.silo_files.slice(0, 3).map((file) => `${file.path} (${Math.round(file.ownership_pct * 100)}%)`).join(", ")}`);
639
+ }
640
+ if (result.warnings.length)
641
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
642
+ return;
643
+ }
644
+ if (command === "decisions") {
645
+ const result = (0, kernel_js_1.kageDecisionIntelligence)(projectArg(args));
646
+ if (args.includes("--json")) {
647
+ console.log(JSON.stringify(result, null, 2));
648
+ return;
649
+ }
650
+ console.log(`Kage decision intelligence: ${result.summary}`);
651
+ if (result.top_decisions.length) {
652
+ console.log("Top why-memory:");
653
+ for (const item of result.top_decisions.slice(0, 10)) {
654
+ console.log(`- ${item.title} (${item.type})`);
655
+ if (item.paths.length)
656
+ console.log(` Paths: ${item.paths.slice(0, 3).join(", ")}`);
657
+ if (item.why)
658
+ console.log(` Why: ${item.why}`);
659
+ }
660
+ }
661
+ if (result.coverage_gaps.length) {
662
+ console.log("Uncovered important paths:");
663
+ for (const gap of result.coverage_gaps.slice(0, 10))
664
+ console.log(`- ${gap.path}: ${gap.reason}`);
665
+ }
666
+ if (result.warnings.length)
667
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
668
+ return;
669
+ }
509
670
  if (command === "code-index") {
510
671
  const result = (0, kernel_js_1.writeCodeIndex)(projectArg(args));
511
672
  if (args.includes("--json")) {
@@ -616,6 +777,82 @@ async function main() {
616
777
  }
617
778
  return;
618
779
  }
780
+ if (command === "module-health") {
781
+ const result = (0, kernel_js_1.kageModuleHealth)(projectArg(args));
782
+ if (args.includes("--json")) {
783
+ console.log(JSON.stringify(result, null, 2));
784
+ return;
785
+ }
786
+ console.log(`Kage module health: ${result.summary}`);
787
+ for (const item of result.modules.slice(0, 20)) {
788
+ console.log(`- ${item.module}: ${item.grade} (${item.score}) - ${item.reasons.join("; ")}`);
789
+ }
790
+ if (result.warnings.length)
791
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
792
+ return;
793
+ }
794
+ if (command === "graph-insights") {
795
+ const result = (0, kernel_js_1.kageGraphInsights)(projectArg(args));
796
+ if (args.includes("--json")) {
797
+ console.log(JSON.stringify(result, null, 2));
798
+ return;
799
+ }
800
+ console.log(`Kage graph insights: ${result.summary}`);
801
+ if (result.central_files.length) {
802
+ console.log("Central files:");
803
+ for (const file of result.central_files.slice(0, 10))
804
+ console.log(`- ${file.path}: pagerank ${file.pagerank}, ${file.dependents} dependent(s)`);
805
+ }
806
+ if (result.dependency_cycles.length) {
807
+ console.log("Dependency cycles:");
808
+ for (const cycle of result.dependency_cycles.slice(0, 5))
809
+ console.log(`- ${cycle.files.join(" -> ")}`);
810
+ }
811
+ if (result.entry_flows.length) {
812
+ console.log("Entry flows:");
813
+ for (const flow of result.entry_flows.slice(0, 5))
814
+ console.log(`- ${flow.path.join(" -> ")}`);
815
+ }
816
+ return;
817
+ }
818
+ if (command === "workspace") {
819
+ const subcommand = args[1];
820
+ if (subcommand === "recall") {
821
+ const query = args.find((arg, index) => index > 1 && !arg.startsWith("--") && !args[index - 1]?.startsWith("--"));
822
+ if (!query)
823
+ usage();
824
+ const result = (0, kernel_js_1.kageWorkspaceRecall)(projectArg(args), query, numberArg(args, "--limit", 8));
825
+ if (args.includes("--json")) {
826
+ console.log(JSON.stringify(result, null, 2));
827
+ return;
828
+ }
829
+ console.log(result.context_block);
830
+ if (result.warnings.length)
831
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
832
+ return;
833
+ }
834
+ const result = (0, kernel_js_1.kageWorkspace)(projectArg(args));
835
+ if (args.includes("--json")) {
836
+ console.log(JSON.stringify(result, null, 2));
837
+ return;
838
+ }
839
+ console.log(`Kage workspace: ${result.summary}`);
840
+ for (const repo of result.repos) {
841
+ const deps = repo.dependencies_on_workspace_repos.length
842
+ ? ` depends on ${repo.dependencies_on_workspace_repos.map((dep) => dep.alias).join(", ")}`
843
+ : "";
844
+ console.log(`- ${repo.alias}: ${repo.path} (${repo.approved_packets} packets, ${repo.code_files} files)${deps}`);
845
+ }
846
+ if (result.route_contracts.length) {
847
+ console.log("Route contracts:");
848
+ for (const contract of result.route_contracts.slice(0, 10)) {
849
+ console.log(`- ${contract.provider_repo} ${contract.method} ${contract.path} -> ${contract.consumer_repo}/${contract.consumer_file}`);
850
+ }
851
+ }
852
+ if (result.warnings.length)
853
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
854
+ return;
855
+ }
619
856
  if (command === "audit") {
620
857
  const result = (0, kernel_js_1.auditProject)(projectArg(args));
621
858
  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 {