mindgraph-mcp 0.25.0 → 0.26.0

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/dist/server.mjs CHANGED
@@ -112,12 +112,21 @@ function unwrapSearchResult(value) {
112
112
  }
113
113
  return void 0;
114
114
  }
115
+ function supersededBy(node) {
116
+ const marker = node.superseded;
117
+ if (!marker) return void 0;
118
+ const head = obj(marker);
119
+ if (!head) return str(node.superseded_by) ?? "";
120
+ const label = (str(head.head_label) ?? str(head.head_uid) ?? "").replace(/\s+/g, " ").trim();
121
+ const short = label.length <= 80 ? label : `${label.slice(0, 79)}\u2026`;
122
+ const date = str(head.head_stated_at);
123
+ return short && date ? `${short} (${date.slice(0, 10)})` : short;
124
+ }
115
125
  function statusTag(node) {
116
126
  const tags = [];
117
- if (node.superseded === true) {
118
- tags.push(
119
- node.superseded_by ? `SUPERSEDED by ${str(node.superseded_by)}` : "SUPERSEDED"
120
- );
127
+ const replacement = supersededBy(node);
128
+ if (replacement !== void 0) {
129
+ tags.push(replacement ? `SUPERSEDED by ${replacement}` : "SUPERSEDED");
121
130
  }
122
131
  const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
123
132
  if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
@@ -387,13 +396,13 @@ function resumeSection(heading, values, state, fallbackType) {
387
396
  const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
388
397
  return items.length > 0 ? { heading, items } : void 0;
389
398
  }
390
- function renderResumeContext(response, maxChars) {
399
+ function renderResumeContext(response, maxChars, repositoryNames = {}) {
391
400
  const payload = obj(response);
392
401
  if (!payload) return void 0;
393
402
  const state = { bounded: false, omitted: 0 };
394
403
  const status = str(payload.status);
395
404
  if (!payload.task && status === "no_eligible_work") {
396
- const scopedOut = Array.isArray(payload.scoped_out_tasks) ? payload.scoped_out_tasks.length : 0;
405
+ const scopedOut = typeof payload.scoped_out_tasks === "number" ? payload.scoped_out_tasks : 0;
397
406
  const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
398
407
  return assemble(
399
408
  "# Resume context",
@@ -407,6 +416,17 @@ function renderResumeContext(response, maxChars) {
407
416
  if (section) sections.push(section);
408
417
  };
409
418
  push(resumeSection("## Current task", [payload.task], state, "Task"));
419
+ const other = otherEligibleWork(payload, repositoryNames);
420
+ if (other.shown > 0) {
421
+ sections.push({
422
+ heading: `## Other eligible work (${other.shown} shown${other.more > 0 ? `, ${other.more} more not listed` : ""})`,
423
+ items: other.groups.flatMap(
424
+ (group) => group.tasks.map(
425
+ (task) => `- ${group.repository}: ${task.label} [${task.uid}]${task.detail ? ` \u2014 ${task.detail}` : ""}`
426
+ )
427
+ )
428
+ });
429
+ }
410
430
  push(
411
431
  resumeSection(
412
432
  "## Goal and project",
@@ -478,6 +498,37 @@ function renderResumeContext(response, maxChars) {
478
498
  if (sections.length === 0) return void 0;
479
499
  return assemble("# Resume context", sections, state, maxChars);
480
500
  }
501
+ function otherEligibleWork(brief, names = {}) {
502
+ const listed = Array.isArray(brief?.other_eligible) ? brief.other_eligible : [];
503
+ const groups = [];
504
+ let shown = 0;
505
+ for (const entry of listed) {
506
+ const task = obj(entry);
507
+ const uid = typeof task?.uid === "string" ? task.uid : void 0;
508
+ if (!task || !uid) continue;
509
+ const scopes = Array.isArray(task.scope_uids) ? task.scope_uids.map(String) : [];
510
+ const named = scopes.find((scope) => names[scope]);
511
+ const repository = named ? names[named] : "Other scopes";
512
+ let group = groups.find((candidate) => candidate.repository === repository);
513
+ if (!group) {
514
+ group = { repository, tasks: [] };
515
+ groups.push(group);
516
+ }
517
+ const label = typeof task.label === "string" && task.label ? task.label : "(unlabeled)";
518
+ const detail = [
519
+ typeof task.status === "string" ? task.status : void 0,
520
+ typeof task.priority === "string" && task.priority ? `priority ${task.priority}` : void 0
521
+ ].filter(Boolean).join(", ");
522
+ group.tasks.push({
523
+ uid,
524
+ label: label.length > 160 ? `${label.slice(0, 159)}\u2026` : label,
525
+ detail
526
+ });
527
+ shown += 1;
528
+ }
529
+ const total = typeof brief?.eligible_total === "number" ? brief.eligible_total : 0;
530
+ return { groups, shown, more: Math.max(0, total - 1 - shown) };
531
+ }
481
532
  function renderNodeList(response, title, limit, maxChars) {
482
533
  let candidates = [];
483
534
  if (Array.isArray(response)) {
@@ -528,15 +579,15 @@ function renderTraversal(response, maxChars) {
528
579
  const labelByUid = /* @__PURE__ */ new Map();
529
580
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
530
581
  if (paths.length > 0) {
531
- const items = paths.map((raw) => obj(raw)).filter((path2) => Boolean(path2)).map((path2, index) => {
532
- const uids = Array.isArray(path2.node_uids) ? path2.node_uids.map(String) : [];
533
- const labels = Array.isArray(path2.labels) ? path2.labels.map(String) : Array.isArray(path2.nodes) ? path2.nodes.map((hop) => {
582
+ const items = paths.map((raw) => obj(raw)).filter((path3) => Boolean(path3)).map((path3, index) => {
583
+ const uids = Array.isArray(path3.node_uids) ? path3.node_uids.map(String) : [];
584
+ const labels = Array.isArray(path3.labels) ? path3.labels.map(String) : Array.isArray(path3.nodes) ? path3.nodes.map((hop) => {
534
585
  const hopNode = obj(hop);
535
586
  return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
536
587
  }) : [];
537
588
  const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
538
- const cost = num(path2.cost) ?? num(path2.path_cost);
539
- const confidence = num(path2.path_confidence);
589
+ const cost = num(path3.cost) ?? num(path3.path_cost);
590
+ const confidence = num(path3.path_confidence);
540
591
  const meta = [
541
592
  cost !== void 0 ? `cost ${cost}` : void 0,
542
593
  confidence !== void 0 ? `confidence ${confidence}` : void 0
@@ -1552,764 +1603,156 @@ ${conversation}`
1552
1603
 
1553
1604
  // src/memory-tool.ts
1554
1605
  import { createHash } from "crypto";
1555
- var RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
1556
- var DEFAULT_OUTPUT_CHARS = 8e3;
1557
- var MIN_OUTPUT_CHARS = 512;
1558
- var MAX_OUTPUT_CHARS = 24e3;
1559
- var HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
1560
- var HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
1561
- var MEMORY_TOOL = {
1562
- name: "mindgraph_memory",
1563
- description: "Read the portable MindGraph memory surface. Use action='context' with mode='resume' or 'continuity' for authoritative task-first work state, and mode='topic' with a short keyword query for prior knowledge relevant to the user's request. Use action='status' to inspect integration capabilities and limitations. This tool never writes; durable capture and correction use the existing typed MindGraph write tools.",
1564
- inputSchema: {
1565
- type: "object",
1566
- properties: {
1567
- action: {
1568
- type: "string",
1569
- enum: ["status", "context"],
1570
- description: "Read integration status or bounded memory context"
1571
- },
1572
- mode: {
1573
- type: "string",
1574
- enum: ["topic", "continuity", "resume"],
1575
- description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
1576
- },
1577
- query: {
1578
- type: "string",
1579
- description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
1580
- },
1581
- project_uid: { type: "string", description: "Optional project scope" },
1582
- task_uid: { type: "string", description: "Optional task scope for resume context" },
1583
- session_uid: { type: "string", description: "Optional durable session scope" },
1584
- scope_uids: {
1585
- type: "array",
1586
- items: { type: "string" },
1587
- description: "Optional explicit graph scopes for continuity/resume"
1588
- },
1589
- node_types: {
1590
- type: "array",
1591
- items: { type: "string" },
1592
- description: "Optional node-type filter for topic context"
1593
- },
1594
- layer: {
1595
- type: "string",
1596
- enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
1597
- description: "Optional cognitive-layer filter for topic context"
1598
- },
1599
- limit: {
1600
- type: "integer",
1601
- minimum: 1,
1602
- maximum: 50,
1603
- default: 8,
1604
- description: "Maximum direct graph items requested before bounded expansion"
1605
- },
1606
- include_chunks: {
1607
- type: "boolean",
1608
- default: false,
1609
- description: "Include bounded source excerpts when topic summaries are insufficient"
1610
- },
1611
- include_documents: {
1612
- type: "boolean",
1613
- default: false,
1614
- description: "Include bounded knowledge articles for topic context"
1615
- },
1616
- valid_at: {
1617
- type: "string",
1618
- description: "Optional ISO timestamp for time-aware topic retrieval"
1619
- },
1620
- graph_expansion_limit: {
1621
- type: "integer",
1622
- minimum: 0,
1623
- maximum: 20,
1624
- default: 3,
1625
- description: "Bounded graph neighbors added after direct topic matches"
1626
- },
1627
- graph_max_depth: {
1628
- type: "integer",
1629
- minimum: 0,
1630
- maximum: 5,
1631
- default: 2,
1632
- description: "Maximum graph-expansion depth for topic context"
1633
- },
1634
- max_output_chars: {
1635
- type: "integer",
1636
- minimum: MIN_OUTPUT_CHARS,
1637
- maximum: MAX_OUTPUT_CHARS,
1638
- default: DEFAULT_OUTPUT_CHARS,
1639
- description: "Hard character budget for model-ready context; whole items are retained or omitted"
1640
- },
1641
- agent_id: { type: "string", description: "Agent identity" }
1642
- },
1643
- required: ["action"]
1644
- },
1645
- outputSchema: {
1646
- type: "object",
1647
- properties: {
1648
- schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
1649
- action: { type: "string", enum: ["status", "context"] },
1650
- profile: { type: "string" },
1651
- server: { type: "object" },
1652
- integration: { type: "object" },
1653
- capabilities: { type: "object" },
1654
- degradation: { type: "array", items: { type: "object" } },
1655
- context_id: { type: "string" },
1656
- mode: { type: "string", enum: ["topic", "continuity", "resume"] },
1657
- scope: { type: "object" },
1658
- rendered_context: { type: "string" },
1659
- rendered_hash: { type: "string" },
1660
- items: { type: "array", items: { type: "object" } },
1661
- retrieval_trace: { type: "object" },
1662
- budget: { type: "object" },
1663
- freshness: { type: "object" },
1664
- warnings: { type: "array", items: { type: "object" } }
1665
- },
1666
- required: ["schema_version", "action"],
1667
- oneOf: [
1668
- {
1669
- properties: { action: { const: "status" } },
1670
- required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
1671
- },
1672
- {
1673
- properties: { action: { const: "context" } },
1674
- required: [
1675
- "context_id",
1676
- "mode",
1677
- "scope",
1678
- "rendered_context",
1679
- "rendered_hash",
1680
- "items",
1681
- "retrieval_trace",
1682
- "budget",
1683
- "freshness",
1684
- "warnings"
1685
- ]
1686
- }
1687
- ]
1688
- },
1689
- annotations: {
1690
- readOnlyHint: true,
1691
- destructiveHint: false,
1692
- idempotentHint: true,
1693
- openWorldHint: false
1606
+
1607
+ // src/workspace-scope.ts
1608
+ import fs from "fs";
1609
+ import path from "path";
1610
+
1611
+ // src/codegraph.ts
1612
+ import { spawn } from "child_process";
1613
+ import {
1614
+ existsSync,
1615
+ mkdirSync,
1616
+ readdirSync,
1617
+ readFileSync,
1618
+ statSync,
1619
+ writeFileSync
1620
+ } from "fs";
1621
+ import { realpath } from "fs/promises";
1622
+ import { homedir } from "os";
1623
+ import {
1624
+ basename,
1625
+ dirname,
1626
+ extname,
1627
+ isAbsolute,
1628
+ join,
1629
+ relative,
1630
+ resolve,
1631
+ sep
1632
+ } from "path";
1633
+ import { randomUUID } from "crypto";
1634
+ var UnknownRepositoryError = class extends Error {
1635
+ constructor(repo) {
1636
+ super(
1637
+ `unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
1638
+ );
1639
+ this.repo = repo;
1640
+ this.name = "UnknownRepositoryError";
1694
1641
  }
1642
+ repo;
1695
1643
  };
1696
- function obj2(value) {
1697
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1698
- }
1699
- function text(value) {
1700
- return typeof value === "string" && value.length > 0 ? value : void 0;
1644
+ function invocationCwd(args) {
1645
+ const context = args.invocation_context;
1646
+ if (context && typeof context === "object" && !Array.isArray(context)) {
1647
+ const cwd = context.cwd;
1648
+ if (typeof cwd === "string" && cwd) return cwd;
1649
+ }
1650
+ return process.cwd();
1701
1651
  }
1702
- function stableJson(value) {
1703
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
1704
- if (value && typeof value === "object") {
1705
- return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
1652
+ function unavailabilityCaveats(reason, message) {
1653
+ const caveats = [message || "codegraph status failed"];
1654
+ if (reason === "absent") {
1655
+ caveats.push(
1656
+ "Optional: install codegraph (https://github.com/colbymchenry/codegraph) and run `codegraph init` in this repository to enable code anchoring and structural recall. Memory and work tools are unaffected."
1657
+ );
1706
1658
  }
1707
- return JSON.stringify(value);
1659
+ return caveats;
1708
1660
  }
1709
- function sha256(value) {
1710
- return createHash("sha256").update(value).digest("hex");
1661
+ var DEFAULT_TIMEOUT_MS = 5e3;
1662
+ var DEFAULT_STATUS_TTL_MS = 6e4;
1663
+ var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
1664
+ function normalizePath(path3) {
1665
+ return path3.replaceAll("\\", "/").replace(/^\.\//, "");
1711
1666
  }
1712
- function outputBudget(value) {
1713
- if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
1714
- return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
1667
+ function normalizeSignature(signature) {
1668
+ return signature.replace(/\s+/g, " ").trim();
1715
1669
  }
1716
- function defined(values) {
1717
- return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
1670
+ function languageFromPath(path3) {
1671
+ const extension = extname(path3).toLowerCase();
1672
+ return {
1673
+ ".c": "c",
1674
+ ".cc": "cpp",
1675
+ ".cpp": "cpp",
1676
+ ".go": "go",
1677
+ ".java": "java",
1678
+ ".js": "javascript",
1679
+ ".jsx": "javascript",
1680
+ ".kt": "kotlin",
1681
+ ".py": "python",
1682
+ ".rb": "ruby",
1683
+ ".rs": "rust",
1684
+ ".swift": "swift",
1685
+ ".ts": "typescript",
1686
+ ".tsx": "typescript"
1687
+ }[extension] ?? "unknown";
1718
1688
  }
1719
- function memoryStatus(config = {}) {
1689
+ function asNode(hit) {
1690
+ return hit.node ?? hit;
1691
+ }
1692
+ function toCandidate(node) {
1693
+ if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
1720
1694
  return {
1721
- schema_version: RESULT_SCHEMA_VERSION,
1722
- action: "status",
1723
- profile: "M0",
1724
- server: defined({
1725
- name: "mindgraph-mcp",
1726
- version: config.serverVersion ?? "unknown",
1727
- base_url: config.baseUrl
1728
- }),
1729
- integration: {
1730
- adapter_state: "model_controlled",
1731
- harness: config.harness ?? "generic",
1732
- configured_tool_profile: config.profile ?? "general",
1733
- automatic_capture: false,
1734
- automatic_delivery: false,
1735
- delivery_verification: "unsupported",
1736
- curator_mode: "disabled"
1737
- },
1738
- scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
1739
- capabilities: {
1740
- status: true,
1741
- topic_context: true,
1742
- authoritative_resume_context: true,
1743
- bounded_rendering: true,
1744
- structured_content: true,
1745
- typed_explicit_writes: true,
1746
- lifecycle_event_ingest: false,
1747
- automatic_context_injection: false,
1748
- curator_mutation: false
1749
- },
1750
- degradation: [
1751
- {
1752
- code: "automatic_capture_unavailable",
1753
- message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
1754
- },
1755
- {
1756
- code: "delivery_unverified",
1757
- message: "A successful MCP read does not prove that the host injected or used the returned context."
1758
- },
1759
- {
1760
- code: "curator_disabled",
1761
- message: "Curator proposals and autonomous mutation are not enabled in M0."
1762
- }
1763
- ]
1695
+ path: normalizePath(node.filePath),
1696
+ language: node.language ?? languageFromPath(node.filePath),
1697
+ kind: node.kind,
1698
+ qualifiedName: node.qualifiedName ?? node.name ?? "",
1699
+ signature: node.signature ?? null,
1700
+ startLine: node.startLine ?? 1,
1701
+ endLine: node.endLine ?? node.startLine ?? 1
1764
1702
  };
1765
1703
  }
1766
- function nodeType(node) {
1767
- const raw = node.node_type;
1768
- if (typeof raw === "string") return raw;
1769
- return text(obj2(raw)?.Custom) ?? "Node";
1770
- }
1771
- function compactItem(raw, fallbackType) {
1772
- const wrapper = obj2(raw);
1773
- if (!wrapper) return void 0;
1774
- const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
1775
- const uid = text(value.uid) ?? text(value.chunk_uid);
1776
- if (!uid) return void 0;
1777
- return defined({
1778
- uid,
1779
- type: fallbackType ?? nodeType(value),
1780
- label: text(value.label) ?? text(value.document_title),
1781
- truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
1782
- superseded: value.superseded === true ? true : void 0,
1783
- currently_valid: value.currently_valid === false ? false : void 0,
1784
- valid_at_time: value.valid_at_time === false ? false : void 0,
1785
- retrieval_origin: text(value.retrieval_origin),
1786
- retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
1787
- });
1788
- }
1789
- function topicCandidates(raw) {
1790
- const payload = obj2(raw);
1791
- if (!payload) return [];
1792
- const graph = obj2(payload.graph);
1793
- const items = [];
1794
- for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
1795
- const item = compactItem(article, "Article");
1796
- if (item) items.push(item);
1797
- }
1798
- const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
1799
- const labels = /* @__PURE__ */ new Map();
1800
- for (const node of rawNodes) {
1801
- const item = compactItem(node);
1802
- if (item) {
1803
- if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
1804
- items.push(item);
1805
- const uid = text(item.uid);
1806
- const label = text(item.label);
1807
- if (uid && label) labels.set(uid, label);
1808
- }
1809
- }
1810
- for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
1811
- const edge = obj2(rawEdge);
1812
- const fromUid = text(edge?.from_uid);
1813
- const toUid = text(edge?.to_uid);
1814
- const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
1815
- if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
1816
- continue;
1817
- }
1818
- items.push(
1819
- defined({
1820
- type: "Relationship",
1821
- from_uid: fromUid,
1822
- to_uid: toUid,
1823
- edge_type: edgeType,
1824
- from_label: labels.get(fromUid),
1825
- to_label: labels.get(toUid)
1826
- })
1827
- );
1828
- }
1829
- for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
1830
- const item = compactItem(chunk, "SourceExcerpt");
1831
- if (item) items.push(item);
1832
- }
1833
- return items;
1834
- }
1835
- function resumeCandidates(raw) {
1836
- const payload = obj2(raw);
1837
- if (!payload) return [];
1838
- const items = [];
1839
- for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
1840
- const item = compactItem(payload[key], key);
1841
- if (item) items.push(item);
1842
- }
1843
- for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
1844
- for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
1845
- const item = compactItem(value, key.replace(/s$/, ""));
1846
- if (item) items.push(item);
1847
- }
1704
+ function normalizeRemote(remote) {
1705
+ const trimmed = remote.trim();
1706
+ if (!trimmed) return null;
1707
+ const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
1708
+ if (scp && !trimmed.includes("://")) {
1709
+ return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
1848
1710
  }
1849
- const knowledge = obj2(payload.knowledge);
1850
- for (const key of ["lessons", "risks"]) {
1851
- for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
1852
- const item = compactItem(value, key.replace(/s$/, ""));
1853
- if (item) items.push(item);
1854
- }
1711
+ try {
1712
+ const url = new URL(trimmed);
1713
+ const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
1714
+ return `${url.host.toLowerCase()}${pathname}`;
1715
+ } catch {
1716
+ return null;
1855
1717
  }
1856
- return items;
1857
1718
  }
1858
- function visibleItems(candidates, rendered2) {
1859
- const seen = /* @__PURE__ */ new Set();
1860
- return candidates.filter((item) => {
1861
- const uid = text(item.uid);
1862
- if (uid) {
1863
- if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
1864
- seen.add(uid);
1865
- return true;
1866
- }
1867
- if (item.type === "Relationship") {
1868
- const from = text(item.from_label);
1869
- const to = text(item.to_label);
1870
- const edgeType = text(item.edge_type);
1871
- if (!from || !to || !edgeType) return false;
1872
- const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
1873
- if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
1874
- seen.add(key);
1875
- return true;
1876
- }
1877
- return false;
1878
- });
1719
+ function pathContains(root, candidate) {
1720
+ const child = relative(root, candidate);
1721
+ return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute(child);
1879
1722
  }
1880
- function latestTimestamp(value) {
1881
- let latest;
1882
- const visit = (current) => {
1883
- if (Array.isArray(current)) {
1884
- for (const item of current) visit(item);
1885
- return;
1886
- }
1887
- const record = obj2(current);
1888
- if (!record) return;
1889
- for (const [key, item] of Object.entries(record)) {
1890
- if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
1891
- latest = item;
1723
+ function nearestExistingDirectory(candidate) {
1724
+ let current = resolve(candidate);
1725
+ for (; ; ) {
1726
+ if (existsSync(current)) {
1727
+ try {
1728
+ return statSync(current).isDirectory() ? current : dirname(current);
1729
+ } catch {
1892
1730
  }
1893
- visit(item);
1894
- }
1895
- };
1896
- visit(value);
1897
- return latest;
1898
- }
1899
- function contextWarnings(raw, rendered2, available, returned) {
1900
- const warnings = [
1901
- {
1902
- code: "delivery_unverified",
1903
- message: "The MCP server returned this context but cannot verify that the host injected or used it."
1904
- }
1905
- ];
1906
- const serialized = JSON.stringify(raw);
1907
- if (available > returned || rendered2.includes("item(s) omitted")) {
1908
- warnings.push({
1909
- code: "context_bounded",
1910
- message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
1911
- });
1912
- }
1913
- if (/"superseded":true/.test(serialized)) {
1914
- warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
1915
- }
1916
- if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
1917
- warnings.push({
1918
- code: "validity_warning",
1919
- message: "Some retrieved items are outside their current or requested validity window."
1920
- });
1921
- }
1922
- const payload = obj2(raw);
1923
- if (payload?.truncated === true || obj2(payload?.truncation)) {
1924
- const truncation = obj2(payload?.truncation);
1925
- if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
1926
- warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
1927
1731
  }
1732
+ const parent = dirname(current);
1733
+ if (parent === current) return current;
1734
+ current = parent;
1928
1735
  }
1929
- if (text(payload?.warning)) {
1930
- warnings.push({ code: "source_warning", message: text(payload?.warning) });
1931
- }
1932
- return warnings;
1933
- }
1934
- function error(message) {
1935
- return {
1936
- content: [{ type: "text", text: JSON.stringify({ error: message }) }],
1937
- isError: true
1938
- };
1939
1736
  }
1940
- async function handleMemoryTool(client, args, config = {}) {
1941
- const action = text(args.action);
1942
- if (action === "status") {
1943
- const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
1944
- return {
1945
- content: [{ type: "text", text: JSON.stringify(status) }],
1946
- structuredContent: status
1947
- };
1948
- }
1949
- if (action !== "context") {
1950
- return error("action must be 'status' or 'context'");
1737
+ var MAX_CHILD_DIRECTORIES = 200;
1738
+ function findChildWorkspaceFiles(start) {
1739
+ let entries;
1740
+ try {
1741
+ entries = readdirSync(start, { withFileTypes: true });
1742
+ } catch {
1743
+ return [];
1951
1744
  }
1952
- const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
1953
- if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
1954
- return error("action='context' requires mode='topic', 'continuity', or 'resume'");
1955
- }
1956
- const budget = outputBudget(args.max_output_chars);
1957
- const agentId = text(args.agent_id) ?? config.agentId;
1958
- let raw;
1959
- let rendered2;
1960
- let strategy;
1961
- let candidates;
1962
- if (mode === "topic") {
1963
- const query = text(args.query);
1964
- if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
1965
- const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
1966
- const request = defined({
1967
- query,
1968
- project_uid: text(args.project_uid),
1969
- node_limit: limit,
1970
- node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
1971
- layer: text(args.layer),
1972
- chunk_limit: args.include_chunks === true ? limit : 0,
1973
- article_limit: args.include_documents === true ? limit : 0,
1974
- valid_at: text(args.valid_at),
1975
- include_graph: true,
1976
- graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
1977
- graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
1978
- });
1979
- raw = await client.retrieveContext(request);
1980
- rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
1981
- strategy = "retrieve_context";
1982
- candidates = topicCandidates(raw);
1983
- } else {
1984
- const scopeUids = Array.from(
1985
- /* @__PURE__ */ new Set([
1986
- ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
1987
- ...text(args.project_uid) ? [text(args.project_uid)] : []
1988
- ])
1989
- );
1990
- raw = await client.plan(
1991
- defined({
1992
- action: "resume_work",
1993
- task_uid: text(args.task_uid),
1994
- session_uid: text(args.session_uid),
1995
- scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
1996
- limit: 1,
1997
- agent_id: agentId
1998
- })
1999
- );
2000
- rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
2001
- strategy = "resume_work";
2002
- candidates = resumeCandidates(raw);
2003
- }
2004
- const items = visibleItems(candidates, rendered2);
2005
- const renderedHash = sha256(rendered2);
2006
- const scope = defined({
2007
- org_id: config.orgId,
2008
- agent_id: agentId,
2009
- harness: config.harness ?? "generic",
2010
- project_uid: text(args.project_uid),
2011
- task_uid: text(args.task_uid),
2012
- session_uid: text(args.session_uid),
2013
- scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
2014
- });
2015
- const contextId = `ctx_${sha256(
2016
- stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
2017
- ).slice(0, 24)}`;
2018
- const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
2019
- const result = {
2020
- schema_version: RESULT_SCHEMA_VERSION,
2021
- action: "context",
2022
- context_id: contextId,
2023
- mode,
2024
- scope,
2025
- rendered_context: rendered2,
2026
- rendered_hash: renderedHash,
2027
- items,
2028
- retrieval_trace: defined({
2029
- strategy,
2030
- query: text(args.query),
2031
- project_uid: text(args.project_uid),
2032
- task_uid: text(args.task_uid),
2033
- session_uid: text(args.session_uid),
2034
- node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
2035
- graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
2036
- graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
2037
- available_items: candidates.length,
2038
- returned_items: items.length,
2039
- omitted_items: Math.max(0, candidates.length - items.length)
2040
- }),
2041
- budget: {
2042
- unit: "characters",
2043
- requested_chars: budget,
2044
- used_chars: rendered2.length,
2045
- available_items: candidates.length,
2046
- returned_items: items.length,
2047
- omitted_items: Math.max(0, candidates.length - items.length),
2048
- bounded
2049
- },
2050
- freshness: defined({
2051
- valid_at: text(args.valid_at),
2052
- latest_source_timestamp: latestTimestamp(raw)
2053
- }),
2054
- warnings: contextWarnings(raw, rendered2, candidates.length, items.length)
2055
- };
2056
- return {
2057
- content: [{ type: "text", text: rendered2 }],
2058
- structuredContent: result
2059
- };
2060
- }
2061
-
2062
- // src/resources.ts
2063
- var RESOURCES = [
2064
- {
2065
- uri: "mindgraph://memory/status",
2066
- name: "Memory Integration Status",
2067
- description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
2068
- mimeType: "application/json"
2069
- },
2070
- {
2071
- uri: "mindgraph://stats",
2072
- name: "Graph Statistics",
2073
- description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
2074
- mimeType: "application/json"
2075
- },
2076
- {
2077
- uri: "mindgraph://goals",
2078
- name: "Active Goals",
2079
- description: "All active goals in the knowledge graph",
2080
- mimeType: "application/json"
2081
- },
2082
- {
2083
- uri: "mindgraph://questions",
2084
- name: "Open Questions",
2085
- description: "All open questions in the knowledge graph",
2086
- mimeType: "application/json"
2087
- },
2088
- {
2089
- uri: "mindgraph://contradictions",
2090
- name: "Contradictions",
2091
- description: "Contradictory claims in the knowledge graph",
2092
- mimeType: "application/json"
2093
- },
2094
- {
2095
- uri: "mindgraph://decisions",
2096
- name: "Open Decisions",
2097
- description: "Unresolved decisions awaiting resolution",
2098
- mimeType: "application/json"
2099
- }
2100
- ];
2101
- var RESOURCE_TEMPLATES = [
2102
- {
2103
- uriTemplate: "mindgraph://node/{uid}",
2104
- name: "Graph Node",
2105
- description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
2106
- mimeType: "application/json"
2107
- },
2108
- {
2109
- uriTemplate: "mindgraph://search/{query}",
2110
- name: "Search Results",
2111
- description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
2112
- mimeType: "application/json"
2113
- },
2114
- {
2115
- uriTemplate: "mindgraph://layer/{layer}",
2116
- name: "Layer Nodes",
2117
- description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
2118
- mimeType: "application/json"
2119
- }
2120
- ];
2121
- var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
2122
- function json(uri, data) {
2123
- return {
2124
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
2125
- };
2126
- }
2127
- async function readResource(client, uri, memoryConfig = {}) {
2128
- switch (uri) {
2129
- case "mindgraph://memory/status":
2130
- return json(uri, memoryStatus(memoryConfig));
2131
- case "mindgraph://stats":
2132
- return json(uri, await client.stats());
2133
- case "mindgraph://goals":
2134
- return json(uri, await client.getGoals());
2135
- case "mindgraph://questions":
2136
- return json(uri, await client.getOpenQuestions());
2137
- case "mindgraph://contradictions":
2138
- return json(uri, await client.getContradictions());
2139
- case "mindgraph://decisions":
2140
- return json(uri, await client.getOpenDecisions());
2141
- }
2142
- const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
2143
- if (nodeMatch) {
2144
- const uid = decodeURIComponent(nodeMatch[1]);
2145
- const node = await client.getNode(uid);
2146
- const edges = await client.getEdges({ from_uid: uid });
2147
- const inEdges = await client.getEdges({ to_uid: uid });
2148
- return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
2149
- }
2150
- const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
2151
- if (searchMatch) {
2152
- const query = decodeURIComponent(searchMatch[1]);
2153
- return json(uri, await client.search(query, { limit: 20 }));
2154
- }
2155
- const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
2156
- if (layerMatch) {
2157
- const layer = decodeURIComponent(layerMatch[1]);
2158
- return json(uri, await client.getNodes({ layer, limit: 50 }));
2159
- }
2160
- throw new Error(`Unknown resource: ${uri}`);
2161
- }
2162
- async function readGovernedResource(client, uri, governanceConfig, memoryConfig = {}) {
2163
- const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
2164
- const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
2165
- const governance = await checkMcpGovernance(
2166
- toolName,
2167
- toolArgs,
2168
- { ...governanceConfig, context: { resource_uri: uri } }
2169
- );
2170
- if (!governance.allowed) {
2171
- throw new Error(governance.message);
2172
- }
2173
- return readResource(client, uri, memoryConfig);
2174
- }
2175
-
2176
- // src/tools.ts
2177
- import { createHash as createHash2 } from "crypto";
2178
-
2179
- // src/codegraph.ts
2180
- import { spawn } from "child_process";
2181
- import {
2182
- existsSync,
2183
- mkdirSync,
2184
- readFileSync,
2185
- statSync,
2186
- writeFileSync
2187
- } from "fs";
2188
- import { realpath } from "fs/promises";
2189
- import { homedir } from "os";
2190
- import {
2191
- basename,
2192
- dirname,
2193
- extname,
2194
- isAbsolute,
2195
- join,
2196
- relative,
2197
- resolve,
2198
- sep
2199
- } from "path";
2200
- import { randomUUID } from "crypto";
2201
- var UnknownRepositoryError = class extends Error {
2202
- constructor(repo) {
2203
- super(
2204
- `unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
2205
- );
2206
- this.repo = repo;
2207
- this.name = "UnknownRepositoryError";
2208
- }
2209
- repo;
2210
- };
2211
- function invocationCwd(args) {
2212
- const context = args.invocation_context;
2213
- if (context && typeof context === "object" && !Array.isArray(context)) {
2214
- const cwd = context.cwd;
2215
- if (typeof cwd === "string" && cwd) return cwd;
2216
- }
2217
- return process.cwd();
2218
- }
2219
- function unavailabilityCaveats(reason, message) {
2220
- const caveats = [message || "codegraph status failed"];
2221
- if (reason === "absent") {
2222
- caveats.push(
2223
- "Optional: install codegraph (https://github.com/colbymchenry/codegraph) and run `codegraph init` in this repository to enable code anchoring and structural recall. Memory and work tools are unaffected."
2224
- );
2225
- }
2226
- return caveats;
2227
- }
2228
- var DEFAULT_TIMEOUT_MS = 5e3;
2229
- var DEFAULT_STATUS_TTL_MS = 6e4;
2230
- var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
2231
- function normalizePath(path2) {
2232
- return path2.replaceAll("\\", "/").replace(/^\.\//, "");
2233
- }
2234
- function normalizeSignature(signature) {
2235
- return signature.replace(/\s+/g, " ").trim();
2236
- }
2237
- function languageFromPath(path2) {
2238
- const extension = extname(path2).toLowerCase();
2239
- return {
2240
- ".c": "c",
2241
- ".cc": "cpp",
2242
- ".cpp": "cpp",
2243
- ".go": "go",
2244
- ".java": "java",
2245
- ".js": "javascript",
2246
- ".jsx": "javascript",
2247
- ".kt": "kotlin",
2248
- ".py": "python",
2249
- ".rb": "ruby",
2250
- ".rs": "rust",
2251
- ".swift": "swift",
2252
- ".ts": "typescript",
2253
- ".tsx": "typescript"
2254
- }[extension] ?? "unknown";
2255
- }
2256
- function asNode(hit) {
2257
- return hit.node ?? hit;
2258
- }
2259
- function toCandidate(node) {
2260
- if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
2261
- return {
2262
- path: normalizePath(node.filePath),
2263
- language: node.language ?? languageFromPath(node.filePath),
2264
- kind: node.kind,
2265
- qualifiedName: node.qualifiedName ?? node.name ?? "",
2266
- signature: node.signature ?? null,
2267
- startLine: node.startLine ?? 1,
2268
- endLine: node.endLine ?? node.startLine ?? 1
2269
- };
2270
- }
2271
- function normalizeRemote(remote) {
2272
- const trimmed = remote.trim();
2273
- if (!trimmed) return null;
2274
- const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
2275
- if (scp && !trimmed.includes("://")) {
2276
- return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
2277
- }
2278
- try {
2279
- const url = new URL(trimmed);
2280
- const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
2281
- return `${url.host.toLowerCase()}${pathname}`;
2282
- } catch {
2283
- return null;
2284
- }
2285
- }
2286
- function pathContains(root, candidate) {
2287
- const child = relative(root, candidate);
2288
- return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute(child);
2289
- }
2290
- function nearestExistingDirectory(candidate) {
2291
- let current = resolve(candidate);
2292
- for (; ; ) {
2293
- if (existsSync(current)) {
2294
- try {
2295
- return statSync(current).isDirectory() ? current : dirname(current);
2296
- } catch {
2297
- }
2298
- }
2299
- const parent = dirname(current);
2300
- if (parent === current) return current;
2301
- current = parent;
2302
- }
2303
- }
2304
- function findWorkspaceFile(start, explicit) {
2305
- if (explicit) return resolve(start, explicit);
2306
- let current = resolve(start);
2307
- for (; ; ) {
2308
- const candidate = join(current, ".mindgraph", "workspace.json");
2309
- if (existsSync(candidate)) return candidate;
2310
- const parent = dirname(current);
2311
- if (parent === current) return void 0;
2312
- current = parent;
1745
+ return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).slice(0, MAX_CHILD_DIRECTORIES).map((entry) => join(start, entry.name, ".mindgraph", "workspace.json")).filter((candidate) => existsSync(candidate)).sort();
1746
+ }
1747
+ function findWorkspaceFile(start, explicit) {
1748
+ if (explicit) return resolve(start, explicit);
1749
+ let current = resolve(start);
1750
+ for (; ; ) {
1751
+ const candidate = join(current, ".mindgraph", "workspace.json");
1752
+ if (existsSync(candidate)) return candidate;
1753
+ const parent = dirname(current);
1754
+ if (parent === current) return void 0;
1755
+ current = parent;
2313
1756
  }
2314
1757
  }
2315
1758
  async function runProcess(executable, args, timeoutMs, env, cwd) {
@@ -2377,6 +1820,7 @@ var CodegraphAdapter = class {
2377
1820
  env;
2378
1821
  cwd;
2379
1822
  workspaceFile;
1823
+ childWorkspaceFiles;
2380
1824
  now;
2381
1825
  identityCache = /* @__PURE__ */ new Map();
2382
1826
  statusCache = /* @__PURE__ */ new Map();
@@ -2389,6 +1833,7 @@ var CodegraphAdapter = class {
2389
1833
  this.cwd,
2390
1834
  options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
2391
1835
  );
1836
+ this.childWorkspaceFiles = !this.workspaceFile && options.childWorkspaces ? findChildWorkspaceFiles(this.cwd) : [];
2392
1837
  this.now = options.now ?? Date.now;
2393
1838
  }
2394
1839
  async resolveRepository(repo, fallbackSpaceUid) {
@@ -2555,925 +2000,1593 @@ var CodegraphAdapter = class {
2555
2000
  if ((status.index?.pendingRefs ?? 0) > 0) {
2556
2001
  caveats.push(`${status.index?.pendingRefs} references remain pending`);
2557
2002
  }
2558
- if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
2559
- availability = {
2560
- available: true,
2561
- stale: caveats.length > 0,
2562
- caveats,
2563
- lastIndexed: status.lastIndexed
2003
+ if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
2004
+ availability = {
2005
+ available: true,
2006
+ stale: caveats.length > 0,
2007
+ caveats,
2008
+ lastIndexed: status.lastIndexed
2009
+ };
2010
+ }
2011
+ this.statusCache.set(repository.repoRoot, {
2012
+ status,
2013
+ availability,
2014
+ expiresAt: this.now() + this.statusTtlMs
2015
+ });
2016
+ return availability;
2017
+ } catch (cause) {
2018
+ const failure = cause;
2019
+ const reason = failure.kind ?? "command_failed";
2020
+ return {
2021
+ available: false,
2022
+ reason,
2023
+ stale: false,
2024
+ caveats: unavailabilityCaveats(reason, failure.message)
2025
+ };
2026
+ }
2027
+ }
2028
+ async resolveRefs(refs, options = {}) {
2029
+ const normalized = refs.map(
2030
+ (ref) => typeof ref === "string" ? { symbol: ref } : ref
2031
+ );
2032
+ const results = await Promise.all(
2033
+ normalized.map(async (ref) => {
2034
+ const repository2 = await this.repositoryForRef(
2035
+ ref,
2036
+ options.repo,
2037
+ options.fallbackSpaceUid
2038
+ );
2039
+ const availability2 = await this.availability(repository2);
2040
+ if (!availability2.available) {
2041
+ return {
2042
+ ref,
2043
+ repository: repository2,
2044
+ availability: availability2,
2045
+ candidates: [],
2046
+ error: availability2.reason
2047
+ };
2048
+ }
2049
+ return {
2050
+ ...await this.resolveOne(ref, repository2),
2051
+ repository: repository2,
2052
+ availability: availability2
2053
+ };
2054
+ })
2055
+ );
2056
+ const repositories = [
2057
+ ...new Map(
2058
+ results.flatMap(
2059
+ (result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
2060
+ )
2061
+ ).values()
2062
+ ];
2063
+ const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
2064
+ if (repositories.length === 0) repositories.push(repository);
2065
+ const availabilities = results.flatMap(
2066
+ (result) => result.availability ? [result.availability] : []
2067
+ );
2068
+ const availability = availabilities.length === 0 ? await this.availability(repository) : {
2069
+ available: availabilities.every((item) => item.available),
2070
+ reason: availabilities.find((item) => !item.available)?.reason,
2071
+ stale: availabilities.some((item) => item.stale),
2072
+ caveats: [
2073
+ ...new Set(availabilities.flatMap((item) => item.caveats))
2074
+ ]
2075
+ };
2076
+ return { repository, repositories, availability, results };
2077
+ }
2078
+ async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
2079
+ if (ref.repo || defaultRepo) {
2080
+ return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
2081
+ }
2082
+ if (ref.path) {
2083
+ const requested = isAbsolute(ref.path) ? resolve(ref.path) : resolve(this.cwd, ref.path);
2084
+ const absolute = await realpath(requested).catch(() => requested);
2085
+ const configured = await this.resolveRepositories(fallbackSpaceUid);
2086
+ const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
2087
+ if (defining) {
2088
+ return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
2089
+ defining.repoRoot,
2090
+ this.env.MINDGRAPH_REPO_ID,
2091
+ defining.spaceUid
2092
+ ) : defining;
2093
+ }
2094
+ const gitRoot = await this.gitRoot(absolute);
2095
+ if (gitRoot) {
2096
+ return this.resolveRepositoryIdentity(
2097
+ gitRoot,
2098
+ this.env.MINDGRAPH_REPO_ID,
2099
+ this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
2100
+ );
2101
+ }
2102
+ }
2103
+ return this.resolveRepository(void 0, fallbackSpaceUid);
2104
+ }
2105
+ async expand(resolved, limit = 20) {
2106
+ const repository = {
2107
+ repoId: resolved.repoId,
2108
+ repoRoot: resolved.repoRoot
2109
+ };
2110
+ const availability = await this.availability(repository);
2111
+ if (!availability.available) {
2112
+ return { availability, callers: [], callees: [], truncated: false };
2113
+ }
2114
+ const cap = Math.min(Math.max(limit, 1), 20);
2115
+ const [callers, callees] = await Promise.all([
2116
+ this.related("callers", resolved, cap + 1),
2117
+ this.related("callees", resolved, cap + 1)
2118
+ ]);
2119
+ return {
2120
+ availability,
2121
+ callers: callers.slice(0, cap),
2122
+ callees: callees.slice(0, cap),
2123
+ truncated: callers.length > cap || callees.length > cap
2124
+ };
2125
+ }
2126
+ async affected(resolved, limit = 50) {
2127
+ const repository = {
2128
+ repoId: resolved.repoId,
2129
+ repoRoot: resolved.repoRoot
2130
+ };
2131
+ const availability = await this.availability(repository);
2132
+ if (!availability.available) {
2133
+ return { availability, affected: [], truncated: false };
2134
+ }
2135
+ const cap = Math.min(Math.max(limit, 1), 50);
2136
+ const raw = await this.runJson([
2137
+ "impact",
2138
+ "-p",
2139
+ resolved.repoRoot,
2140
+ "-j",
2141
+ "-d",
2142
+ "2",
2143
+ resolved.qualifiedName
2144
+ ]);
2145
+ const refs = (raw.affected ?? []).map((node) => ({
2146
+ path: node.filePath,
2147
+ line: node.startLine,
2148
+ symbol: node.qualifiedName ?? node.name,
2149
+ kind: node.kind
2150
+ }));
2151
+ const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
2152
+ return {
2153
+ availability,
2154
+ affected: resolvedRefs.slice(0, cap),
2155
+ truncated: refs.length > cap
2156
+ };
2157
+ }
2158
+ configuredRepositories() {
2159
+ const configured = [];
2160
+ const workspaceFiles = this.workspaceFile ? [this.workspaceFile] : this.childWorkspaceFiles;
2161
+ for (const workspaceFile of workspaceFiles) {
2162
+ if (!existsSync(workspaceFile)) continue;
2163
+ try {
2164
+ const parsed = JSON.parse(
2165
+ readFileSync(workspaceFile, "utf8")
2166
+ );
2167
+ if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
2168
+ const configDirectory = dirname(workspaceFile);
2169
+ const base = basename(configDirectory) === ".mindgraph" ? dirname(configDirectory) : configDirectory;
2170
+ for (const repository of parsed.repositories) {
2171
+ if (!repository || typeof repository.root !== "string") continue;
2172
+ configured.push({
2173
+ repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
2174
+ repoRoot: resolve(base, repository.root),
2175
+ spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
2176
+ });
2177
+ }
2178
+ }
2179
+ } catch {
2180
+ }
2181
+ }
2182
+ const raw = this.env.MINDGRAPH_CODE_REPOS;
2183
+ if (raw) {
2184
+ try {
2185
+ const parsed = JSON.parse(raw);
2186
+ for (const [repoId, config] of Object.entries(parsed)) {
2187
+ configured.push({
2188
+ repoId,
2189
+ repoRoot: resolve(
2190
+ this.cwd,
2191
+ typeof config === "string" ? config : config.root
2192
+ ),
2193
+ spaceUid: typeof config === "string" ? void 0 : config.space_uid
2194
+ });
2195
+ }
2196
+ } catch {
2197
+ }
2198
+ }
2199
+ return [
2200
+ ...new Map(
2201
+ configured.map((repository) => [repository.repoRoot, repository])
2202
+ ).values()
2203
+ ];
2204
+ }
2205
+ async resolveOne(ref, repository) {
2206
+ const search = ref.symbol || ref.path;
2207
+ if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
2208
+ return { ref, candidates: [], error: "invalid_ref" };
2209
+ }
2210
+ try {
2211
+ const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
2212
+ if (ref.kind) args.push("-k", ref.kind);
2213
+ args.push(search);
2214
+ const hits = await this.runJson(args);
2215
+ let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
2216
+ if (ref.path) {
2217
+ const absolutePath = isAbsolute(ref.path) ? await realpath(ref.path).catch(() => ref.path) : void 0;
2218
+ const wanted = normalizePath(
2219
+ absolutePath ? relative(repository.repoRoot, absolutePath) : ref.path
2220
+ );
2221
+ candidates = candidates.filter((candidate) => candidate.path === wanted);
2222
+ }
2223
+ if (ref.language) {
2224
+ candidates = candidates.filter(
2225
+ (candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
2226
+ );
2227
+ }
2228
+ if (ref.kind) {
2229
+ candidates = candidates.filter(
2230
+ (candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
2231
+ );
2232
+ }
2233
+ if (ref.symbol) {
2234
+ const exact = candidates.filter((candidate) => {
2235
+ const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
2236
+ return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
2237
+ });
2238
+ if (exact.length > 0) candidates = exact;
2239
+ }
2240
+ if (ref.signature) {
2241
+ const signature = normalizeSignature(ref.signature);
2242
+ candidates = candidates.filter(
2243
+ (candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
2244
+ );
2245
+ }
2246
+ if (ref.line !== void 0) {
2247
+ candidates = candidates.filter(
2248
+ (candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
2249
+ );
2250
+ if (candidates.length > 1) {
2251
+ const narrowest = Math.min(
2252
+ ...candidates.map((candidate) => candidate.endLine - candidate.startLine)
2253
+ );
2254
+ const narrowed = candidates.filter(
2255
+ (candidate) => candidate.endLine - candidate.startLine === narrowest
2256
+ );
2257
+ if (narrowed.length === 1) candidates = narrowed;
2258
+ }
2259
+ }
2260
+ if (candidates.length !== 1) {
2261
+ return {
2262
+ ref,
2263
+ candidates,
2264
+ error: candidates.length === 0 ? "not_found" : "ambiguous"
2564
2265
  };
2565
2266
  }
2566
- this.statusCache.set(repository.repoRoot, {
2567
- status,
2568
- availability,
2569
- expiresAt: this.now() + this.statusTtlMs
2570
- });
2571
- return availability;
2267
+ return {
2268
+ ref,
2269
+ candidates,
2270
+ resolved: {
2271
+ repoId: repository.repoId,
2272
+ repoRoot: repository.repoRoot,
2273
+ ...candidates[0]
2274
+ }
2275
+ };
2572
2276
  } catch (cause) {
2573
2277
  const failure = cause;
2574
- const reason = failure.kind ?? "command_failed";
2575
2278
  return {
2576
- available: false,
2577
- reason,
2578
- stale: false,
2579
- caveats: unavailabilityCaveats(reason, failure.message)
2279
+ ref,
2280
+ candidates: [],
2281
+ error: failure.kind ?? "command_failed"
2580
2282
  };
2581
2283
  }
2582
2284
  }
2583
- async resolveRefs(refs, options = {}) {
2584
- const normalized = refs.map(
2585
- (ref) => typeof ref === "string" ? { symbol: ref } : ref
2586
- );
2587
- const results = await Promise.all(
2588
- normalized.map(async (ref) => {
2589
- const repository2 = await this.repositoryForRef(
2590
- ref,
2591
- options.repo,
2592
- options.fallbackSpaceUid
2285
+ async related(action, resolved, limit) {
2286
+ const raw = await this.runJson([
2287
+ action,
2288
+ "-p",
2289
+ resolved.repoRoot,
2290
+ "-j",
2291
+ "-l",
2292
+ String(limit),
2293
+ resolved.qualifiedName
2294
+ ]);
2295
+ const refs = (raw[action] ?? []).map((node) => ({
2296
+ path: node.filePath,
2297
+ line: node.startLine,
2298
+ symbol: node.qualifiedName ?? node.name,
2299
+ kind: node.kind
2300
+ }));
2301
+ return this.resolveRelatedRefs(refs, {
2302
+ repoId: resolved.repoId,
2303
+ repoRoot: resolved.repoRoot
2304
+ });
2305
+ }
2306
+ async resolveRelatedRefs(refs, repository) {
2307
+ const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
2308
+ const seen = /* @__PURE__ */ new Set();
2309
+ return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
2310
+ const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
2311
+ if (seen.has(key)) return false;
2312
+ seen.add(key);
2313
+ return true;
2314
+ });
2315
+ }
2316
+ async runJson(args) {
2317
+ const candidates = [
2318
+ this.env.MINDGRAPH_CODEGRAPH_BIN,
2319
+ "codegraph",
2320
+ resolve(homedir(), ".local", "bin", "codegraph")
2321
+ ].filter((candidate, index, all) => {
2322
+ return Boolean(candidate) && all.indexOf(candidate) === index;
2323
+ });
2324
+ let lastFailure;
2325
+ for (const executable of candidates) {
2326
+ try {
2327
+ const stdout = await runProcess(
2328
+ executable,
2329
+ args,
2330
+ this.timeoutMs,
2331
+ this.env,
2332
+ this.cwd
2593
2333
  );
2594
- const availability2 = await this.availability(repository2);
2595
- if (!availability2.available) {
2596
- return {
2334
+ return JSON.parse(stdout);
2335
+ } catch (cause) {
2336
+ const failure = cause;
2337
+ lastFailure = failure;
2338
+ if (failure.kind !== "absent") throw failure;
2339
+ }
2340
+ }
2341
+ throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
2342
+ kind: "absent"
2343
+ });
2344
+ }
2345
+ };
2346
+ function repositoryIdentityKey(repository) {
2347
+ return { v: 1, kind: "repository", repo_id: repository.repoId };
2348
+ }
2349
+ function codeRefIdentityKey(ref) {
2350
+ if (ref.kind === "file") {
2351
+ return {
2352
+ v: 1,
2353
+ kind: "file",
2354
+ repo_id: ref.repoId,
2355
+ path: ref.path
2356
+ };
2357
+ }
2358
+ return {
2359
+ v: 1,
2360
+ kind: "symbol",
2361
+ repo_id: ref.repoId,
2362
+ language: ref.language,
2363
+ symbol_kind: ref.kind,
2364
+ qualified_name: ref.qualifiedName,
2365
+ disambiguator: {
2366
+ signature: ref.signature ?? "",
2367
+ path: ref.path
2368
+ }
2369
+ };
2370
+ }
2371
+
2372
+ // src/code-tool.ts
2373
+ var ok2 = (data) => ({
2374
+ content: [{ type: "text", text: JSON.stringify(data) }]
2375
+ });
2376
+ var err3 = (code, message, details) => ({
2377
+ content: [
2378
+ {
2379
+ type: "text",
2380
+ text: JSON.stringify({ error: message, code, details })
2381
+ }
2382
+ ],
2383
+ isError: true
2384
+ });
2385
+ var CODE_TOOL = {
2386
+ name: "mindgraph_code",
2387
+ description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
2388
+ inputSchema: {
2389
+ type: "object",
2390
+ properties: {
2391
+ action: {
2392
+ type: "string",
2393
+ enum: ["anchor", "recall", "expand", "affected"]
2394
+ },
2395
+ code_refs: {
2396
+ type: "array",
2397
+ maxItems: 50,
2398
+ items: {
2399
+ oneOf: [
2400
+ { type: "string" },
2401
+ {
2402
+ type: "object",
2403
+ properties: {
2404
+ repo: { type: "string" },
2405
+ path: { type: "string" },
2406
+ line: { type: "integer", minimum: 1 },
2407
+ symbol: { type: "string" },
2408
+ kind: { type: "string" },
2409
+ language: { type: "string" },
2410
+ signature: { type: "string" }
2411
+ },
2412
+ additionalProperties: false
2413
+ }
2414
+ ]
2415
+ }
2416
+ },
2417
+ anchor_uids: {
2418
+ type: "array",
2419
+ maxItems: 50,
2420
+ items: { type: "string" },
2421
+ description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
2422
+ },
2423
+ repo: {
2424
+ type: "string",
2425
+ description: "Configured repository id or repository root"
2426
+ },
2427
+ repo_space_uid: {
2428
+ type: "string",
2429
+ description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
2430
+ },
2431
+ task_uid: { type: "string" },
2432
+ execution_uid: { type: "string" },
2433
+ limit: { type: "integer", minimum: 1, maximum: 50 },
2434
+ agent_id: { type: "string" }
2435
+ },
2436
+ required: ["action"]
2437
+ },
2438
+ annotations: {
2439
+ readOnlyHint: false,
2440
+ destructiveHint: false,
2441
+ idempotentHint: true,
2442
+ openWorldHint: true
2443
+ }
2444
+ };
2445
+ async function handleCodeTool(client, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
2446
+ const codeClient = client;
2447
+ const action = args.action;
2448
+ const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
2449
+ const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
2450
+ const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
2451
+ const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
2452
+ const options = {
2453
+ repo: typeof args.repo === "string" ? args.repo : void 0,
2454
+ fallbackSpaceUid
2455
+ };
2456
+ const limit = typeof args.limit === "number" ? args.limit : void 0;
2457
+ try {
2458
+ switch (action) {
2459
+ case "anchor": {
2460
+ if (refs.length === 0) return err3("missing_code_refs", "code_refs is required for anchor");
2461
+ const resolution = await adapter.resolveRefs(refs, options);
2462
+ const resolved = resolution.results.flatMap(
2463
+ (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
2464
+ );
2465
+ const unresolved = resolution.results.filter((result) => !result.resolved);
2466
+ if (resolved.length === 0) {
2467
+ return ok2({ ...resolution, anchors: [], wrote: false });
2468
+ }
2469
+ const missingSpace = resolved.find(
2470
+ ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
2471
+ );
2472
+ if (missingSpace) {
2473
+ return err3(
2474
+ "missing_repository_space",
2475
+ `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
2476
+ );
2477
+ }
2478
+ const repositoryAnchors = /* @__PURE__ */ new Map();
2479
+ for (const { repository } of resolved) {
2480
+ if (repositoryAnchors.has(repository.repoRoot)) continue;
2481
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
2482
+ const anchored = await anchorRepository(
2483
+ codeClient,
2484
+ repository,
2485
+ spaceUid,
2486
+ agentId
2487
+ );
2488
+ repositoryAnchors.set(repository.repoRoot, {
2489
+ ...anchored,
2490
+ repository
2491
+ });
2492
+ }
2493
+ const anchors = [];
2494
+ for (const { ref, repository } of resolved) {
2495
+ const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
2496
+ if (!repositoryAnchor.uid) continue;
2497
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
2498
+ const anchor = await anchorCodeRef(
2499
+ codeClient,
2597
2500
  ref,
2598
- repository: repository2,
2599
- availability: availability2,
2600
- candidates: [],
2601
- error: availability2.reason
2602
- };
2501
+ repositoryAnchor.uid,
2502
+ spaceUid,
2503
+ agentId
2504
+ );
2505
+ anchors.push(anchor);
2603
2506
  }
2604
- return {
2605
- ...await this.resolveOne(ref, repository2),
2606
- repository: repository2,
2607
- availability: availability2
2608
- };
2609
- })
2610
- );
2611
- const repositories = [
2612
- ...new Map(
2613
- results.flatMap(
2614
- (result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
2615
- )
2616
- ).values()
2617
- ];
2618
- const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
2619
- if (repositories.length === 0) repositories.push(repository);
2620
- const availabilities = results.flatMap(
2621
- (result) => result.availability ? [result.availability] : []
2622
- );
2623
- const availability = availabilities.length === 0 ? await this.availability(repository) : {
2624
- available: availabilities.every((item) => item.available),
2625
- reason: availabilities.find((item) => !item.available)?.reason,
2626
- stale: availabilities.some((item) => item.stale),
2627
- caveats: [
2628
- ...new Set(availabilities.flatMap((item) => item.caveats))
2629
- ]
2630
- };
2631
- return { repository, repositories, availability, results };
2632
- }
2633
- async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
2634
- if (ref.repo || defaultRepo) {
2635
- return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
2636
- }
2637
- if (ref.path) {
2638
- const requested = isAbsolute(ref.path) ? resolve(ref.path) : resolve(this.cwd, ref.path);
2639
- const absolute = await realpath(requested).catch(() => requested);
2640
- const configured = await this.resolveRepositories(fallbackSpaceUid);
2641
- const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
2642
- if (defining) {
2643
- return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
2644
- defining.repoRoot,
2645
- this.env.MINDGRAPH_REPO_ID,
2646
- defining.spaceUid
2647
- ) : defining;
2507
+ return ok2({
2508
+ availability: resolution.availability,
2509
+ repository: repositoryAnchors.values().next().value,
2510
+ repositories: [...repositoryAnchors.values()],
2511
+ anchors,
2512
+ unresolved,
2513
+ wrote: anchors.some((anchor) => anchor.created)
2514
+ });
2648
2515
  }
2649
- const gitRoot = await this.gitRoot(absolute);
2650
- if (gitRoot) {
2651
- return this.resolveRepositoryIdentity(
2652
- gitRoot,
2653
- this.env.MINDGRAPH_REPO_ID,
2654
- this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
2516
+ case "recall": {
2517
+ const materialized = new Set(anchorUids);
2518
+ let resolution;
2519
+ if (refs.length > 0) {
2520
+ resolution = await adapter.resolveRefs(refs, options);
2521
+ for (const result of resolution.results) {
2522
+ if (!result.resolved) continue;
2523
+ const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
2524
+ if (uid) materialized.add(uid);
2525
+ }
2526
+ }
2527
+ if (materialized.size === 0) {
2528
+ return ok2({
2529
+ availability: resolution?.availability,
2530
+ anchors: [],
2531
+ context: [],
2532
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
2533
+ });
2534
+ }
2535
+ const context = await Promise.all(
2536
+ [...materialized].map(async (uid) => ({
2537
+ anchor_uid: uid,
2538
+ graph: await codeClient.traverse({
2539
+ action: "neighborhood",
2540
+ start_uid: uid,
2541
+ depth: 2,
2542
+ max_nodes: Math.min(limit ?? 20, 20)
2543
+ })
2544
+ }))
2655
2545
  );
2546
+ return ok2({
2547
+ availability: resolution?.availability,
2548
+ anchors: [...materialized],
2549
+ context,
2550
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
2551
+ });
2656
2552
  }
2657
- }
2658
- return this.resolveRepository(void 0, fallbackSpaceUid);
2659
- }
2660
- async expand(resolved, limit = 20) {
2661
- const repository = {
2662
- repoId: resolved.repoId,
2663
- repoRoot: resolved.repoRoot
2664
- };
2665
- const availability = await this.availability(repository);
2666
- if (!availability.available) {
2667
- return { availability, callers: [], callees: [], truncated: false };
2668
- }
2669
- const cap = Math.min(Math.max(limit, 1), 20);
2670
- const [callers, callees] = await Promise.all([
2671
- this.related("callers", resolved, cap + 1),
2672
- this.related("callees", resolved, cap + 1)
2673
- ]);
2674
- return {
2675
- availability,
2676
- callers: callers.slice(0, cap),
2677
- callees: callees.slice(0, cap),
2678
- truncated: callers.length > cap || callees.length > cap
2679
- };
2680
- }
2681
- async affected(resolved, limit = 50) {
2682
- const repository = {
2683
- repoId: resolved.repoId,
2684
- repoRoot: resolved.repoRoot
2685
- };
2686
- const availability = await this.availability(repository);
2687
- if (!availability.available) {
2688
- return { availability, affected: [], truncated: false };
2689
- }
2690
- const cap = Math.min(Math.max(limit, 1), 50);
2691
- const raw = await this.runJson([
2692
- "impact",
2693
- "-p",
2694
- resolved.repoRoot,
2695
- "-j",
2696
- "-d",
2697
- "2",
2698
- resolved.qualifiedName
2699
- ]);
2700
- const refs = (raw.affected ?? []).map((node) => ({
2701
- path: node.filePath,
2702
- line: node.startLine,
2703
- symbol: node.qualifiedName ?? node.name,
2704
- kind: node.kind
2705
- }));
2706
- const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
2707
- return {
2708
- availability,
2709
- affected: resolvedRefs.slice(0, cap),
2710
- truncated: refs.length > cap
2711
- };
2712
- }
2713
- configuredRepositories() {
2714
- const configured = [];
2715
- if (this.workspaceFile && existsSync(this.workspaceFile)) {
2716
- try {
2717
- const parsed = JSON.parse(
2718
- readFileSync(this.workspaceFile, "utf8")
2553
+ case "expand": {
2554
+ const target = await resolveSingleTarget(
2555
+ codeClient,
2556
+ adapter,
2557
+ refs,
2558
+ anchorUids,
2559
+ options
2719
2560
  );
2720
- if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
2721
- const configDirectory = dirname(this.workspaceFile);
2722
- const base = basename(configDirectory) === ".mindgraph" ? dirname(configDirectory) : configDirectory;
2723
- for (const repository of parsed.repositories) {
2724
- if (!repository || typeof repository.root !== "string") continue;
2725
- configured.push({
2726
- repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
2727
- repoRoot: resolve(base, repository.root),
2728
- spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
2729
- });
2730
- }
2731
- }
2732
- } catch {
2561
+ if ("error" in target) return target.error;
2562
+ const expanded = await adapter.expand(target.ref, limit);
2563
+ const joined = await joinMaterialized(
2564
+ codeClient,
2565
+ [...expanded.callers, ...expanded.callees],
2566
+ agentId,
2567
+ Math.min(limit ?? 20, 20)
2568
+ );
2569
+ return ok2({ anchor: target.ref, ...expanded, materialized: joined });
2733
2570
  }
2734
- }
2735
- const raw = this.env.MINDGRAPH_CODE_REPOS;
2736
- if (raw) {
2737
- try {
2738
- const parsed = JSON.parse(raw);
2739
- for (const [repoId, config] of Object.entries(parsed)) {
2740
- configured.push({
2741
- repoId,
2742
- repoRoot: resolve(
2743
- this.cwd,
2744
- typeof config === "string" ? config : config.root
2745
- ),
2746
- spaceUid: typeof config === "string" ? void 0 : config.space_uid
2571
+ case "affected": {
2572
+ const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
2573
+ let graphContext;
2574
+ const graphRefs = [];
2575
+ let unavailableRepositories = [];
2576
+ if (targetUid) {
2577
+ graphContext = await codeClient.traverse({
2578
+ action: "neighborhood",
2579
+ start_uid: targetUid,
2580
+ depth: 2,
2581
+ max_nodes: 50
2582
+ });
2583
+ const fromGraph = await codeRefsFromGraphContext(
2584
+ codeClient,
2585
+ adapter,
2586
+ graphContext
2587
+ );
2588
+ graphRefs.push(...fromGraph.refs);
2589
+ unavailableRepositories = fromGraph.unavailable;
2590
+ }
2591
+ if (refs.length > 0 || anchorUids.length > 0) {
2592
+ const target = await resolveSingleTarget(
2593
+ codeClient,
2594
+ adapter,
2595
+ refs,
2596
+ anchorUids,
2597
+ options
2598
+ );
2599
+ if ("error" in target) return target.error;
2600
+ graphRefs.unshift(target.ref);
2601
+ }
2602
+ const unique = uniqueRefs(graphRefs);
2603
+ if (unique.length === 0) {
2604
+ return ok2({
2605
+ target_uid: targetUid,
2606
+ graph_context: graphContext,
2607
+ affected: [],
2608
+ degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
2609
+ ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
2747
2610
  });
2748
2611
  }
2749
- } catch {
2750
- }
2751
- }
2752
- return [
2753
- ...new Map(
2754
- configured.map((repository) => [repository.repoRoot, repository])
2755
- ).values()
2756
- ];
2757
- }
2758
- async resolveOne(ref, repository) {
2759
- const search = ref.symbol || ref.path;
2760
- if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
2761
- return { ref, candidates: [], error: "invalid_ref" };
2762
- }
2763
- try {
2764
- const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
2765
- if (ref.kind) args.push("-k", ref.kind);
2766
- args.push(search);
2767
- const hits = await this.runJson(args);
2768
- let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
2769
- if (ref.path) {
2770
- const absolutePath = isAbsolute(ref.path) ? await realpath(ref.path).catch(() => ref.path) : void 0;
2771
- const wanted = normalizePath(
2772
- absolutePath ? relative(repository.repoRoot, absolutePath) : ref.path
2773
- );
2774
- candidates = candidates.filter((candidate) => candidate.path === wanted);
2775
- }
2776
- if (ref.language) {
2777
- candidates = candidates.filter(
2778
- (candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
2612
+ const impacts = await Promise.all(
2613
+ unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
2779
2614
  );
2780
- }
2781
- if (ref.kind) {
2782
- candidates = candidates.filter(
2783
- (candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
2615
+ const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
2616
+ const joined = await joinMaterialized(
2617
+ codeClient,
2618
+ structural,
2619
+ agentId,
2620
+ Math.min(limit ?? 50, 50)
2784
2621
  );
2785
- }
2786
- if (ref.symbol) {
2787
- const exact = candidates.filter((candidate) => {
2788
- const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
2789
- return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
2622
+ return ok2({
2623
+ target_uid: targetUid,
2624
+ graph_context: graphContext,
2625
+ impacts,
2626
+ materialized: joined,
2627
+ ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
2790
2628
  });
2791
- if (exact.length > 0) candidates = exact;
2792
2629
  }
2793
- if (ref.signature) {
2794
- const signature = normalizeSignature(ref.signature);
2795
- candidates = candidates.filter(
2796
- (candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
2797
- );
2630
+ default:
2631
+ return err3("unknown_action", `unknown mindgraph_code action: ${action}`);
2632
+ }
2633
+ } catch (cause) {
2634
+ if (cause instanceof UnknownRepositoryError) {
2635
+ return err3("unknown_repository", cause.message);
2636
+ }
2637
+ return toolError(cause, "code_tool_failed");
2638
+ }
2639
+ }
2640
+ async function attachCodeRefsToToolResult(client, result, args, adapter) {
2641
+ if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
2642
+ return result;
2643
+ }
2644
+ const original = JSON.parse(result.content[0]?.text ?? "null");
2645
+ const targetUids = primaryUids(original);
2646
+ if (targetUids.length === 0) {
2647
+ return ok2({
2648
+ result: original,
2649
+ code_anchors: {
2650
+ wrote: false,
2651
+ caveat: "the write response exposed no primary UID to attach"
2798
2652
  }
2799
- if (ref.line !== void 0) {
2800
- candidates = candidates.filter(
2801
- (candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
2802
- );
2803
- if (candidates.length > 1) {
2804
- const narrowest = Math.min(
2805
- ...candidates.map((candidate) => candidate.endLine - candidate.startLine)
2806
- );
2807
- const narrowed = candidates.filter(
2808
- (candidate) => candidate.endLine - candidate.startLine === narrowest
2653
+ });
2654
+ }
2655
+ const anchorResult = await handleCodeTool(
2656
+ client,
2657
+ {
2658
+ action: "anchor",
2659
+ code_refs: args.code_refs,
2660
+ repo: args.repo,
2661
+ repo_space_uid: args.repo_space_uid,
2662
+ agent_id: args.agent_id,
2663
+ // Keeps the default adapter anchored to the live session directory
2664
+ // rather than wherever the MCP server process was launched.
2665
+ invocation_context: args.invocation_context
2666
+ },
2667
+ adapter
2668
+ );
2669
+ const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
2670
+ if (!anchorResult.isError) {
2671
+ const codeClient = client;
2672
+ const anchorUids = (anchors.anchors ?? []).flatMap(
2673
+ (anchor) => anchor.uid ? [anchor.uid] : []
2674
+ );
2675
+ const relateFailures = [];
2676
+ for (const targetUid of targetUids) {
2677
+ for (const anchorUid of anchorUids) {
2678
+ try {
2679
+ await codeClient.entity({
2680
+ action: "relate",
2681
+ source_uid: targetUid,
2682
+ target_uid: anchorUid,
2683
+ edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
2684
+ agent_id: args.agent_id
2685
+ });
2686
+ } catch (cause) {
2687
+ relateFailures.push(
2688
+ `${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
2809
2689
  );
2810
- if (narrowed.length === 1) candidates = narrowed;
2811
2690
  }
2812
2691
  }
2813
- if (candidates.length !== 1) {
2814
- return {
2815
- ref,
2816
- candidates,
2817
- error: candidates.length === 0 ? "not_found" : "ambiguous"
2818
- };
2819
- }
2820
- return {
2821
- ref,
2822
- candidates,
2823
- resolved: {
2824
- repoId: repository.repoId,
2825
- repoRoot: repository.repoRoot,
2826
- ...candidates[0]
2692
+ }
2693
+ if (relateFailures.length > 0) {
2694
+ return ok2({
2695
+ result: original,
2696
+ code_anchors: {
2697
+ ...anchors,
2698
+ caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
2827
2699
  }
2828
- };
2829
- } catch (cause) {
2830
- const failure = cause;
2700
+ });
2701
+ }
2702
+ }
2703
+ return ok2({ result: original, code_anchors: anchors });
2704
+ }
2705
+ async function anchorRepository(client, repository, spaceUid, agentId) {
2706
+ return client.entity({
2707
+ action: "create",
2708
+ label: repository.repoId,
2709
+ props: {
2710
+ entity_type: "repository",
2711
+ canonical_name: repository.repoId,
2712
+ identifiers: { repo_id: repository.repoId }
2713
+ },
2714
+ identity: {
2715
+ namespace: "external.code",
2716
+ key_version: 1,
2717
+ key: repositoryIdentityKey(repository)
2718
+ },
2719
+ identity_space_uid: spaceUid,
2720
+ agent_id: agentId
2721
+ });
2722
+ }
2723
+ async function anchorCodeRef(client, ref, repositoryUid, spaceUid, agentId) {
2724
+ const response = await client.entity({
2725
+ action: "create",
2726
+ label: ref.qualifiedName,
2727
+ props: {
2728
+ entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
2729
+ canonical_name: ref.qualifiedName,
2730
+ identifiers: {
2731
+ repo_id: ref.repoId,
2732
+ path: ref.path,
2733
+ qualified_name: ref.qualifiedName
2734
+ },
2735
+ attributes: { code_ref: storedCodeRef(ref) }
2736
+ },
2737
+ identity: {
2738
+ namespace: "external.code",
2739
+ key_version: 1,
2740
+ key: codeRefIdentityKey(ref)
2741
+ },
2742
+ identity_space_uid: spaceUid,
2743
+ agent_id: agentId
2744
+ });
2745
+ if (response.uid && response.created !== false && response.status !== "existing") {
2746
+ await client.entity({
2747
+ action: "relate",
2748
+ source_uid: response.uid,
2749
+ target_uid: repositoryUid,
2750
+ edge_type: "PART_OF",
2751
+ agent_id: agentId
2752
+ });
2753
+ }
2754
+ return { ...response, ref };
2755
+ }
2756
+ async function resolveMaterializedUid(client, ref, agentId) {
2757
+ const response = await client.entity({
2758
+ action: "resolve_identity",
2759
+ identity: {
2760
+ namespace: "external.code",
2761
+ key_version: 1,
2762
+ key: codeRefIdentityKey(ref)
2763
+ },
2764
+ agent_id: agentId
2765
+ });
2766
+ return response.status === "existing" ? response.uid : void 0;
2767
+ }
2768
+ async function codeRefFromAnchor(client, adapter, uid) {
2769
+ const node = await client.getNode(uid);
2770
+ const attributes = node.props?.attributes;
2771
+ if (!attributes || typeof attributes !== "object") return void 0;
2772
+ const codeRef = attributes.code_ref;
2773
+ if (!isStoredCodeRef(codeRef)) return void 0;
2774
+ try {
2775
+ const repository = await adapter.resolveRepository(codeRef.repoId);
2776
+ return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
2777
+ } catch (cause) {
2778
+ if (cause instanceof UnknownRepositoryError) {
2779
+ return { missingRepo: codeRef.repoId };
2780
+ }
2781
+ throw cause;
2782
+ }
2783
+ }
2784
+ async function resolveSingleTarget(client, adapter, refs, anchorUids, options) {
2785
+ if (refs.length > 1 || anchorUids.length > 1) {
2786
+ return { error: err3("ambiguous_target", "expand/affected accepts exactly one code target") };
2787
+ }
2788
+ if (anchorUids.length === 1) {
2789
+ const outcome = await codeRefFromAnchor(client, adapter, anchorUids[0]);
2790
+ if (!outcome) {
2791
+ return { error: err3("invalid_anchor", "anchor UID does not contain a typed code_ref") };
2792
+ }
2793
+ if ("missingRepo" in outcome) {
2831
2794
  return {
2832
- ref,
2833
- candidates: [],
2834
- error: failure.kind ?? "command_failed"
2795
+ error: err3(
2796
+ "repository_unavailable",
2797
+ `repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
2798
+ )
2835
2799
  };
2836
2800
  }
2801
+ return { ref: outcome.ref };
2837
2802
  }
2838
- async related(action, resolved, limit) {
2839
- const raw = await this.runJson([
2840
- action,
2841
- "-p",
2842
- resolved.repoRoot,
2843
- "-j",
2844
- "-l",
2845
- String(limit),
2846
- resolved.qualifiedName
2847
- ]);
2848
- const refs = (raw[action] ?? []).map((node) => ({
2849
- path: node.filePath,
2850
- line: node.startLine,
2851
- symbol: node.qualifiedName ?? node.name,
2852
- kind: node.kind
2853
- }));
2854
- return this.resolveRelatedRefs(refs, {
2855
- repoId: resolved.repoId,
2856
- repoRoot: resolved.repoRoot
2857
- });
2803
+ if (refs.length !== 1) {
2804
+ return { error: err3("missing_code_ref", "one code_ref or anchor_uid is required") };
2858
2805
  }
2859
- async resolveRelatedRefs(refs, repository) {
2860
- const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
2861
- const seen = /* @__PURE__ */ new Set();
2862
- return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
2863
- const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
2864
- if (seen.has(key)) return false;
2865
- seen.add(key);
2866
- return true;
2867
- });
2806
+ const resolution = await adapter.resolveRefs(refs, options);
2807
+ const result = resolution.results[0];
2808
+ if (!result.resolved) {
2809
+ return {
2810
+ error: ok2({
2811
+ availability: resolution.availability,
2812
+ result,
2813
+ wrote: false
2814
+ })
2815
+ };
2868
2816
  }
2869
- async runJson(args) {
2870
- const candidates = [
2871
- this.env.MINDGRAPH_CODEGRAPH_BIN,
2872
- "codegraph",
2873
- resolve(homedir(), ".local", "bin", "codegraph")
2874
- ].filter((candidate, index, all) => {
2875
- return Boolean(candidate) && all.indexOf(candidate) === index;
2817
+ return { ref: result.resolved };
2818
+ }
2819
+ async function joinMaterialized(client, refs, agentId, limit) {
2820
+ const joined = [];
2821
+ for (const ref of uniqueRefs(refs).slice(0, limit)) {
2822
+ const uid = await resolveMaterializedUid(client, ref, agentId);
2823
+ if (!uid) continue;
2824
+ joined.push({
2825
+ ref,
2826
+ anchor_uid: uid,
2827
+ graph: await client.traverse({
2828
+ action: "neighborhood",
2829
+ start_uid: uid,
2830
+ depth: 2,
2831
+ max_nodes: 20
2832
+ })
2876
2833
  });
2877
- let lastFailure;
2878
- for (const executable of candidates) {
2879
- try {
2880
- const stdout = await runProcess(
2881
- executable,
2882
- args,
2883
- this.timeoutMs,
2884
- this.env,
2885
- this.cwd
2886
- );
2887
- return JSON.parse(stdout);
2888
- } catch (cause) {
2889
- const failure = cause;
2890
- lastFailure = failure;
2891
- if (failure.kind !== "absent") throw failure;
2834
+ }
2835
+ return joined;
2836
+ }
2837
+ async function codeRefsFromGraphContext(client, adapter, context) {
2838
+ const uids = /* @__PURE__ */ new Set();
2839
+ collectUids(context, uids);
2840
+ if (uids.size === 0) return { refs: [], unavailable: [] };
2841
+ const nodes = client.getNodesBatch ? await client.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client.getNode(uid).catch(() => void 0)));
2842
+ const stored = nodes.flatMap((node) => {
2843
+ if (!node) return [];
2844
+ const attributes = node.props?.attributes;
2845
+ if (!attributes || typeof attributes !== "object") return [];
2846
+ const codeRef = attributes.code_ref;
2847
+ return isStoredCodeRef(codeRef) ? [codeRef] : [];
2848
+ });
2849
+ const refs = [];
2850
+ const unavailable = /* @__PURE__ */ new Set();
2851
+ for (const ref of stored) {
2852
+ try {
2853
+ const repository = await adapter.resolveRepository(ref.repoId);
2854
+ refs.push({ ...ref, repoRoot: repository.repoRoot });
2855
+ } catch (cause) {
2856
+ if (cause instanceof UnknownRepositoryError) {
2857
+ unavailable.add(ref.repoId);
2858
+ } else {
2859
+ throw cause;
2892
2860
  }
2893
2861
  }
2894
- throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
2895
- kind: "absent"
2896
- });
2897
2862
  }
2898
- };
2899
- function repositoryIdentityKey(repository) {
2900
- return { v: 1, kind: "repository", repo_id: repository.repoId };
2863
+ return { refs, unavailable: [...unavailable].sort() };
2901
2864
  }
2902
- function codeRefIdentityKey(ref) {
2903
- if (ref.kind === "file") {
2904
- return {
2905
- v: 1,
2906
- kind: "file",
2907
- repo_id: ref.repoId,
2908
- path: ref.path
2909
- };
2865
+ function collectUids(value, uids) {
2866
+ if (Array.isArray(value)) {
2867
+ value.forEach((item) => collectUids(item, uids));
2868
+ return;
2910
2869
  }
2911
- return {
2912
- v: 1,
2913
- kind: "symbol",
2914
- repo_id: ref.repoId,
2915
- language: ref.language,
2916
- symbol_kind: ref.kind,
2917
- qualified_name: ref.qualifiedName,
2918
- disambiguator: {
2919
- signature: ref.signature ?? "",
2920
- path: ref.path
2870
+ if (!value || typeof value !== "object") return;
2871
+ for (const [key, child] of Object.entries(value)) {
2872
+ if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
2873
+ uids.add(child);
2874
+ } else {
2875
+ collectUids(child, uids);
2921
2876
  }
2922
- };
2877
+ }
2878
+ }
2879
+ function isStoredCodeRef(value) {
2880
+ if (!value || typeof value !== "object") return false;
2881
+ const ref = value;
2882
+ return typeof ref.repoId === "string" && typeof ref.path === "string" && typeof ref.language === "string" && typeof ref.kind === "string" && typeof ref.qualifiedName === "string" && (typeof ref.signature === "string" || ref.signature === null) && typeof ref.startLine === "number" && typeof ref.endLine === "number";
2883
+ }
2884
+ function storedCodeRef(ref) {
2885
+ const { repoRoot: _localOnly, ...stored } = ref;
2886
+ return stored;
2887
+ }
2888
+ function uniqueRefs(refs) {
2889
+ const seen = /* @__PURE__ */ new Set();
2890
+ return refs.filter((ref) => {
2891
+ const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
2892
+ if (seen.has(key)) return false;
2893
+ seen.add(key);
2894
+ return true;
2895
+ });
2896
+ }
2897
+ function primaryUids(value) {
2898
+ if (!value || typeof value !== "object") return [];
2899
+ const object = value;
2900
+ const keys = [
2901
+ "uid",
2902
+ "claim_uid",
2903
+ "goal_uid",
2904
+ "project_uid",
2905
+ "milestone_uid",
2906
+ "decision_uid",
2907
+ "task_uid",
2908
+ "plan_uid",
2909
+ "step_uid",
2910
+ "execution_uid",
2911
+ "lesson_uid"
2912
+ ];
2913
+ return [
2914
+ ...new Set(
2915
+ keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
2916
+ )
2917
+ ];
2923
2918
  }
2924
2919
 
2925
- // src/code-tool.ts
2926
- var ok2 = (data) => ({
2927
- content: [{ type: "text", text: JSON.stringify(data) }]
2928
- });
2929
- var err3 = (code, message, details) => ({
2930
- content: [
2931
- {
2932
- type: "text",
2933
- text: JSON.stringify({ error: message, code, details })
2934
- }
2935
- ],
2936
- isError: true
2937
- });
2938
- var CODE_TOOL = {
2939
- name: "mindgraph_code",
2940
- description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
2920
+ // src/workspace-scope.ts
2921
+ var WORKSPACE_RESUME_LIMIT = 9;
2922
+ var SCOPE_CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
2923
+
2924
+ // src/memory-tool.ts
2925
+ var RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
2926
+ var DEFAULT_OUTPUT_CHARS = 8e3;
2927
+ var MIN_OUTPUT_CHARS = 512;
2928
+ var MAX_OUTPUT_CHARS = 24e3;
2929
+ var HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
2930
+ var HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
2931
+ var MEMORY_TOOL = {
2932
+ name: "mindgraph_memory",
2933
+ description: "Read the portable MindGraph memory surface. Use action='context' with mode='resume' or 'continuity' for authoritative task-first work state, and mode='topic' with a short keyword query for prior knowledge relevant to the user's request. Use action='status' to inspect integration capabilities and limitations. This tool never writes; durable capture and correction use the existing typed MindGraph write tools.",
2941
2934
  inputSchema: {
2942
2935
  type: "object",
2943
2936
  properties: {
2944
2937
  action: {
2945
2938
  type: "string",
2946
- enum: ["anchor", "recall", "expand", "affected"]
2939
+ enum: ["status", "context"],
2940
+ description: "Read integration status or bounded memory context"
2947
2941
  },
2948
- code_refs: {
2942
+ mode: {
2943
+ type: "string",
2944
+ enum: ["topic", "continuity", "resume"],
2945
+ description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
2946
+ },
2947
+ query: {
2948
+ type: "string",
2949
+ description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
2950
+ },
2951
+ project_uid: { type: "string", description: "Optional project scope" },
2952
+ task_uid: { type: "string", description: "Optional task scope for resume context" },
2953
+ session_uid: { type: "string", description: "Optional durable session scope" },
2954
+ scope_uids: {
2949
2955
  type: "array",
2950
- maxItems: 50,
2951
- items: {
2952
- oneOf: [
2953
- { type: "string" },
2954
- {
2955
- type: "object",
2956
- properties: {
2957
- repo: { type: "string" },
2958
- path: { type: "string" },
2959
- line: { type: "integer", minimum: 1 },
2960
- symbol: { type: "string" },
2961
- kind: { type: "string" },
2962
- language: { type: "string" },
2963
- signature: { type: "string" }
2964
- },
2965
- additionalProperties: false
2966
- }
2967
- ]
2968
- }
2956
+ items: { type: "string" },
2957
+ description: "Optional explicit graph scopes for continuity/resume"
2969
2958
  },
2970
- anchor_uids: {
2959
+ scope: {
2960
+ type: "string",
2961
+ enum: ["workspace"],
2962
+ description: "For continuity/resume on a local server: select work across every repository the working directory's .mindgraph/workspace.json declares, and list the other eligible tasks grouped by repository"
2963
+ },
2964
+ node_types: {
2971
2965
  type: "array",
2972
- maxItems: 50,
2973
2966
  items: { type: "string" },
2974
- description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
2967
+ description: "Optional node-type filter for topic context"
2975
2968
  },
2976
- repo: {
2969
+ layer: {
2977
2970
  type: "string",
2978
- description: "Configured repository id or repository root"
2971
+ enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
2972
+ description: "Optional cognitive-layer filter for topic context"
2979
2973
  },
2980
- repo_space_uid: {
2974
+ limit: {
2975
+ type: "integer",
2976
+ minimum: 1,
2977
+ maximum: 50,
2978
+ default: 8,
2979
+ description: "Maximum direct graph items requested before bounded expansion"
2980
+ },
2981
+ include_chunks: {
2982
+ type: "boolean",
2983
+ default: false,
2984
+ description: "Include bounded source excerpts when topic summaries are insufficient"
2985
+ },
2986
+ include_documents: {
2987
+ type: "boolean",
2988
+ default: false,
2989
+ description: "Include bounded knowledge articles for topic context"
2990
+ },
2991
+ valid_at: {
2981
2992
  type: "string",
2982
- description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
2993
+ description: "Optional ISO timestamp for time-aware topic retrieval"
2983
2994
  },
2984
- task_uid: { type: "string" },
2985
- execution_uid: { type: "string" },
2986
- limit: { type: "integer", minimum: 1, maximum: 50 },
2987
- agent_id: { type: "string" }
2995
+ graph_expansion_limit: {
2996
+ type: "integer",
2997
+ minimum: 0,
2998
+ maximum: 20,
2999
+ default: 3,
3000
+ description: "Bounded graph neighbors added after direct topic matches"
3001
+ },
3002
+ graph_max_depth: {
3003
+ type: "integer",
3004
+ minimum: 0,
3005
+ maximum: 5,
3006
+ default: 2,
3007
+ description: "Maximum graph-expansion depth for topic context"
3008
+ },
3009
+ max_output_chars: {
3010
+ type: "integer",
3011
+ minimum: MIN_OUTPUT_CHARS,
3012
+ maximum: MAX_OUTPUT_CHARS,
3013
+ default: DEFAULT_OUTPUT_CHARS,
3014
+ description: "Hard character budget for model-ready context; whole items are retained or omitted"
3015
+ },
3016
+ agent_id: { type: "string", description: "Agent identity" }
2988
3017
  },
2989
3018
  required: ["action"]
2990
3019
  },
2991
- annotations: {
2992
- readOnlyHint: false,
2993
- destructiveHint: false,
2994
- idempotentHint: true,
2995
- openWorldHint: true
2996
- }
2997
- };
2998
- async function handleCodeTool(client, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
2999
- const codeClient = client;
3000
- const action = args.action;
3001
- const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
3002
- const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
3003
- const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
3004
- const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
3005
- const options = {
3006
- repo: typeof args.repo === "string" ? args.repo : void 0,
3007
- fallbackSpaceUid
3008
- };
3009
- const limit = typeof args.limit === "number" ? args.limit : void 0;
3010
- try {
3011
- switch (action) {
3012
- case "anchor": {
3013
- if (refs.length === 0) return err3("missing_code_refs", "code_refs is required for anchor");
3014
- const resolution = await adapter.resolveRefs(refs, options);
3015
- const resolved = resolution.results.flatMap(
3016
- (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
3017
- );
3018
- const unresolved = resolution.results.filter((result) => !result.resolved);
3019
- if (resolved.length === 0) {
3020
- return ok2({ ...resolution, anchors: [], wrote: false });
3021
- }
3022
- const missingSpace = resolved.find(
3023
- ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
3024
- );
3025
- if (missingSpace) {
3026
- return err3(
3027
- "missing_repository_space",
3028
- `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
3029
- );
3030
- }
3031
- const repositoryAnchors = /* @__PURE__ */ new Map();
3032
- for (const { repository } of resolved) {
3033
- if (repositoryAnchors.has(repository.repoRoot)) continue;
3034
- const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
3035
- const anchored = await anchorRepository(
3036
- codeClient,
3037
- repository,
3038
- spaceUid,
3039
- agentId
3040
- );
3041
- repositoryAnchors.set(repository.repoRoot, {
3042
- ...anchored,
3043
- repository
3044
- });
3045
- }
3046
- const anchors = [];
3047
- for (const { ref, repository } of resolved) {
3048
- const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
3049
- if (!repositoryAnchor.uid) continue;
3050
- const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
3051
- const anchor = await anchorCodeRef(
3052
- codeClient,
3053
- ref,
3054
- repositoryAnchor.uid,
3055
- spaceUid,
3056
- agentId
3057
- );
3058
- anchors.push(anchor);
3059
- }
3060
- return ok2({
3061
- availability: resolution.availability,
3062
- repository: repositoryAnchors.values().next().value,
3063
- repositories: [...repositoryAnchors.values()],
3064
- anchors,
3065
- unresolved,
3066
- wrote: anchors.some((anchor) => anchor.created)
3067
- });
3068
- }
3069
- case "recall": {
3070
- const materialized = new Set(anchorUids);
3071
- let resolution;
3072
- if (refs.length > 0) {
3073
- resolution = await adapter.resolveRefs(refs, options);
3074
- for (const result of resolution.results) {
3075
- if (!result.resolved) continue;
3076
- const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
3077
- if (uid) materialized.add(uid);
3078
- }
3079
- }
3080
- if (materialized.size === 0) {
3081
- return ok2({
3082
- availability: resolution?.availability,
3083
- anchors: [],
3084
- context: [],
3085
- unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
3086
- });
3087
- }
3088
- const context = await Promise.all(
3089
- [...materialized].map(async (uid) => ({
3090
- anchor_uid: uid,
3091
- graph: await codeClient.traverse({
3092
- action: "neighborhood",
3093
- start_uid: uid,
3094
- depth: 2,
3095
- max_nodes: Math.min(limit ?? 20, 20)
3096
- })
3097
- }))
3098
- );
3099
- return ok2({
3100
- availability: resolution?.availability,
3101
- anchors: [...materialized],
3102
- context,
3103
- unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
3104
- });
3105
- }
3106
- case "expand": {
3107
- const target = await resolveSingleTarget(
3108
- codeClient,
3109
- adapter,
3110
- refs,
3111
- anchorUids,
3112
- options
3113
- );
3114
- if ("error" in target) return target.error;
3115
- const expanded = await adapter.expand(target.ref, limit);
3116
- const joined = await joinMaterialized(
3117
- codeClient,
3118
- [...expanded.callers, ...expanded.callees],
3119
- agentId,
3120
- Math.min(limit ?? 20, 20)
3121
- );
3122
- return ok2({ anchor: target.ref, ...expanded, materialized: joined });
3123
- }
3124
- case "affected": {
3125
- const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
3126
- let graphContext;
3127
- const graphRefs = [];
3128
- let unavailableRepositories = [];
3129
- if (targetUid) {
3130
- graphContext = await codeClient.traverse({
3131
- action: "neighborhood",
3132
- start_uid: targetUid,
3133
- depth: 2,
3134
- max_nodes: 50
3135
- });
3136
- const fromGraph = await codeRefsFromGraphContext(
3137
- codeClient,
3138
- adapter,
3139
- graphContext
3140
- );
3141
- graphRefs.push(...fromGraph.refs);
3142
- unavailableRepositories = fromGraph.unavailable;
3143
- }
3144
- if (refs.length > 0 || anchorUids.length > 0) {
3145
- const target = await resolveSingleTarget(
3146
- codeClient,
3147
- adapter,
3148
- refs,
3149
- anchorUids,
3150
- options
3151
- );
3152
- if ("error" in target) return target.error;
3153
- graphRefs.unshift(target.ref);
3154
- }
3155
- const unique = uniqueRefs(graphRefs);
3156
- if (unique.length === 0) {
3157
- return ok2({
3158
- target_uid: targetUid,
3159
- graph_context: graphContext,
3160
- affected: [],
3161
- degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
3162
- ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
3163
- });
3164
- }
3165
- const impacts = await Promise.all(
3166
- unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
3167
- );
3168
- const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
3169
- const joined = await joinMaterialized(
3170
- codeClient,
3171
- structural,
3172
- agentId,
3173
- Math.min(limit ?? 50, 50)
3174
- );
3175
- return ok2({
3176
- target_uid: targetUid,
3177
- graph_context: graphContext,
3178
- impacts,
3179
- materialized: joined,
3180
- ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
3181
- });
3020
+ outputSchema: {
3021
+ type: "object",
3022
+ properties: {
3023
+ schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
3024
+ action: { type: "string", enum: ["status", "context"] },
3025
+ profile: { type: "string" },
3026
+ server: { type: "object" },
3027
+ integration: { type: "object" },
3028
+ capabilities: { type: "object" },
3029
+ degradation: { type: "array", items: { type: "object" } },
3030
+ context_id: { type: "string" },
3031
+ mode: { type: "string", enum: ["topic", "continuity", "resume"] },
3032
+ scope: { type: "object" },
3033
+ rendered_context: { type: "string" },
3034
+ rendered_hash: { type: "string" },
3035
+ items: { type: "array", items: { type: "object" } },
3036
+ retrieval_trace: { type: "object" },
3037
+ budget: { type: "object" },
3038
+ freshness: { type: "object" },
3039
+ warnings: { type: "array", items: { type: "object" } }
3040
+ },
3041
+ required: ["schema_version", "action"],
3042
+ oneOf: [
3043
+ {
3044
+ properties: { action: { const: "status" } },
3045
+ required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
3046
+ },
3047
+ {
3048
+ properties: { action: { const: "context" } },
3049
+ required: [
3050
+ "context_id",
3051
+ "mode",
3052
+ "scope",
3053
+ "rendered_context",
3054
+ "rendered_hash",
3055
+ "items",
3056
+ "retrieval_trace",
3057
+ "budget",
3058
+ "freshness",
3059
+ "warnings"
3060
+ ]
3182
3061
  }
3183
- default:
3184
- return err3("unknown_action", `unknown mindgraph_code action: ${action}`);
3185
- }
3186
- } catch (cause) {
3187
- if (cause instanceof UnknownRepositoryError) {
3188
- return err3("unknown_repository", cause.message);
3189
- }
3190
- return toolError(cause, "code_tool_failed");
3062
+ ]
3063
+ },
3064
+ annotations: {
3065
+ readOnlyHint: true,
3066
+ destructiveHint: false,
3067
+ idempotentHint: true,
3068
+ openWorldHint: false
3191
3069
  }
3070
+ };
3071
+ function obj2(value) {
3072
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3192
3073
  }
3193
- async function attachCodeRefsToToolResult(client, result, args, adapter) {
3194
- if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
3195
- return result;
3196
- }
3197
- const original = JSON.parse(result.content[0]?.text ?? "null");
3198
- const targetUids = primaryUids(original);
3199
- if (targetUids.length === 0) {
3200
- return ok2({
3201
- result: original,
3202
- code_anchors: {
3203
- wrote: false,
3204
- caveat: "the write response exposed no primary UID to attach"
3205
- }
3206
- });
3074
+ function text(value) {
3075
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3076
+ }
3077
+ function stableJson(value) {
3078
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
3079
+ if (value && typeof value === "object") {
3080
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
3207
3081
  }
3208
- const anchorResult = await handleCodeTool(
3209
- client,
3210
- {
3211
- action: "anchor",
3212
- code_refs: args.code_refs,
3213
- repo: args.repo,
3214
- repo_space_uid: args.repo_space_uid,
3215
- agent_id: args.agent_id,
3216
- // Keeps the default adapter anchored to the live session directory
3217
- // rather than wherever the MCP server process was launched.
3218
- invocation_context: args.invocation_context
3082
+ return JSON.stringify(value);
3083
+ }
3084
+ function sha256(value) {
3085
+ return createHash("sha256").update(value).digest("hex");
3086
+ }
3087
+ function outputBudget(value) {
3088
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
3089
+ return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
3090
+ }
3091
+ function defined(values) {
3092
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
3093
+ }
3094
+ function memoryStatus(config = {}) {
3095
+ return {
3096
+ schema_version: RESULT_SCHEMA_VERSION,
3097
+ action: "status",
3098
+ profile: "M0",
3099
+ server: defined({
3100
+ name: "mindgraph-mcp",
3101
+ version: config.serverVersion ?? "unknown",
3102
+ base_url: config.baseUrl
3103
+ }),
3104
+ integration: {
3105
+ adapter_state: "model_controlled",
3106
+ harness: config.harness ?? "generic",
3107
+ configured_tool_profile: config.profile ?? "general",
3108
+ automatic_capture: false,
3109
+ automatic_delivery: false,
3110
+ delivery_verification: "unsupported",
3111
+ curator_mode: "disabled"
3219
3112
  },
3220
- adapter
3221
- );
3222
- const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
3223
- if (!anchorResult.isError) {
3224
- const codeClient = client;
3225
- const anchorUids = (anchors.anchors ?? []).flatMap(
3226
- (anchor) => anchor.uid ? [anchor.uid] : []
3227
- );
3228
- const relateFailures = [];
3229
- for (const targetUid of targetUids) {
3230
- for (const anchorUid of anchorUids) {
3231
- try {
3232
- await codeClient.entity({
3233
- action: "relate",
3234
- source_uid: targetUid,
3235
- target_uid: anchorUid,
3236
- edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
3237
- agent_id: args.agent_id
3238
- });
3239
- } catch (cause) {
3240
- relateFailures.push(
3241
- `${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
3242
- );
3243
- }
3113
+ scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
3114
+ capabilities: {
3115
+ status: true,
3116
+ topic_context: true,
3117
+ authoritative_resume_context: true,
3118
+ bounded_rendering: true,
3119
+ structured_content: true,
3120
+ typed_explicit_writes: true,
3121
+ lifecycle_event_ingest: false,
3122
+ automatic_context_injection: false,
3123
+ curator_mutation: false
3124
+ },
3125
+ degradation: [
3126
+ {
3127
+ code: "automatic_capture_unavailable",
3128
+ message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
3129
+ },
3130
+ {
3131
+ code: "delivery_unverified",
3132
+ message: "A successful MCP read does not prove that the host injected or used the returned context."
3133
+ },
3134
+ {
3135
+ code: "curator_disabled",
3136
+ message: "Curator proposals and autonomous mutation are not enabled in M0."
3244
3137
  }
3138
+ ]
3139
+ };
3140
+ }
3141
+ function nodeType(node) {
3142
+ const raw = node.node_type;
3143
+ if (typeof raw === "string") return raw;
3144
+ return text(obj2(raw)?.Custom) ?? "Node";
3145
+ }
3146
+ function compactItem(raw, fallbackType) {
3147
+ const wrapper = obj2(raw);
3148
+ if (!wrapper) return void 0;
3149
+ const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
3150
+ const uid = text(value.uid) ?? text(value.chunk_uid);
3151
+ if (!uid) return void 0;
3152
+ return defined({
3153
+ uid,
3154
+ type: fallbackType ?? nodeType(value),
3155
+ label: text(value.label) ?? text(value.document_title),
3156
+ truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
3157
+ // `true` from /retrieve, an object naming the replacement from /retrieve/context.
3158
+ superseded: value.superseded ? true : void 0,
3159
+ currently_valid: value.currently_valid === false ? false : void 0,
3160
+ valid_at_time: value.valid_at_time === false ? false : void 0,
3161
+ retrieval_origin: text(value.retrieval_origin),
3162
+ retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
3163
+ });
3164
+ }
3165
+ function topicCandidates(raw) {
3166
+ const payload = obj2(raw);
3167
+ if (!payload) return [];
3168
+ const graph = obj2(payload.graph);
3169
+ const items = [];
3170
+ for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
3171
+ const item = compactItem(article, "Article");
3172
+ if (item) items.push(item);
3173
+ }
3174
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
3175
+ const labels = /* @__PURE__ */ new Map();
3176
+ for (const node of rawNodes) {
3177
+ const item = compactItem(node);
3178
+ if (item) {
3179
+ if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
3180
+ items.push(item);
3181
+ const uid = text(item.uid);
3182
+ const label = text(item.label);
3183
+ if (uid && label) labels.set(uid, label);
3245
3184
  }
3246
- if (relateFailures.length > 0) {
3247
- return ok2({
3248
- result: original,
3249
- code_anchors: {
3250
- ...anchors,
3251
- caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
3252
- }
3253
- });
3185
+ }
3186
+ for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
3187
+ const edge = obj2(rawEdge);
3188
+ const fromUid = text(edge?.from_uid);
3189
+ const toUid = text(edge?.to_uid);
3190
+ const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
3191
+ if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
3192
+ continue;
3193
+ }
3194
+ items.push(
3195
+ defined({
3196
+ type: "Relationship",
3197
+ from_uid: fromUid,
3198
+ to_uid: toUid,
3199
+ edge_type: edgeType,
3200
+ from_label: labels.get(fromUid),
3201
+ to_label: labels.get(toUid)
3202
+ })
3203
+ );
3204
+ }
3205
+ for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
3206
+ const item = compactItem(chunk, "SourceExcerpt");
3207
+ if (item) items.push(item);
3208
+ }
3209
+ return items;
3210
+ }
3211
+ function resumeCandidates(raw) {
3212
+ const payload = obj2(raw);
3213
+ if (!payload) return [];
3214
+ const items = [];
3215
+ for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
3216
+ const item = compactItem(payload[key], key);
3217
+ if (item) items.push(item);
3218
+ }
3219
+ for (const value of Array.isArray(payload.other_eligible) ? payload.other_eligible : []) {
3220
+ const item = compactItem(value, "Task");
3221
+ if (item) items.push(item);
3222
+ }
3223
+ for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
3224
+ for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
3225
+ const item = compactItem(value, key.replace(/s$/, ""));
3226
+ if (item) items.push(item);
3254
3227
  }
3255
3228
  }
3256
- return ok2({ result: original, code_anchors: anchors });
3229
+ const knowledge = obj2(payload.knowledge);
3230
+ for (const key of ["lessons", "risks"]) {
3231
+ for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
3232
+ const item = compactItem(value, key.replace(/s$/, ""));
3233
+ if (item) items.push(item);
3234
+ }
3235
+ }
3236
+ return items;
3257
3237
  }
3258
- async function anchorRepository(client, repository, spaceUid, agentId) {
3259
- return client.entity({
3260
- action: "create",
3261
- label: repository.repoId,
3262
- props: {
3263
- entity_type: "repository",
3264
- canonical_name: repository.repoId,
3265
- identifiers: { repo_id: repository.repoId }
3266
- },
3267
- identity: {
3268
- namespace: "external.code",
3269
- key_version: 1,
3270
- key: repositoryIdentityKey(repository)
3271
- },
3272
- identity_space_uid: spaceUid,
3273
- agent_id: agentId
3238
+ function visibleItems(candidates, rendered2) {
3239
+ const seen = /* @__PURE__ */ new Set();
3240
+ return candidates.filter((item) => {
3241
+ const uid = text(item.uid);
3242
+ if (uid) {
3243
+ if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
3244
+ seen.add(uid);
3245
+ return true;
3246
+ }
3247
+ if (item.type === "Relationship") {
3248
+ const from = text(item.from_label);
3249
+ const to = text(item.to_label);
3250
+ const edgeType = text(item.edge_type);
3251
+ if (!from || !to || !edgeType) return false;
3252
+ const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
3253
+ if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
3254
+ seen.add(key);
3255
+ return true;
3256
+ }
3257
+ return false;
3274
3258
  });
3275
3259
  }
3276
- async function anchorCodeRef(client, ref, repositoryUid, spaceUid, agentId) {
3277
- const response = await client.entity({
3278
- action: "create",
3279
- label: ref.qualifiedName,
3280
- props: {
3281
- entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
3282
- canonical_name: ref.qualifiedName,
3283
- identifiers: {
3284
- repo_id: ref.repoId,
3285
- path: ref.path,
3286
- qualified_name: ref.qualifiedName
3287
- },
3288
- attributes: { code_ref: storedCodeRef(ref) }
3289
- },
3290
- identity: {
3291
- namespace: "external.code",
3292
- key_version: 1,
3293
- key: codeRefIdentityKey(ref)
3294
- },
3295
- identity_space_uid: spaceUid,
3296
- agent_id: agentId
3297
- });
3298
- if (response.uid && response.created !== false && response.status !== "existing") {
3299
- await client.entity({
3300
- action: "relate",
3301
- source_uid: response.uid,
3302
- target_uid: repositoryUid,
3303
- edge_type: "PART_OF",
3304
- agent_id: agentId
3260
+ function latestTimestamp(value) {
3261
+ let latest;
3262
+ const visit = (current) => {
3263
+ if (Array.isArray(current)) {
3264
+ for (const item of current) visit(item);
3265
+ return;
3266
+ }
3267
+ const record = obj2(current);
3268
+ if (!record) return;
3269
+ for (const [key, item] of Object.entries(record)) {
3270
+ if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
3271
+ latest = item;
3272
+ }
3273
+ visit(item);
3274
+ }
3275
+ };
3276
+ visit(value);
3277
+ return latest;
3278
+ }
3279
+ function contextWarnings(raw, rendered2, available, returned) {
3280
+ const warnings = [
3281
+ {
3282
+ code: "delivery_unverified",
3283
+ message: "The MCP server returned this context but cannot verify that the host injected or used it."
3284
+ }
3285
+ ];
3286
+ const serialized = JSON.stringify(raw);
3287
+ if (available > returned || rendered2.includes("item(s) omitted")) {
3288
+ warnings.push({
3289
+ code: "context_bounded",
3290
+ message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
3305
3291
  });
3306
3292
  }
3307
- return { ...response, ref };
3293
+ if (/"superseded":(?:true|\{)/.test(serialized)) {
3294
+ warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
3295
+ }
3296
+ if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
3297
+ warnings.push({
3298
+ code: "validity_warning",
3299
+ message: "Some retrieved items are outside their current or requested validity window."
3300
+ });
3301
+ }
3302
+ const payload = obj2(raw);
3303
+ if (payload?.truncated === true || obj2(payload?.truncation)) {
3304
+ const truncation = obj2(payload?.truncation);
3305
+ if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
3306
+ warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
3307
+ }
3308
+ }
3309
+ if (text(payload?.warning)) {
3310
+ warnings.push({ code: "source_warning", message: text(payload?.warning) });
3311
+ }
3312
+ return warnings;
3308
3313
  }
3309
- async function resolveMaterializedUid(client, ref, agentId) {
3310
- const response = await client.entity({
3311
- action: "resolve_identity",
3312
- identity: {
3313
- namespace: "external.code",
3314
- key_version: 1,
3315
- key: codeRefIdentityKey(ref)
3316
- },
3317
- agent_id: agentId
3318
- });
3319
- return response.status === "existing" ? response.uid : void 0;
3314
+ function error(message) {
3315
+ return {
3316
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
3317
+ isError: true
3318
+ };
3320
3319
  }
3321
- async function codeRefFromAnchor(client, adapter, uid) {
3322
- const node = await client.getNode(uid);
3323
- const attributes = node.props?.attributes;
3324
- if (!attributes || typeof attributes !== "object") return void 0;
3325
- const codeRef = attributes.code_ref;
3326
- if (!isStoredCodeRef(codeRef)) return void 0;
3327
- try {
3328
- const repository = await adapter.resolveRepository(codeRef.repoId);
3329
- return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
3330
- } catch (cause) {
3331
- if (cause instanceof UnknownRepositoryError) {
3332
- return { missingRepo: codeRef.repoId };
3320
+ async function handleMemoryTool(client, args, config = {}) {
3321
+ const action = text(args.action);
3322
+ if (action === "status") {
3323
+ const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
3324
+ return {
3325
+ content: [{ type: "text", text: JSON.stringify(status) }],
3326
+ structuredContent: status
3327
+ };
3328
+ }
3329
+ if (action !== "context") {
3330
+ return error("action must be 'status' or 'context'");
3331
+ }
3332
+ const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
3333
+ if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
3334
+ return error("action='context' requires mode='topic', 'continuity', or 'resume'");
3335
+ }
3336
+ const budget = outputBudget(args.max_output_chars);
3337
+ const agentId = text(args.agent_id) ?? config.agentId;
3338
+ let raw;
3339
+ let rendered2;
3340
+ let strategy;
3341
+ let candidates;
3342
+ let workspace;
3343
+ if (mode === "topic") {
3344
+ const query = text(args.query);
3345
+ if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
3346
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
3347
+ const request = defined({
3348
+ query,
3349
+ project_uid: text(args.project_uid),
3350
+ node_limit: limit,
3351
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
3352
+ layer: text(args.layer),
3353
+ chunk_limit: args.include_chunks === true ? limit : 0,
3354
+ article_limit: args.include_documents === true ? limit : 0,
3355
+ valid_at: text(args.valid_at),
3356
+ include_graph: true,
3357
+ graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
3358
+ graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
3359
+ });
3360
+ raw = await client.retrieveContext(request);
3361
+ rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
3362
+ strategy = "retrieve_context";
3363
+ candidates = topicCandidates(raw);
3364
+ } else {
3365
+ if (args.scope !== void 0 && args.scope !== "workspace") {
3366
+ return error("scope must be 'workspace' when given");
3333
3367
  }
3334
- throw cause;
3368
+ if (args.scope === "workspace") {
3369
+ if (!config.resolveWorkspaceScope) {
3370
+ return error(
3371
+ "scope='workspace' needs the local MindGraph MCP server, which reads .mindgraph/workspace.json beside the checkout; this server has no working directory. Pass scope_uids instead."
3372
+ );
3373
+ }
3374
+ workspace = await config.resolveWorkspaceScope(agentId ?? "mcp");
3375
+ if (workspace.uids.length === 0) {
3376
+ return error(
3377
+ `scope='workspace' resolved no repositories${workspace.failures.length > 0 ? ` (${workspace.failures.join("; ")})` : ""}`
3378
+ );
3379
+ }
3380
+ }
3381
+ const scopeUids = Array.from(
3382
+ /* @__PURE__ */ new Set([
3383
+ ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
3384
+ ...text(args.project_uid) ? [text(args.project_uid)] : [],
3385
+ ...workspace?.uids ?? []
3386
+ ])
3387
+ );
3388
+ raw = await client.plan(
3389
+ defined({
3390
+ action: "resume_work",
3391
+ task_uid: text(args.task_uid),
3392
+ session_uid: text(args.session_uid),
3393
+ scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
3394
+ limit: workspace ? WORKSPACE_RESUME_LIMIT : 1,
3395
+ agent_id: agentId
3396
+ })
3397
+ );
3398
+ rendered2 = renderResumeContext(raw, budget, workspace?.names) ?? "No eligible work was found in the requested scope.";
3399
+ strategy = "resume_work";
3400
+ candidates = resumeCandidates(raw);
3335
3401
  }
3402
+ const items = visibleItems(candidates, rendered2);
3403
+ const renderedHash = sha256(rendered2);
3404
+ const scope = defined({
3405
+ org_id: config.orgId,
3406
+ agent_id: agentId,
3407
+ harness: config.harness ?? "generic",
3408
+ project_uid: text(args.project_uid),
3409
+ task_uid: text(args.task_uid),
3410
+ session_uid: text(args.session_uid),
3411
+ scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0,
3412
+ workspace_repositories: workspace ? Object.values(workspace.names).sort() : void 0
3413
+ });
3414
+ const contextId = `ctx_${sha256(
3415
+ stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
3416
+ ).slice(0, 24)}`;
3417
+ const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
3418
+ const result = {
3419
+ schema_version: RESULT_SCHEMA_VERSION,
3420
+ action: "context",
3421
+ context_id: contextId,
3422
+ mode,
3423
+ scope,
3424
+ rendered_context: rendered2,
3425
+ rendered_hash: renderedHash,
3426
+ items,
3427
+ retrieval_trace: defined({
3428
+ strategy,
3429
+ query: text(args.query),
3430
+ project_uid: text(args.project_uid),
3431
+ task_uid: text(args.task_uid),
3432
+ session_uid: text(args.session_uid),
3433
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
3434
+ graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
3435
+ graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
3436
+ available_items: candidates.length,
3437
+ returned_items: items.length,
3438
+ omitted_items: Math.max(0, candidates.length - items.length)
3439
+ }),
3440
+ budget: {
3441
+ unit: "characters",
3442
+ requested_chars: budget,
3443
+ used_chars: rendered2.length,
3444
+ available_items: candidates.length,
3445
+ returned_items: items.length,
3446
+ omitted_items: Math.max(0, candidates.length - items.length),
3447
+ bounded
3448
+ },
3449
+ freshness: defined({
3450
+ valid_at: text(args.valid_at),
3451
+ latest_source_timestamp: latestTimestamp(raw)
3452
+ }),
3453
+ warnings: [
3454
+ ...contextWarnings(raw, rendered2, candidates.length, items.length),
3455
+ ...workspace && workspace.failures.length > 0 ? [
3456
+ {
3457
+ code: "workspace_scope_partial",
3458
+ message: `${workspace.failures.length} declared repositories did not resolve and were left out: ${workspace.failures.join("; ")}`
3459
+ }
3460
+ ] : []
3461
+ ]
3462
+ };
3463
+ return {
3464
+ content: [{ type: "text", text: rendered2 }],
3465
+ structuredContent: result
3466
+ };
3336
3467
  }
3337
- async function resolveSingleTarget(client, adapter, refs, anchorUids, options) {
3338
- if (refs.length > 1 || anchorUids.length > 1) {
3339
- return { error: err3("ambiguous_target", "expand/affected accepts exactly one code target") };
3340
- }
3341
- if (anchorUids.length === 1) {
3342
- const outcome = await codeRefFromAnchor(client, adapter, anchorUids[0]);
3343
- if (!outcome) {
3344
- return { error: err3("invalid_anchor", "anchor UID does not contain a typed code_ref") };
3345
- }
3346
- if ("missingRepo" in outcome) {
3347
- return {
3348
- error: err3(
3349
- "repository_unavailable",
3350
- `repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
3351
- )
3352
- };
3353
- }
3354
- return { ref: outcome.ref };
3355
- }
3356
- if (refs.length !== 1) {
3357
- return { error: err3("missing_code_ref", "one code_ref or anchor_uid is required") };
3468
+
3469
+ // src/resources.ts
3470
+ var RESOURCES = [
3471
+ {
3472
+ uri: "mindgraph://memory/status",
3473
+ name: "Memory Integration Status",
3474
+ description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
3475
+ mimeType: "application/json"
3476
+ },
3477
+ {
3478
+ uri: "mindgraph://stats",
3479
+ name: "Graph Statistics",
3480
+ description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
3481
+ mimeType: "application/json"
3482
+ },
3483
+ {
3484
+ uri: "mindgraph://goals",
3485
+ name: "Active Goals",
3486
+ description: "All active goals in the knowledge graph",
3487
+ mimeType: "application/json"
3488
+ },
3489
+ {
3490
+ uri: "mindgraph://questions",
3491
+ name: "Open Questions",
3492
+ description: "All open questions in the knowledge graph",
3493
+ mimeType: "application/json"
3494
+ },
3495
+ {
3496
+ uri: "mindgraph://contradictions",
3497
+ name: "Contradictions",
3498
+ description: "Contradictory claims in the knowledge graph",
3499
+ mimeType: "application/json"
3500
+ },
3501
+ {
3502
+ uri: "mindgraph://decisions",
3503
+ name: "Open Decisions",
3504
+ description: "Unresolved decisions awaiting resolution",
3505
+ mimeType: "application/json"
3358
3506
  }
3359
- const resolution = await adapter.resolveRefs(refs, options);
3360
- const result = resolution.results[0];
3361
- if (!result.resolved) {
3362
- return {
3363
- error: ok2({
3364
- availability: resolution.availability,
3365
- result,
3366
- wrote: false
3367
- })
3368
- };
3507
+ ];
3508
+ var RESOURCE_TEMPLATES = [
3509
+ {
3510
+ uriTemplate: "mindgraph://node/{uid}",
3511
+ name: "Graph Node",
3512
+ description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
3513
+ mimeType: "application/json"
3514
+ },
3515
+ {
3516
+ uriTemplate: "mindgraph://search/{query}",
3517
+ name: "Search Results",
3518
+ description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
3519
+ mimeType: "application/json"
3520
+ },
3521
+ {
3522
+ uriTemplate: "mindgraph://layer/{layer}",
3523
+ name: "Layer Nodes",
3524
+ description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
3525
+ mimeType: "application/json"
3369
3526
  }
3370
- return { ref: result.resolved };
3527
+ ];
3528
+ var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
3529
+ function json(uri, data) {
3530
+ return {
3531
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
3532
+ };
3371
3533
  }
3372
- async function joinMaterialized(client, refs, agentId, limit) {
3373
- const joined = [];
3374
- for (const ref of uniqueRefs(refs).slice(0, limit)) {
3375
- const uid = await resolveMaterializedUid(client, ref, agentId);
3376
- if (!uid) continue;
3377
- joined.push({
3378
- ref,
3379
- anchor_uid: uid,
3380
- graph: await client.traverse({
3381
- action: "neighborhood",
3382
- start_uid: uid,
3383
- depth: 2,
3384
- max_nodes: 20
3385
- })
3386
- });
3534
+ async function readResource(client, uri, memoryConfig = {}) {
3535
+ switch (uri) {
3536
+ case "mindgraph://memory/status":
3537
+ return json(uri, memoryStatus(memoryConfig));
3538
+ case "mindgraph://stats":
3539
+ return json(uri, await client.stats());
3540
+ case "mindgraph://goals":
3541
+ return json(uri, await client.getGoals());
3542
+ case "mindgraph://questions":
3543
+ return json(uri, await client.getOpenQuestions());
3544
+ case "mindgraph://contradictions":
3545
+ return json(uri, await client.getContradictions());
3546
+ case "mindgraph://decisions":
3547
+ return json(uri, await client.getOpenDecisions());
3387
3548
  }
3388
- return joined;
3389
- }
3390
- async function codeRefsFromGraphContext(client, adapter, context) {
3391
- const uids = /* @__PURE__ */ new Set();
3392
- collectUids(context, uids);
3393
- if (uids.size === 0) return { refs: [], unavailable: [] };
3394
- const nodes = client.getNodesBatch ? await client.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client.getNode(uid).catch(() => void 0)));
3395
- const stored = nodes.flatMap((node) => {
3396
- if (!node) return [];
3397
- const attributes = node.props?.attributes;
3398
- if (!attributes || typeof attributes !== "object") return [];
3399
- const codeRef = attributes.code_ref;
3400
- return isStoredCodeRef(codeRef) ? [codeRef] : [];
3401
- });
3402
- const refs = [];
3403
- const unavailable = /* @__PURE__ */ new Set();
3404
- for (const ref of stored) {
3405
- try {
3406
- const repository = await adapter.resolveRepository(ref.repoId);
3407
- refs.push({ ...ref, repoRoot: repository.repoRoot });
3408
- } catch (cause) {
3409
- if (cause instanceof UnknownRepositoryError) {
3410
- unavailable.add(ref.repoId);
3411
- } else {
3412
- throw cause;
3413
- }
3414
- }
3549
+ const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
3550
+ if (nodeMatch) {
3551
+ const uid = decodeURIComponent(nodeMatch[1]);
3552
+ const node = await client.getNode(uid);
3553
+ const edges = await client.getEdges({ from_uid: uid });
3554
+ const inEdges = await client.getEdges({ to_uid: uid });
3555
+ return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
3415
3556
  }
3416
- return { refs, unavailable: [...unavailable].sort() };
3417
- }
3418
- function collectUids(value, uids) {
3419
- if (Array.isArray(value)) {
3420
- value.forEach((item) => collectUids(item, uids));
3421
- return;
3557
+ const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
3558
+ if (searchMatch) {
3559
+ const query = decodeURIComponent(searchMatch[1]);
3560
+ return json(uri, await client.search(query, { limit: 20 }));
3422
3561
  }
3423
- if (!value || typeof value !== "object") return;
3424
- for (const [key, child] of Object.entries(value)) {
3425
- if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
3426
- uids.add(child);
3427
- } else {
3428
- collectUids(child, uids);
3429
- }
3562
+ const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
3563
+ if (layerMatch) {
3564
+ const layer = decodeURIComponent(layerMatch[1]);
3565
+ return json(uri, await client.getNodes({ layer, limit: 50 }));
3430
3566
  }
3567
+ throw new Error(`Unknown resource: ${uri}`);
3431
3568
  }
3432
- function isStoredCodeRef(value) {
3433
- if (!value || typeof value !== "object") return false;
3434
- const ref = value;
3435
- return typeof ref.repoId === "string" && typeof ref.path === "string" && typeof ref.language === "string" && typeof ref.kind === "string" && typeof ref.qualifiedName === "string" && (typeof ref.signature === "string" || ref.signature === null) && typeof ref.startLine === "number" && typeof ref.endLine === "number";
3436
- }
3437
- function storedCodeRef(ref) {
3438
- const { repoRoot: _localOnly, ...stored } = ref;
3439
- return stored;
3440
- }
3441
- function uniqueRefs(refs) {
3442
- const seen = /* @__PURE__ */ new Set();
3443
- return refs.filter((ref) => {
3444
- const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
3445
- if (seen.has(key)) return false;
3446
- seen.add(key);
3447
- return true;
3448
- });
3449
- }
3450
- function primaryUids(value) {
3451
- if (!value || typeof value !== "object") return [];
3452
- const object = value;
3453
- const keys = [
3454
- "uid",
3455
- "claim_uid",
3456
- "goal_uid",
3457
- "project_uid",
3458
- "milestone_uid",
3459
- "decision_uid",
3460
- "task_uid",
3461
- "plan_uid",
3462
- "step_uid",
3463
- "execution_uid",
3464
- "lesson_uid"
3465
- ];
3466
- return [
3467
- ...new Set(
3468
- keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
3469
- )
3470
- ];
3569
+ async function readGovernedResource(client, uri, governanceConfig, memoryConfig = {}) {
3570
+ const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
3571
+ const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
3572
+ const governance = await checkMcpGovernance(
3573
+ toolName,
3574
+ toolArgs,
3575
+ { ...governanceConfig, context: { resource_uri: uri } }
3576
+ );
3577
+ if (!governance.allowed) {
3578
+ throw new Error(governance.message);
3579
+ }
3580
+ return readResource(client, uri, memoryConfig);
3471
3581
  }
3472
3582
 
3583
+ // src/tools.ts
3584
+ import { createHash as createHash2 } from "crypto";
3585
+
3473
3586
  // src/sync-tool.ts
3474
3587
  import crypto from "crypto";
3475
- import fs from "fs";
3476
- import path from "path";
3588
+ import fs2 from "fs";
3589
+ import path2 from "path";
3477
3590
  var ok3 = (data) => ({
3478
3591
  content: [{ type: "text", text: JSON.stringify(data) }]
3479
3592
  });
@@ -3550,12 +3663,12 @@ var SYNC_TOOL = {
3550
3663
  }
3551
3664
  };
3552
3665
  function safePath(root, candidate) {
3553
- const absolute = path.resolve(root, candidate);
3554
- const relative2 = path.relative(root, absolute);
3555
- if (relative2.startsWith("..") || path.isAbsolute(relative2)) {
3666
+ const absolute = path2.resolve(root, candidate);
3667
+ const relative2 = path2.relative(root, absolute);
3668
+ if (relative2.startsWith("..") || path2.isAbsolute(relative2)) {
3556
3669
  throw new Error(`path escapes repository root: ${candidate}`);
3557
3670
  }
3558
- if (fs.existsSync(absolute)) {
3671
+ if (fs2.existsSync(absolute)) {
3559
3672
  if (!pathContains(realPathOf(root), realPathOf(absolute))) {
3560
3673
  throw new Error(`path escapes repository root via symlink: ${candidate}`);
3561
3674
  }
@@ -3564,25 +3677,25 @@ function safePath(root, candidate) {
3564
3677
  }
3565
3678
  function realPathOf(target) {
3566
3679
  try {
3567
- return fs.realpathSync(target);
3680
+ return fs2.realpathSync(target);
3568
3681
  } catch {
3569
- return path.resolve(target);
3682
+ return path2.resolve(target);
3570
3683
  }
3571
3684
  }
3572
3685
  function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
3573
- if (!fs.existsSync(target)) return [];
3686
+ if (!fs2.existsSync(target)) return [];
3574
3687
  const real = realPathOf(target);
3575
3688
  if (!pathContains(realRoot, real)) return [];
3576
- const stat = fs.statSync(target);
3689
+ const stat = fs2.statSync(target);
3577
3690
  if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
3578
3691
  if (!stat.isDirectory() || visited.has(real)) return [];
3579
3692
  visited.add(real);
3580
- return fs.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(path.join(target, entry.name), realRoot, visited));
3693
+ return fs2.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(path2.join(target, entry.name), realRoot, visited));
3581
3694
  }
3582
3695
  function fileDescriptor(root, absolute) {
3583
- const content = fs.readFileSync(absolute, "utf8");
3696
+ const content = fs2.readFileSync(absolute, "utf8");
3584
3697
  return {
3585
- logical_path: path.relative(root, absolute).split(path.sep).join("/"),
3698
+ logical_path: path2.relative(root, absolute).split(path2.sep).join("/"),
3586
3699
  content_hash: crypto.createHash("sha256").update(content).digest("hex"),
3587
3700
  bytes: Buffer.byteLength(content),
3588
3701
  content
@@ -3652,7 +3765,7 @@ async function handleSyncTool(client, args) {
3652
3765
  }
3653
3766
  if (action === "status" && request.logical_path && request.file_present !== false) {
3654
3767
  const absolute = safePath(root, String(request.logical_path));
3655
- request.file_present = fs.existsSync(absolute);
3768
+ request.file_present = fs2.existsSync(absolute);
3656
3769
  if (request.file_present && !request.content_hash) {
3657
3770
  request.content_hash = fileDescriptor(root, absolute).content_hash;
3658
3771
  }