@kage-core/kage-graph-mcp 1.1.31 → 1.1.32

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
@@ -44,8 +44,9 @@ Restart your agent once after setup so MCP tools reload.
44
44
  - memory-code links so project knowledge points at the code it affects
45
45
  - decision intelligence for why-memory coverage, stale/weak packets, and
46
46
  important files that still lack linked repo knowledge
47
- - lightweight workspace recall across sibling repos, including package and
48
- route-contract links when existing code graphs expose them
47
+ - lightweight workspace recall across sibling repos, including package,
48
+ route-contract, and topic/event contract links when existing code graphs
49
+ expose them
49
50
  - local git intelligence for risk, reviewers, contributor profiles, co-change
50
51
  warnings, ownership silos, and module health
51
52
  - `AGENTS.md` bootstrap instructions so agents recall context automatically
package/dist/cli.js CHANGED
@@ -849,6 +849,12 @@ async function main() {
849
849
  console.log(`- ${contract.provider_repo} ${contract.method} ${contract.path} -> ${contract.consumer_repo}/${contract.consumer_file}`);
850
850
  }
851
851
  }
852
+ if (result.topic_contracts.length) {
853
+ console.log("Topic/event contracts:");
854
+ for (const contract of result.topic_contracts.slice(0, 10)) {
855
+ console.log(`- ${contract.producer_repo} ${contract.topic} -> ${contract.consumer_repo}/${contract.consumer_file}`);
856
+ }
857
+ }
852
858
  if (result.warnings.length)
853
859
  console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
854
860
  return;
package/dist/index.js CHANGED
@@ -322,7 +322,7 @@ function listTools() {
322
322
  },
