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