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/index.js CHANGED
@@ -61,18 +61,18 @@ function resolvePendingCall(toolName, toolInput, options = {}) {
61
61
  let matches = [];
62
62
  for (const name of names) {
63
63
  try {
64
- const record = JSON.parse(
64
+ const record2 = JSON.parse(
65
65
  import_node_fs.default.readFileSync(import_node_path.default.join(dir, name), "utf8")
66
66
  );
67
- if (typeof record.createdAt === "number" && now - record.createdAt <= TTL_MS && record.fills && typeof record.fills === "object") {
68
- matches.push(record);
67
+ if (typeof record2.createdAt === "number" && now - record2.createdAt <= TTL_MS && record2.fills && typeof record2.fills === "object") {
68
+ matches.push(record2);
69
69
  }
70
70
  } catch {
71
71
  }
72
72
  }
73
73
  if (matches.length > 1) {
74
74
  const projectDir = options.projectDir ?? process.env.CLAUDE_PROJECT_DIR;
75
- matches = projectDir ? matches.filter((record) => record.projectDir === projectDir) : [];
75
+ matches = projectDir ? matches.filter((record2) => record2.projectDir === projectDir) : [];
76
76
  }
77
77
  return matches.length === 1 ? matches[0].fills : void 0;
78
78
  }
@@ -224,12 +224,21 @@ function unwrapSearchResult(value) {
224
224
  }
225
225
  return void 0;
226
226
  }
227
+ function supersededBy(node) {
228
+ const marker = node.superseded;
229
+ if (!marker) return void 0;
230
+ const head = obj(marker);
231
+ if (!head) return str(node.superseded_by) ?? "";
232
+ const label = (str(head.head_label) ?? str(head.head_uid) ?? "").replace(/\s+/g, " ").trim();
233
+ const short = label.length <= 80 ? label : `${label.slice(0, 79)}\u2026`;
234
+ const date = str(head.head_stated_at);
235
+ return short && date ? `${short} (${date.slice(0, 10)})` : short;
236
+ }
227
237
  function statusTag(node) {
228
238
  const tags = [];
229
- if (node.superseded === true) {
230
- tags.push(
231
- node.superseded_by ? `SUPERSEDED by ${str(node.superseded_by)}` : "SUPERSEDED"
232
- );
239
+ const replacement = supersededBy(node);
240
+ if (replacement !== void 0) {
241
+ tags.push(replacement ? `SUPERSEDED by ${replacement}` : "SUPERSEDED");
233
242
  }
234
243
  const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
235
244
  if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
@@ -499,13 +508,13 @@ function resumeSection(heading, values, state, fallbackType) {
499
508
  const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
500
509
  return items.length > 0 ? { heading, items } : void 0;
501
510
  }
502
- function renderResumeContext(response, maxChars) {
511
+ function renderResumeContext(response, maxChars, repositoryNames = {}) {
503
512
  const payload = obj(response);
504
513
  if (!payload) return void 0;
505
514
  const state = { bounded: false, omitted: 0 };
506
515
  const status = str(payload.status);
507
516
  if (!payload.task && status === "no_eligible_work") {
508
- const scopedOut = Array.isArray(payload.scoped_out_tasks) ? payload.scoped_out_tasks.length : 0;
517
+ const scopedOut = typeof payload.scoped_out_tasks === "number" ? payload.scoped_out_tasks : 0;
509
518
  const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
510
519
  return assemble(
511
520
  "# Resume context",
@@ -519,6 +528,17 @@ function renderResumeContext(response, maxChars) {
519
528
  if (section) sections.push(section);
520
529
  };
521
530
  push(resumeSection("## Current task", [payload.task], state, "Task"));
531
+ const other = otherEligibleWork(payload, repositoryNames);
532
+ if (other.shown > 0) {
533
+ sections.push({
534
+ heading: `## Other eligible work (${other.shown} shown${other.more > 0 ? `, ${other.more} more not listed` : ""})`,
535
+ items: other.groups.flatMap(
536
+ (group) => group.tasks.map(
537
+ (task) => `- ${group.repository}: ${task.label} [${task.uid}]${task.detail ? ` \u2014 ${task.detail}` : ""}`
538
+ )
539
+ )
540
+ });
541
+ }
522
542
  push(
523
543
  resumeSection(
524
544
  "## Goal and project",
@@ -590,6 +610,37 @@ function renderResumeContext(response, maxChars) {
590
610
  if (sections.length === 0) return void 0;
591
611
  return assemble("# Resume context", sections, state, maxChars);
592
612
  }
613
+ function otherEligibleWork(brief, names = {}) {
614
+ const listed = Array.isArray(brief?.other_eligible) ? brief.other_eligible : [];
615
+ const groups = [];
616
+ let shown = 0;
617
+ for (const entry of listed) {
618
+ const task = obj(entry);
619
+ const uid = typeof task?.uid === "string" ? task.uid : void 0;
620
+ if (!task || !uid) continue;
621
+ const scopes = Array.isArray(task.scope_uids) ? task.scope_uids.map(String) : [];
622
+ const named = scopes.find((scope) => names[scope]);
623
+ const repository = named ? names[named] : "Other scopes";
624
+ let group = groups.find((candidate) => candidate.repository === repository);
625
+ if (!group) {
626
+ group = { repository, tasks: [] };
627
+ groups.push(group);
628
+ }
629
+ const label = typeof task.label === "string" && task.label ? task.label : "(unlabeled)";
630
+ const detail = [
631
+ typeof task.status === "string" ? task.status : void 0,
632
+ typeof task.priority === "string" && task.priority ? `priority ${task.priority}` : void 0
633
+ ].filter(Boolean).join(", ");
634
+ group.tasks.push({
635
+ uid,
636
+ label: label.length > 160 ? `${label.slice(0, 159)}\u2026` : label,
637
+ detail
638
+ });
639
+ shown += 1;
640
+ }
641
+ const total = typeof brief?.eligible_total === "number" ? brief.eligible_total : 0;
642
+ return { groups, shown, more: Math.max(0, total - 1 - shown) };
643
+ }
593
644
  function renderNodeList(response, title, limit, maxChars) {
594
645
  let candidates = [];
595
646
  if (Array.isArray(response)) {
@@ -640,15 +691,15 @@ function renderTraversal(response, maxChars) {
640
691
  const labelByUid = /* @__PURE__ */ new Map();
641
692
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
642
693
  if (paths.length > 0) {
643
- const items = paths.map((raw) => obj(raw)).filter((path6) => Boolean(path6)).map((path6, index) => {
644
- const uids = Array.isArray(path6.node_uids) ? path6.node_uids.map(String) : [];
645
- const labels = Array.isArray(path6.labels) ? path6.labels.map(String) : Array.isArray(path6.nodes) ? path6.nodes.map((hop) => {
694
+ const items = paths.map((raw) => obj(raw)).filter((path7) => Boolean(path7)).map((path7, index) => {
695
+ const uids = Array.isArray(path7.node_uids) ? path7.node_uids.map(String) : [];
696
+ const labels = Array.isArray(path7.labels) ? path7.labels.map(String) : Array.isArray(path7.nodes) ? path7.nodes.map((hop) => {
646
697
  const hopNode = obj(hop);
647
698
  return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
648
699
  }) : [];
649
700
  const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
650
- const cost = num(path6.cost) ?? num(path6.path_cost);
651
- const confidence = num(path6.path_confidence);
701
+ const cost = num(path7.cost) ?? num(path7.path_cost);
702
+ const confidence = num(path7.path_confidence);
652
703
  const meta = [
653
704
  cost !== void 0 ? `cost ${cost}` : void 0,
654
705
  confidence !== void 0 ? `confidence ${confidence}` : void 0
@@ -1650,777 +1701,168 @@ ${conversation}`
1650
1701
  }
1651
1702
 
1652
1703
  // src/memory-tool.ts
1704
+ var import_node_crypto4 = require("crypto");
1705
+
1706
+ // src/workspace-scope.ts
1707
+ var import_node_fs4 = __toESM(require("fs"));
1708
+ var import_node_path4 = __toESM(require("path"));
1709
+
1710
+ // src/codegraph.ts
1711
+ var import_node_child_process = require("child_process");
1712
+ var import_node_fs3 = require("fs");
1713
+ var import_promises = require("fs/promises");
1714
+ var import_node_os3 = require("os");
1715
+ var import_node_path3 = require("path");
1653
1716
  var import_node_crypto3 = require("crypto");
1654
- var RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
1655
- var DEFAULT_OUTPUT_CHARS = 8e3;
1656
- var MIN_OUTPUT_CHARS = 512;
1657
- var MAX_OUTPUT_CHARS = 24e3;
1658
- var HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
1659
- var HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
1660
- var MEMORY_TOOL = {
1661
- name: "mindgraph_memory",
1662
- 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.",
1663
- inputSchema: {
1664
- type: "object",
1665
- properties: {
1666
- action: {
1667
- type: "string",
1668
- enum: ["status", "context"],
1669
- description: "Read integration status or bounded memory context"
1670
- },
1671
- mode: {
1672
- type: "string",
1673
- enum: ["topic", "continuity", "resume"],
1674
- description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
1675
- },
1676
- query: {
1677
- type: "string",
1678
- description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
1679
- },
1680
- project_uid: { type: "string", description: "Optional project scope" },
1681
- task_uid: { type: "string", description: "Optional task scope for resume context" },
1682
- session_uid: { type: "string", description: "Optional durable session scope" },
1683
- scope_uids: {
1684
- type: "array",
1685
- items: { type: "string" },
1686
- description: "Optional explicit graph scopes for continuity/resume"
1687
- },
1688
- node_types: {
1689
- type: "array",
1690
- items: { type: "string" },
1691
- description: "Optional node-type filter for topic context"
1692
- },
1693
- layer: {
1694
- type: "string",
1695
- enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
1696
- description: "Optional cognitive-layer filter for topic context"
1697
- },
1698
- limit: {
1699
- type: "integer",
1700
- minimum: 1,
1701
- maximum: 50,
1702
- default: 8,
1703
- description: "Maximum direct graph items requested before bounded expansion"
1704
- },
1705
- include_chunks: {
1706
- type: "boolean",
1707
- default: false,
1708
- description: "Include bounded source excerpts when topic summaries are insufficient"
1709
- },
1710
- include_documents: {
1711
- type: "boolean",
1712
- default: false,
1713
- description: "Include bounded knowledge articles for topic context"
1714
- },
1715
- valid_at: {
1716
- type: "string",
1717
- description: "Optional ISO timestamp for time-aware topic retrieval"
1718
- },
1719
- graph_expansion_limit: {
1720
- type: "integer",
1721
- minimum: 0,
1722
- maximum: 20,
1723
- default: 3,
1724
- description: "Bounded graph neighbors added after direct topic matches"
1725
- },
1726
- graph_max_depth: {
1727
- type: "integer",
1728
- minimum: 0,
1729
- maximum: 5,
1730
- default: 2,
1731
- description: "Maximum graph-expansion depth for topic context"
1732
- },
1733
- max_output_chars: {
1734
- type: "integer",
1735
- minimum: MIN_OUTPUT_CHARS,
1736
- maximum: MAX_OUTPUT_CHARS,
1737
- default: DEFAULT_OUTPUT_CHARS,
1738
- description: "Hard character budget for model-ready context; whole items are retained or omitted"
1739
- },
1740
- agent_id: { type: "string", description: "Agent identity" }
1741
- },
1742
- required: ["action"]
1743
- },
1744
- outputSchema: {
1745
- type: "object",
1746
- properties: {
1747
- schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
1748
- action: { type: "string", enum: ["status", "context"] },
1749
- profile: { type: "string" },
1750
- server: { type: "object" },
1751
- integration: { type: "object" },
1752
- capabilities: { type: "object" },
1753
- degradation: { type: "array", items: { type: "object" } },
1754
- context_id: { type: "string" },
1755
- mode: { type: "string", enum: ["topic", "continuity", "resume"] },
1756
- scope: { type: "object" },
1757
- rendered_context: { type: "string" },
1758
- rendered_hash: { type: "string" },
1759
- items: { type: "array", items: { type: "object" } },
1760
- retrieval_trace: { type: "object" },
1761
- budget: { type: "object" },
1762
- freshness: { type: "object" },
1763
- warnings: { type: "array", items: { type: "object" } }
1764
- },
1765
- required: ["schema_version", "action"],
1766
- oneOf: [
1767
- {
1768
- properties: { action: { const: "status" } },
1769
- required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
1770
- },
1771
- {
1772
- properties: { action: { const: "context" } },
1773
- required: [
1774
- "context_id",
1775
- "mode",
1776
- "scope",
1777
- "rendered_context",
1778
- "rendered_hash",
1779
- "items",
1780
- "retrieval_trace",
1781
- "budget",
1782
- "freshness",
1783
- "warnings"
1784
- ]
1785
- }
1786
- ]
1787
- },
1788
- annotations: {
1789
- readOnlyHint: true,
1790
- destructiveHint: false,
1791
- idempotentHint: true,
1792
- openWorldHint: false
1717
+ var UnknownRepositoryError = class extends Error {
1718
+ constructor(repo) {
1719
+ super(
1720
+ `unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
1721
+ );
1722
+ this.repo = repo;
1723
+ this.name = "UnknownRepositoryError";
1793
1724
  }
1725
+ repo;
1794
1726
  };
1795
- function obj2(value) {
1796
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1797
- }
1798
- function text(value) {
1799
- return typeof value === "string" && value.length > 0 ? value : void 0;
1727
+ function invocationCwd(args) {
1728
+ const context = args.invocation_context;
1729
+ if (context && typeof context === "object" && !Array.isArray(context)) {
1730
+ const cwd = context.cwd;
1731
+ if (typeof cwd === "string" && cwd) return cwd;
1732
+ }
1733
+ return process.cwd();
1800
1734
  }
1801
- function stableJson2(value) {
1802
- if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
1803
- if (value && typeof value === "object") {
1804
- return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
1735
+ function unavailabilityCaveats(reason, message) {
1736
+ const caveats = [message || "codegraph status failed"];
1737
+ if (reason === "absent") {
1738
+ caveats.push(
1739
+ "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."
1740
+ );
1805
1741
  }
1806
- return JSON.stringify(value);
1742
+ return caveats;
1807
1743
  }
1808
- function sha256(value) {
1809
- return (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
1744
+ var DEFAULT_TIMEOUT_MS = 5e3;
1745
+ var DEFAULT_STATUS_TTL_MS = 6e4;
1746
+ var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
1747
+ function normalizePath(path7) {
1748
+ return path7.replaceAll("\\", "/").replace(/^\.\//, "");
1810
1749
  }
1811
- function outputBudget(value) {
1812
- if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
1813
- return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
1750
+ function normalizeSignature(signature) {
1751
+ return signature.replace(/\s+/g, " ").trim();
1814
1752
  }
1815
- function defined(values) {
1816
- return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
1753
+ function languageFromPath(path7) {
1754
+ const extension = (0, import_node_path3.extname)(path7).toLowerCase();
1755
+ return {
1756
+ ".c": "c",
1757
+ ".cc": "cpp",
1758
+ ".cpp": "cpp",
1759
+ ".go": "go",
1760
+ ".java": "java",
1761
+ ".js": "javascript",
1762
+ ".jsx": "javascript",
1763
+ ".kt": "kotlin",
1764
+ ".py": "python",
1765
+ ".rb": "ruby",
1766
+ ".rs": "rust",
1767
+ ".swift": "swift",
1768
+ ".ts": "typescript",
1769
+ ".tsx": "typescript"
1770
+ }[extension] ?? "unknown";
1817
1771
  }
1818
- function memoryStatus(config = {}) {
1772
+ function asNode(hit) {
1773
+ return hit.node ?? hit;
1774
+ }
1775
+ function toCandidate(node) {
1776
+ if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
1819
1777
  return {
1820
- schema_version: RESULT_SCHEMA_VERSION,
1821
- action: "status",
1822
- profile: "M0",
1823
- server: defined({
1824
- name: "mindgraph-mcp",
1825
- version: config.serverVersion ?? "unknown",
1826
- base_url: config.baseUrl
1827
- }),
1828
- integration: {
1829
- adapter_state: "model_controlled",
1830
- harness: config.harness ?? "generic",
1831
- configured_tool_profile: config.profile ?? "general",
1832
- automatic_capture: false,
1833
- automatic_delivery: false,
1834
- delivery_verification: "unsupported",
1835
- curator_mode: "disabled"
1836
- },
1837
- scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
1838
- capabilities: {
1839
- status: true,
1840
- topic_context: true,
1841
- authoritative_resume_context: true,
1842
- bounded_rendering: true,
1843
- structured_content: true,
1844
- typed_explicit_writes: true,
1845
- lifecycle_event_ingest: false,
1846
- automatic_context_injection: false,
1847
- curator_mutation: false
1848
- },
1849
- degradation: [
1850
- {
1851
- code: "automatic_capture_unavailable",
1852
- message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
1853
- },
1854
- {
1855
- code: "delivery_unverified",
1856
- message: "A successful MCP read does not prove that the host injected or used the returned context."
1857
- },
1858
- {
1859
- code: "curator_disabled",
1860
- message: "Curator proposals and autonomous mutation are not enabled in M0."
1861
- }
1862
- ]
1778
+ path: normalizePath(node.filePath),
1779
+ language: node.language ?? languageFromPath(node.filePath),
1780
+ kind: node.kind,
1781
+ qualifiedName: node.qualifiedName ?? node.name ?? "",
1782
+ signature: node.signature ?? null,
1783
+ startLine: node.startLine ?? 1,
1784
+ endLine: node.endLine ?? node.startLine ?? 1
1863
1785
  };
1864
1786
  }
1865
- function nodeType(node) {
1866
- const raw = node.node_type;
1867
- if (typeof raw === "string") return raw;
1868
- return text(obj2(raw)?.Custom) ?? "Node";
1869
- }
1870
- function compactItem(raw, fallbackType) {
1871
- const wrapper = obj2(raw);
1872
- if (!wrapper) return void 0;
1873
- const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
1874
- const uid = text(value.uid) ?? text(value.chunk_uid);
1875
- if (!uid) return void 0;
1876
- return defined({
1877
- uid,
1878
- type: fallbackType ?? nodeType(value),
1879
- label: text(value.label) ?? text(value.document_title),
1880
- truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
1881
- superseded: value.superseded === true ? true : void 0,
1882
- currently_valid: value.currently_valid === false ? false : void 0,
1883
- valid_at_time: value.valid_at_time === false ? false : void 0,
1884
- retrieval_origin: text(value.retrieval_origin),
1885
- retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
1886
- });
1887
- }
1888
- function topicCandidates(raw) {
1889
- const payload = obj2(raw);
1890
- if (!payload) return [];
1891
- const graph = obj2(payload.graph);
1892
- const items = [];
1893
- for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
1894
- const item = compactItem(article, "Article");
1895
- if (item) items.push(item);
1896
- }
1897
- const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
1898
- const labels = /* @__PURE__ */ new Map();
1899
- for (const node of rawNodes) {
1900
- const item = compactItem(node);
1901
- if (item) {
1902
- if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
1903
- items.push(item);
1904
- const uid = text(item.uid);
1905
- const label = text(item.label);
1906
- if (uid && label) labels.set(uid, label);
1907
- }
1908
- }
1909
- for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
1910
- const edge = obj2(rawEdge);
1911
- const fromUid = text(edge?.from_uid);
1912
- const toUid = text(edge?.to_uid);
1913
- const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
1914
- if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
1915
- continue;
1916
- }
1917
- items.push(
1918
- defined({
1919
- type: "Relationship",
1920
- from_uid: fromUid,
1921
- to_uid: toUid,
1922
- edge_type: edgeType,
1923
- from_label: labels.get(fromUid),
1924
- to_label: labels.get(toUid)
1925
- })
1926
- );
1927
- }
1928
- for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
1929
- const item = compactItem(chunk, "SourceExcerpt");
1930
- if (item) items.push(item);
1931
- }
1932
- return items;
1933
- }
1934
- function resumeCandidates(raw) {
1935
- const payload = obj2(raw);
1936
- if (!payload) return [];
1937
- const items = [];
1938
- for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
1939
- const item = compactItem(payload[key], key);
1940
- if (item) items.push(item);
1941
- }
1942
- for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
1943
- for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
1944
- const item = compactItem(value, key.replace(/s$/, ""));
1945
- if (item) items.push(item);
1946
- }
1787
+ function normalizeRemote(remote) {
1788
+ const trimmed = remote.trim();
1789
+ if (!trimmed) return null;
1790
+ const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
1791
+ if (scp && !trimmed.includes("://")) {
1792
+ return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
1947
1793
  }
1948
- const knowledge = obj2(payload.knowledge);
1949
- for (const key of ["lessons", "risks"]) {
1950
- for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
1951
- const item = compactItem(value, key.replace(/s$/, ""));
1952
- if (item) items.push(item);
1953
- }
1794
+ try {
1795
+ const url = new URL(trimmed);
1796
+ const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
1797
+ return `${url.host.toLowerCase()}${pathname}`;
1798
+ } catch {
1799
+ return null;
1954
1800
  }
1955
- return items;
1956
1801
  }
1957
- function visibleItems(candidates, rendered2) {
1958
- const seen = /* @__PURE__ */ new Set();
1959
- return candidates.filter((item) => {
1960
- const uid = text(item.uid);
1961
- if (uid) {
1962
- if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
1963
- seen.add(uid);
1964
- return true;
1965
- }
1966
- if (item.type === "Relationship") {
1967
- const from = text(item.from_label);
1968
- const to = text(item.to_label);
1969
- const edgeType = text(item.edge_type);
1970
- if (!from || !to || !edgeType) return false;
1971
- const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
1972
- if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
1973
- seen.add(key);
1974
- return true;
1975
- }
1976
- return false;
1977
- });
1802
+ function pathContains(root, candidate) {
1803
+ const child = (0, import_node_path3.relative)(root, candidate);
1804
+ return child === "" || child !== ".." && !child.startsWith(`..${import_node_path3.sep}`) && !(0, import_node_path3.isAbsolute)(child);
1978
1805
  }
1979
- function latestTimestamp(value) {
1980
- let latest;
1981
- const visit = (current) => {
1982
- if (Array.isArray(current)) {
1983
- for (const item of current) visit(item);
1984
- return;
1985
- }
1986
- const record = obj2(current);
1987
- if (!record) return;
1988
- for (const [key, item] of Object.entries(record)) {
1989
- 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))) {
1990
- latest = item;
1806
+ function nearestExistingDirectory(candidate) {
1807
+ let current = (0, import_node_path3.resolve)(candidate);
1808
+ for (; ; ) {
1809
+ if ((0, import_node_fs3.existsSync)(current)) {
1810
+ try {
1811
+ return (0, import_node_fs3.statSync)(current).isDirectory() ? current : (0, import_node_path3.dirname)(current);
1812
+ } catch {
1991
1813
  }
1992
- visit(item);
1993
- }
1994
- };
1995
- visit(value);
1996
- return latest;
1997
- }
1998
- function contextWarnings(raw, rendered2, available, returned) {
1999
- const warnings = [
2000
- {
2001
- code: "delivery_unverified",
2002
- message: "The MCP server returned this context but cannot verify that the host injected or used it."
2003
- }
2004
- ];
2005
- const serialized = JSON.stringify(raw);
2006
- if (available > returned || rendered2.includes("item(s) omitted")) {
2007
- warnings.push({
2008
- code: "context_bounded",
2009
- message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
2010
- });
2011
- }
2012
- if (/"superseded":true/.test(serialized)) {
2013
- warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
2014
- }
2015
- if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
2016
- warnings.push({
2017
- code: "validity_warning",
2018
- message: "Some retrieved items are outside their current or requested validity window."
2019
- });
2020
- }
2021
- const payload = obj2(raw);
2022
- if (payload?.truncated === true || obj2(payload?.truncation)) {
2023
- const truncation = obj2(payload?.truncation);
2024
- if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
2025
- warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
2026
1814
  }
1815
+ const parent = (0, import_node_path3.dirname)(current);
1816
+ if (parent === current) return current;
1817
+ current = parent;
2027
1818
  }
2028
- if (text(payload?.warning)) {
2029
- warnings.push({ code: "source_warning", message: text(payload?.warning) });
1819
+ }
1820
+ var MAX_CHILD_DIRECTORIES = 200;
1821
+ function findChildWorkspaceFiles(start) {
1822
+ let entries;
1823
+ try {
1824
+ entries = (0, import_node_fs3.readdirSync)(start, { withFileTypes: true });
1825
+ } catch {
1826
+ return [];
2030
1827
  }
2031
- return warnings;
1828
+ return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).slice(0, MAX_CHILD_DIRECTORIES).map((entry) => (0, import_node_path3.join)(start, entry.name, ".mindgraph", "workspace.json")).filter((candidate) => (0, import_node_fs3.existsSync)(candidate)).sort();
2032
1829
  }
2033
- function error(message) {
2034
- return {
2035
- content: [{ type: "text", text: JSON.stringify({ error: message }) }],
2036
- isError: true
2037
- };
1830
+ function findWorkspaceFile(start, explicit) {
1831
+ if (explicit) return (0, import_node_path3.resolve)(start, explicit);
1832
+ let current = (0, import_node_path3.resolve)(start);
1833
+ for (; ; ) {
1834
+ const candidate = (0, import_node_path3.join)(current, ".mindgraph", "workspace.json");
1835
+ if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
1836
+ const parent = (0, import_node_path3.dirname)(current);
1837
+ if (parent === current) return void 0;
1838
+ current = parent;
1839
+ }
2038
1840
  }
2039
- async function handleMemoryTool(client2, args, config = {}) {
2040
- const action = text(args.action);
2041
- if (action === "status") {
2042
- const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
2043
- return {
2044
- content: [{ type: "text", text: JSON.stringify(status) }],
2045
- structuredContent: status
2046
- };
2047
- }
2048
- if (action !== "context") {
2049
- return error("action must be 'status' or 'context'");
2050
- }
2051
- const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
2052
- if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
2053
- return error("action='context' requires mode='topic', 'continuity', or 'resume'");
2054
- }
2055
- const budget = outputBudget(args.max_output_chars);
2056
- const agentId = text(args.agent_id) ?? config.agentId;
2057
- let raw;
2058
- let rendered2;
2059
- let strategy;
2060
- let candidates;
2061
- if (mode === "topic") {
2062
- const query = text(args.query);
2063
- if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
2064
- const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
2065
- const request = defined({
2066
- query,
2067
- project_uid: text(args.project_uid),
2068
- node_limit: limit,
2069
- node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
2070
- layer: text(args.layer),
2071
- chunk_limit: args.include_chunks === true ? limit : 0,
2072
- article_limit: args.include_documents === true ? limit : 0,
2073
- valid_at: text(args.valid_at),
2074
- include_graph: true,
2075
- graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
2076
- graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
2077
- });
2078
- raw = await client2.retrieveContext(request);
2079
- rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
2080
- strategy = "retrieve_context";
2081
- candidates = topicCandidates(raw);
2082
- } else {
2083
- const scopeUids = Array.from(
2084
- /* @__PURE__ */ new Set([
2085
- ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
2086
- ...text(args.project_uid) ? [text(args.project_uid)] : []
2087
- ])
2088
- );
2089
- raw = await client2.plan(
2090
- defined({
2091
- action: "resume_work",
2092
- task_uid: text(args.task_uid),
2093
- session_uid: text(args.session_uid),
2094
- scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
2095
- limit: 1,
2096
- agent_id: agentId
2097
- })
2098
- );
2099
- rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
2100
- strategy = "resume_work";
2101
- candidates = resumeCandidates(raw);
2102
- }
2103
- const items = visibleItems(candidates, rendered2);
2104
- const renderedHash = sha256(rendered2);
2105
- const scope = defined({
2106
- org_id: config.orgId,
2107
- agent_id: agentId,
2108
- harness: config.harness ?? "generic",
2109
- project_uid: text(args.project_uid),
2110
- task_uid: text(args.task_uid),
2111
- session_uid: text(args.session_uid),
2112
- scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
2113
- });
2114
- const contextId = `ctx_${sha256(
2115
- stableJson2({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
2116
- ).slice(0, 24)}`;
2117
- const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
2118
- const result = {
2119
- schema_version: RESULT_SCHEMA_VERSION,
2120
- action: "context",
2121
- context_id: contextId,
2122
- mode,
2123
- scope,
2124
- rendered_context: rendered2,
2125
- rendered_hash: renderedHash,
2126
- items,
2127
- retrieval_trace: defined({
2128
- strategy,
2129
- query: text(args.query),
2130
- project_uid: text(args.project_uid),
2131
- task_uid: text(args.task_uid),
2132
- session_uid: text(args.session_uid),
2133
- node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
2134
- graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
2135
- graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
2136
- available_items: candidates.length,
2137
- returned_items: items.length,
2138
- omitted_items: Math.max(0, candidates.length - items.length)
2139
- }),
2140
- budget: {
2141
- unit: "characters",
2142
- requested_chars: budget,
2143
- used_chars: rendered2.length,
2144
- available_items: candidates.length,
2145
- returned_items: items.length,
2146
- omitted_items: Math.max(0, candidates.length - items.length),
2147
- bounded
2148
- },
2149
- freshness: defined({
2150
- valid_at: text(args.valid_at),
2151
- latest_source_timestamp: latestTimestamp(raw)
2152
- }),
2153
- warnings: contextWarnings(raw, rendered2, candidates.length, items.length)
2154
- };
2155
- return {
2156
- content: [{ type: "text", text: rendered2 }],
2157
- structuredContent: result
2158
- };
2159
- }
2160
-
2161
- // src/resources.ts
2162
- var RESOURCES = [
2163
- {
2164
- uri: "mindgraph://memory/status",
2165
- name: "Memory Integration Status",
2166
- description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
2167
- mimeType: "application/json"
2168
- },
2169
- {
2170
- uri: "mindgraph://stats",
2171
- name: "Graph Statistics",
2172
- description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
2173
- mimeType: "application/json"
2174
- },
2175
- {
2176
- uri: "mindgraph://goals",
2177
- name: "Active Goals",
2178
- description: "All active goals in the knowledge graph",
2179
- mimeType: "application/json"
2180
- },
2181
- {
2182
- uri: "mindgraph://questions",
2183
- name: "Open Questions",
2184
- description: "All open questions in the knowledge graph",
2185
- mimeType: "application/json"
2186
- },
2187
- {
2188
- uri: "mindgraph://contradictions",
2189
- name: "Contradictions",
2190
- description: "Contradictory claims in the knowledge graph",
2191
- mimeType: "application/json"
2192
- },
2193
- {
2194
- uri: "mindgraph://decisions",
2195
- name: "Open Decisions",
2196
- description: "Unresolved decisions awaiting resolution",
2197
- mimeType: "application/json"
2198
- }
2199
- ];
2200
- var RESOURCE_TEMPLATES = [
2201
- {
2202
- uriTemplate: "mindgraph://node/{uid}",
2203
- name: "Graph Node",
2204
- description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
2205
- mimeType: "application/json"
2206
- },
2207
- {
2208
- uriTemplate: "mindgraph://search/{query}",
2209
- name: "Search Results",
2210
- description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
2211
- mimeType: "application/json"
2212
- },
2213
- {
2214
- uriTemplate: "mindgraph://layer/{layer}",
2215
- name: "Layer Nodes",
2216
- description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
2217
- mimeType: "application/json"
2218
- }
2219
- ];
2220
- var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
2221
- function json(uri, data) {
2222
- return {
2223
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
2224
- };
2225
- }
2226
- async function readResource(client2, uri, memoryConfig = {}) {
2227
- switch (uri) {
2228
- case "mindgraph://memory/status":
2229
- return json(uri, memoryStatus(memoryConfig));
2230
- case "mindgraph://stats":
2231
- return json(uri, await client2.stats());
2232
- case "mindgraph://goals":
2233
- return json(uri, await client2.getGoals());
2234
- case "mindgraph://questions":
2235
- return json(uri, await client2.getOpenQuestions());
2236
- case "mindgraph://contradictions":
2237
- return json(uri, await client2.getContradictions());
2238
- case "mindgraph://decisions":
2239
- return json(uri, await client2.getOpenDecisions());
2240
- }
2241
- const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
2242
- if (nodeMatch) {
2243
- const uid = decodeURIComponent(nodeMatch[1]);
2244
- const node = await client2.getNode(uid);
2245
- const edges = await client2.getEdges({ from_uid: uid });
2246
- const inEdges = await client2.getEdges({ to_uid: uid });
2247
- return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
2248
- }
2249
- const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
2250
- if (searchMatch) {
2251
- const query = decodeURIComponent(searchMatch[1]);
2252
- return json(uri, await client2.search(query, { limit: 20 }));
2253
- }
2254
- const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
2255
- if (layerMatch) {
2256
- const layer = decodeURIComponent(layerMatch[1]);
2257
- return json(uri, await client2.getNodes({ layer, limit: 50 }));
2258
- }
2259
- throw new Error(`Unknown resource: ${uri}`);
2260
- }
2261
- async function readGovernedResource(client2, uri, governanceConfig, memoryConfig = {}) {
2262
- const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
2263
- const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
2264
- const governance = await checkMcpGovernance(
2265
- toolName,
2266
- toolArgs,
2267
- { ...governanceConfig, context: { resource_uri: uri } }
2268
- );
2269
- if (!governance.allowed) {
2270
- throw new Error(governance.message);
2271
- }
2272
- return readResource(client2, uri, memoryConfig);
2273
- }
2274
-
2275
- // src/tools.ts
2276
- var import_node_crypto6 = require("crypto");
2277
-
2278
- // src/codegraph.ts
2279
- var import_node_child_process = require("child_process");
2280
- var import_node_fs3 = require("fs");
2281
- var import_promises = require("fs/promises");
2282
- var import_node_os3 = require("os");
2283
- var import_node_path3 = require("path");
2284
- var import_node_crypto4 = require("crypto");
2285
- var UnknownRepositoryError = class extends Error {
2286
- constructor(repo) {
2287
- super(
2288
- `unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
2289
- );
2290
- this.repo = repo;
2291
- this.name = "UnknownRepositoryError";
2292
- }
2293
- repo;
2294
- };
2295
- function invocationCwd(args) {
2296
- const context = args.invocation_context;
2297
- if (context && typeof context === "object" && !Array.isArray(context)) {
2298
- const cwd = context.cwd;
2299
- if (typeof cwd === "string" && cwd) return cwd;
2300
- }
2301
- return process.cwd();
2302
- }
2303
- function unavailabilityCaveats(reason, message) {
2304
- const caveats = [message || "codegraph status failed"];
2305
- if (reason === "absent") {
2306
- caveats.push(
2307
- "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."
2308
- );
2309
- }
2310
- return caveats;
2311
- }
2312
- var DEFAULT_TIMEOUT_MS = 5e3;
2313
- var DEFAULT_STATUS_TTL_MS = 6e4;
2314
- var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
2315
- function normalizePath(path6) {
2316
- return path6.replaceAll("\\", "/").replace(/^\.\//, "");
2317
- }
2318
- function normalizeSignature(signature) {
2319
- return signature.replace(/\s+/g, " ").trim();
2320
- }
2321
- function languageFromPath(path6) {
2322
- const extension = (0, import_node_path3.extname)(path6).toLowerCase();
2323
- return {
2324
- ".c": "c",
2325
- ".cc": "cpp",
2326
- ".cpp": "cpp",
2327
- ".go": "go",
2328
- ".java": "java",
2329
- ".js": "javascript",
2330
- ".jsx": "javascript",
2331
- ".kt": "kotlin",
2332
- ".py": "python",
2333
- ".rb": "ruby",
2334
- ".rs": "rust",
2335
- ".swift": "swift",
2336
- ".ts": "typescript",
2337
- ".tsx": "typescript"
2338
- }[extension] ?? "unknown";
2339
- }
2340
- function asNode(hit) {
2341
- return hit.node ?? hit;
2342
- }
2343
- function toCandidate(node) {
2344
- if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
2345
- return {
2346
- path: normalizePath(node.filePath),
2347
- language: node.language ?? languageFromPath(node.filePath),
2348
- kind: node.kind,
2349
- qualifiedName: node.qualifiedName ?? node.name ?? "",
2350
- signature: node.signature ?? null,
2351
- startLine: node.startLine ?? 1,
2352
- endLine: node.endLine ?? node.startLine ?? 1
2353
- };
2354
- }
2355
- function normalizeRemote(remote) {
2356
- const trimmed = remote.trim();
2357
- if (!trimmed) return null;
2358
- const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
2359
- if (scp && !trimmed.includes("://")) {
2360
- return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
2361
- }
2362
- try {
2363
- const url = new URL(trimmed);
2364
- const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
2365
- return `${url.host.toLowerCase()}${pathname}`;
2366
- } catch {
2367
- return null;
2368
- }
2369
- }
2370
- function pathContains(root, candidate) {
2371
- const child = (0, import_node_path3.relative)(root, candidate);
2372
- return child === "" || child !== ".." && !child.startsWith(`..${import_node_path3.sep}`) && !(0, import_node_path3.isAbsolute)(child);
2373
- }
2374
- function nearestExistingDirectory(candidate) {
2375
- let current = (0, import_node_path3.resolve)(candidate);
2376
- for (; ; ) {
2377
- if ((0, import_node_fs3.existsSync)(current)) {
2378
- try {
2379
- return (0, import_node_fs3.statSync)(current).isDirectory() ? current : (0, import_node_path3.dirname)(current);
2380
- } catch {
2381
- }
2382
- }
2383
- const parent = (0, import_node_path3.dirname)(current);
2384
- if (parent === current) return current;
2385
- current = parent;
2386
- }
2387
- }
2388
- function findWorkspaceFile(start, explicit) {
2389
- if (explicit) return (0, import_node_path3.resolve)(start, explicit);
2390
- let current = (0, import_node_path3.resolve)(start);
2391
- for (; ; ) {
2392
- const candidate = (0, import_node_path3.join)(current, ".mindgraph", "workspace.json");
2393
- if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
2394
- const parent = (0, import_node_path3.dirname)(current);
2395
- if (parent === current) return void 0;
2396
- current = parent;
2397
- }
2398
- }
2399
- async function runProcess(executable, args, timeoutMs, env, cwd) {
2400
- return await new Promise((resolvePromise, rejectPromise) => {
2401
- const child = (0, import_node_child_process.spawn)(executable, args, {
2402
- cwd,
2403
- env,
2404
- stdio: ["ignore", "pipe", "pipe"],
2405
- windowsHide: true
2406
- });
2407
- const stdout = [];
2408
- const stderr = [];
2409
- let outputBytes = 0;
2410
- let timedOut = false;
2411
- const timer = setTimeout(() => {
2412
- timedOut = true;
2413
- child.kill("SIGTERM");
2414
- setTimeout(() => child.kill("SIGKILL"), 250).unref();
2415
- }, timeoutMs);
2416
- timer.unref();
2417
- const collect = (target) => (chunk) => {
2418
- outputBytes += chunk.length;
2419
- if (outputBytes > MAX_OUTPUT_BYTES) {
2420
- child.kill("SIGTERM");
2421
- return;
2422
- }
2423
- target.push(chunk);
1841
+ async function runProcess(executable, args, timeoutMs, env, cwd) {
1842
+ return await new Promise((resolvePromise, rejectPromise) => {
1843
+ const child = (0, import_node_child_process.spawn)(executable, args, {
1844
+ cwd,
1845
+ env,
1846
+ stdio: ["ignore", "pipe", "pipe"],
1847
+ windowsHide: true
1848
+ });
1849
+ const stdout = [];
1850
+ const stderr = [];
1851
+ let outputBytes = 0;
1852
+ let timedOut = false;
1853
+ const timer = setTimeout(() => {
1854
+ timedOut = true;
1855
+ child.kill("SIGTERM");
1856
+ setTimeout(() => child.kill("SIGKILL"), 250).unref();
1857
+ }, timeoutMs);
1858
+ timer.unref();
1859
+ const collect = (target) => (chunk) => {
1860
+ outputBytes += chunk.length;
1861
+ if (outputBytes > MAX_OUTPUT_BYTES) {
1862
+ child.kill("SIGTERM");
1863
+ return;
1864
+ }
1865
+ target.push(chunk);
2424
1866
  };
2425
1867
  child.stdout.on("data", collect(stdout));
2426
1868
  child.stderr.on("data", collect(stderr));
@@ -2461,6 +1903,7 @@ var CodegraphAdapter = class {
2461
1903
  env;
2462
1904
  cwd;
2463
1905
  workspaceFile;
1906
+ childWorkspaceFiles;
2464
1907
  now;
2465
1908
  identityCache = /* @__PURE__ */ new Map();
2466
1909
  statusCache = /* @__PURE__ */ new Map();
@@ -2473,6 +1916,7 @@ var CodegraphAdapter = class {
2473
1916
  this.cwd,
2474
1917
  options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
2475
1918
  );
1919
+ this.childWorkspaceFiles = !this.workspaceFile && options.childWorkspaces ? findChildWorkspaceFiles(this.cwd) : [];
2476
1920
  this.now = options.now ?? Date.now;
2477
1921
  }
2478
1922
  async resolveRepository(repo, fallbackSpaceUid) {
@@ -2570,7 +2014,7 @@ var CodegraphAdapter = class {
2570
2014
  } catch {
2571
2015
  }
2572
2016
  (0, import_node_fs3.mkdirSync)(identityDir, { recursive: true, mode: 448 });
2573
- const generated = (0, import_node_crypto4.randomUUID)();
2017
+ const generated = (0, import_node_crypto3.randomUUID)();
2574
2018
  try {
2575
2019
  (0, import_node_fs3.writeFileSync)(identityFile, `${generated}
2576
2020
  `, {
@@ -2794,770 +2238,1550 @@ var CodegraphAdapter = class {
2794
2238
  truncated: refs.length > cap
2795
2239
  };
2796
2240
  }
2797
- configuredRepositories() {
2798
- const configured = [];
2799
- if (this.workspaceFile && (0, import_node_fs3.existsSync)(this.workspaceFile)) {
2800
- try {
2801
- const parsed = JSON.parse(
2802
- (0, import_node_fs3.readFileSync)(this.workspaceFile, "utf8")
2241
+ configuredRepositories() {
2242
+ const configured = [];
2243
+ const workspaceFiles = this.workspaceFile ? [this.workspaceFile] : this.childWorkspaceFiles;
2244
+ for (const workspaceFile of workspaceFiles) {
2245
+ if (!(0, import_node_fs3.existsSync)(workspaceFile)) continue;
2246
+ try {
2247
+ const parsed = JSON.parse(
2248
+ (0, import_node_fs3.readFileSync)(workspaceFile, "utf8")
2249
+ );
2250
+ if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
2251
+ const configDirectory = (0, import_node_path3.dirname)(workspaceFile);
2252
+ const base = (0, import_node_path3.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path3.dirname)(configDirectory) : configDirectory;
2253
+ for (const repository of parsed.repositories) {
2254
+ if (!repository || typeof repository.root !== "string") continue;
2255
+ configured.push({
2256
+ repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
2257
+ repoRoot: (0, import_node_path3.resolve)(base, repository.root),
2258
+ spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
2259
+ });
2260
+ }
2261
+ }
2262
+ } catch {
2263
+ }
2264
+ }
2265
+ const raw = this.env.MINDGRAPH_CODE_REPOS;
2266
+ if (raw) {
2267
+ try {
2268
+ const parsed = JSON.parse(raw);
2269
+ for (const [repoId, config] of Object.entries(parsed)) {
2270
+ configured.push({
2271
+ repoId,
2272
+ repoRoot: (0, import_node_path3.resolve)(
2273
+ this.cwd,
2274
+ typeof config === "string" ? config : config.root
2275
+ ),
2276
+ spaceUid: typeof config === "string" ? void 0 : config.space_uid
2277
+ });
2278
+ }
2279
+ } catch {
2280
+ }
2281
+ }
2282
+ return [
2283
+ ...new Map(
2284
+ configured.map((repository) => [repository.repoRoot, repository])
2285
+ ).values()
2286
+ ];
2287
+ }
2288
+ async resolveOne(ref, repository) {
2289
+ const search = ref.symbol || ref.path;
2290
+ if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
2291
+ return { ref, candidates: [], error: "invalid_ref" };
2292
+ }
2293
+ try {
2294
+ const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
2295
+ if (ref.kind) args.push("-k", ref.kind);
2296
+ args.push(search);
2297
+ const hits = await this.runJson(args);
2298
+ let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
2299
+ if (ref.path) {
2300
+ const absolutePath = (0, import_node_path3.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
2301
+ const wanted = normalizePath(
2302
+ absolutePath ? (0, import_node_path3.relative)(repository.repoRoot, absolutePath) : ref.path
2303
+ );
2304
+ candidates = candidates.filter((candidate) => candidate.path === wanted);
2305
+ }
2306
+ if (ref.language) {
2307
+ candidates = candidates.filter(
2308
+ (candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
2309
+ );
2310
+ }
2311
+ if (ref.kind) {
2312
+ candidates = candidates.filter(
2313
+ (candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
2314
+ );
2315
+ }
2316
+ if (ref.symbol) {
2317
+ const exact = candidates.filter((candidate) => {
2318
+ const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
2319
+ return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
2320
+ });
2321
+ if (exact.length > 0) candidates = exact;
2322
+ }
2323
+ if (ref.signature) {
2324
+ const signature = normalizeSignature(ref.signature);
2325
+ candidates = candidates.filter(
2326
+ (candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
2327
+ );
2328
+ }
2329
+ if (ref.line !== void 0) {
2330
+ candidates = candidates.filter(
2331
+ (candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
2332
+ );
2333
+ if (candidates.length > 1) {
2334
+ const narrowest = Math.min(
2335
+ ...candidates.map((candidate) => candidate.endLine - candidate.startLine)
2336
+ );
2337
+ const narrowed = candidates.filter(
2338
+ (candidate) => candidate.endLine - candidate.startLine === narrowest
2339
+ );
2340
+ if (narrowed.length === 1) candidates = narrowed;
2341
+ }
2342
+ }
2343
+ if (candidates.length !== 1) {
2344
+ return {
2345
+ ref,
2346
+ candidates,
2347
+ error: candidates.length === 0 ? "not_found" : "ambiguous"
2348
+ };
2349
+ }
2350
+ return {
2351
+ ref,
2352
+ candidates,
2353
+ resolved: {
2354
+ repoId: repository.repoId,
2355
+ repoRoot: repository.repoRoot,
2356
+ ...candidates[0]
2357
+ }
2358
+ };
2359
+ } catch (cause) {
2360
+ const failure = cause;
2361
+ return {
2362
+ ref,
2363
+ candidates: [],
2364
+ error: failure.kind ?? "command_failed"
2365
+ };
2366
+ }
2367
+ }
2368
+ async related(action, resolved, limit) {
2369
+ const raw = await this.runJson([
2370
+ action,
2371
+ "-p",
2372
+ resolved.repoRoot,
2373
+ "-j",
2374
+ "-l",
2375
+ String(limit),
2376
+ resolved.qualifiedName
2377
+ ]);
2378
+ const refs = (raw[action] ?? []).map((node) => ({
2379
+ path: node.filePath,
2380
+ line: node.startLine,
2381
+ symbol: node.qualifiedName ?? node.name,
2382
+ kind: node.kind
2383
+ }));
2384
+ return this.resolveRelatedRefs(refs, {
2385
+ repoId: resolved.repoId,
2386
+ repoRoot: resolved.repoRoot
2387
+ });
2388
+ }
2389
+ async resolveRelatedRefs(refs, repository) {
2390
+ const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
2391
+ const seen = /* @__PURE__ */ new Set();
2392
+ return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
2393
+ const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
2394
+ if (seen.has(key)) return false;
2395
+ seen.add(key);
2396
+ return true;
2397
+ });
2398
+ }
2399
+ async runJson(args) {
2400
+ const candidates = [
2401
+ this.env.MINDGRAPH_CODEGRAPH_BIN,
2402
+ "codegraph",
2403
+ (0, import_node_path3.resolve)((0, import_node_os3.homedir)(), ".local", "bin", "codegraph")
2404
+ ].filter((candidate, index, all) => {
2405
+ return Boolean(candidate) && all.indexOf(candidate) === index;
2406
+ });
2407
+ let lastFailure;
2408
+ for (const executable of candidates) {
2409
+ try {
2410
+ const stdout = await runProcess(
2411
+ executable,
2412
+ args,
2413
+ this.timeoutMs,
2414
+ this.env,
2415
+ this.cwd
2416
+ );
2417
+ return JSON.parse(stdout);
2418
+ } catch (cause) {
2419
+ const failure = cause;
2420
+ lastFailure = failure;
2421
+ if (failure.kind !== "absent") throw failure;
2422
+ }
2423
+ }
2424
+ throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
2425
+ kind: "absent"
2426
+ });
2427
+ }
2428
+ };
2429
+ function repositoryIdentityKey(repository) {
2430
+ return { v: 1, kind: "repository", repo_id: repository.repoId };
2431
+ }
2432
+ function codeRefIdentityKey(ref) {
2433
+ if (ref.kind === "file") {
2434
+ return {
2435
+ v: 1,
2436
+ kind: "file",
2437
+ repo_id: ref.repoId,
2438
+ path: ref.path
2439
+ };
2440
+ }
2441
+ return {
2442
+ v: 1,
2443
+ kind: "symbol",
2444
+ repo_id: ref.repoId,
2445
+ language: ref.language,
2446
+ symbol_kind: ref.kind,
2447
+ qualified_name: ref.qualifiedName,
2448
+ disambiguator: {
2449
+ signature: ref.signature ?? "",
2450
+ path: ref.path
2451
+ }
2452
+ };
2453
+ }
2454
+
2455
+ // src/code-tool.ts
2456
+ var ok2 = (data) => ({
2457
+ content: [{ type: "text", text: JSON.stringify(data) }]
2458
+ });
2459
+ var err3 = (code, message, details) => ({
2460
+ content: [
2461
+ {
2462
+ type: "text",
2463
+ text: JSON.stringify({ error: message, code, details })
2464
+ }
2465
+ ],
2466
+ isError: true
2467
+ });
2468
+ var CODE_TOOL = {
2469
+ name: "mindgraph_code",
2470
+ 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.",
2471
+ inputSchema: {
2472
+ type: "object",
2473
+ properties: {
2474
+ action: {
2475
+ type: "string",
2476
+ enum: ["anchor", "recall", "expand", "affected"]
2477
+ },
2478
+ code_refs: {
2479
+ type: "array",
2480
+ maxItems: 50,
2481
+ items: {
2482
+ oneOf: [
2483
+ { type: "string" },
2484
+ {
2485
+ type: "object",
2486
+ properties: {
2487
+ repo: { type: "string" },
2488
+ path: { type: "string" },
2489
+ line: { type: "integer", minimum: 1 },
2490
+ symbol: { type: "string" },
2491
+ kind: { type: "string" },
2492
+ language: { type: "string" },
2493
+ signature: { type: "string" }
2494
+ },
2495
+ additionalProperties: false
2496
+ }
2497
+ ]
2498
+ }
2499
+ },
2500
+ anchor_uids: {
2501
+ type: "array",
2502
+ maxItems: 50,
2503
+ items: { type: "string" },
2504
+ description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
2505
+ },
2506
+ repo: {
2507
+ type: "string",
2508
+ description: "Configured repository id or repository root"
2509
+ },
2510
+ repo_space_uid: {
2511
+ type: "string",
2512
+ description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
2513
+ },
2514
+ task_uid: { type: "string" },
2515
+ execution_uid: { type: "string" },
2516
+ limit: { type: "integer", minimum: 1, maximum: 50 },
2517
+ agent_id: { type: "string" }
2518
+ },
2519
+ required: ["action"]
2520
+ },
2521
+ annotations: {
2522
+ readOnlyHint: false,
2523
+ destructiveHint: false,
2524
+ idempotentHint: true,
2525
+ openWorldHint: true
2526
+ }
2527
+ };
2528
+ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
2529
+ const codeClient = client2;
2530
+ const action = args.action;
2531
+ const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
2532
+ const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
2533
+ const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
2534
+ const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
2535
+ const options = {
2536
+ repo: typeof args.repo === "string" ? args.repo : void 0,
2537
+ fallbackSpaceUid
2538
+ };
2539
+ const limit = typeof args.limit === "number" ? args.limit : void 0;
2540
+ try {
2541
+ switch (action) {
2542
+ case "anchor": {
2543
+ if (refs.length === 0) return err3("missing_code_refs", "code_refs is required for anchor");
2544
+ const resolution = await adapter.resolveRefs(refs, options);
2545
+ const resolved = resolution.results.flatMap(
2546
+ (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
2803
2547
  );
2804
- if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
2805
- const configDirectory = (0, import_node_path3.dirname)(this.workspaceFile);
2806
- const base = (0, import_node_path3.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path3.dirname)(configDirectory) : configDirectory;
2807
- for (const repository of parsed.repositories) {
2808
- if (!repository || typeof repository.root !== "string") continue;
2809
- configured.push({
2810
- repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
2811
- repoRoot: (0, import_node_path3.resolve)(base, repository.root),
2812
- spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
2813
- });
2814
- }
2548
+ const unresolved = resolution.results.filter((result) => !result.resolved);
2549
+ if (resolved.length === 0) {
2550
+ return ok2({ ...resolution, anchors: [], wrote: false });
2815
2551
  }
2816
- } catch {
2817
- }
2818
- }
2819
- const raw = this.env.MINDGRAPH_CODE_REPOS;
2820
- if (raw) {
2821
- try {
2822
- const parsed = JSON.parse(raw);
2823
- for (const [repoId, config] of Object.entries(parsed)) {
2824
- configured.push({
2825
- repoId,
2826
- repoRoot: (0, import_node_path3.resolve)(
2827
- this.cwd,
2828
- typeof config === "string" ? config : config.root
2829
- ),
2830
- spaceUid: typeof config === "string" ? void 0 : config.space_uid
2552
+ const missingSpace = resolved.find(
2553
+ ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
2554
+ );
2555
+ if (missingSpace) {
2556
+ return err3(
2557
+ "missing_repository_space",
2558
+ `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
2559
+ );
2560
+ }
2561
+ const repositoryAnchors = /* @__PURE__ */ new Map();
2562
+ for (const { repository } of resolved) {
2563
+ if (repositoryAnchors.has(repository.repoRoot)) continue;
2564
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
2565
+ const anchored = await anchorRepository(
2566
+ codeClient,
2567
+ repository,
2568
+ spaceUid,
2569
+ agentId
2570
+ );
2571
+ repositoryAnchors.set(repository.repoRoot, {
2572
+ ...anchored,
2573
+ repository
2831
2574
  });
2832
2575
  }
2833
- } catch {
2576
+ const anchors = [];
2577
+ for (const { ref, repository } of resolved) {
2578
+ const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
2579
+ if (!repositoryAnchor.uid) continue;
2580
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
2581
+ const anchor = await anchorCodeRef(
2582
+ codeClient,
2583
+ ref,
2584
+ repositoryAnchor.uid,
2585
+ spaceUid,
2586
+ agentId
2587
+ );
2588
+ anchors.push(anchor);
2589
+ }
2590
+ return ok2({
2591
+ availability: resolution.availability,
2592
+ repository: repositoryAnchors.values().next().value,
2593
+ repositories: [...repositoryAnchors.values()],
2594
+ anchors,
2595
+ unresolved,
2596
+ wrote: anchors.some((anchor) => anchor.created)
2597
+ });
2834
2598
  }
2835
- }
2836
- return [
2837
- ...new Map(
2838
- configured.map((repository) => [repository.repoRoot, repository])
2839
- ).values()
2840
- ];
2841
- }
2842
- async resolveOne(ref, repository) {
2843
- const search = ref.symbol || ref.path;
2844
- if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
2845
- return { ref, candidates: [], error: "invalid_ref" };
2846
- }
2847
- try {
2848
- const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
2849
- if (ref.kind) args.push("-k", ref.kind);
2850
- args.push(search);
2851
- const hits = await this.runJson(args);
2852
- let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
2853
- if (ref.path) {
2854
- const absolutePath = (0, import_node_path3.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
2855
- const wanted = normalizePath(
2856
- absolutePath ? (0, import_node_path3.relative)(repository.repoRoot, absolutePath) : ref.path
2599
+ case "recall": {
2600
+ const materialized = new Set(anchorUids);
2601
+ let resolution;
2602
+ if (refs.length > 0) {
2603
+ resolution = await adapter.resolveRefs(refs, options);
2604
+ for (const result of resolution.results) {
2605
+ if (!result.resolved) continue;
2606
+ const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
2607
+ if (uid) materialized.add(uid);
2608
+ }
2609
+ }
2610
+ if (materialized.size === 0) {
2611
+ return ok2({
2612
+ availability: resolution?.availability,
2613
+ anchors: [],
2614
+ context: [],
2615
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
2616
+ });
2617
+ }
2618
+ const context = await Promise.all(
2619
+ [...materialized].map(async (uid) => ({
2620
+ anchor_uid: uid,
2621
+ graph: await codeClient.traverse({
2622
+ action: "neighborhood",
2623
+ start_uid: uid,
2624
+ depth: 2,
2625
+ max_nodes: Math.min(limit ?? 20, 20)
2626
+ })
2627
+ }))
2857
2628
  );
2858
- candidates = candidates.filter((candidate) => candidate.path === wanted);
2629
+ return ok2({
2630
+ availability: resolution?.availability,
2631
+ anchors: [...materialized],
2632
+ context,
2633
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
2634
+ });
2859
2635
  }
2860
- if (ref.language) {
2861
- candidates = candidates.filter(
2862
- (candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
2636
+ case "expand": {
2637
+ const target = await resolveSingleTarget(
2638
+ codeClient,
2639
+ adapter,
2640
+ refs,
2641
+ anchorUids,
2642
+ options
2643
+ );
2644
+ if ("error" in target) return target.error;
2645
+ const expanded = await adapter.expand(target.ref, limit);
2646
+ const joined = await joinMaterialized(
2647
+ codeClient,
2648
+ [...expanded.callers, ...expanded.callees],
2649
+ agentId,
2650
+ Math.min(limit ?? 20, 20)
2863
2651
  );
2652
+ return ok2({ anchor: target.ref, ...expanded, materialized: joined });
2864
2653
  }
2865
- if (ref.kind) {
2866
- candidates = candidates.filter(
2867
- (candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
2654
+ case "affected": {
2655
+ const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
2656
+ let graphContext;
2657
+ const graphRefs = [];
2658
+ let unavailableRepositories = [];
2659
+ if (targetUid) {
2660
+ graphContext = await codeClient.traverse({
2661
+ action: "neighborhood",
2662
+ start_uid: targetUid,
2663
+ depth: 2,
2664
+ max_nodes: 50
2665
+ });
2666
+ const fromGraph = await codeRefsFromGraphContext(
2667
+ codeClient,
2668
+ adapter,
2669
+ graphContext
2670
+ );
2671
+ graphRefs.push(...fromGraph.refs);
2672
+ unavailableRepositories = fromGraph.unavailable;
2673
+ }
2674
+ if (refs.length > 0 || anchorUids.length > 0) {
2675
+ const target = await resolveSingleTarget(
2676
+ codeClient,
2677
+ adapter,
2678
+ refs,
2679
+ anchorUids,
2680
+ options
2681
+ );
2682
+ if ("error" in target) return target.error;
2683
+ graphRefs.unshift(target.ref);
2684
+ }
2685
+ const unique = uniqueRefs(graphRefs);
2686
+ if (unique.length === 0) {
2687
+ return ok2({
2688
+ target_uid: targetUid,
2689
+ graph_context: graphContext,
2690
+ affected: [],
2691
+ degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
2692
+ ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
2693
+ });
2694
+ }
2695
+ const impacts = await Promise.all(
2696
+ unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
2697
+ );
2698
+ const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
2699
+ const joined = await joinMaterialized(
2700
+ codeClient,
2701
+ structural,
2702
+ agentId,
2703
+ Math.min(limit ?? 50, 50)
2868
2704
  );
2869
- }
2870
- if (ref.symbol) {
2871
- const exact = candidates.filter((candidate) => {
2872
- const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
2873
- return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
2705
+ return ok2({
2706
+ target_uid: targetUid,
2707
+ graph_context: graphContext,
2708
+ impacts,
2709
+ materialized: joined,
2710
+ ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
2874
2711
  });
2875
- if (exact.length > 0) candidates = exact;
2876
2712
  }
2877
- if (ref.signature) {
2878
- const signature = normalizeSignature(ref.signature);
2879
- candidates = candidates.filter(
2880
- (candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
2881
- );
2713
+ default:
2714
+ return err3("unknown_action", `unknown mindgraph_code action: ${action}`);
2715
+ }
2716
+ } catch (cause) {
2717
+ if (cause instanceof UnknownRepositoryError) {
2718
+ return err3("unknown_repository", cause.message);
2719
+ }
2720
+ return toolError(cause, "code_tool_failed");
2721
+ }
2722
+ }
2723
+ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
2724
+ if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
2725
+ return result;
2726
+ }
2727
+ const original = JSON.parse(result.content[0]?.text ?? "null");
2728
+ const targetUids = primaryUids(original);
2729
+ if (targetUids.length === 0) {
2730
+ return ok2({
2731
+ result: original,
2732
+ code_anchors: {
2733
+ wrote: false,
2734
+ caveat: "the write response exposed no primary UID to attach"
2882
2735
  }
2883
- if (ref.line !== void 0) {
2884
- candidates = candidates.filter(
2885
- (candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
2886
- );
2887
- if (candidates.length > 1) {
2888
- const narrowest = Math.min(
2889
- ...candidates.map((candidate) => candidate.endLine - candidate.startLine)
2890
- );
2891
- const narrowed = candidates.filter(
2892
- (candidate) => candidate.endLine - candidate.startLine === narrowest
2736
+ });
2737
+ }
2738
+ const anchorResult = await handleCodeTool(
2739
+ client2,
2740
+ {
2741
+ action: "anchor",
2742
+ code_refs: args.code_refs,
2743
+ repo: args.repo,
2744
+ repo_space_uid: args.repo_space_uid,
2745
+ agent_id: args.agent_id,
2746
+ // Keeps the default adapter anchored to the live session directory
2747
+ // rather than wherever the MCP server process was launched.
2748
+ invocation_context: args.invocation_context
2749
+ },
2750
+ adapter
2751
+ );
2752
+ const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
2753
+ if (!anchorResult.isError) {
2754
+ const codeClient = client2;
2755
+ const anchorUids = (anchors.anchors ?? []).flatMap(
2756
+ (anchor) => anchor.uid ? [anchor.uid] : []
2757
+ );
2758
+ const relateFailures = [];
2759
+ for (const targetUid of targetUids) {
2760
+ for (const anchorUid of anchorUids) {
2761
+ try {
2762
+ await codeClient.entity({
2763
+ action: "relate",
2764
+ source_uid: targetUid,
2765
+ target_uid: anchorUid,
2766
+ edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
2767
+ agent_id: args.agent_id
2768
+ });
2769
+ } catch (cause) {
2770
+ relateFailures.push(
2771
+ `${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
2893
2772
  );
2894
- if (narrowed.length === 1) candidates = narrowed;
2895
2773
  }
2896
2774
  }
2897
- if (candidates.length !== 1) {
2898
- return {
2899
- ref,
2900
- candidates,
2901
- error: candidates.length === 0 ? "not_found" : "ambiguous"
2902
- };
2903
- }
2904
- return {
2905
- ref,
2906
- candidates,
2907
- resolved: {
2908
- repoId: repository.repoId,
2909
- repoRoot: repository.repoRoot,
2910
- ...candidates[0]
2775
+ }
2776
+ if (relateFailures.length > 0) {
2777
+ return ok2({
2778
+ result: original,
2779
+ code_anchors: {
2780
+ ...anchors,
2781
+ caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
2911
2782
  }
2912
- };
2913
- } catch (cause) {
2914
- const failure = cause;
2783
+ });
2784
+ }
2785
+ }
2786
+ return ok2({ result: original, code_anchors: anchors });
2787
+ }
2788
+ async function anchorRepository(client2, repository, spaceUid, agentId) {
2789
+ return client2.entity({
2790
+ action: "create",
2791
+ label: repository.repoId,
2792
+ props: {
2793
+ entity_type: "repository",
2794
+ canonical_name: repository.repoId,
2795
+ identifiers: { repo_id: repository.repoId }
2796
+ },
2797
+ identity: {
2798
+ namespace: "external.code",
2799
+ key_version: 1,
2800
+ key: repositoryIdentityKey(repository)
2801
+ },
2802
+ identity_space_uid: spaceUid,
2803
+ agent_id: agentId
2804
+ });
2805
+ }
2806
+ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
2807
+ const response = await client2.entity({
2808
+ action: "create",
2809
+ label: ref.qualifiedName,
2810
+ props: {
2811
+ entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
2812
+ canonical_name: ref.qualifiedName,
2813
+ identifiers: {
2814
+ repo_id: ref.repoId,
2815
+ path: ref.path,
2816
+ qualified_name: ref.qualifiedName
2817
+ },
2818
+ attributes: { code_ref: storedCodeRef(ref) }
2819
+ },
2820
+ identity: {
2821
+ namespace: "external.code",
2822
+ key_version: 1,
2823
+ key: codeRefIdentityKey(ref)
2824
+ },
2825
+ identity_space_uid: spaceUid,
2826
+ agent_id: agentId
2827
+ });
2828
+ if (response.uid && response.created !== false && response.status !== "existing") {
2829
+ await client2.entity({
2830
+ action: "relate",
2831
+ source_uid: response.uid,
2832
+ target_uid: repositoryUid,
2833
+ edge_type: "PART_OF",
2834
+ agent_id: agentId
2835
+ });
2836
+ }
2837
+ return { ...response, ref };
2838
+ }
2839
+ async function resolveMaterializedUid(client2, ref, agentId) {
2840
+ const response = await client2.entity({
2841
+ action: "resolve_identity",
2842
+ identity: {
2843
+ namespace: "external.code",
2844
+ key_version: 1,
2845
+ key: codeRefIdentityKey(ref)
2846
+ },
2847
+ agent_id: agentId
2848
+ });
2849
+ return response.status === "existing" ? response.uid : void 0;
2850
+ }
2851
+ async function codeRefFromAnchor(client2, adapter, uid) {
2852
+ const node = await client2.getNode(uid);
2853
+ const attributes = node.props?.attributes;
2854
+ if (!attributes || typeof attributes !== "object") return void 0;
2855
+ const codeRef = attributes.code_ref;
2856
+ if (!isStoredCodeRef(codeRef)) return void 0;
2857
+ try {
2858
+ const repository = await adapter.resolveRepository(codeRef.repoId);
2859
+ return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
2860
+ } catch (cause) {
2861
+ if (cause instanceof UnknownRepositoryError) {
2862
+ return { missingRepo: codeRef.repoId };
2863
+ }
2864
+ throw cause;
2865
+ }
2866
+ }
2867
+ async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
2868
+ if (refs.length > 1 || anchorUids.length > 1) {
2869
+ return { error: err3("ambiguous_target", "expand/affected accepts exactly one code target") };
2870
+ }
2871
+ if (anchorUids.length === 1) {
2872
+ const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
2873
+ if (!outcome) {
2874
+ return { error: err3("invalid_anchor", "anchor UID does not contain a typed code_ref") };
2875
+ }
2876
+ if ("missingRepo" in outcome) {
2915
2877
  return {
2916
- ref,
2917
- candidates: [],
2918
- error: failure.kind ?? "command_failed"
2878
+ error: err3(
2879
+ "repository_unavailable",
2880
+ `repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
2881
+ )
2919
2882
  };
2920
2883
  }
2884
+ return { ref: outcome.ref };
2921
2885
  }
2922
- async related(action, resolved, limit) {
2923
- const raw = await this.runJson([
2924
- action,
2925
- "-p",
2926
- resolved.repoRoot,
2927
- "-j",
2928
- "-l",
2929
- String(limit),
2930
- resolved.qualifiedName
2931
- ]);
2932
- const refs = (raw[action] ?? []).map((node) => ({
2933
- path: node.filePath,
2934
- line: node.startLine,
2935
- symbol: node.qualifiedName ?? node.name,
2936
- kind: node.kind
2937
- }));
2938
- return this.resolveRelatedRefs(refs, {
2939
- repoId: resolved.repoId,
2940
- repoRoot: resolved.repoRoot
2941
- });
2886
+ if (refs.length !== 1) {
2887
+ return { error: err3("missing_code_ref", "one code_ref or anchor_uid is required") };
2942
2888
  }
2943
- async resolveRelatedRefs(refs, repository) {
2944
- const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
2945
- const seen = /* @__PURE__ */ new Set();
2946
- return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
2947
- const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
2948
- if (seen.has(key)) return false;
2949
- seen.add(key);
2950
- return true;
2951
- });
2889
+ const resolution = await adapter.resolveRefs(refs, options);
2890
+ const result = resolution.results[0];
2891
+ if (!result.resolved) {
2892
+ return {
2893
+ error: ok2({
2894
+ availability: resolution.availability,
2895
+ result,
2896
+ wrote: false
2897
+ })
2898
+ };
2952
2899
  }
2953
- async runJson(args) {
2954
- const candidates = [
2955
- this.env.MINDGRAPH_CODEGRAPH_BIN,
2956
- "codegraph",
2957
- (0, import_node_path3.resolve)((0, import_node_os3.homedir)(), ".local", "bin", "codegraph")
2958
- ].filter((candidate, index, all) => {
2959
- return Boolean(candidate) && all.indexOf(candidate) === index;
2900
+ return { ref: result.resolved };
2901
+ }
2902
+ async function joinMaterialized(client2, refs, agentId, limit) {
2903
+ const joined = [];
2904
+ for (const ref of uniqueRefs(refs).slice(0, limit)) {
2905
+ const uid = await resolveMaterializedUid(client2, ref, agentId);
2906
+ if (!uid) continue;
2907
+ joined.push({
2908
+ ref,
2909
+ anchor_uid: uid,
2910
+ graph: await client2.traverse({
2911
+ action: "neighborhood",
2912
+ start_uid: uid,
2913
+ depth: 2,
2914
+ max_nodes: 20
2915
+ })
2960
2916
  });
2961
- let lastFailure;
2962
- for (const executable of candidates) {
2963
- try {
2964
- const stdout = await runProcess(
2965
- executable,
2966
- args,
2967
- this.timeoutMs,
2968
- this.env,
2969
- this.cwd
2970
- );
2971
- return JSON.parse(stdout);
2972
- } catch (cause) {
2973
- const failure = cause;
2974
- lastFailure = failure;
2975
- if (failure.kind !== "absent") throw failure;
2917
+ }
2918
+ return joined;
2919
+ }
2920
+ async function codeRefsFromGraphContext(client2, adapter, context) {
2921
+ const uids = /* @__PURE__ */ new Set();
2922
+ collectUids(context, uids);
2923
+ if (uids.size === 0) return { refs: [], unavailable: [] };
2924
+ const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
2925
+ const stored = nodes.flatMap((node) => {
2926
+ if (!node) return [];
2927
+ const attributes = node.props?.attributes;
2928
+ if (!attributes || typeof attributes !== "object") return [];
2929
+ const codeRef = attributes.code_ref;
2930
+ return isStoredCodeRef(codeRef) ? [codeRef] : [];
2931
+ });
2932
+ const refs = [];
2933
+ const unavailable = /* @__PURE__ */ new Set();
2934
+ for (const ref of stored) {
2935
+ try {
2936
+ const repository = await adapter.resolveRepository(ref.repoId);
2937
+ refs.push({ ...ref, repoRoot: repository.repoRoot });
2938
+ } catch (cause) {
2939
+ if (cause instanceof UnknownRepositoryError) {
2940
+ unavailable.add(ref.repoId);
2941
+ } else {
2942
+ throw cause;
2976
2943
  }
2977
2944
  }
2978
- throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
2979
- kind: "absent"
2980
- });
2981
2945
  }
2982
- };
2983
- function repositoryIdentityKey(repository) {
2984
- return { v: 1, kind: "repository", repo_id: repository.repoId };
2946
+ return { refs, unavailable: [...unavailable].sort() };
2985
2947
  }
2986
- function codeRefIdentityKey(ref) {
2987
- if (ref.kind === "file") {
2988
- return {
2989
- v: 1,
2990
- kind: "file",
2991
- repo_id: ref.repoId,
2992
- path: ref.path
2993
- };
2948
+ function collectUids(value, uids) {
2949
+ if (Array.isArray(value)) {
2950
+ value.forEach((item) => collectUids(item, uids));
2951
+ return;
2994
2952
  }
2995
- return {
2996
- v: 1,
2997
- kind: "symbol",
2998
- repo_id: ref.repoId,
2999
- language: ref.language,
3000
- symbol_kind: ref.kind,
3001
- qualified_name: ref.qualifiedName,
3002
- disambiguator: {
3003
- signature: ref.signature ?? "",
3004
- path: ref.path
2953
+ if (!value || typeof value !== "object") return;
2954
+ for (const [key, child] of Object.entries(value)) {
2955
+ if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
2956
+ uids.add(child);
2957
+ } else {
2958
+ collectUids(child, uids);
3005
2959
  }
3006
- };
2960
+ }
2961
+ }
2962
+ function isStoredCodeRef(value) {
2963
+ if (!value || typeof value !== "object") return false;
2964
+ const ref = value;
2965
+ 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";
2966
+ }
2967
+ function storedCodeRef(ref) {
2968
+ const { repoRoot: _localOnly, ...stored } = ref;
2969
+ return stored;
2970
+ }
2971
+ function uniqueRefs(refs) {
2972
+ const seen = /* @__PURE__ */ new Set();
2973
+ return refs.filter((ref) => {
2974
+ const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
2975
+ if (seen.has(key)) return false;
2976
+ seen.add(key);
2977
+ return true;
2978
+ });
2979
+ }
2980
+ function primaryUids(value) {
2981
+ if (!value || typeof value !== "object") return [];
2982
+ const object = value;
2983
+ const keys = [
2984
+ "uid",
2985
+ "claim_uid",
2986
+ "goal_uid",
2987
+ "project_uid",
2988
+ "milestone_uid",
2989
+ "decision_uid",
2990
+ "task_uid",
2991
+ "plan_uid",
2992
+ "step_uid",
2993
+ "execution_uid",
2994
+ "lesson_uid"
2995
+ ];
2996
+ return [
2997
+ ...new Set(
2998
+ keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
2999
+ )
3000
+ ];
3007
3001
  }
3008
3002
 
3009
- // src/code-tool.ts
3010
- var ok2 = (data) => ({
3011
- content: [{ type: "text", text: JSON.stringify(data) }]
3012
- });
3013
- var err3 = (code, message, details) => ({
3014
- content: [
3015
- {
3016
- type: "text",
3017
- text: JSON.stringify({ error: message, code, details })
3018
- }
3019
- ],
3020
- isError: true
3021
- });
3022
- var CODE_TOOL = {
3023
- name: "mindgraph_code",
3024
- 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.",
3025
- inputSchema: {
3026
- type: "object",
3027
- properties: {
3028
- action: {
3029
- type: "string",
3030
- enum: ["anchor", "recall", "expand", "affected"]
3031
- },
3032
- code_refs: {
3033
- type: "array",
3034
- maxItems: 50,
3035
- items: {
3036
- oneOf: [
3037
- { type: "string" },
3038
- {
3039
- type: "object",
3040
- properties: {
3041
- repo: { type: "string" },
3042
- path: { type: "string" },
3043
- line: { type: "integer", minimum: 1 },
3044
- symbol: { type: "string" },
3045
- kind: { type: "string" },
3046
- language: { type: "string" },
3047
- signature: { type: "string" }
3048
- },
3049
- additionalProperties: false
3050
- }
3051
- ]
3052
- }
3053
- },
3054
- anchor_uids: {
3055
- type: "array",
3056
- maxItems: 50,
3057
- items: { type: "string" },
3058
- description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
3059
- },
3060
- repo: {
3061
- type: "string",
3062
- description: "Configured repository id or repository root"
3063
- },
3064
- repo_space_uid: {
3065
- type: "string",
3066
- description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
3067
- },
3068
- task_uid: { type: "string" },
3069
- execution_uid: { type: "string" },
3070
- limit: { type: "integer", minimum: 1, maximum: 50 },
3071
- agent_id: { type: "string" }
3072
- },
3073
- required: ["action"]
3074
- },
3075
- annotations: {
3076
- readOnlyHint: false,
3077
- destructiveHint: false,
3078
- idempotentHint: true,
3079
- openWorldHint: true
3003
+ // src/workspace-scope.ts
3004
+ var WORKSPACE_RESUME_LIMIT = 9;
3005
+ var SCOPE_CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
3006
+ var SCOPE_CACHE_FILE = "repository-scopes.json";
3007
+ function readScopeCache(cache) {
3008
+ if (!cache) return {};
3009
+ try {
3010
+ const parsed = JSON.parse(
3011
+ import_node_fs4.default.readFileSync(import_node_path4.default.join(cache.dir, SCOPE_CACHE_FILE), "utf8")
3012
+ );
3013
+ if (parsed.v !== 1 || parsed.tenant !== cache.tenant || !parsed.entries) return {};
3014
+ const now = (cache.now ?? Date.now)();
3015
+ return Object.fromEntries(
3016
+ Object.entries(parsed.entries).filter(
3017
+ ([, entry]) => typeof entry?.uid === "string" && typeof entry.at === "number" && now - entry.at >= 0 && now - entry.at < SCOPE_CACHE_TTL_MS
3018
+ )
3019
+ );
3020
+ } catch {
3021
+ return {};
3080
3022
  }
3081
- };
3082
- async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
3083
- const codeClient = client2;
3084
- const action = args.action;
3085
- const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
3086
- const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
3087
- const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
3088
- const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
3089
- const options = {
3090
- repo: typeof args.repo === "string" ? args.repo : void 0,
3091
- fallbackSpaceUid
3092
- };
3093
- const limit = typeof args.limit === "number" ? args.limit : void 0;
3023
+ }
3024
+ function writeScopeCache(cache, entries) {
3025
+ if (!cache) return;
3094
3026
  try {
3095
- switch (action) {
3096
- case "anchor": {
3097
- if (refs.length === 0) return err3("missing_code_refs", "code_refs is required for anchor");
3098
- const resolution = await adapter.resolveRefs(refs, options);
3099
- const resolved = resolution.results.flatMap(
3100
- (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
3101
- );
3102
- const unresolved = resolution.results.filter((result) => !result.resolved);
3103
- if (resolved.length === 0) {
3104
- return ok2({ ...resolution, anchors: [], wrote: false });
3105
- }
3106
- const missingSpace = resolved.find(
3107
- ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
3027
+ import_node_fs4.default.mkdirSync(cache.dir, { recursive: true, mode: 448 });
3028
+ const file = import_node_path4.default.join(cache.dir, SCOPE_CACHE_FILE);
3029
+ const temporary = `${file}.${process.pid}.tmp`;
3030
+ import_node_fs4.default.writeFileSync(
3031
+ temporary,
3032
+ JSON.stringify({ v: 1, tenant: cache.tenant, entries }),
3033
+ { mode: 384 }
3034
+ );
3035
+ import_node_fs4.default.renameSync(temporary, file);
3036
+ } catch {
3037
+ }
3038
+ }
3039
+ function record(value) {
3040
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3041
+ }
3042
+ async function resolveRepositoryScope(cwd, client2, agentId, options = {}) {
3043
+ if (!client2.entity) return { uids: [], names: {}, failures: [] };
3044
+ const adapter = new CodegraphAdapter({
3045
+ cwd,
3046
+ workspaceFile: options.workspaceFile,
3047
+ childWorkspaces: true
3048
+ });
3049
+ const configured = await adapter.resolveRepositories();
3050
+ const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
3051
+ const failures = [];
3052
+ const names = {};
3053
+ const cached = readScopeCache(options.cache);
3054
+ const fresh = {};
3055
+ const stamp = (options.cache?.now ?? Date.now)();
3056
+ const resolved = await Promise.all(
3057
+ repositories.map(async (repository) => {
3058
+ const remembered = cached[repository.repoId];
3059
+ if (remembered) {
3060
+ names[remembered.uid] = repository.repoId;
3061
+ return remembered.uid;
3062
+ }
3063
+ try {
3064
+ const payload = record(
3065
+ await client2.entity({
3066
+ action: "resolve_identity",
3067
+ identity: {
3068
+ namespace: "external.code",
3069
+ key_version: 1,
3070
+ key: repositoryIdentityKey(repository)
3071
+ },
3072
+ agent_id: agentId
3073
+ })
3108
3074
  );
3109
- if (missingSpace) {
3110
- return err3(
3111
- "missing_repository_space",
3112
- `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
3113
- );
3075
+ if (payload?.status === "existing" && typeof payload.uid === "string" && payload.uid) {
3076
+ names[payload.uid] = repository.repoId;
3077
+ fresh[repository.repoId] = { uid: payload.uid, at: stamp };
3078
+ return payload.uid;
3114
3079
  }
3115
- const repositoryAnchors = /* @__PURE__ */ new Map();
3116
- for (const { repository } of resolved) {
3117
- if (repositoryAnchors.has(repository.repoRoot)) continue;
3118
- const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
3080
+ if (payload?.status === "absent") {
3119
3081
  const anchored = await anchorRepository(
3120
- codeClient,
3082
+ client2,
3121
3083
  repository,
3122
- spaceUid,
3123
- agentId
3124
- );
3125
- repositoryAnchors.set(repository.repoRoot, {
3126
- ...anchored,
3127
- repository
3128
- });
3129
- }
3130
- const anchors = [];
3131
- for (const { ref, repository } of resolved) {
3132
- const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
3133
- if (!repositoryAnchor.uid) continue;
3134
- const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
3135
- const anchor = await anchorCodeRef(
3136
- codeClient,
3137
- ref,
3138
- repositoryAnchor.uid,
3139
- spaceUid,
3084
+ repository.spaceUid ?? `space:agent:${agentId}`,
3140
3085
  agentId
3141
3086
  );
3142
- anchors.push(anchor);
3143
- }
3144
- return ok2({
3145
- availability: resolution.availability,
3146
- repository: repositoryAnchors.values().next().value,
3147
- repositories: [...repositoryAnchors.values()],
3148
- anchors,
3149
- unresolved,
3150
- wrote: anchors.some((anchor) => anchor.created)
3151
- });
3152
- }
3153
- case "recall": {
3154
- const materialized = new Set(anchorUids);
3155
- let resolution;
3156
- if (refs.length > 0) {
3157
- resolution = await adapter.resolveRefs(refs, options);
3158
- for (const result of resolution.results) {
3159
- if (!result.resolved) continue;
3160
- const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
3161
- if (uid) materialized.add(uid);
3087
+ if (anchored?.uid) {
3088
+ names[anchored.uid] = repository.repoId;
3089
+ fresh[repository.repoId] = { uid: anchored.uid, at: stamp };
3090
+ return anchored.uid;
3162
3091
  }
3163
- }
3164
- if (materialized.size === 0) {
3165
- return ok2({
3166
- availability: resolution?.availability,
3167
- anchors: [],
3168
- context: [],
3169
- unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
3170
- });
3171
- }
3172
- const context = await Promise.all(
3173
- [...materialized].map(async (uid) => ({
3174
- anchor_uid: uid,
3175
- graph: await codeClient.traverse({
3176
- action: "neighborhood",
3177
- start_uid: uid,
3178
- depth: 2,
3179
- max_nodes: Math.min(limit ?? 20, 20)
3180
- })
3181
- }))
3182
- );
3183
- return ok2({
3184
- availability: resolution?.availability,
3185
- anchors: [...materialized],
3186
- context,
3187
- unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
3188
- });
3189
- }
3190
- case "expand": {
3191
- const target = await resolveSingleTarget(
3192
- codeClient,
3193
- adapter,
3194
- refs,
3195
- anchorUids,
3196
- options
3197
- );
3198
- if ("error" in target) return target.error;
3199
- const expanded = await adapter.expand(target.ref, limit);
3200
- const joined = await joinMaterialized(
3201
- codeClient,
3202
- [...expanded.callers, ...expanded.callees],
3203
- agentId,
3204
- Math.min(limit ?? 20, 20)
3205
- );
3206
- return ok2({ anchor: target.ref, ...expanded, materialized: joined });
3207
- }
3208
- case "affected": {
3209
- const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
3210
- let graphContext;
3211
- const graphRefs = [];
3212
- let unavailableRepositories = [];
3213
- if (targetUid) {
3214
- graphContext = await codeClient.traverse({
3215
- action: "neighborhood",
3216
- start_uid: targetUid,
3217
- depth: 2,
3218
- max_nodes: 50
3219
- });
3220
- const fromGraph = await codeRefsFromGraphContext(
3221
- codeClient,
3222
- adapter,
3223
- graphContext
3224
- );
3225
- graphRefs.push(...fromGraph.refs);
3226
- unavailableRepositories = fromGraph.unavailable;
3227
- }
3228
- if (refs.length > 0 || anchorUids.length > 0) {
3229
- const target = await resolveSingleTarget(
3230
- codeClient,
3231
- adapter,
3232
- refs,
3233
- anchorUids,
3234
- options
3092
+ failures.push(
3093
+ `${repository.repoId}: anchor ${anchored?.status ?? "failed"}`
3235
3094
  );
3236
- if ("error" in target) return target.error;
3237
- graphRefs.unshift(target.ref);
3238
- }
3239
- const unique = uniqueRefs(graphRefs);
3240
- if (unique.length === 0) {
3241
- return ok2({
3242
- target_uid: targetUid,
3243
- graph_context: graphContext,
3244
- affected: [],
3245
- degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
3246
- ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
3247
- });
3095
+ return void 0;
3248
3096
  }
3249
- const impacts = await Promise.all(
3250
- unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
3097
+ failures.push(
3098
+ `${repository.repoId}: ${typeof payload?.status === "string" && payload.status ? payload.status : "unresolved"}`
3251
3099
  );
3252
- const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
3253
- const joined = await joinMaterialized(
3254
- codeClient,
3255
- structural,
3256
- agentId,
3257
- Math.min(limit ?? 50, 50)
3100
+ return void 0;
3101
+ } catch (error2) {
3102
+ failures.push(
3103
+ `${repository.repoId}: ${error2?.message || "resolve failed"}`
3258
3104
  );
3259
- return ok2({
3260
- target_uid: targetUid,
3261
- graph_context: graphContext,
3262
- impacts,
3263
- materialized: joined,
3264
- ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
3265
- });
3105
+ return void 0;
3106
+ }
3107
+ })
3108
+ );
3109
+ if (Object.keys(fresh).length > 0) {
3110
+ writeScopeCache(options.cache, { ...cached, ...fresh });
3111
+ }
3112
+ return {
3113
+ uids: [...new Set(resolved.filter((uid) => Boolean(uid)))],
3114
+ names,
3115
+ failures
3116
+ };
3117
+ }
3118
+
3119
+ // src/memory-tool.ts
3120
+ var RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
3121
+ var DEFAULT_OUTPUT_CHARS = 8e3;
3122
+ var MIN_OUTPUT_CHARS = 512;
3123
+ var MAX_OUTPUT_CHARS = 24e3;
3124
+ var HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
3125
+ var HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
3126
+ var MEMORY_TOOL = {
3127
+ name: "mindgraph_memory",
3128
+ 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.",
3129
+ inputSchema: {
3130
+ type: "object",
3131
+ properties: {
3132
+ action: {
3133
+ type: "string",
3134
+ enum: ["status", "context"],
3135
+ description: "Read integration status or bounded memory context"
3136
+ },
3137
+ mode: {
3138
+ type: "string",
3139
+ enum: ["topic", "continuity", "resume"],
3140
+ description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
3141
+ },
3142
+ query: {
3143
+ type: "string",
3144
+ description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
3145
+ },
3146
+ project_uid: { type: "string", description: "Optional project scope" },
3147
+ task_uid: { type: "string", description: "Optional task scope for resume context" },
3148
+ session_uid: { type: "string", description: "Optional durable session scope" },
3149
+ scope_uids: {
3150
+ type: "array",
3151
+ items: { type: "string" },
3152
+ description: "Optional explicit graph scopes for continuity/resume"
3153
+ },
3154
+ scope: {
3155
+ type: "string",
3156
+ enum: ["workspace"],
3157
+ 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"
3158
+ },
3159
+ node_types: {
3160
+ type: "array",
3161
+ items: { type: "string" },
3162
+ description: "Optional node-type filter for topic context"
3163
+ },
3164
+ layer: {
3165
+ type: "string",
3166
+ enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
3167
+ description: "Optional cognitive-layer filter for topic context"
3168
+ },
3169
+ limit: {
3170
+ type: "integer",
3171
+ minimum: 1,
3172
+ maximum: 50,
3173
+ default: 8,
3174
+ description: "Maximum direct graph items requested before bounded expansion"
3175
+ },
3176
+ include_chunks: {
3177
+ type: "boolean",
3178
+ default: false,
3179
+ description: "Include bounded source excerpts when topic summaries are insufficient"
3180
+ },
3181
+ include_documents: {
3182
+ type: "boolean",
3183
+ default: false,
3184
+ description: "Include bounded knowledge articles for topic context"
3185
+ },
3186
+ valid_at: {
3187
+ type: "string",
3188
+ description: "Optional ISO timestamp for time-aware topic retrieval"
3189
+ },
3190
+ graph_expansion_limit: {
3191
+ type: "integer",
3192
+ minimum: 0,
3193
+ maximum: 20,
3194
+ default: 3,
3195
+ description: "Bounded graph neighbors added after direct topic matches"
3196
+ },
3197
+ graph_max_depth: {
3198
+ type: "integer",
3199
+ minimum: 0,
3200
+ maximum: 5,
3201
+ default: 2,
3202
+ description: "Maximum graph-expansion depth for topic context"
3203
+ },
3204
+ max_output_chars: {
3205
+ type: "integer",
3206
+ minimum: MIN_OUTPUT_CHARS,
3207
+ maximum: MAX_OUTPUT_CHARS,
3208
+ default: DEFAULT_OUTPUT_CHARS,
3209
+ description: "Hard character budget for model-ready context; whole items are retained or omitted"
3210
+ },
3211
+ agent_id: { type: "string", description: "Agent identity" }
3212
+ },
3213
+ required: ["action"]
3214
+ },
3215
+ outputSchema: {
3216
+ type: "object",
3217
+ properties: {
3218
+ schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
3219
+ action: { type: "string", enum: ["status", "context"] },
3220
+ profile: { type: "string" },
3221
+ server: { type: "object" },
3222
+ integration: { type: "object" },
3223
+ capabilities: { type: "object" },
3224
+ degradation: { type: "array", items: { type: "object" } },
3225
+ context_id: { type: "string" },
3226
+ mode: { type: "string", enum: ["topic", "continuity", "resume"] },
3227
+ scope: { type: "object" },
3228
+ rendered_context: { type: "string" },
3229
+ rendered_hash: { type: "string" },
3230
+ items: { type: "array", items: { type: "object" } },
3231
+ retrieval_trace: { type: "object" },
3232
+ budget: { type: "object" },
3233
+ freshness: { type: "object" },
3234
+ warnings: { type: "array", items: { type: "object" } }
3235
+ },
3236
+ required: ["schema_version", "action"],
3237
+ oneOf: [
3238
+ {
3239
+ properties: { action: { const: "status" } },
3240
+ required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
3241
+ },
3242
+ {
3243
+ properties: { action: { const: "context" } },
3244
+ required: [
3245
+ "context_id",
3246
+ "mode",
3247
+ "scope",
3248
+ "rendered_context",
3249
+ "rendered_hash",
3250
+ "items",
3251
+ "retrieval_trace",
3252
+ "budget",
3253
+ "freshness",
3254
+ "warnings"
3255
+ ]
3266
3256
  }
3267
- default:
3268
- return err3("unknown_action", `unknown mindgraph_code action: ${action}`);
3269
- }
3270
- } catch (cause) {
3271
- if (cause instanceof UnknownRepositoryError) {
3272
- return err3("unknown_repository", cause.message);
3273
- }
3274
- return toolError(cause, "code_tool_failed");
3257
+ ]
3258
+ },
3259
+ annotations: {
3260
+ readOnlyHint: true,
3261
+ destructiveHint: false,
3262
+ idempotentHint: true,
3263
+ openWorldHint: false
3275
3264
  }
3265
+ };
3266
+ function obj2(value) {
3267
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3276
3268
  }
3277
- async function attachCodeRefsToToolResult(client2, result, args, adapter) {
3278
- if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
3279
- return result;
3269
+ function text(value) {
3270
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3271
+ }
3272
+ function stableJson2(value) {
3273
+ if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
3274
+ if (value && typeof value === "object") {
3275
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
3280
3276
  }
3281
- const original = JSON.parse(result.content[0]?.text ?? "null");
3282
- const targetUids = primaryUids(original);
3283
- if (targetUids.length === 0) {
3284
- return ok2({
3285
- result: original,
3286
- code_anchors: {
3287
- wrote: false,
3288
- caveat: "the write response exposed no primary UID to attach"
3277
+ return JSON.stringify(value);
3278
+ }
3279
+ function sha256(value) {
3280
+ return (0, import_node_crypto4.createHash)("sha256").update(value).digest("hex");
3281
+ }
3282
+ function outputBudget(value) {
3283
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
3284
+ return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
3285
+ }
3286
+ function defined(values) {
3287
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
3288
+ }
3289
+ function memoryStatus(config = {}) {
3290
+ return {
3291
+ schema_version: RESULT_SCHEMA_VERSION,
3292
+ action: "status",
3293
+ profile: "M0",
3294
+ server: defined({
3295
+ name: "mindgraph-mcp",
3296
+ version: config.serverVersion ?? "unknown",
3297
+ base_url: config.baseUrl
3298
+ }),
3299
+ integration: {
3300
+ adapter_state: "model_controlled",
3301
+ harness: config.harness ?? "generic",
3302
+ configured_tool_profile: config.profile ?? "general",
3303
+ automatic_capture: false,
3304
+ automatic_delivery: false,
3305
+ delivery_verification: "unsupported",
3306
+ curator_mode: "disabled"
3307
+ },
3308
+ scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
3309
+ capabilities: {
3310
+ status: true,
3311
+ topic_context: true,
3312
+ authoritative_resume_context: true,
3313
+ bounded_rendering: true,
3314
+ structured_content: true,
3315
+ typed_explicit_writes: true,
3316
+ lifecycle_event_ingest: false,
3317
+ automatic_context_injection: false,
3318
+ curator_mutation: false
3319
+ },
3320
+ degradation: [
3321
+ {
3322
+ code: "automatic_capture_unavailable",
3323
+ message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
3324
+ },
3325
+ {
3326
+ code: "delivery_unverified",
3327
+ message: "A successful MCP read does not prove that the host injected or used the returned context."
3328
+ },
3329
+ {
3330
+ code: "curator_disabled",
3331
+ message: "Curator proposals and autonomous mutation are not enabled in M0."
3289
3332
  }
3290
- });
3333
+ ]
3334
+ };
3335
+ }
3336
+ function nodeType(node) {
3337
+ const raw = node.node_type;
3338
+ if (typeof raw === "string") return raw;
3339
+ return text(obj2(raw)?.Custom) ?? "Node";
3340
+ }
3341
+ function compactItem(raw, fallbackType) {
3342
+ const wrapper = obj2(raw);
3343
+ if (!wrapper) return void 0;
3344
+ const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
3345
+ const uid = text(value.uid) ?? text(value.chunk_uid);
3346
+ if (!uid) return void 0;
3347
+ return defined({
3348
+ uid,
3349
+ type: fallbackType ?? nodeType(value),
3350
+ label: text(value.label) ?? text(value.document_title),
3351
+ truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
3352
+ // `true` from /retrieve, an object naming the replacement from /retrieve/context.
3353
+ superseded: value.superseded ? true : void 0,
3354
+ currently_valid: value.currently_valid === false ? false : void 0,
3355
+ valid_at_time: value.valid_at_time === false ? false : void 0,
3356
+ retrieval_origin: text(value.retrieval_origin),
3357
+ retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
3358
+ });
3359
+ }
3360
+ function topicCandidates(raw) {
3361
+ const payload = obj2(raw);
3362
+ if (!payload) return [];
3363
+ const graph = obj2(payload.graph);
3364
+ const items = [];
3365
+ for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
3366
+ const item = compactItem(article, "Article");
3367
+ if (item) items.push(item);
3291
3368
  }
3292
- const anchorResult = await handleCodeTool(
3293
- client2,
3294
- {
3295
- action: "anchor",
3296
- code_refs: args.code_refs,
3297
- repo: args.repo,
3298
- repo_space_uid: args.repo_space_uid,
3299
- agent_id: args.agent_id,
3300
- // Keeps the default adapter anchored to the live session directory
3301
- // rather than wherever the MCP server process was launched.
3302
- invocation_context: args.invocation_context
3303
- },
3304
- adapter
3305
- );
3306
- const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
3307
- if (!anchorResult.isError) {
3308
- const codeClient = client2;
3309
- const anchorUids = (anchors.anchors ?? []).flatMap(
3310
- (anchor) => anchor.uid ? [anchor.uid] : []
3369
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
3370
+ const labels = /* @__PURE__ */ new Map();
3371
+ for (const node of rawNodes) {
3372
+ const item = compactItem(node);
3373
+ if (item) {
3374
+ if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
3375
+ items.push(item);
3376
+ const uid = text(item.uid);
3377
+ const label = text(item.label);
3378
+ if (uid && label) labels.set(uid, label);
3379
+ }
3380
+ }
3381
+ for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
3382
+ const edge = obj2(rawEdge);
3383
+ const fromUid = text(edge?.from_uid);
3384
+ const toUid = text(edge?.to_uid);
3385
+ const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
3386
+ if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
3387
+ continue;
3388
+ }
3389
+ items.push(
3390
+ defined({
3391
+ type: "Relationship",
3392
+ from_uid: fromUid,
3393
+ to_uid: toUid,
3394
+ edge_type: edgeType,
3395
+ from_label: labels.get(fromUid),
3396
+ to_label: labels.get(toUid)
3397
+ })
3311
3398
  );
3312
- const relateFailures = [];
3313
- for (const targetUid of targetUids) {
3314
- for (const anchorUid of anchorUids) {
3315
- try {
3316
- await codeClient.entity({
3317
- action: "relate",
3318
- source_uid: targetUid,
3319
- target_uid: anchorUid,
3320
- edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
3321
- agent_id: args.agent_id
3322
- });
3323
- } catch (cause) {
3324
- relateFailures.push(
3325
- `${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
3326
- );
3327
- }
3328
- }
3399
+ }
3400
+ for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
3401
+ const item = compactItem(chunk, "SourceExcerpt");
3402
+ if (item) items.push(item);
3403
+ }
3404
+ return items;
3405
+ }
3406
+ function resumeCandidates(raw) {
3407
+ const payload = obj2(raw);
3408
+ if (!payload) return [];
3409
+ const items = [];
3410
+ for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
3411
+ const item = compactItem(payload[key], key);
3412
+ if (item) items.push(item);
3413
+ }
3414
+ for (const value of Array.isArray(payload.other_eligible) ? payload.other_eligible : []) {
3415
+ const item = compactItem(value, "Task");
3416
+ if (item) items.push(item);
3417
+ }
3418
+ for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
3419
+ for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
3420
+ const item = compactItem(value, key.replace(/s$/, ""));
3421
+ if (item) items.push(item);
3329
3422
  }
3330
- if (relateFailures.length > 0) {
3331
- return ok2({
3332
- result: original,
3333
- code_anchors: {
3334
- ...anchors,
3335
- caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
3336
- }
3337
- });
3423
+ }
3424
+ const knowledge = obj2(payload.knowledge);
3425
+ for (const key of ["lessons", "risks"]) {
3426
+ for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
3427
+ const item = compactItem(value, key.replace(/s$/, ""));
3428
+ if (item) items.push(item);
3338
3429
  }
3339
3430
  }
3340
- return ok2({ result: original, code_anchors: anchors });
3431
+ return items;
3341
3432
  }
3342
- async function anchorRepository(client2, repository, spaceUid, agentId) {
3343
- return client2.entity({
3344
- action: "create",
3345
- label: repository.repoId,
3346
- props: {
3347
- entity_type: "repository",
3348
- canonical_name: repository.repoId,
3349
- identifiers: { repo_id: repository.repoId }
3350
- },
3351
- identity: {
3352
- namespace: "external.code",
3353
- key_version: 1,
3354
- key: repositoryIdentityKey(repository)
3355
- },
3356
- identity_space_uid: spaceUid,
3357
- agent_id: agentId
3433
+ function visibleItems(candidates, rendered2) {
3434
+ const seen = /* @__PURE__ */ new Set();
3435
+ return candidates.filter((item) => {
3436
+ const uid = text(item.uid);
3437
+ if (uid) {
3438
+ if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
3439
+ seen.add(uid);
3440
+ return true;
3441
+ }
3442
+ if (item.type === "Relationship") {
3443
+ const from = text(item.from_label);
3444
+ const to = text(item.to_label);
3445
+ const edgeType = text(item.edge_type);
3446
+ if (!from || !to || !edgeType) return false;
3447
+ const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
3448
+ if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
3449
+ seen.add(key);
3450
+ return true;
3451
+ }
3452
+ return false;
3358
3453
  });
3359
3454
  }
3360
- async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
3361
- const response = await client2.entity({
3362
- action: "create",
3363
- label: ref.qualifiedName,
3364
- props: {
3365
- entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
3366
- canonical_name: ref.qualifiedName,
3367
- identifiers: {
3368
- repo_id: ref.repoId,
3369
- path: ref.path,
3370
- qualified_name: ref.qualifiedName
3371
- },
3372
- attributes: { code_ref: storedCodeRef(ref) }
3373
- },
3374
- identity: {
3375
- namespace: "external.code",
3376
- key_version: 1,
3377
- key: codeRefIdentityKey(ref)
3378
- },
3379
- identity_space_uid: spaceUid,
3380
- agent_id: agentId
3381
- });
3382
- if (response.uid && response.created !== false && response.status !== "existing") {
3383
- await client2.entity({
3384
- action: "relate",
3385
- source_uid: response.uid,
3386
- target_uid: repositoryUid,
3387
- edge_type: "PART_OF",
3388
- agent_id: agentId
3455
+ function latestTimestamp(value) {
3456
+ let latest;
3457
+ const visit = (current) => {
3458
+ if (Array.isArray(current)) {
3459
+ for (const item of current) visit(item);
3460
+ return;
3461
+ }
3462
+ const record2 = obj2(current);
3463
+ if (!record2) return;
3464
+ for (const [key, item] of Object.entries(record2)) {
3465
+ 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))) {
3466
+ latest = item;
3467
+ }
3468
+ visit(item);
3469
+ }
3470
+ };
3471
+ visit(value);
3472
+ return latest;
3473
+ }
3474
+ function contextWarnings(raw, rendered2, available, returned) {
3475
+ const warnings = [
3476
+ {
3477
+ code: "delivery_unverified",
3478
+ message: "The MCP server returned this context but cannot verify that the host injected or used it."
3479
+ }
3480
+ ];
3481
+ const serialized = JSON.stringify(raw);
3482
+ if (available > returned || rendered2.includes("item(s) omitted")) {
3483
+ warnings.push({
3484
+ code: "context_bounded",
3485
+ message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
3389
3486
  });
3390
3487
  }
3391
- return { ...response, ref };
3488
+ if (/"superseded":(?:true|\{)/.test(serialized)) {
3489
+ warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
3490
+ }
3491
+ if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
3492
+ warnings.push({
3493
+ code: "validity_warning",
3494
+ message: "Some retrieved items are outside their current or requested validity window."
3495
+ });
3496
+ }
3497
+ const payload = obj2(raw);
3498
+ if (payload?.truncated === true || obj2(payload?.truncation)) {
3499
+ const truncation = obj2(payload?.truncation);
3500
+ if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
3501
+ warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
3502
+ }
3503
+ }
3504
+ if (text(payload?.warning)) {
3505
+ warnings.push({ code: "source_warning", message: text(payload?.warning) });
3506
+ }
3507
+ return warnings;
3392
3508
  }
3393
- async function resolveMaterializedUid(client2, ref, agentId) {
3394
- const response = await client2.entity({
3395
- action: "resolve_identity",
3396
- identity: {
3397
- namespace: "external.code",
3398
- key_version: 1,
3399
- key: codeRefIdentityKey(ref)
3400
- },
3401
- agent_id: agentId
3402
- });
3403
- return response.status === "existing" ? response.uid : void 0;
3509
+ function error(message) {
3510
+ return {
3511
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
3512
+ isError: true
3513
+ };
3404
3514
  }
3405
- async function codeRefFromAnchor(client2, adapter, uid) {
3406
- const node = await client2.getNode(uid);
3407
- const attributes = node.props?.attributes;
3408
- if (!attributes || typeof attributes !== "object") return void 0;
3409
- const codeRef = attributes.code_ref;
3410
- if (!isStoredCodeRef(codeRef)) return void 0;
3411
- try {
3412
- const repository = await adapter.resolveRepository(codeRef.repoId);
3413
- return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
3414
- } catch (cause) {
3415
- if (cause instanceof UnknownRepositoryError) {
3416
- return { missingRepo: codeRef.repoId };
3515
+ async function handleMemoryTool(client2, args, config = {}) {
3516
+ const action = text(args.action);
3517
+ if (action === "status") {
3518
+ const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
3519
+ return {
3520
+ content: [{ type: "text", text: JSON.stringify(status) }],
3521
+ structuredContent: status
3522
+ };
3523
+ }
3524
+ if (action !== "context") {
3525
+ return error("action must be 'status' or 'context'");
3526
+ }
3527
+ const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
3528
+ if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
3529
+ return error("action='context' requires mode='topic', 'continuity', or 'resume'");
3530
+ }
3531
+ const budget = outputBudget(args.max_output_chars);
3532
+ const agentId = text(args.agent_id) ?? config.agentId;
3533
+ let raw;
3534
+ let rendered2;
3535
+ let strategy;
3536
+ let candidates;
3537
+ let workspace;
3538
+ if (mode === "topic") {
3539
+ const query = text(args.query);
3540
+ if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
3541
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
3542
+ const request = defined({
3543
+ query,
3544
+ project_uid: text(args.project_uid),
3545
+ node_limit: limit,
3546
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
3547
+ layer: text(args.layer),
3548
+ chunk_limit: args.include_chunks === true ? limit : 0,
3549
+ article_limit: args.include_documents === true ? limit : 0,
3550
+ valid_at: text(args.valid_at),
3551
+ include_graph: true,
3552
+ graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
3553
+ graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
3554
+ });
3555
+ raw = await client2.retrieveContext(request);
3556
+ rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
3557
+ strategy = "retrieve_context";
3558
+ candidates = topicCandidates(raw);
3559
+ } else {
3560
+ if (args.scope !== void 0 && args.scope !== "workspace") {
3561
+ return error("scope must be 'workspace' when given");
3417
3562
  }
3418
- throw cause;
3563
+ if (args.scope === "workspace") {
3564
+ if (!config.resolveWorkspaceScope) {
3565
+ return error(
3566
+ "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."
3567
+ );
3568
+ }
3569
+ workspace = await config.resolveWorkspaceScope(agentId ?? "mcp");
3570
+ if (workspace.uids.length === 0) {
3571
+ return error(
3572
+ `scope='workspace' resolved no repositories${workspace.failures.length > 0 ? ` (${workspace.failures.join("; ")})` : ""}`
3573
+ );
3574
+ }
3575
+ }
3576
+ const scopeUids = Array.from(
3577
+ /* @__PURE__ */ new Set([
3578
+ ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
3579
+ ...text(args.project_uid) ? [text(args.project_uid)] : [],
3580
+ ...workspace?.uids ?? []
3581
+ ])
3582
+ );
3583
+ raw = await client2.plan(
3584
+ defined({
3585
+ action: "resume_work",
3586
+ task_uid: text(args.task_uid),
3587
+ session_uid: text(args.session_uid),
3588
+ scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
3589
+ limit: workspace ? WORKSPACE_RESUME_LIMIT : 1,
3590
+ agent_id: agentId
3591
+ })
3592
+ );
3593
+ rendered2 = renderResumeContext(raw, budget, workspace?.names) ?? "No eligible work was found in the requested scope.";
3594
+ strategy = "resume_work";
3595
+ candidates = resumeCandidates(raw);
3419
3596
  }
3597
+ const items = visibleItems(candidates, rendered2);
3598
+ const renderedHash = sha256(rendered2);
3599
+ const scope = defined({
3600
+ org_id: config.orgId,
3601
+ agent_id: agentId,
3602
+ harness: config.harness ?? "generic",
3603
+ project_uid: text(args.project_uid),
3604
+ task_uid: text(args.task_uid),
3605
+ session_uid: text(args.session_uid),
3606
+ scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0,
3607
+ workspace_repositories: workspace ? Object.values(workspace.names).sort() : void 0
3608
+ });
3609
+ const contextId = `ctx_${sha256(
3610
+ stableJson2({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
3611
+ ).slice(0, 24)}`;
3612
+ const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
3613
+ const result = {
3614
+ schema_version: RESULT_SCHEMA_VERSION,
3615
+ action: "context",
3616
+ context_id: contextId,
3617
+ mode,
3618
+ scope,
3619
+ rendered_context: rendered2,
3620
+ rendered_hash: renderedHash,
3621
+ items,
3622
+ retrieval_trace: defined({
3623
+ strategy,
3624
+ query: text(args.query),
3625
+ project_uid: text(args.project_uid),
3626
+ task_uid: text(args.task_uid),
3627
+ session_uid: text(args.session_uid),
3628
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
3629
+ graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
3630
+ graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
3631
+ available_items: candidates.length,
3632
+ returned_items: items.length,
3633
+ omitted_items: Math.max(0, candidates.length - items.length)
3634
+ }),
3635
+ budget: {
3636
+ unit: "characters",
3637
+ requested_chars: budget,
3638
+ used_chars: rendered2.length,
3639
+ available_items: candidates.length,
3640
+ returned_items: items.length,
3641
+ omitted_items: Math.max(0, candidates.length - items.length),
3642
+ bounded
3643
+ },
3644
+ freshness: defined({
3645
+ valid_at: text(args.valid_at),
3646
+ latest_source_timestamp: latestTimestamp(raw)
3647
+ }),
3648
+ warnings: [
3649
+ ...contextWarnings(raw, rendered2, candidates.length, items.length),
3650
+ ...workspace && workspace.failures.length > 0 ? [
3651
+ {
3652
+ code: "workspace_scope_partial",
3653
+ message: `${workspace.failures.length} declared repositories did not resolve and were left out: ${workspace.failures.join("; ")}`
3654
+ }
3655
+ ] : []
3656
+ ]
3657
+ };
3658
+ return {
3659
+ content: [{ type: "text", text: rendered2 }],
3660
+ structuredContent: result
3661
+ };
3420
3662
  }
3421
- async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
3422
- if (refs.length > 1 || anchorUids.length > 1) {
3423
- return { error: err3("ambiguous_target", "expand/affected accepts exactly one code target") };
3424
- }
3425
- if (anchorUids.length === 1) {
3426
- const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
3427
- if (!outcome) {
3428
- return { error: err3("invalid_anchor", "anchor UID does not contain a typed code_ref") };
3429
- }
3430
- if ("missingRepo" in outcome) {
3431
- return {
3432
- error: err3(
3433
- "repository_unavailable",
3434
- `repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
3435
- )
3436
- };
3437
- }
3438
- return { ref: outcome.ref };
3439
- }
3440
- if (refs.length !== 1) {
3441
- return { error: err3("missing_code_ref", "one code_ref or anchor_uid is required") };
3663
+
3664
+ // src/resources.ts
3665
+ var RESOURCES = [
3666
+ {
3667
+ uri: "mindgraph://memory/status",
3668
+ name: "Memory Integration Status",
3669
+ description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
3670
+ mimeType: "application/json"
3671
+ },
3672
+ {
3673
+ uri: "mindgraph://stats",
3674
+ name: "Graph Statistics",
3675
+ description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
3676
+ mimeType: "application/json"
3677
+ },
3678
+ {
3679
+ uri: "mindgraph://goals",
3680
+ name: "Active Goals",
3681
+ description: "All active goals in the knowledge graph",
3682
+ mimeType: "application/json"
3683
+ },
3684
+ {
3685
+ uri: "mindgraph://questions",
3686
+ name: "Open Questions",
3687
+ description: "All open questions in the knowledge graph",
3688
+ mimeType: "application/json"
3689
+ },
3690
+ {
3691
+ uri: "mindgraph://contradictions",
3692
+ name: "Contradictions",
3693
+ description: "Contradictory claims in the knowledge graph",
3694
+ mimeType: "application/json"
3695
+ },
3696
+ {
3697
+ uri: "mindgraph://decisions",
3698
+ name: "Open Decisions",
3699
+ description: "Unresolved decisions awaiting resolution",
3700
+ mimeType: "application/json"
3442
3701
  }
3443
- const resolution = await adapter.resolveRefs(refs, options);
3444
- const result = resolution.results[0];
3445
- if (!result.resolved) {
3446
- return {
3447
- error: ok2({
3448
- availability: resolution.availability,
3449
- result,
3450
- wrote: false
3451
- })
3452
- };
3702
+ ];
3703
+ var RESOURCE_TEMPLATES = [
3704
+ {
3705
+ uriTemplate: "mindgraph://node/{uid}",
3706
+ name: "Graph Node",
3707
+ description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
3708
+ mimeType: "application/json"
3709
+ },
3710
+ {
3711
+ uriTemplate: "mindgraph://search/{query}",
3712
+ name: "Search Results",
3713
+ description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
3714
+ mimeType: "application/json"
3715
+ },
3716
+ {
3717
+ uriTemplate: "mindgraph://layer/{layer}",
3718
+ name: "Layer Nodes",
3719
+ description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
3720
+ mimeType: "application/json"
3453
3721
  }
3454
- return { ref: result.resolved };
3722
+ ];
3723
+ var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
3724
+ function json(uri, data) {
3725
+ return {
3726
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
3727
+ };
3455
3728
  }
3456
- async function joinMaterialized(client2, refs, agentId, limit) {
3457
- const joined = [];
3458
- for (const ref of uniqueRefs(refs).slice(0, limit)) {
3459
- const uid = await resolveMaterializedUid(client2, ref, agentId);
3460
- if (!uid) continue;
3461
- joined.push({
3462
- ref,
3463
- anchor_uid: uid,
3464
- graph: await client2.traverse({
3465
- action: "neighborhood",
3466
- start_uid: uid,
3467
- depth: 2,
3468
- max_nodes: 20
3469
- })
3470
- });
3729
+ async function readResource(client2, uri, memoryConfig = {}) {
3730
+ switch (uri) {
3731
+ case "mindgraph://memory/status":
3732
+ return json(uri, memoryStatus(memoryConfig));
3733
+ case "mindgraph://stats":
3734
+ return json(uri, await client2.stats());
3735
+ case "mindgraph://goals":
3736
+ return json(uri, await client2.getGoals());
3737
+ case "mindgraph://questions":
3738
+ return json(uri, await client2.getOpenQuestions());
3739
+ case "mindgraph://contradictions":
3740
+ return json(uri, await client2.getContradictions());
3741
+ case "mindgraph://decisions":
3742
+ return json(uri, await client2.getOpenDecisions());
3471
3743
  }
3472
- return joined;
3473
- }
3474
- async function codeRefsFromGraphContext(client2, adapter, context) {
3475
- const uids = /* @__PURE__ */ new Set();
3476
- collectUids(context, uids);
3477
- if (uids.size === 0) return { refs: [], unavailable: [] };
3478
- const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
3479
- const stored = nodes.flatMap((node) => {
3480
- if (!node) return [];
3481
- const attributes = node.props?.attributes;
3482
- if (!attributes || typeof attributes !== "object") return [];
3483
- const codeRef = attributes.code_ref;
3484
- return isStoredCodeRef(codeRef) ? [codeRef] : [];
3485
- });
3486
- const refs = [];
3487
- const unavailable = /* @__PURE__ */ new Set();
3488
- for (const ref of stored) {
3489
- try {
3490
- const repository = await adapter.resolveRepository(ref.repoId);
3491
- refs.push({ ...ref, repoRoot: repository.repoRoot });
3492
- } catch (cause) {
3493
- if (cause instanceof UnknownRepositoryError) {
3494
- unavailable.add(ref.repoId);
3495
- } else {
3496
- throw cause;
3497
- }
3498
- }
3744
+ const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
3745
+ if (nodeMatch) {
3746
+ const uid = decodeURIComponent(nodeMatch[1]);
3747
+ const node = await client2.getNode(uid);
3748
+ const edges = await client2.getEdges({ from_uid: uid });
3749
+ const inEdges = await client2.getEdges({ to_uid: uid });
3750
+ return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
3499
3751
  }
3500
- return { refs, unavailable: [...unavailable].sort() };
3501
- }
3502
- function collectUids(value, uids) {
3503
- if (Array.isArray(value)) {
3504
- value.forEach((item) => collectUids(item, uids));
3505
- return;
3752
+ const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
3753
+ if (searchMatch) {
3754
+ const query = decodeURIComponent(searchMatch[1]);
3755
+ return json(uri, await client2.search(query, { limit: 20 }));
3506
3756
  }
3507
- if (!value || typeof value !== "object") return;
3508
- for (const [key, child] of Object.entries(value)) {
3509
- if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
3510
- uids.add(child);
3511
- } else {
3512
- collectUids(child, uids);
3513
- }
3757
+ const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
3758
+ if (layerMatch) {
3759
+ const layer = decodeURIComponent(layerMatch[1]);
3760
+ return json(uri, await client2.getNodes({ layer, limit: 50 }));
3514
3761
  }
3762
+ throw new Error(`Unknown resource: ${uri}`);
3515
3763
  }
3516
- function isStoredCodeRef(value) {
3517
- if (!value || typeof value !== "object") return false;
3518
- const ref = value;
3519
- 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";
3520
- }
3521
- function storedCodeRef(ref) {
3522
- const { repoRoot: _localOnly, ...stored } = ref;
3523
- return stored;
3524
- }
3525
- function uniqueRefs(refs) {
3526
- const seen = /* @__PURE__ */ new Set();
3527
- return refs.filter((ref) => {
3528
- const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
3529
- if (seen.has(key)) return false;
3530
- seen.add(key);
3531
- return true;
3532
- });
3533
- }
3534
- function primaryUids(value) {
3535
- if (!value || typeof value !== "object") return [];
3536
- const object = value;
3537
- const keys = [
3538
- "uid",
3539
- "claim_uid",
3540
- "goal_uid",
3541
- "project_uid",
3542
- "milestone_uid",
3543
- "decision_uid",
3544
- "task_uid",
3545
- "plan_uid",
3546
- "step_uid",
3547
- "execution_uid",
3548
- "lesson_uid"
3549
- ];
3550
- return [
3551
- ...new Set(
3552
- keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
3553
- )
3554
- ];
3764
+ async function readGovernedResource(client2, uri, governanceConfig, memoryConfig = {}) {
3765
+ const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
3766
+ const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
3767
+ const governance = await checkMcpGovernance(
3768
+ toolName,
3769
+ toolArgs,
3770
+ { ...governanceConfig, context: { resource_uri: uri } }
3771
+ );
3772
+ if (!governance.allowed) {
3773
+ throw new Error(governance.message);
3774
+ }
3775
+ return readResource(client2, uri, memoryConfig);
3555
3776
  }
3556
3777
 
3778
+ // src/tools.ts
3779
+ var import_node_crypto6 = require("crypto");
3780
+
3557
3781
  // src/sync-tool.ts
3558
3782
  var import_node_crypto5 = __toESM(require("crypto"));
3559
- var import_node_fs4 = __toESM(require("fs"));
3560
- var import_node_path4 = __toESM(require("path"));
3783
+ var import_node_fs5 = __toESM(require("fs"));
3784
+ var import_node_path5 = __toESM(require("path"));
3561
3785
  var ok3 = (data) => ({
3562
3786
  content: [{ type: "text", text: JSON.stringify(data) }]
3563
3787
  });
@@ -3634,12 +3858,12 @@ var SYNC_TOOL = {
3634
3858
  }
3635
3859
  };
3636
3860
  function safePath(root, candidate) {
3637
- const absolute = import_node_path4.default.resolve(root, candidate);
3638
- const relative2 = import_node_path4.default.relative(root, absolute);
3639
- if (relative2.startsWith("..") || import_node_path4.default.isAbsolute(relative2)) {
3861
+ const absolute = import_node_path5.default.resolve(root, candidate);
3862
+ const relative2 = import_node_path5.default.relative(root, absolute);
3863
+ if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
3640
3864
  throw new Error(`path escapes repository root: ${candidate}`);
3641
3865
  }
3642
- if (import_node_fs4.default.existsSync(absolute)) {
3866
+ if (import_node_fs5.default.existsSync(absolute)) {
3643
3867
  if (!pathContains(realPathOf(root), realPathOf(absolute))) {
3644
3868
  throw new Error(`path escapes repository root via symlink: ${candidate}`);
3645
3869
  }
@@ -3648,25 +3872,25 @@ function safePath(root, candidate) {
3648
3872
  }
3649
3873
  function realPathOf(target) {
3650
3874
  try {
3651
- return import_node_fs4.default.realpathSync(target);
3875
+ return import_node_fs5.default.realpathSync(target);
3652
3876
  } catch {
3653
- return import_node_path4.default.resolve(target);
3877
+ return import_node_path5.default.resolve(target);
3654
3878
  }
3655
3879
  }
3656
3880
  function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
3657
- if (!import_node_fs4.default.existsSync(target)) return [];
3881
+ if (!import_node_fs5.default.existsSync(target)) return [];
3658
3882
  const real = realPathOf(target);
3659
3883
  if (!pathContains(realRoot, real)) return [];
3660
- const stat = import_node_fs4.default.statSync(target);
3884
+ const stat = import_node_fs5.default.statSync(target);
3661
3885
  if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
3662
3886
  if (!stat.isDirectory() || visited.has(real)) return [];
3663
3887
  visited.add(real);
3664
- return import_node_fs4.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path4.default.join(target, entry.name), realRoot, visited));
3888
+ return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name), realRoot, visited));
3665
3889
  }
3666
3890
  function fileDescriptor(root, absolute) {
3667
- const content = import_node_fs4.default.readFileSync(absolute, "utf8");
3891
+ const content = import_node_fs5.default.readFileSync(absolute, "utf8");
3668
3892
  return {
3669
- logical_path: import_node_path4.default.relative(root, absolute).split(import_node_path4.default.sep).join("/"),
3893
+ logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
3670
3894
  content_hash: import_node_crypto5.default.createHash("sha256").update(content).digest("hex"),
3671
3895
  bytes: Buffer.byteLength(content),
3672
3896
  content
@@ -3736,7 +3960,7 @@ async function handleSyncTool(client2, args) {
3736
3960
  }
3737
3961
  if (action === "status" && request.logical_path && request.file_present !== false) {
3738
3962
  const absolute = safePath(root, String(request.logical_path));
3739
- request.file_present = import_node_fs4.default.existsSync(absolute);
3963
+ request.file_present = import_node_fs5.default.existsSync(absolute);
3740
3964
  if (request.file_present && !request.content_hash) {
3741
3965
  request.content_hash = fileDescriptor(root, absolute).content_hash;
3742
3966
  }
@@ -6358,16 +6582,16 @@ function buildMindGraphServer(wiring) {
6358
6582
  }
6359
6583
 
6360
6584
  // src/index.ts
6361
- var import_node_path6 = __toESM(require("path"));
6585
+ var import_node_path7 = __toESM(require("path"));
6362
6586
 
6363
6587
  // src/hook-installer.ts
6364
- var import_node_fs5 = __toESM(require("fs"));
6588
+ var import_node_fs6 = __toESM(require("fs"));
6365
6589
  var import_node_os4 = __toESM(require("os"));
6366
- var import_node_path5 = __toESM(require("path"));
6590
+ var import_node_path6 = __toESM(require("path"));
6367
6591
  var import_node_crypto7 = __toESM(require("crypto"));
6368
6592
  function storedFeatures(homeDir) {
6369
6593
  const { promptRecall, capture } = loadHookEnv(
6370
- import_node_path5.default.join(homeDir || import_node_os4.default.homedir(), ".mindgraph")
6594
+ import_node_path6.default.join(homeDir || import_node_os4.default.homedir(), ".mindgraph")
6371
6595
  );
6372
6596
  return { promptRecall, capture };
6373
6597
  }
@@ -6381,24 +6605,24 @@ function windowsCommand(harness) {
6381
6605
  }
6382
6606
  var RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
6383
6607
  function installHookRunner(sourceFile, homeDir, version) {
6384
- const target = import_node_path5.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
6385
- import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(target), { recursive: true });
6608
+ const target = import_node_path6.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
6609
+ import_node_fs6.default.mkdirSync(import_node_path6.default.dirname(target), { recursive: true });
6386
6610
  const temporary = `${target}.${process.pid}.${import_node_crypto7.default.randomUUID()}.tmp`;
6387
- import_node_fs5.default.copyFileSync(sourceFile, temporary);
6388
- import_node_fs5.default.renameSync(temporary, target);
6389
- const versionPath = import_node_path5.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
6611
+ import_node_fs6.default.copyFileSync(sourceFile, temporary);
6612
+ import_node_fs6.default.renameSync(temporary, target);
6613
+ const versionPath = import_node_path6.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
6390
6614
  if (version) {
6391
- import_node_fs5.default.writeFileSync(versionPath, `${version}
6615
+ import_node_fs6.default.writeFileSync(versionPath, `${version}
6392
6616
  `);
6393
6617
  } else {
6394
- import_node_fs5.default.rmSync(versionPath, { force: true });
6618
+ import_node_fs6.default.rmSync(versionPath, { force: true });
6395
6619
  }
6396
6620
  return target;
6397
6621
  }
6398
6622
  function installedHookRunnerVersion(homeDir) {
6399
6623
  try {
6400
- const raw = import_node_fs5.default.readFileSync(
6401
- import_node_path5.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE),
6624
+ const raw = import_node_fs6.default.readFileSync(
6625
+ import_node_path6.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE),
6402
6626
  "utf8"
6403
6627
  ).trim();
6404
6628
  return raw || void 0;
@@ -6424,10 +6648,10 @@ function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
6424
6648
  return { refreshed: false, reason: "disabled" };
6425
6649
  }
6426
6650
  const home = homeDir || import_node_os4.default.homedir();
6427
- if (!import_node_fs5.default.existsSync(import_node_path5.default.join(home, RUNNER_RELATIVE))) {
6651
+ if (!import_node_fs6.default.existsSync(import_node_path6.default.join(home, RUNNER_RELATIVE))) {
6428
6652
  return { refreshed: false, reason: "no_runner" };
6429
6653
  }
6430
- if (!import_node_fs5.default.existsSync(sourceBundle)) {
6654
+ if (!import_node_fs6.default.existsSync(sourceBundle)) {
6431
6655
  return { refreshed: false, reason: "no_source" };
6432
6656
  }
6433
6657
  const server = parseSemver(serverVersion);
@@ -6458,8 +6682,8 @@ function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
6458
6682
  for (const scope of scopes) {
6459
6683
  try {
6460
6684
  const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
6461
- if (!import_node_fs5.default.existsSync(file)) continue;
6462
- if (!import_node_fs5.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
6685
+ if (!import_node_fs6.default.existsSync(file)) continue;
6686
+ if (!import_node_fs6.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
6463
6687
  installHooks(
6464
6688
  "claude-code",
6465
6689
  scope,
@@ -6479,15 +6703,15 @@ function versionSkewNote(serverVersion, homeDir) {
6479
6703
  if (!runnerVersion || runnerVersion === serverVersion) return void 0;
6480
6704
  return `NOTE: the installed MindGraph hook runner is v${runnerVersion} but this MCP server is v${serverVersion} \u2014 hook behavior and tool contracts may be out of sync. Tell the user to run: npx -y mindgraph-mcp@latest install-hooks (refreshes the pinned runner).`;
6481
6705
  }
6482
- function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path5.default.join(import_node_os4.default.homedir(), ".codex"), homeDir) {
6706
+ function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path6.default.join(import_node_os4.default.homedir(), ".codex"), homeDir) {
6483
6707
  if (harness === "codex") {
6484
- return scope === "user" ? import_node_path5.default.join(codexHomeDir, "hooks.json") : import_node_path5.default.join(projectDir, ".codex", "hooks.json");
6708
+ return scope === "user" ? import_node_path6.default.join(codexHomeDir, "hooks.json") : import_node_path6.default.join(projectDir, ".codex", "hooks.json");
6485
6709
  }
6486
- return scope === "user" ? import_node_path5.default.join(homeDir || import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path5.default.join(projectDir, ".claude", "settings.json");
6710
+ return scope === "user" ? import_node_path6.default.join(homeDir || import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path6.default.join(projectDir, ".claude", "settings.json");
6487
6711
  }
6488
6712
  function readSettings(file) {
6489
- if (!import_node_fs5.default.existsSync(file)) return {};
6490
- return JSON.parse(import_node_fs5.default.readFileSync(file, "utf8"));
6713
+ if (!import_node_fs6.default.existsSync(file)) return {};
6714
+ return JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
6491
6715
  }
6492
6716
  function ownedHook(harness, timeout, additionalContextLimit) {
6493
6717
  return {
@@ -6576,13 +6800,13 @@ function entryHasOwnedHook(value) {
6576
6800
  return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
6577
6801
  }
6578
6802
  function writeSettings(file, settings) {
6579
- import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
6803
+ import_node_fs6.default.mkdirSync(import_node_path6.default.dirname(file), { recursive: true });
6580
6804
  const temporary = `${file}.${process.pid}.${import_node_crypto7.default.randomUUID()}.tmp`;
6581
- import_node_fs5.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
6805
+ import_node_fs6.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
6582
6806
  `, {
6583
6807
  mode: 384
6584
6808
  });
6585
- import_node_fs5.default.renameSync(temporary, file);
6809
+ import_node_fs6.default.renameSync(temporary, file);
6586
6810
  }
6587
6811
  function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir, features = {}) {
6588
6812
  const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
@@ -6715,7 +6939,7 @@ if (PROFILE === "coding") {
6715
6939
  }
6716
6940
  var RUNNER_REFRESH = (() => {
6717
6941
  try {
6718
- return refreshHookRunner("0.25.0", import_node_path6.default.join(__dirname, "cli.js"));
6942
+ return refreshHookRunner("0.26.0", import_node_path7.default.join(__dirname, "cli.js"));
6719
6943
  } catch {
6720
6944
  return void 0;
6721
6945
  }
@@ -6729,12 +6953,12 @@ if (RUNNER_REFRESH?.refreshed) {
6729
6953
  }
6730
6954
  })();
6731
6955
  console.error(
6732
- `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.25.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6956
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.26.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6733
6957
  );
6734
6958
  }
6735
6959
  var HOOK_SKEW_NOTE = (() => {
6736
6960
  try {
6737
- return versionSkewNote("0.25.0");
6961
+ return versionSkewNote("0.26.0");
6738
6962
  } catch {
6739
6963
  return void 0;
6740
6964
  }
@@ -6755,16 +6979,21 @@ var GOVERNANCE_CONFIG = {
6755
6979
  }
6756
6980
  };
6757
6981
  var MEMORY_RUNTIME_CONFIG = {
6758
- serverVersion: "0.25.0",
6982
+ serverVersion: "0.26.0",
6759
6983
  baseUrl: BASE_URL,
6760
6984
  profile: PROFILE,
6761
6985
  harness: HARNESS,
6762
6986
  orgId: ORG_ID,
6763
- agentId: AGENT_ID
6987
+ agentId: AGENT_ID,
6988
+ resolveWorkspaceScope: (agentId) => resolveRepositoryScope(
6989
+ process.env.CLAUDE_PROJECT_DIR || process.cwd(),
6990
+ client,
6991
+ agentId
6992
+ )
6764
6993
  };
6765
6994
  var buildServer = () => buildMindGraphServer({
6766
6995
  client,
6767
- serverVersion: "0.25.0",
6996
+ serverVersion: "0.26.0",
6768
6997
  instructions: INSTRUCTIONS,
6769
6998
  profile: PROFILE,
6770
6999
  agentId: AGENT_ID,
@@ -6777,7 +7006,7 @@ async function main() {
6777
7006
  onerror: (error2) => console.error("MindGraph MCP transport error:", error2.message)
6778
7007
  });
6779
7008
  console.error(
6780
- `MindGraph MCP server v${"0.25.0"} running on stdio (${BASE_URL})`
7009
+ `MindGraph MCP server v${"0.26.0"} running on stdio (${BASE_URL})`
6781
7010
  );
6782
7011
  }
6783
7012
  main().catch((error2) => {