323
323
  {
324
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.",
325
+ description: "Summarize a local multi-repo workspace: discovered git repos, Kage memory coverage, code graph counts, package dependencies, route contracts, and topic/event contracts between repos. Use when a task spans multiple sibling repos.",
326
326
  inputSchema: {
327
327
  type: "object",
328
328
  properties: {
package/dist/kernel.js CHANGED
@@ -6487,6 +6487,96 @@ function workspaceRouteContracts(workspaceDir, repos) {
6487
6487
  .sort((a, b) => a.provider_repo.localeCompare(b.provider_repo) || a.path.localeCompare(b.path) || a.consumer_repo.localeCompare(b.consumer_repo))
6488
6488
  .slice(0, 50);
6489
6489
  }
6490
+ const TOPIC_PRODUCER_METHODS = "publish|produce|send|emit|enqueue|dispatch";
6491
+ const TOPIC_CONSUMER_METHODS = "subscribe|consume|listen|handle";
6492
+ function likelyWorkspaceTopic(value) {
6493
+ const topic = value.trim();
6494
+ if (topic.length < 3 || topic.length > 120)
6495
+ return false;
6496
+ if (topic.startsWith("/") || /^https?:\/\//i.test(topic))
6497
+ return false;
6498
+ if (/\s/.test(topic))
6499
+ return false;
6500
+ if (/[.:-]/.test(topic))
6501
+ return true;
6502
+ if (/_/.test(topic) && /^[a-z0-9_]+$/i.test(topic))
6503
+ return true;
6504
+ return /\b(created|updated|deleted|requested|completed|failed|received|changed)$/i.test(topic);
6505
+ }
6506
+ function extractWorkspaceTopicMentions(repo, file, text) {
6507
+ const mentions = [];
6508
+ const patterns = [
6509
+ { role: "producer", regex: new RegExp(`\\b(?:${TOPIC_PRODUCER_METHODS})\\s*\\(\\s*["'\`]([^"'\`]+)["'\`]`, "gi") },
6510
+ { role: "producer", regex: new RegExp(`\\.(?:${TOPIC_PRODUCER_METHODS})\\s*\\(\\s*["'\`]([^"'\`]+)["'\`]`, "gi") },
6511
+ { role: "consumer", regex: new RegExp(`\\b(?:${TOPIC_CONSUMER_METHODS})\\s*\\(\\s*["'\`]([^"'\`]+)["'\`]`, "gi") },
6512
+ { role: "consumer", regex: new RegExp(`\\.(?:${TOPIC_CONSUMER_METHODS})\\s*\\(\\s*["'\`]([^"'\`]+)["'\`]`, "gi") },
6513
+ ];
6514
+ for (const pattern of patterns) {
6515
+ for (const match of text.matchAll(pattern.regex)) {
6516
+ const topic = match[1]?.trim();
6517
+ if (!topic || !likelyWorkspaceTopic(topic))
6518
+ continue;
6519
+ mentions.push({
6520
+ repo,
6521
+ file,
6522
+ topic,
6523
+ role: pattern.role,
6524
+ evidence: `${file} ${pattern.role === "producer" ? "publishes" : "subscribes to"} ${topic}`,
6525
+ });
6526
+ }
6527
+ }
6528
+ return mentions;
6529
+ }
6530
+ function workspaceTopicContracts(workspaceDir, repos) {
6531
+ const mentions = [];
6532
+ for (const repo of repos) {
6533
+ const repoRoot = repo.path === "." ? workspaceDir : (0, node_path_1.join)(workspaceDir, repo.path);
6534
+ const graph = readCurrentCodeGraph(repoRoot);
6535
+ if (!graph)
6536
+ continue;
6537
+ for (const file of graph.files) {
6538
+ if (!["source", "config", "manifest"].includes(file.kind))
6539
+ continue;
6540
+ if (file.size_bytes > MAX_CODE_FILE_BYTES)
6541
+ continue;
6542
+ const absolutePath = (0, node_path_1.join)(repoRoot, file.path);
6543
+ if (!(0, node_fs_1.existsSync)(absolutePath))
6544
+ continue;
6545
+ try {
6546
+ mentions.push(...extractWorkspaceTopicMentions(repo.alias, file.path, (0, node_fs_1.readFileSync)(absolutePath, "utf8")));
6547
+ }
6548
+ catch {
6549
+ continue;
6550
+ }
6551
+ }
6552
+ }
6553
+ const producers = mentions.filter((mention) => mention.role === "producer");
6554
+ const consumers = mentions.filter((mention) => mention.role === "consumer");
6555
+ const contracts = [];
6556
+ const seen = new Set();
6557
+ for (const producer of producers) {
6558
+ for (const consumer of consumers) {
6559
+ if (producer.topic !== consumer.topic || producer.repo === consumer.repo)
6560
+ continue;
6561
+ const key = `${producer.repo}\0${producer.file}\0${consumer.repo}\0${consumer.file}\0${producer.topic}`;
6562
+ if (seen.has(key))
6563
+ continue;
6564
+ seen.add(key);
6565
+ contracts.push({
6566
+ topic: producer.topic,
6567
+ producer_repo: producer.repo,
6568
+ producer_file: producer.file,
6569
+ consumer_repo: consumer.repo,
6570
+ consumer_file: consumer.file,
6571
+ confidence: /[.:/-]/.test(producer.topic) ? "high" : "medium",
6572
+ evidence: `${producer.evidence}; ${consumer.evidence}`,
6573
+ });
6574
+ }
6575
+ }
6576
+ return contracts
6577
+ .sort((a, b) => a.topic.localeCompare(b.topic) || a.producer_repo.localeCompare(b.producer_repo) || a.consumer_repo.localeCompare(b.consumer_repo))
6578
+ .slice(0, 50);
6579
+ }
6490
6580
  function kageWorkspace(projectDir) {
6491
6581
  const root = (0, node_path_1.resolve)(projectDir);
6492
6582
  const warnings = [];
@@ -6512,6 +6602,7 @@ function kageWorkspace(projectDir) {
6512
6602
  return { ...rest, dependencies_on_workspace_repos: deps };
6513
6603
  });
6514
6604
  const routeContracts = workspaceRouteContracts(root, repos);
6605
+ const topicContracts = workspaceTopicContracts(root, repos);
6515
6606
  if (repos.length && repos.every((repo) => !repo.indexed))
6516
6607
  warnings.push("Workspace repos were found, but none has .agent_memory yet. Run kage init or kage refresh in each repo you want searchable.");
6517
6608
  return {
@@ -6521,8 +6612,9 @@ function kageWorkspace(projectDir) {
6521
6612
  repos,
6522
6613
  package_dependencies: packageDependencies.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to)),
6523
6614
  route_contracts: routeContracts,
6615
+ topic_contracts: topicContracts,
6524
6616
  warnings,
6525
- summary: `${repos.length} repo(s), ${repos.filter((repo) => repo.indexed).length} with Kage memory, ${packageDependencies.length} workspace package dependenc${packageDependencies.length === 1 ? "y" : "ies"}, ${routeContracts.length} route contract link(s).`,
6617
+ summary: `${repos.length} repo(s), ${repos.filter((repo) => repo.indexed).length} with Kage memory, ${packageDependencies.length} workspace package dependenc${packageDependencies.length === 1 ? "y" : "ies"}, ${routeContracts.length} route contract link(s), ${topicContracts.length} topic contract link(s).`,
6526
6618
  };
6527
6619
  }
6528
6620
  function kageWorkspaceRecall(projectDir, query, limit = 8) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "1.1.31",
3
+ "version": "1.1.32",
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": [
package/viewer/app.js CHANGED
@@ -2619,6 +2619,7 @@
2619
2619
  var repos = Array.isArray(workspace.repos) ? workspace.repos : [];
2620
2620
  var deps = Array.isArray(workspace.package_dependencies) ? workspace.package_dependencies : [];
2621
2621
  var contracts = Array.isArray(workspace.route_contracts) ? workspace.route_contracts : [];
2622
+ var topics = Array.isArray(workspace.topic_contracts) ? workspace.topic_contracts : [];
2622
2623
  cards.push({
2623
2624
  title: "Workspace",
2624
2625
  kicker: "multi-repo memory",
@@ -2627,7 +2628,8 @@
2627
2628
  ["Repos", String(repos.length)],
2628
2629
  ["Indexed", String(repos.filter(function (repo) { return repo.indexed; }).length)],
2629
2630
  ["Package deps", String(deps.length)],
2630
- ["Route links", String(contracts.length)]
2631
+ ["Route links", String(contracts.length)],
2632
+ ["Topic links", String(topics.length)]
2631
2633
  ].concat(repos.slice(0, 2).map(function (repo) { return [repo.alias, repo.approved_packets + " packets, " + repo.code_files + " files"]; }))
2632
2634
  });
2633
2635
  }