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