mindgraph-mcp 0.16.0 → 0.18.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 +57 -4
- package/dist/cli.js +879 -335
- package/dist/index.js +718 -119
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -94,6 +94,24 @@ var import_promises = require("fs/promises");
|
|
|
94
94
|
var import_node_os2 = require("os");
|
|
95
95
|
var import_node_path2 = require("path");
|
|
96
96
|
var import_node_crypto2 = require("crypto");
|
|
97
|
+
var UnknownRepositoryError = class extends Error {
|
|
98
|
+
constructor(repo) {
|
|
99
|
+
super(
|
|
100
|
+
`unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
|
|
101
|
+
);
|
|
102
|
+
this.repo = repo;
|
|
103
|
+
this.name = "UnknownRepositoryError";
|
|
104
|
+
}
|
|
105
|
+
repo;
|
|
106
|
+
};
|
|
107
|
+
function invocationCwd(args) {
|
|
108
|
+
const context = args.invocation_context;
|
|
109
|
+
if (context && typeof context === "object" && !Array.isArray(context)) {
|
|
110
|
+
const cwd = context.cwd;
|
|
111
|
+
if (typeof cwd === "string" && cwd) return cwd;
|
|
112
|
+
}
|
|
113
|
+
return process.cwd();
|
|
114
|
+
}
|
|
97
115
|
function unavailabilityCaveats(reason, message) {
|
|
98
116
|
const caveats = [message || "codegraph status failed"];
|
|
99
117
|
if (reason === "absent") {
|
|
@@ -106,14 +124,14 @@ function unavailabilityCaveats(reason, message) {
|
|
|
106
124
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
107
125
|
var DEFAULT_STATUS_TTL_MS = 6e4;
|
|
108
126
|
var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
109
|
-
function normalizePath(
|
|
110
|
-
return
|
|
127
|
+
function normalizePath(path5) {
|
|
128
|
+
return path5.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
111
129
|
}
|
|
112
130
|
function normalizeSignature(signature) {
|
|
113
131
|
return signature.replace(/\s+/g, " ").trim();
|
|
114
132
|
}
|
|
115
|
-
function languageFromPath(
|
|
116
|
-
const extension = (0, import_node_path2.extname)(
|
|
133
|
+
function languageFromPath(path5) {
|
|
134
|
+
const extension = (0, import_node_path2.extname)(path5).toLowerCase();
|
|
117
135
|
return {
|
|
118
136
|
".c": "c",
|
|
119
137
|
".cc": "cpp",
|
|
@@ -281,7 +299,26 @@ var CodegraphAdapter = class {
|
|
|
281
299
|
selected.spaceUid
|
|
282
300
|
) : selected;
|
|
283
301
|
}
|
|
284
|
-
|
|
302
|
+
if (repo) {
|
|
303
|
+
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, repo)).catch(
|
|
304
|
+
() => (0, import_node_path2.resolve)(this.cwd, repo)
|
|
305
|
+
);
|
|
306
|
+
if (!(0, import_node_fs2.existsSync)(requestedPath)) {
|
|
307
|
+
const current = await this.currentRepository(fallbackSpaceUid);
|
|
308
|
+
if (current.repoId === repo) return current;
|
|
309
|
+
throw new UnknownRepositoryError(repo);
|
|
310
|
+
}
|
|
311
|
+
const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
|
|
312
|
+
return this.resolveRepositoryIdentity(
|
|
313
|
+
repoRoot,
|
|
314
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
315
|
+
this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return this.currentRepository(fallbackSpaceUid);
|
|
319
|
+
}
|
|
320
|
+
async currentRepository(fallbackSpaceUid) {
|
|
321
|
+
const requestedRoot = this.env.MINDGRAPH_REPO_ROOT || this.cwd;
|
|
285
322
|
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
|
|
286
323
|
() => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
|
|
287
324
|
);
|
|
@@ -371,6 +408,14 @@ var CodegraphAdapter = class {
|
|
|
371
408
|
).catch(() => "");
|
|
372
409
|
return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
|
|
373
410
|
}
|
|
411
|
+
/** Why the workspace map yielded no repositories — for callers that were
|
|
412
|
+
* explicitly asked to fan out and would otherwise return silently empty. */
|
|
413
|
+
workspaceDiagnostic() {
|
|
414
|
+
if (!this.workspaceFile || !(0, import_node_fs2.existsSync)(this.workspaceFile)) {
|
|
415
|
+
return "no .mindgraph/workspace.json was found between the working directory and the filesystem root";
|
|
416
|
+
}
|
|
417
|
+
return `${this.workspaceFile} is malformed or declares no repositories`;
|
|
418
|
+
}
|
|
374
419
|
async availability(repository) {
|
|
375
420
|
const cached = this.statusCache.get(repository.repoRoot);
|
|
376
421
|
if (cached && cached.expiresAt > this.now()) return cached.availability;
|
|
@@ -777,20 +822,20 @@ function codeRefIdentityKey(ref) {
|
|
|
777
822
|
|
|
778
823
|
// src/code-tool.ts
|
|
779
824
|
var ok = (data) => ({
|
|
780
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
825
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
781
826
|
});
|
|
782
827
|
var err = (code, message, details) => ({
|
|
783
828
|
content: [
|
|
784
829
|
{
|
|
785
830
|
type: "text",
|
|
786
|
-
text: JSON.stringify({ error: message, code, details }
|
|
831
|
+
text: JSON.stringify({ error: message, code, details })
|
|
787
832
|
}
|
|
788
833
|
],
|
|
789
834
|
isError: true
|
|
790
835
|
});
|
|
791
836
|
var CODE_TOOL = {
|
|
792
837
|
name: "mindgraph_code",
|
|
793
|
-
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.",
|
|
838
|
+
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.",
|
|
794
839
|
inputSchema: {
|
|
795
840
|
type: "object",
|
|
796
841
|
properties: {
|
|
@@ -832,7 +877,7 @@ var CODE_TOOL = {
|
|
|
832
877
|
},
|
|
833
878
|
repo_space_uid: {
|
|
834
879
|
type: "string",
|
|
835
|
-
description: "Writable shared Project/repository Space for new anchors"
|
|
880
|
+
description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
|
|
836
881
|
},
|
|
837
882
|
task_uid: { type: "string" },
|
|
838
883
|
execution_uid: { type: "string" },
|
|
@@ -848,13 +893,13 @@ var CODE_TOOL = {
|
|
|
848
893
|
openWorldHint: true
|
|
849
894
|
}
|
|
850
895
|
};
|
|
851
|
-
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
896
|
+
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
|
|
852
897
|
const codeClient = client2;
|
|
853
898
|
const action = args.action;
|
|
854
899
|
const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
|
|
855
900
|
const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
|
|
856
901
|
const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
|
|
857
|
-
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid :
|
|
902
|
+
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
|
|
858
903
|
const options = {
|
|
859
904
|
repo: typeof args.repo === "string" ? args.repo : void 0,
|
|
860
905
|
fallbackSpaceUid
|
|
@@ -978,6 +1023,7 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
978
1023
|
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
979
1024
|
let graphContext;
|
|
980
1025
|
const graphRefs = [];
|
|
1026
|
+
let unavailableRepositories = [];
|
|
981
1027
|
if (targetUid) {
|
|
982
1028
|
graphContext = await codeClient.traverse({
|
|
983
1029
|
action: "neighborhood",
|
|
@@ -985,9 +1031,13 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
985
1031
|
depth: 2,
|
|
986
1032
|
max_nodes: 50
|
|
987
1033
|
});
|
|
988
|
-
|
|
989
|
-
|
|
1034
|
+
const fromGraph = await codeRefsFromGraphContext(
|
|
1035
|
+
codeClient,
|
|
1036
|
+
adapter,
|
|
1037
|
+
graphContext
|
|
990
1038
|
);
|
|
1039
|
+
graphRefs.push(...fromGraph.refs);
|
|
1040
|
+
unavailableRepositories = fromGraph.unavailable;
|
|
991
1041
|
}
|
|
992
1042
|
if (refs.length > 0 || anchorUids.length > 0) {
|
|
993
1043
|
const target = await resolveSingleTarget(
|
|
@@ -1006,7 +1056,8 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1006
1056
|
target_uid: targetUid,
|
|
1007
1057
|
graph_context: graphContext,
|
|
1008
1058
|
affected: [],
|
|
1009
|
-
degradation: "no materialized code anchors were attached"
|
|
1059
|
+
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
1060
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
1010
1061
|
});
|
|
1011
1062
|
}
|
|
1012
1063
|
const impacts = await Promise.all(
|
|
@@ -1023,13 +1074,17 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1023
1074
|
target_uid: targetUid,
|
|
1024
1075
|
graph_context: graphContext,
|
|
1025
1076
|
impacts,
|
|
1026
|
-
materialized: joined
|
|
1077
|
+
materialized: joined,
|
|
1078
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
1027
1079
|
});
|
|
1028
1080
|
}
|
|
1029
1081
|
default:
|
|
1030
1082
|
return err("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
1031
1083
|
}
|
|
1032
1084
|
} catch (cause) {
|
|
1085
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
1086
|
+
return err("unknown_repository", cause.message);
|
|
1087
|
+
}
|
|
1033
1088
|
return err("code_tool_failed", errorDetail(cause));
|
|
1034
1089
|
}
|
|
1035
1090
|
}
|
|
@@ -1055,7 +1110,10 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
|
1055
1110
|
code_refs: args.code_refs,
|
|
1056
1111
|
repo: args.repo,
|
|
1057
1112
|
repo_space_uid: args.repo_space_uid,
|
|
1058
|
-
agent_id: args.agent_id
|
|
1113
|
+
agent_id: args.agent_id,
|
|
1114
|
+
// Keeps the default adapter anchored to the live session directory
|
|
1115
|
+
// rather than wherever the MCP server process was launched.
|
|
1116
|
+
invocation_context: args.invocation_context
|
|
1059
1117
|
},
|
|
1060
1118
|
adapter
|
|
1061
1119
|
);
|
|
@@ -1065,17 +1123,33 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
|
1065
1123
|
const anchorUids = (anchors.anchors ?? []).flatMap(
|
|
1066
1124
|
(anchor) => anchor.uid ? [anchor.uid] : []
|
|
1067
1125
|
);
|
|
1126
|
+
const relateFailures = [];
|
|
1068
1127
|
for (const targetUid of targetUids) {
|
|
1069
1128
|
for (const anchorUid of anchorUids) {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1129
|
+
try {
|
|
1130
|
+
await codeClient.entity({
|
|
1131
|
+
action: "relate",
|
|
1132
|
+
source_uid: targetUid,
|
|
1133
|
+
target_uid: anchorUid,
|
|
1134
|
+
edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
|
|
1135
|
+
agent_id: args.agent_id
|
|
1136
|
+
});
|
|
1137
|
+
} catch (cause) {
|
|
1138
|
+
relateFailures.push(
|
|
1139
|
+
`${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1077
1142
|
}
|
|
1078
1143
|
}
|
|
1144
|
+
if (relateFailures.length > 0) {
|
|
1145
|
+
return ok({
|
|
1146
|
+
result: original,
|
|
1147
|
+
code_anchors: {
|
|
1148
|
+
...anchors,
|
|
1149
|
+
caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1079
1153
|
}
|
|
1080
1154
|
return ok({ result: original, code_anchors: anchors });
|
|
1081
1155
|
}
|
|
@@ -1148,16 +1222,34 @@ async function codeRefFromAnchor(client2, adapter, uid) {
|
|
|
1148
1222
|
if (!attributes || typeof attributes !== "object") return void 0;
|
|
1149
1223
|
const codeRef = attributes.code_ref;
|
|
1150
1224
|
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
1151
|
-
|
|
1152
|
-
|
|
1225
|
+
try {
|
|
1226
|
+
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
1227
|
+
return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
|
|
1228
|
+
} catch (cause) {
|
|
1229
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
1230
|
+
return { missingRepo: codeRef.repoId };
|
|
1231
|
+
}
|
|
1232
|
+
throw cause;
|
|
1233
|
+
}
|
|
1153
1234
|
}
|
|
1154
1235
|
async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
|
|
1155
1236
|
if (refs.length > 1 || anchorUids.length > 1) {
|
|
1156
1237
|
return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
|
|
1157
1238
|
}
|
|
1158
1239
|
if (anchorUids.length === 1) {
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1240
|
+
const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
|
|
1241
|
+
if (!outcome) {
|
|
1242
|
+
return { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
|
|
1243
|
+
}
|
|
1244
|
+
if ("missingRepo" in outcome) {
|
|
1245
|
+
return {
|
|
1246
|
+
error: err(
|
|
1247
|
+
"repository_unavailable",
|
|
1248
|
+
`repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
|
|
1249
|
+
)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
return { ref: outcome.ref };
|
|
1161
1253
|
}
|
|
1162
1254
|
if (refs.length !== 1) {
|
|
1163
1255
|
return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
@@ -1196,7 +1288,7 @@ async function joinMaterialized(client2, refs, agentId, limit) {
|
|
|
1196
1288
|
async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
1197
1289
|
const uids = /* @__PURE__ */ new Set();
|
|
1198
1290
|
collectUids(context, uids);
|
|
1199
|
-
if (uids.size === 0) return [];
|
|
1291
|
+
if (uids.size === 0) return { refs: [], unavailable: [] };
|
|
1200
1292
|
const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
|
|
1201
1293
|
const stored = nodes.flatMap((node) => {
|
|
1202
1294
|
if (!node) return [];
|
|
@@ -1205,12 +1297,21 @@ async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
|
1205
1297
|
const codeRef = attributes.code_ref;
|
|
1206
1298
|
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
1207
1299
|
});
|
|
1208
|
-
|
|
1209
|
-
|
|
1300
|
+
const refs = [];
|
|
1301
|
+
const unavailable = /* @__PURE__ */ new Set();
|
|
1302
|
+
for (const ref of stored) {
|
|
1303
|
+
try {
|
|
1210
1304
|
const repository = await adapter.resolveRepository(ref.repoId);
|
|
1211
|
-
|
|
1212
|
-
})
|
|
1213
|
-
|
|
1305
|
+
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
1306
|
+
} catch (cause) {
|
|
1307
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
1308
|
+
unavailable.add(ref.repoId);
|
|
1309
|
+
} else {
|
|
1310
|
+
throw cause;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return { refs, unavailable: [...unavailable].sort() };
|
|
1214
1315
|
}
|
|
1215
1316
|
function collectUids(value, uids) {
|
|
1216
1317
|
if (Array.isArray(value)) {
|
|
@@ -1272,7 +1373,7 @@ var import_node_crypto3 = __toESM(require("crypto"));
|
|
|
1272
1373
|
var import_node_fs3 = __toESM(require("fs"));
|
|
1273
1374
|
var import_node_path3 = __toESM(require("path"));
|
|
1274
1375
|
var ok2 = (data) => ({
|
|
1275
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
1376
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
1276
1377
|
});
|
|
1277
1378
|
var err2 = (code, message) => ({
|
|
1278
1379
|
content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
|
|
@@ -1352,14 +1453,29 @@ function safePath(root, candidate) {
|
|
|
1352
1453
|
if (relative2.startsWith("..") || import_node_path3.default.isAbsolute(relative2)) {
|
|
1353
1454
|
throw new Error(`path escapes repository root: ${candidate}`);
|
|
1354
1455
|
}
|
|
1456
|
+
if (import_node_fs3.default.existsSync(absolute)) {
|
|
1457
|
+
if (!pathContains(realPathOf(root), realPathOf(absolute))) {
|
|
1458
|
+
throw new Error(`path escapes repository root via symlink: ${candidate}`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1355
1461
|
return absolute;
|
|
1356
1462
|
}
|
|
1357
|
-
function
|
|
1463
|
+
function realPathOf(target) {
|
|
1464
|
+
try {
|
|
1465
|
+
return import_node_fs3.default.realpathSync(target);
|
|
1466
|
+
} catch {
|
|
1467
|
+
return import_node_path3.default.resolve(target);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
1358
1471
|
if (!import_node_fs3.default.existsSync(target)) return [];
|
|
1472
|
+
const real = realPathOf(target);
|
|
1473
|
+
if (!pathContains(realRoot, real)) return [];
|
|
1359
1474
|
const stat = import_node_fs3.default.statSync(target);
|
|
1360
1475
|
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
1361
|
-
if (!stat.isDirectory()) return [];
|
|
1362
|
-
|
|
1476
|
+
if (!stat.isDirectory() || visited.has(real)) return [];
|
|
1477
|
+
visited.add(real);
|
|
1478
|
+
return import_node_fs3.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path3.default.join(target, entry.name), realRoot, visited));
|
|
1363
1479
|
}
|
|
1364
1480
|
function fileDescriptor(root, absolute) {
|
|
1365
1481
|
const content = import_node_fs3.default.readFileSync(absolute, "utf8");
|
|
@@ -1374,9 +1490,7 @@ async function handleSyncTool(client2, args) {
|
|
|
1374
1490
|
try {
|
|
1375
1491
|
const action = String(args.action || "");
|
|
1376
1492
|
const context = args.invocation_context || {};
|
|
1377
|
-
const adapter = new CodegraphAdapter({
|
|
1378
|
-
cwd: context.cwd || process.cwd()
|
|
1379
|
-
});
|
|
1493
|
+
const adapter = new CodegraphAdapter({ cwd: invocationCwd(args) });
|
|
1380
1494
|
const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
|
|
1381
1495
|
const repository = await adapter.resolveRepository(requestedRepo);
|
|
1382
1496
|
const root = repository.repoRoot;
|
|
@@ -1385,6 +1499,12 @@ async function handleSyncTool(client2, args) {
|
|
|
1385
1499
|
const remote = client2;
|
|
1386
1500
|
if (action === "scan") {
|
|
1387
1501
|
const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
|
|
1502
|
+
if (args.workspace === true && repositories.length === 0) {
|
|
1503
|
+
return err2(
|
|
1504
|
+
"workspace_not_configured",
|
|
1505
|
+
`workspace scan found no declared repositories: ${adapter.workspaceDiagnostic()}`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1388
1508
|
const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
|
|
1389
1509
|
const scans = [];
|
|
1390
1510
|
for (const selected of repositories) {
|
|
@@ -1441,10 +1561,11 @@ async function handleSyncTool(client2, args) {
|
|
|
1441
1561
|
}
|
|
1442
1562
|
}
|
|
1443
1563
|
async function scanRepository(remote, repository, requested, provider, agentId) {
|
|
1564
|
+
const realRoot = realPathOf(repository.repoRoot);
|
|
1444
1565
|
const files = [
|
|
1445
1566
|
...new Set(
|
|
1446
1567
|
requested.flatMap(
|
|
1447
|
-
(candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
|
|
1568
|
+
(candidate) => markdownFiles(safePath(repository.repoRoot, candidate), realRoot)
|
|
1448
1569
|
)
|
|
1449
1570
|
)
|
|
1450
1571
|
].sort();
|
|
@@ -1502,12 +1623,23 @@ function clip(value, max, state) {
|
|
|
1502
1623
|
}
|
|
1503
1624
|
function isNodeShaped(value) {
|
|
1504
1625
|
const node = obj(value);
|
|
1505
|
-
return Boolean(
|
|
1626
|
+
return Boolean(
|
|
1627
|
+
node && str(node.uid) && (str(node.label) || str(node.node_type) || obj(node.node_type))
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
function nodeDisplayType(node) {
|
|
1631
|
+
const props = obj(node.props);
|
|
1632
|
+
const plain = str(node.node_type);
|
|
1633
|
+
return (plain && plain !== "Custom" ? plain : void 0) ?? str(obj(node.node_type)?.Custom) ?? str(props?.object_type) ?? str(props?.type_name) ?? str(obj(props?.data)?.domain_type) ?? plain ?? "Node";
|
|
1634
|
+
}
|
|
1635
|
+
function nodeFields(node) {
|
|
1636
|
+
const props = obj(node.props);
|
|
1637
|
+
return obj(props?.fields) ?? obj(obj(props?.data)?.fields);
|
|
1506
1638
|
}
|
|
1507
1639
|
function unwrapSearchResult(value) {
|
|
1508
1640
|
if (isNodeShaped(value)) return value;
|
|
1509
1641
|
const wrapper = obj(value);
|
|
1510
|
-
const inner = obj(wrapper?.node);
|
|
1642
|
+
const inner = obj(wrapper?.node) ?? obj(wrapper?.object);
|
|
1511
1643
|
if (inner && isNodeShaped(inner)) {
|
|
1512
1644
|
const score = num(wrapper.score);
|
|
1513
1645
|
return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
|
|
@@ -1545,7 +1677,7 @@ function sourceTag(node) {
|
|
|
1545
1677
|
return `from: ${title}${extra}`;
|
|
1546
1678
|
}
|
|
1547
1679
|
function layer7Fields(node, state) {
|
|
1548
|
-
const fields =
|
|
1680
|
+
const fields = nodeFields(node);
|
|
1549
1681
|
if (!fields) return void 0;
|
|
1550
1682
|
const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
|
|
1551
1683
|
if (pairs.length === 0) return void 0;
|
|
@@ -1557,7 +1689,7 @@ function nodeSummaryText(node, state) {
|
|
|
1557
1689
|
}
|
|
1558
1690
|
function nodeLines(node, state) {
|
|
1559
1691
|
const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
|
|
1560
|
-
const type =
|
|
1692
|
+
const type = nodeDisplayType(node);
|
|
1561
1693
|
const confidence = num(node.confidence);
|
|
1562
1694
|
const score = num(node.retrieval_score);
|
|
1563
1695
|
const parenthetical = [
|
|
@@ -1576,19 +1708,19 @@ function nodeLines(node, state) {
|
|
|
1576
1708
|
if (source) lines.push(` - ${source}`);
|
|
1577
1709
|
const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
|
|
1578
1710
|
if (believers.length > 0) {
|
|
1579
|
-
const
|
|
1711
|
+
const rendered2 = believers.slice(0, 3).map((raw) => {
|
|
1580
1712
|
const believer = obj(raw);
|
|
1581
1713
|
const conf = num(believer?.confidence);
|
|
1582
1714
|
return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
|
|
1583
1715
|
}).join(", ");
|
|
1584
|
-
lines.push(` - believed by: ${
|
|
1716
|
+
lines.push(` - believed by: ${rendered2}`);
|
|
1585
1717
|
}
|
|
1586
1718
|
return lines.join("\n");
|
|
1587
1719
|
}
|
|
1588
1720
|
function groupedNodeSections(nodes, state, headingPrefix = "") {
|
|
1589
1721
|
const groups = /* @__PURE__ */ new Map();
|
|
1590
1722
|
for (const node of nodes) {
|
|
1591
|
-
const type =
|
|
1723
|
+
const type = nodeDisplayType(node);
|
|
1592
1724
|
const group = groups.get(type) ?? [];
|
|
1593
1725
|
group.push(node);
|
|
1594
1726
|
groups.set(type, group);
|
|
@@ -1604,7 +1736,7 @@ function groupedNodeSections(nodes, state, headingPrefix = "") {
|
|
|
1604
1736
|
}
|
|
1605
1737
|
return sections;
|
|
1606
1738
|
}
|
|
1607
|
-
function relationshipItems(edges, labelByUid, state) {
|
|
1739
|
+
function relationshipItems(edges, labelByUid, state, options) {
|
|
1608
1740
|
const lines = [];
|
|
1609
1741
|
for (const raw of edges) {
|
|
1610
1742
|
const edge = obj(raw);
|
|
@@ -1614,8 +1746,8 @@ function relationshipItems(edges, labelByUid, state) {
|
|
|
1614
1746
|
const to = str(edge.to_uid);
|
|
1615
1747
|
if (CURATION_EDGE_TYPES.has(type)) continue;
|
|
1616
1748
|
if (!from || !to || from === to) continue;
|
|
1617
|
-
const fromLabel = labelByUid.get(from);
|
|
1618
|
-
const toLabel = labelByUid.get(to);
|
|
1749
|
+
const fromLabel = labelByUid.get(from) ?? (options?.fallbackToUid ? `[${from}]` : void 0);
|
|
1750
|
+
const toLabel = labelByUid.get(to) ?? (options?.fallbackToUid ? `[${to}]` : void 0);
|
|
1619
1751
|
if (!fromLabel || !toLabel) continue;
|
|
1620
1752
|
lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
|
|
1621
1753
|
}
|
|
@@ -1658,8 +1790,8 @@ function assemble(header, sections, state) {
|
|
|
1658
1790
|
sectionUsed += marker.length + 1;
|
|
1659
1791
|
}
|
|
1660
1792
|
lines.push(
|
|
1661
|
-
section.items.length === 0 ? section.heading : `${section.heading}
|
|
1662
|
-
${kept.join("\n")}`
|
|
1793
|
+
section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
|
|
1794
|
+
${kept.join("\n")}` : kept.join("\n")
|
|
1663
1795
|
);
|
|
1664
1796
|
used += headingCost + sectionUsed;
|
|
1665
1797
|
}
|
|
@@ -1790,15 +1922,15 @@ function renderTraversal(response) {
|
|
|
1790
1922
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
1791
1923
|
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
1792
1924
|
if (paths.length > 0) {
|
|
1793
|
-
const items = paths.map((raw) => obj(raw)).filter((
|
|
1794
|
-
const uids = Array.isArray(
|
|
1795
|
-
const labels = Array.isArray(
|
|
1925
|
+
const items = paths.map((raw) => obj(raw)).filter((path5) => Boolean(path5)).map((path5, index) => {
|
|
1926
|
+
const uids = Array.isArray(path5.node_uids) ? path5.node_uids.map(String) : [];
|
|
1927
|
+
const labels = Array.isArray(path5.labels) ? path5.labels.map(String) : Array.isArray(path5.nodes) ? path5.nodes.map((hop) => {
|
|
1796
1928
|
const hopNode = obj(hop);
|
|
1797
1929
|
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
1798
1930
|
}) : [];
|
|
1799
1931
|
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
1800
|
-
const cost = num(
|
|
1801
|
-
const confidence = num(
|
|
1932
|
+
const cost = num(path5.cost) ?? num(path5.path_cost);
|
|
1933
|
+
const confidence = num(path5.path_confidence);
|
|
1802
1934
|
const meta = [
|
|
1803
1935
|
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
1804
1936
|
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
@@ -1822,6 +1954,157 @@ function renderTraversal(response) {
|
|
|
1822
1954
|
}
|
|
1823
1955
|
return assemble("# Graph traversal", sections, state);
|
|
1824
1956
|
}
|
|
1957
|
+
function neighborLine(value) {
|
|
1958
|
+
const node = obj(value);
|
|
1959
|
+
if (!node) return void 0;
|
|
1960
|
+
const label = str(node.label) || "(unlabeled)";
|
|
1961
|
+
const uid = str(node.uid);
|
|
1962
|
+
const type = str(node.node_type);
|
|
1963
|
+
return `- ${label}${uid ? ` [${uid}]` : ""}${type ? ` (${type})` : ""}`;
|
|
1964
|
+
}
|
|
1965
|
+
function renderOntologyAnswer(response) {
|
|
1966
|
+
const payload = obj(response);
|
|
1967
|
+
if (!payload) return void 0;
|
|
1968
|
+
const objects = Array.isArray(payload.objects) ? payload.objects.filter(isNodeShaped) : isNodeShaped(payload.object) ? [payload.object] : [];
|
|
1969
|
+
const cognitive = obj(payload.cognitive_context);
|
|
1970
|
+
const provenance = Array.isArray(payload.provenance) ? payload.provenance : [];
|
|
1971
|
+
const hasQuerySignature = Array.isArray(payload.objects) || obj(payload.confidence) !== void 0;
|
|
1972
|
+
const cognitiveEmpty = !cognitive || Object.values(cognitive).every((v) => !Array.isArray(v) || v.length === 0);
|
|
1973
|
+
if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
|
|
1974
|
+
return hasQuerySignature ? "No matching domain objects." : void 0;
|
|
1975
|
+
}
|
|
1976
|
+
const state = { bounded: false };
|
|
1977
|
+
const sections = [];
|
|
1978
|
+
if (objects.length > 0) {
|
|
1979
|
+
sections.push(...groupedNodeSections(objects, state));
|
|
1980
|
+
}
|
|
1981
|
+
const labelByUid = /* @__PURE__ */ new Map();
|
|
1982
|
+
for (const node of objects) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
1983
|
+
const graph = obj(payload.graph);
|
|
1984
|
+
for (const raw of Array.isArray(graph?.nodes) ? graph.nodes : []) {
|
|
1985
|
+
const node = obj(raw);
|
|
1986
|
+
const uid = str(node?.uid);
|
|
1987
|
+
if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
|
|
1988
|
+
}
|
|
1989
|
+
if (cognitive) {
|
|
1990
|
+
for (const rawNodes of Object.values(cognitive)) {
|
|
1991
|
+
if (!Array.isArray(rawNodes)) continue;
|
|
1992
|
+
for (const raw of rawNodes) {
|
|
1993
|
+
const node = obj(raw);
|
|
1994
|
+
const uid = str(node?.uid);
|
|
1995
|
+
if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
const relations = Array.isArray(payload.relations) ? payload.relations : [];
|
|
2000
|
+
const relationLines = relationshipItems(relations, labelByUid, state, { fallbackToUid: true });
|
|
2001
|
+
if (relationLines.length > 0) {
|
|
2002
|
+
sections.push({ heading: "## Relations", items: relationLines });
|
|
2003
|
+
}
|
|
2004
|
+
if (cognitive) {
|
|
2005
|
+
for (const [category, rawNodes] of Object.entries(cognitive)) {
|
|
2006
|
+
if (!Array.isArray(rawNodes) || rawNodes.length === 0) continue;
|
|
2007
|
+
const items = rawNodes.map(neighborLine).filter((line) => Boolean(line));
|
|
2008
|
+
if (items.length > 0) {
|
|
2009
|
+
sections.push({ heading: `### Cognitive context \u2014 ${category}`, items });
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
if (provenance.length > 0) {
|
|
2014
|
+
const items = provenance.map((raw) => obj(raw)).filter((entry) => Boolean(entry)).map((entry) => {
|
|
2015
|
+
const span = clip(entry.text_span, QUOTE_MAX, state) || "";
|
|
2016
|
+
const by = str(entry.ingested_by_name);
|
|
2017
|
+
const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${str(entry.source_uid) || "?"}]`;
|
|
2018
|
+
return str(entry.span_kind) === "anchor" ? `- "${span}"${suffix}` : `- context: ${span}${suffix}`;
|
|
2019
|
+
});
|
|
2020
|
+
sections.push({ heading: "## Sources", items });
|
|
2021
|
+
}
|
|
2022
|
+
const confidence = num(obj(payload.confidence)?.overall);
|
|
2023
|
+
const notes = [];
|
|
2024
|
+
if (confidence !== void 0) notes.push(`overall confidence ${confidence}`);
|
|
2025
|
+
if (payload.has_more === true) notes.push("more results exist (has_more)");
|
|
2026
|
+
if (payload.seed_cap_hit === true) notes.push("seed cap hit \u2014 narrow the query for a complete set");
|
|
2027
|
+
if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
|
|
2028
|
+
return assemble(
|
|
2029
|
+
`# Ontology results (${objects.length} object${objects.length === 1 ? "" : "s"})`,
|
|
2030
|
+
sections,
|
|
2031
|
+
state
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
function renderObjectList(response, title) {
|
|
2035
|
+
const payload = obj(response);
|
|
2036
|
+
if (!payload) return void 0;
|
|
2037
|
+
const rawItems = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.rows) && payload.rows.some((row) => unwrapSearchResult(row)) ? payload.rows : void 0;
|
|
2038
|
+
const items = (rawItems ?? []).map(unwrapSearchResult).filter((node) => Boolean(node));
|
|
2039
|
+
if (items.length === 0) {
|
|
2040
|
+
return rawItems ? `# ${title} (0 results)` : void 0;
|
|
2041
|
+
}
|
|
2042
|
+
const state = { bounded: false };
|
|
2043
|
+
const sections = groupedNodeSections(items, state);
|
|
2044
|
+
const total = num(payload.total_count);
|
|
2045
|
+
const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
|
|
2046
|
+
const notes = [];
|
|
2047
|
+
if (payload.has_more === true) notes.push("more results exist");
|
|
2048
|
+
if (truncations.length > 0) notes.push(`truncated: ${truncations.join(", ")}`);
|
|
2049
|
+
if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
|
|
2050
|
+
const count = total !== void 0 && total !== items.length ? `${items.length} of ${total}` : `${items.length}`;
|
|
2051
|
+
return assemble(`# ${title} (${count})`, sections, state);
|
|
2052
|
+
}
|
|
2053
|
+
var JOBS_RENDER_MAX = 20;
|
|
2054
|
+
function renderJobs(response) {
|
|
2055
|
+
const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
|
|
2056
|
+
if (!Array.isArray(jobs)) return void 0;
|
|
2057
|
+
if (jobs.length === 0) return "No ingestion jobs.";
|
|
2058
|
+
const state = { bounded: false };
|
|
2059
|
+
const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
|
|
2060
|
+
const shown = sorted.slice(0, JOBS_RENDER_MAX);
|
|
2061
|
+
if (shown.length < sorted.length) state.bounded = true;
|
|
2062
|
+
const items = shown.map((job) => {
|
|
2063
|
+
const progress = obj(job.progress);
|
|
2064
|
+
const done = num(progress?.processed_chunks);
|
|
2065
|
+
const totalChunks = num(progress?.total_chunks);
|
|
2066
|
+
const bits = [
|
|
2067
|
+
str(job.status) || "?",
|
|
2068
|
+
done !== void 0 && totalChunks !== void 0 ? `${done}/${totalChunks} chunks` : void 0,
|
|
2069
|
+
num(progress?.nodes_created) !== void 0 ? `${num(progress?.nodes_created)} nodes` : void 0,
|
|
2070
|
+
num(job.queue_position) !== void 0 ? `queue #${num(job.queue_position)}` : void 0,
|
|
2071
|
+
str(job.error) ? `error: ${clip(job.error, 160, state)}` : void 0
|
|
2072
|
+
].filter(Boolean);
|
|
2073
|
+
return `- [${str(job.id) || "?"}] ${clip(job.title, 120, state) || "(untitled)"} \u2014 ${bits.join(", ")}`;
|
|
2074
|
+
});
|
|
2075
|
+
return assemble(
|
|
2076
|
+
`# Ingestion jobs (${shown.length}${shown.length < sorted.length ? ` of ${sorted.length}` : ""})`,
|
|
2077
|
+
[{ heading: "", items }],
|
|
2078
|
+
state
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
function renderSignals(response) {
|
|
2082
|
+
const payload = obj(response);
|
|
2083
|
+
if (!payload) return void 0;
|
|
2084
|
+
const state = { bounded: false };
|
|
2085
|
+
const sections = [];
|
|
2086
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
2087
|
+
if (!Array.isArray(value) || value.length === 0) continue;
|
|
2088
|
+
const items = value.map((raw) => obj(raw)).filter((item) => Boolean(item)).map((item) => {
|
|
2089
|
+
const fromLabel = str(item.from_label);
|
|
2090
|
+
const toLabel = str(item.to_label);
|
|
2091
|
+
const anyLabelKey = Object.keys(item).find(
|
|
2092
|
+
(k) => k.endsWith("_label") && str(item[k])
|
|
2093
|
+
);
|
|
2094
|
+
const label = str(item.label) ?? str(item.title) ?? str(item.name) ?? (fromLabel && toLabel ? `${fromLabel} \u2194 ${toLabel}` : void 0) ?? (anyLabelKey ? str(item[anyLabelKey]) : void 0) ?? str(item.uid) ?? str(item.target_uid) ?? "(item)";
|
|
2095
|
+
const uid = str(item.uid) ?? str(item.target_uid);
|
|
2096
|
+
const numbers = Object.entries(item).filter(([, v]) => typeof v === "number").slice(0, 4).map(([k, v]) => `${k} ${Math.round(v * 100) / 100}`);
|
|
2097
|
+
const lists = Object.entries(item).filter(([, v]) => Array.isArray(v) && v.length > 0).slice(0, 2).map(([k, v]) => `${k}: ${v.length}`);
|
|
2098
|
+
const meta = [...numbers, ...lists].join(", ");
|
|
2099
|
+
return `- ${clip(label, 160, state)}${uid && uid !== label ? ` [${uid}]` : ""}${meta ? ` (${meta})` : ""}`;
|
|
2100
|
+
});
|
|
2101
|
+
if (items.length > 0) {
|
|
2102
|
+
sections.push({ heading: `## ${key.replace(/_/g, " ")}`, items });
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
if (sections.length === 0) return void 0;
|
|
2106
|
+
return assemble("# Synthesis signals", sections, state);
|
|
2107
|
+
}
|
|
1825
2108
|
|
|
1826
2109
|
// src/tools.ts
|
|
1827
2110
|
var TOOLS = [
|
|
@@ -2631,6 +2914,11 @@ var TOOLS = [
|
|
|
2631
2914
|
type: "string",
|
|
2632
2915
|
description: "Job ID to check status (for job_status action)"
|
|
2633
2916
|
},
|
|
2917
|
+
format: {
|
|
2918
|
+
type: "string",
|
|
2919
|
+
enum: ["text", "json"],
|
|
2920
|
+
description: "job_status without job_id: 'text' (default) renders the 20 most recent jobs; 'json' returns the full raw list."
|
|
2921
|
+
},
|
|
2634
2922
|
agent_id: {
|
|
2635
2923
|
type: "string",
|
|
2636
2924
|
description: "Agent identity"
|
|
@@ -2666,6 +2954,11 @@ var TOOLS = [
|
|
|
2666
2954
|
type: "string",
|
|
2667
2955
|
description: "Job ID to poll (for action='job_status')"
|
|
2668
2956
|
},
|
|
2957
|
+
format: {
|
|
2958
|
+
type: "string",
|
|
2959
|
+
enum: ["text", "json"],
|
|
2960
|
+
description: "signals action: 'text' (default) renders signal sections; 'json' returns the raw payload."
|
|
2961
|
+
},
|
|
2669
2962
|
agent_id: {
|
|
2670
2963
|
type: "string",
|
|
2671
2964
|
description: "Agent identity"
|
|
@@ -2774,6 +3067,11 @@ var TOOLS = [
|
|
|
2774
3067
|
items: { type: "string" },
|
|
2775
3068
|
description: "Chunk/document UIDs to extract from (for action=extract)"
|
|
2776
3069
|
},
|
|
3070
|
+
format: {
|
|
3071
|
+
type: "string",
|
|
3072
|
+
enum: ["text", "json"],
|
|
3073
|
+
description: "Output format for the read actions query/search/objects/object/object_context (default 'text'): a compact rendered block. Pass 'json' for the raw server response."
|
|
3074
|
+
},
|
|
2777
3075
|
agent_id: {
|
|
2778
3076
|
type: "string",
|
|
2779
3077
|
description: "Agent identity"
|
|
@@ -2816,9 +3114,14 @@ function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
|
|
|
2816
3114
|
}
|
|
2817
3115
|
function ok3(data) {
|
|
2818
3116
|
return {
|
|
2819
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
3117
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
2820
3118
|
};
|
|
2821
3119
|
}
|
|
3120
|
+
function renderedResult(format, raw, render) {
|
|
3121
|
+
if (format === "json") return ok3(raw);
|
|
3122
|
+
const text = render(raw);
|
|
3123
|
+
return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
|
|
3124
|
+
}
|
|
2822
3125
|
function err3(message) {
|
|
2823
3126
|
return {
|
|
2824
3127
|
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
@@ -3430,15 +3733,11 @@ async function handleRetrieve(client2, args) {
|
|
|
3430
3733
|
max_cost,
|
|
3431
3734
|
agent_id
|
|
3432
3735
|
} = args;
|
|
3433
|
-
const
|
|
3434
|
-
if (format === "json") return ok3(raw);
|
|
3435
|
-
const text = render(raw);
|
|
3436
|
-
return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
|
|
3437
|
-
};
|
|
3736
|
+
const rendered2 = (raw, render) => renderedResult(format, raw, render);
|
|
3438
3737
|
switch (action) {
|
|
3439
3738
|
case "context":
|
|
3440
3739
|
if (!query) return err3("query is required for context retrieval");
|
|
3441
|
-
return
|
|
3740
|
+
return rendered2(
|
|
3442
3741
|
await client2.retrieveContext({
|
|
3443
3742
|
query,
|
|
3444
3743
|
node_limit: limit,
|
|
@@ -3463,7 +3762,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3463
3762
|
);
|
|
3464
3763
|
case "text":
|
|
3465
3764
|
if (!query) return err3("query is required for text search");
|
|
3466
|
-
return
|
|
3765
|
+
return rendered2(
|
|
3467
3766
|
await client2.retrieve({
|
|
3468
3767
|
action: "text",
|
|
3469
3768
|
query,
|
|
@@ -3477,7 +3776,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3477
3776
|
);
|
|
3478
3777
|
case "semantic":
|
|
3479
3778
|
if (!query) return err3("query is required for semantic search");
|
|
3480
|
-
return
|
|
3779
|
+
return rendered2(
|
|
3481
3780
|
await client2.retrieve({
|
|
3482
3781
|
action: "semantic",
|
|
3483
3782
|
query,
|
|
@@ -3491,7 +3790,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3491
3790
|
);
|
|
3492
3791
|
case "hybrid":
|
|
3493
3792
|
if (!query) return err3("query is required for hybrid search");
|
|
3494
|
-
return
|
|
3793
|
+
return rendered2(
|
|
3495
3794
|
await client2.retrieve({
|
|
3496
3795
|
action: "hybrid",
|
|
3497
3796
|
query,
|
|
@@ -3513,7 +3812,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3513
3812
|
case "weak_claims":
|
|
3514
3813
|
case "pending_approvals":
|
|
3515
3814
|
case "unresolved_contradictions":
|
|
3516
|
-
return
|
|
3815
|
+
return rendered2(
|
|
3517
3816
|
await client2.retrieve({
|
|
3518
3817
|
action,
|
|
3519
3818
|
limit,
|
|
@@ -3523,7 +3822,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3523
3822
|
(raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
|
|
3524
3823
|
);
|
|
3525
3824
|
case "stale_derivations":
|
|
3526
|
-
return
|
|
3825
|
+
return rendered2(
|
|
3527
3826
|
await client2.retrieve({
|
|
3528
3827
|
action: "stale_derivations",
|
|
3529
3828
|
limit,
|
|
@@ -3532,7 +3831,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3532
3831
|
(raw) => renderNodeList(raw, "stale derivations", limit)
|
|
3533
3832
|
);
|
|
3534
3833
|
case "preferences":
|
|
3535
|
-
return
|
|
3834
|
+
return rendered2(
|
|
3536
3835
|
await client2.retrieve({
|
|
3537
3836
|
action: "preferences",
|
|
3538
3837
|
...query ? { query, k: limit } : {},
|
|
@@ -3543,7 +3842,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3543
3842
|
);
|
|
3544
3843
|
case "layer":
|
|
3545
3844
|
if (!layer) return err3("layer is required for layer retrieval");
|
|
3546
|
-
return
|
|
3845
|
+
return rendered2(
|
|
3547
3846
|
await client2.retrieve({
|
|
3548
3847
|
action: "layer",
|
|
3549
3848
|
layer,
|
|
@@ -3554,7 +3853,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3554
3853
|
(raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
|
|
3555
3854
|
);
|
|
3556
3855
|
case "recent":
|
|
3557
|
-
return
|
|
3856
|
+
return rendered2(
|
|
3558
3857
|
await client2.retrieve({
|
|
3559
3858
|
action: "recent",
|
|
3560
3859
|
limit,
|
|
@@ -3569,7 +3868,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3569
3868
|
);
|
|
3570
3869
|
// Document inventory
|
|
3571
3870
|
case "document_index":
|
|
3572
|
-
return
|
|
3871
|
+
return rendered2(
|
|
3573
3872
|
await client2.getNodes({
|
|
3574
3873
|
node_type: "Document",
|
|
3575
3874
|
limit: limit ?? 100
|
|
@@ -3579,7 +3878,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3579
3878
|
// Graph traversal
|
|
3580
3879
|
case "chain":
|
|
3581
3880
|
if (!start_uid) return err3("start_uid is required for chain traversal");
|
|
3582
|
-
return
|
|
3881
|
+
return rendered2(
|
|
3583
3882
|
await client2.traverse({
|
|
3584
3883
|
action: "chain",
|
|
3585
3884
|
start_uid,
|
|
@@ -3592,7 +3891,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3592
3891
|
);
|
|
3593
3892
|
case "neighborhood":
|
|
3594
3893
|
if (!start_uid) return err3("start_uid is required for neighborhood traversal");
|
|
3595
|
-
return
|
|
3894
|
+
return rendered2(
|
|
3596
3895
|
await client2.traverse({
|
|
3597
3896
|
action: "neighborhood",
|
|
3598
3897
|
start_uid,
|
|
@@ -3608,7 +3907,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3608
3907
|
case "path":
|
|
3609
3908
|
if (!start_uid) return err3("start_uid is required for path traversal");
|
|
3610
3909
|
if (!end_uid) return err3("end_uid is required for path traversal");
|
|
3611
|
-
return
|
|
3910
|
+
return rendered2(
|
|
3612
3911
|
await client2.traverse({
|
|
3613
3912
|
action: "path",
|
|
3614
3913
|
start_uid,
|
|
@@ -3624,7 +3923,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3624
3923
|
);
|
|
3625
3924
|
case "subgraph":
|
|
3626
3925
|
if (!start_uid) return err3("start_uid is required for subgraph traversal");
|
|
3627
|
-
return
|
|
3926
|
+
return rendered2(
|
|
3628
3927
|
await client2.traverse({
|
|
3629
3928
|
action: "subgraph",
|
|
3630
3929
|
start_uid,
|
|
@@ -3640,7 +3939,7 @@ async function handleRetrieve(client2, args) {
|
|
|
3640
3939
|
case "top_k_paths":
|
|
3641
3940
|
if (!start_uid) return err3("start_uid is required for top_k_paths");
|
|
3642
3941
|
if (!end_uid) return err3("end_uid is required for top_k_paths");
|
|
3643
|
-
return
|
|
3942
|
+
return rendered2(
|
|
3644
3943
|
await client2.traverse({
|
|
3645
3944
|
action: "top_k_paths",
|
|
3646
3945
|
start_uid,
|
|
@@ -3677,6 +3976,7 @@ async function handleIngest(client2, args) {
|
|
|
3677
3976
|
chunk_size,
|
|
3678
3977
|
chunk_overlap,
|
|
3679
3978
|
job_id,
|
|
3979
|
+
format,
|
|
3680
3980
|
agent_id
|
|
3681
3981
|
} = args;
|
|
3682
3982
|
const src = source_uri ?? source;
|
|
@@ -3746,19 +4046,21 @@ async function handleIngest(client2, args) {
|
|
|
3746
4046
|
if (job_id) {
|
|
3747
4047
|
return ok3(await client2.getJob(job_id));
|
|
3748
4048
|
}
|
|
3749
|
-
return
|
|
4049
|
+
return renderedResult(format, await client2.listJobs(), renderJobs);
|
|
3750
4050
|
default:
|
|
3751
4051
|
return err3(`Unknown ingest action: ${action}`);
|
|
3752
4052
|
}
|
|
3753
4053
|
}
|
|
3754
4054
|
async function handleSynthesize(client2, args) {
|
|
3755
|
-
const { action, project_uid, signals, target_types, job_id } = args;
|
|
4055
|
+
const { action, project_uid, signals, target_types, job_id, format } = args;
|
|
3756
4056
|
switch (action) {
|
|
3757
4057
|
case "signals":
|
|
3758
4058
|
if (!project_uid)
|
|
3759
4059
|
return err3("project_uid is required for action='signals'");
|
|
3760
|
-
return
|
|
3761
|
-
|
|
4060
|
+
return renderedResult(
|
|
4061
|
+
format,
|
|
4062
|
+
await client2.signals(project_uid, { signals, target_types }),
|
|
4063
|
+
renderSignals
|
|
3762
4064
|
);
|
|
3763
4065
|
case "run":
|
|
3764
4066
|
if (!project_uid)
|
|
@@ -3792,6 +4094,7 @@ async function handleOntology(client2, args) {
|
|
|
3792
4094
|
relation_type,
|
|
3793
4095
|
fields,
|
|
3794
4096
|
source_uids,
|
|
4097
|
+
format,
|
|
3795
4098
|
agent_id
|
|
3796
4099
|
} = args;
|
|
3797
4100
|
switch (action) {
|
|
@@ -3807,7 +4110,8 @@ async function handleOntology(client2, args) {
|
|
|
3807
4110
|
"schema_id is required for action='query' (use action='schemas' to find the active schema)"
|
|
3808
4111
|
);
|
|
3809
4112
|
}
|
|
3810
|
-
return
|
|
4113
|
+
return renderedResult(
|
|
4114
|
+
format,
|
|
3811
4115
|
await client2.queryOntology({
|
|
3812
4116
|
query,
|
|
3813
4117
|
schema_id,
|
|
@@ -3816,29 +4120,42 @@ async function handleOntology(client2, args) {
|
|
|
3816
4120
|
include_sources: include_sources ?? true,
|
|
3817
4121
|
depth,
|
|
3818
4122
|
limit
|
|
3819
|
-
})
|
|
4123
|
+
}),
|
|
4124
|
+
renderOntologyAnswer
|
|
3820
4125
|
);
|
|
3821
4126
|
}
|
|
3822
4127
|
case "search":
|
|
3823
4128
|
if (!query) return err3("query is required for action='search'");
|
|
3824
|
-
return
|
|
4129
|
+
return renderedResult(
|
|
4130
|
+
format,
|
|
3825
4131
|
await client2.searchDomainObjects(query, {
|
|
3826
4132
|
schema_id,
|
|
3827
4133
|
object_types,
|
|
3828
4134
|
limit
|
|
3829
|
-
})
|
|
4135
|
+
}),
|
|
4136
|
+
(raw) => renderObjectList(raw, `Domain search: ${query}`)
|
|
3830
4137
|
);
|
|
3831
4138
|
case "objects":
|
|
3832
4139
|
if (!schema_id) return err3("schema_id is required for action='objects'");
|
|
3833
|
-
return
|
|
3834
|
-
|
|
4140
|
+
return renderedResult(
|
|
4141
|
+
format,
|
|
4142
|
+
await client2.listDomainObjects({ schema_id, object_type, limit }),
|
|
4143
|
+
(raw) => renderObjectList(raw, `Domain objects${object_type ? `: ${object_type}` : ""}`)
|
|
3835
4144
|
);
|
|
3836
4145
|
case "object":
|
|
3837
4146
|
if (!uid) return err3("uid is required for action='object'");
|
|
3838
|
-
return
|
|
4147
|
+
return renderedResult(
|
|
4148
|
+
format,
|
|
4149
|
+
await client2.getDomainObject(uid),
|
|
4150
|
+
(raw) => renderNodeList([raw], "Domain object")
|
|
4151
|
+
);
|
|
3839
4152
|
case "object_context":
|
|
3840
4153
|
if (!uid) return err3("uid is required for action='object_context'");
|
|
3841
|
-
return
|
|
4154
|
+
return renderedResult(
|
|
4155
|
+
format,
|
|
4156
|
+
await client2.getDomainObjectContext(uid, depth),
|
|
4157
|
+
renderOntologyAnswer
|
|
4158
|
+
);
|
|
3842
4159
|
case "proposals":
|
|
3843
4160
|
return ok3(
|
|
3844
4161
|
await client2.listOntologyProposals({
|
|
@@ -3892,8 +4209,14 @@ async function handleOntology(client2, args) {
|
|
|
3892
4209
|
|
|
3893
4210
|
// src/generated-tools.ts
|
|
3894
4211
|
var ok4 = (data) => ({
|
|
3895
|
-
|
|
4212
|
+
// Compact on purpose — pretty-printing doubled the token cost.
|
|
4213
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
3896
4214
|
});
|
|
4215
|
+
var rendered = (format, raw, render) => {
|
|
4216
|
+
if (format === "json") return ok4(raw);
|
|
4217
|
+
const text = render(raw);
|
|
4218
|
+
return text !== void 0 ? { content: [{ type: "text", text }] } : ok4(raw);
|
|
4219
|
+
};
|
|
3897
4220
|
var err4 = (message) => ({
|
|
3898
4221
|
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
3899
4222
|
isError: true
|
|
@@ -3932,10 +4255,21 @@ async function getGeneratedTools(client2) {
|
|
|
3932
4255
|
);
|
|
3933
4256
|
}
|
|
3934
4257
|
byName.set(d.name, d);
|
|
4258
|
+
const inputSchema = d.maps_to === "structured_query" ? d.input_schema : {
|
|
4259
|
+
...d.input_schema,
|
|
4260
|
+
properties: {
|
|
4261
|
+
...d.input_schema.properties,
|
|
4262
|
+
format: {
|
|
4263
|
+
type: "string",
|
|
4264
|
+
enum: ["text", "json"],
|
|
4265
|
+
description: "Output format (default 'text': compact rendered block). Pass 'json' for the raw server response."
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
};
|
|
3935
4269
|
tools.push({
|
|
3936
4270
|
name: d.name,
|
|
3937
4271
|
description: d.description,
|
|
3938
|
-
inputSchema
|
|
4272
|
+
inputSchema
|
|
3939
4273
|
});
|
|
3940
4274
|
}
|
|
3941
4275
|
cache = { tools, byName, fetchedAt: Date.now() };
|
|
@@ -3964,31 +4298,41 @@ async function handleGeneratedTool(client2, desc, args) {
|
|
|
3964
4298
|
if (!query) return err4(`query is required for ${desc.name}`);
|
|
3965
4299
|
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
3966
4300
|
const filters = Array.isArray(args.filters) ? args.filters : void 0;
|
|
3967
|
-
return
|
|
4301
|
+
return rendered(
|
|
4302
|
+
args.format,
|
|
3968
4303
|
await ontologyClient.searchDomainObjects(query, {
|
|
3969
4304
|
schema_id: desc.schema_id,
|
|
3970
4305
|
object_types: [desc.object_type],
|
|
3971
4306
|
filters,
|
|
3972
4307
|
limit
|
|
3973
|
-
})
|
|
4308
|
+
}),
|
|
4309
|
+
(raw) => renderObjectList(raw, `${desc.object_type} search: ${query}`)
|
|
3974
4310
|
);
|
|
3975
4311
|
}
|
|
3976
4312
|
case "object": {
|
|
3977
4313
|
const uid = args.uid;
|
|
3978
4314
|
if (!uid) return err4(`uid is required for ${desc.name}`);
|
|
3979
|
-
return
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
4315
|
+
return rendered(
|
|
4316
|
+
args.format,
|
|
4317
|
+
await ontologyClient.getDomainObject(uid, {
|
|
4318
|
+
schema_id: desc.schema_id,
|
|
4319
|
+
object_type: desc.object_type
|
|
4320
|
+
}),
|
|
4321
|
+
(raw) => renderNodeList([raw], desc.object_type)
|
|
4322
|
+
);
|
|
3983
4323
|
}
|
|
3984
4324
|
case "object_context": {
|
|
3985
4325
|
const uid = args.uid;
|
|
3986
4326
|
if (!uid) return err4(`uid is required for ${desc.name}`);
|
|
3987
4327
|
const depth = typeof args.depth === "number" ? args.depth : void 0;
|
|
3988
|
-
return
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4328
|
+
return rendered(
|
|
4329
|
+
args.format,
|
|
4330
|
+
await ontologyClient.getDomainObjectContext(uid, depth, {
|
|
4331
|
+
schema_id: desc.schema_id,
|
|
4332
|
+
object_type: desc.object_type
|
|
4333
|
+
}),
|
|
4334
|
+
renderOntologyAnswer
|
|
4335
|
+
);
|
|
3992
4336
|
}
|
|
3993
4337
|
case "related": {
|
|
3994
4338
|
const uid = args.uid;
|
|
@@ -4001,18 +4345,22 @@ async function handleGeneratedTool(client2, desc, args) {
|
|
|
4001
4345
|
"related ontology tools require a graph-aware version of the mindgraph SDK"
|
|
4002
4346
|
);
|
|
4003
4347
|
}
|
|
4004
|
-
return
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4348
|
+
return rendered(
|
|
4349
|
+
args.format,
|
|
4350
|
+
await ontologyClient.queryRelatedDomainObjects({
|
|
4351
|
+
schema_id: desc.schema_id,
|
|
4352
|
+
entry_type: desc.object_type,
|
|
4353
|
+
uid,
|
|
4354
|
+
relation: desc.relation,
|
|
4355
|
+
entry_role: desc.entry_role,
|
|
4356
|
+
far_type: desc.far_type,
|
|
4357
|
+
where: Array.isArray(args.where) ? args.where : void 0,
|
|
4358
|
+
include_provenance: args.include_provenance === true,
|
|
4359
|
+
limit: typeof args.limit === "number" ? args.limit : void 0,
|
|
4360
|
+
offset: typeof args.offset === "number" ? args.offset : void 0
|
|
4361
|
+
}),
|
|
4362
|
+
(raw) => renderObjectList(raw, `${desc.far_type} related via ${desc.relation}`)
|
|
4363
|
+
);
|
|
4016
4364
|
}
|
|
4017
4365
|
case "structured_query": {
|
|
4018
4366
|
const select = args.select;
|
|
@@ -4213,7 +4561,7 @@ async function checkMcpGovernance(toolName, args, config) {
|
|
|
4213
4561
|
if (!response) {
|
|
4214
4562
|
return {
|
|
4215
4563
|
allowed: false,
|
|
4216
|
-
message: `MindGraph
|
|
4564
|
+
message: `MindGraph at ${config.baseUrl} is unreachable (${lastFailure}; ${retryDelays.length + 1} attempts). The tool call was NOT evaluated against policy and NOT executed \u2014 this is a connectivity failure, not a governance denial. Check the network and MINDGRAPH_BASE_URL, then retry.`
|
|
4217
4565
|
};
|
|
4218
4566
|
}
|
|
4219
4567
|
if (!response.ok) {
|
|
@@ -4331,7 +4679,7 @@ var RESOURCE_TEMPLATES = [
|
|
|
4331
4679
|
var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
|
|
4332
4680
|
function json(uri, data) {
|
|
4333
4681
|
return {
|
|
4334
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data
|
|
4682
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
|
|
4335
4683
|
};
|
|
4336
4684
|
}
|
|
4337
4685
|
async function readResource(client2, uri) {
|
|
@@ -4379,6 +4727,225 @@ async function readGovernedResource(client2, uri, governanceConfig) {
|
|
|
4379
4727
|
return readResource(client2, uri);
|
|
4380
4728
|
}
|
|
4381
4729
|
|
|
4730
|
+
// src/index.ts
|
|
4731
|
+
var import_node_path5 = __toESM(require("path"));
|
|
4732
|
+
|
|
4733
|
+
// src/hook-installer.ts
|
|
4734
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
4735
|
+
var import_node_os3 = __toESM(require("os"));
|
|
4736
|
+
var import_node_path4 = __toESM(require("path"));
|
|
4737
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
4738
|
+
var OWNER_MARKER = "--owner mindgraph";
|
|
4739
|
+
var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
4740
|
+
function command(harness) {
|
|
4741
|
+
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
4742
|
+
}
|
|
4743
|
+
function windowsCommand(harness) {
|
|
4744
|
+
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
4745
|
+
}
|
|
4746
|
+
var RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
|
|
4747
|
+
function installHookRunner(sourceFile, homeDir, version) {
|
|
4748
|
+
const target = import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_RELATIVE);
|
|
4749
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
|
|
4750
|
+
const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
4751
|
+
import_node_fs4.default.copyFileSync(sourceFile, temporary);
|
|
4752
|
+
import_node_fs4.default.renameSync(temporary, target);
|
|
4753
|
+
const versionPath = import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_VERSION_RELATIVE);
|
|
4754
|
+
if (version) {
|
|
4755
|
+
import_node_fs4.default.writeFileSync(versionPath, `${version}
|
|
4756
|
+
`);
|
|
4757
|
+
} else {
|
|
4758
|
+
import_node_fs4.default.rmSync(versionPath, { force: true });
|
|
4759
|
+
}
|
|
4760
|
+
return target;
|
|
4761
|
+
}
|
|
4762
|
+
function installedHookRunnerVersion(homeDir) {
|
|
4763
|
+
try {
|
|
4764
|
+
const raw = import_node_fs4.default.readFileSync(
|
|
4765
|
+
import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_VERSION_RELATIVE),
|
|
4766
|
+
"utf8"
|
|
4767
|
+
).trim();
|
|
4768
|
+
return raw || void 0;
|
|
4769
|
+
} catch {
|
|
4770
|
+
return void 0;
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
function parseSemver(value) {
|
|
4774
|
+
if (!value) return void 0;
|
|
4775
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
|
|
4776
|
+
if (!match) return void 0;
|
|
4777
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4778
|
+
}
|
|
4779
|
+
function semverNewer(a, b) {
|
|
4780
|
+
for (let index = 0; index < 3; index += 1) {
|
|
4781
|
+
if (a[index] !== b[index]) return a[index] > b[index];
|
|
4782
|
+
}
|
|
4783
|
+
return false;
|
|
4784
|
+
}
|
|
4785
|
+
function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
|
|
4786
|
+
try {
|
|
4787
|
+
if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
|
|
4788
|
+
return { refreshed: false, reason: "disabled" };
|
|
4789
|
+
}
|
|
4790
|
+
const home = homeDir || import_node_os3.default.homedir();
|
|
4791
|
+
if (!import_node_fs4.default.existsSync(import_node_path4.default.join(home, RUNNER_RELATIVE))) {
|
|
4792
|
+
return { refreshed: false, reason: "no_runner" };
|
|
4793
|
+
}
|
|
4794
|
+
if (!import_node_fs4.default.existsSync(sourceBundle)) {
|
|
4795
|
+
return { refreshed: false, reason: "no_source" };
|
|
4796
|
+
}
|
|
4797
|
+
const server2 = parseSemver(serverVersion);
|
|
4798
|
+
if (!server2) return { refreshed: false, reason: "unparseable" };
|
|
4799
|
+
const runnerVersion = installedHookRunnerVersion(home);
|
|
4800
|
+
if (runnerVersion !== void 0) {
|
|
4801
|
+
const pinned = parseSemver(runnerVersion);
|
|
4802
|
+
if (!pinned) {
|
|
4803
|
+
return { refreshed: false, reason: "unparseable", runnerVersion };
|
|
4804
|
+
}
|
|
4805
|
+
if (!semverNewer(server2, pinned)) {
|
|
4806
|
+
return {
|
|
4807
|
+
refreshed: false,
|
|
4808
|
+
reason: runnerVersion === serverVersion ? "current" : "downgrade",
|
|
4809
|
+
runnerVersion
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
installHookRunner(sourceBundle, home, serverVersion);
|
|
4814
|
+
return { refreshed: true, reason: "updated", runnerVersion };
|
|
4815
|
+
} catch {
|
|
4816
|
+
return { refreshed: false, reason: "error" };
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
|
|
4820
|
+
const refreshed = [];
|
|
4821
|
+
const scopes = ["user", "project"];
|
|
4822
|
+
for (const scope of scopes) {
|
|
4823
|
+
try {
|
|
4824
|
+
const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
|
|
4825
|
+
if (!import_node_fs4.default.existsSync(file)) continue;
|
|
4826
|
+
if (!import_node_fs4.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
|
|
4827
|
+
installHooks("claude-code", scope, projectDir, void 0, homeDir);
|
|
4828
|
+
refreshed.push(file);
|
|
4829
|
+
} catch {
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
return refreshed;
|
|
4833
|
+
}
|
|
4834
|
+
function versionSkewNote(serverVersion, homeDir) {
|
|
4835
|
+
const runnerVersion = installedHookRunnerVersion(homeDir);
|
|
4836
|
+
if (!runnerVersion || runnerVersion === serverVersion) return void 0;
|
|
4837
|
+
return `NOTE: the installed MindGraph hook runner is v${runnerVersion} but this MCP server is v${serverVersion} \u2014 hook behavior and tool contracts may be out of sync. Tell the user to run: npx -y mindgraph-mcp@latest install-hooks (refreshes the pinned runner).`;
|
|
4838
|
+
}
|
|
4839
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os3.default.homedir(), ".codex"), homeDir) {
|
|
4840
|
+
if (harness === "codex") {
|
|
4841
|
+
return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
|
|
4842
|
+
}
|
|
4843
|
+
return scope === "user" ? import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
|
|
4844
|
+
}
|
|
4845
|
+
function readSettings(file) {
|
|
4846
|
+
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
4847
|
+
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
4848
|
+
}
|
|
4849
|
+
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
4850
|
+
return {
|
|
4851
|
+
type: "command",
|
|
4852
|
+
command: command(harness),
|
|
4853
|
+
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
4854
|
+
timeout,
|
|
4855
|
+
statusMessage: "Syncing MindGraph session context",
|
|
4856
|
+
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
4857
|
+
};
|
|
4858
|
+
}
|
|
4859
|
+
function desiredEntries(harness) {
|
|
4860
|
+
const common = {
|
|
4861
|
+
SessionStart: [
|
|
4862
|
+
{
|
|
4863
|
+
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
4864
|
+
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
4865
|
+
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
4866
|
+
// open, but no brief reached the model. The same installed runner
|
|
4867
|
+
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
4868
|
+
// The declared Codex context limit must not undercut the brief's own
|
|
4869
|
+
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
4870
|
+
// truncation the renderer knew nothing about.
|
|
4871
|
+
hooks: [
|
|
4872
|
+
ownedHook(
|
|
4873
|
+
harness,
|
|
4874
|
+
30,
|
|
4875
|
+
harness === "codex" ? 1e4 : void 0
|
|
4876
|
+
)
|
|
4877
|
+
]
|
|
4878
|
+
}
|
|
4879
|
+
],
|
|
4880
|
+
PreToolUse: [
|
|
4881
|
+
{
|
|
4882
|
+
matcher: "mcp__mindgraph__.*",
|
|
4883
|
+
// Repository resolution over a large multi-repo workspace plus three
|
|
4884
|
+
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
4885
|
+
// PreToolUse means the call goes through untagged.
|
|
4886
|
+
hooks: [ownedHook(harness, 5)]
|
|
4887
|
+
}
|
|
4888
|
+
],
|
|
4889
|
+
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
4890
|
+
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
4891
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
|
|
4892
|
+
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
4893
|
+
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
4894
|
+
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
4895
|
+
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
4896
|
+
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
4897
|
+
// map), so the codex adapter instead makes at most ONE call there.
|
|
4898
|
+
SessionEnd: [
|
|
4899
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
4900
|
+
]
|
|
4901
|
+
};
|
|
4902
|
+
return harness === "claude-code" ? {
|
|
4903
|
+
...common,
|
|
4904
|
+
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
4905
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
4906
|
+
} : common;
|
|
4907
|
+
}
|
|
4908
|
+
function hookCommand(value) {
|
|
4909
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4910
|
+
const command2 = value.command;
|
|
4911
|
+
return typeof command2 === "string" ? command2 : void 0;
|
|
4912
|
+
}
|
|
4913
|
+
function entryHasOwnedHook(value) {
|
|
4914
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4915
|
+
const hooks = value.hooks;
|
|
4916
|
+
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
4917
|
+
}
|
|
4918
|
+
function writeSettings(file, settings) {
|
|
4919
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
4920
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
4921
|
+
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
4922
|
+
`, {
|
|
4923
|
+
mode: 384
|
|
4924
|
+
});
|
|
4925
|
+
import_node_fs4.default.renameSync(temporary, file);
|
|
4926
|
+
}
|
|
4927
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir) {
|
|
4928
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
|
|
4929
|
+
const settings = readSettings(file);
|
|
4930
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
4931
|
+
let added = 0;
|
|
4932
|
+
let updated = 0;
|
|
4933
|
+
for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
|
|
4934
|
+
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
4935
|
+
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
4936
|
+
const hadOwned = kept.length !== current.length;
|
|
4937
|
+
for (const entry of wanted) {
|
|
4938
|
+
kept.push(entry);
|
|
4939
|
+
if (hadOwned) updated += 1;
|
|
4940
|
+
else added += 1;
|
|
4941
|
+
}
|
|
4942
|
+
hooks[event] = kept;
|
|
4943
|
+
}
|
|
4944
|
+
settings.hooks = hooks;
|
|
4945
|
+
writeSettings(file, settings);
|
|
4946
|
+
return { path: file, added, updated };
|
|
4947
|
+
}
|
|
4948
|
+
|
|
4382
4949
|
// src/index.ts
|
|
4383
4950
|
var API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
4384
4951
|
var BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
@@ -4501,10 +5068,42 @@ if (PROFILE === "coding") {
|
|
|
4501
5068
|
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
4502
5069
|
).join("\n");
|
|
4503
5070
|
}
|
|
5071
|
+
var RUNNER_REFRESH = (() => {
|
|
5072
|
+
try {
|
|
5073
|
+
return refreshHookRunner("0.18.0", import_node_path5.default.join(__dirname, "cli.js"));
|
|
5074
|
+
} catch {
|
|
5075
|
+
return void 0;
|
|
5076
|
+
}
|
|
5077
|
+
})();
|
|
5078
|
+
if (RUNNER_REFRESH?.refreshed) {
|
|
5079
|
+
const refreshedSettings = (() => {
|
|
5080
|
+
try {
|
|
5081
|
+
return refreshOwnedClaudeHooks();
|
|
5082
|
+
} catch {
|
|
5083
|
+
return [];
|
|
5084
|
+
}
|
|
5085
|
+
})();
|
|
5086
|
+
console.error(
|
|
5087
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.18.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
5088
|
+
);
|
|
5089
|
+
}
|
|
5090
|
+
var HOOK_SKEW_NOTE = (() => {
|
|
5091
|
+
try {
|
|
5092
|
+
return versionSkewNote("0.18.0");
|
|
5093
|
+
} catch {
|
|
5094
|
+
return void 0;
|
|
5095
|
+
}
|
|
5096
|
+
})();
|
|
5097
|
+
if (HOOK_SKEW_NOTE) {
|
|
5098
|
+
INSTRUCTIONS += `
|
|
5099
|
+
|
|
5100
|
+
${HOOK_SKEW_NOTE}`;
|
|
5101
|
+
console.error(`[mindgraph-mcp] ${HOOK_SKEW_NOTE}`);
|
|
5102
|
+
}
|
|
4504
5103
|
var server = new import_server.Server(
|
|
4505
5104
|
{
|
|
4506
5105
|
name: "mindgraph",
|
|
4507
|
-
version: "0.
|
|
5106
|
+
version: "0.18.0"
|
|
4508
5107
|
},
|
|
4509
5108
|
{
|
|
4510
5109
|
capabilities: {
|
|
@@ -4718,7 +5317,7 @@ async function main() {
|
|
|
4718
5317
|
const transport = new import_stdio.StdioServerTransport();
|
|
4719
5318
|
await server.connect(transport);
|
|
4720
5319
|
console.error(
|
|
4721
|
-
`MindGraph MCP server v${"0.
|
|
5320
|
+
`MindGraph MCP server v${"0.18.0"} running on stdio (${BASE_URL})`
|
|
4722
5321
|
);
|
|
4723
5322
|
}
|
|
4724
5323
|
main().catch((error) => {
|