mindgraph-mcp 0.17.0 → 0.19.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 +58 -2
- package/dist/cli.js +1440 -452
- package/dist/index.js +563 -52
- package/package.json +2 -2
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;
|
|
@@ -790,7 +835,7 @@ var err = (code, message, details) => ({
|
|
|
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)) {
|
|
@@ -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();
|
|
@@ -1801,15 +1922,15 @@ function renderTraversal(response) {
|
|
|
1801
1922
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
1802
1923
|
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
1803
1924
|
if (paths.length > 0) {
|
|
1804
|
-
const items = paths.map((raw) => obj(raw)).filter((
|
|
1805
|
-
const uids = Array.isArray(
|
|
1806
|
-
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) => {
|
|
1807
1928
|
const hopNode = obj(hop);
|
|
1808
1929
|
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
1809
1930
|
}) : [];
|
|
1810
1931
|
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
1811
|
-
const cost = num(
|
|
1812
|
-
const confidence = num(
|
|
1932
|
+
const cost = num(path5.cost) ?? num(path5.path_cost);
|
|
1933
|
+
const confidence = num(path5.path_confidence);
|
|
1813
1934
|
const meta = [
|
|
1814
1935
|
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
1815
1936
|
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
@@ -1989,7 +2110,7 @@ function renderSignals(response) {
|
|
|
1989
2110
|
var TOOLS = [
|
|
1990
2111
|
{
|
|
1991
2112
|
name: "mindgraph_capture",
|
|
1992
|
-
description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes
|
|
2113
|
+
description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes, 'lesson' for a durable learning, and 'skill' for a reusable procedure authored from completed work. Skill capture always creates a candidate requiring admin review; it never publishes. Skill names must be 1-64 lowercase a-z, digits, or hyphens, with no leading/trailing/consecutive hyphens and no 'anthropic' or 'claude'. Provide at least one provenance field and write instructions, not a transcript dump.",
|
|
1993
2114
|
inputSchema: {
|
|
1994
2115
|
type: "object",
|
|
1995
2116
|
properties: {
|
|
@@ -2002,7 +2123,8 @@ var TOOLS = [
|
|
|
2002
2123
|
"snippet",
|
|
2003
2124
|
"concept",
|
|
2004
2125
|
"journal",
|
|
2005
|
-
"lesson"
|
|
2126
|
+
"lesson",
|
|
2127
|
+
"skill"
|
|
2006
2128
|
],
|
|
2007
2129
|
description: "Type of knowledge to capture"
|
|
2008
2130
|
},
|
|
@@ -2016,11 +2138,30 @@ var TOOLS = [
|
|
|
2016
2138
|
},
|
|
2017
2139
|
content: {
|
|
2018
2140
|
type: "string",
|
|
2019
|
-
|
|
2141
|
+
maxLength: 65536,
|
|
2142
|
+
description: "Journal body (action=journal) or complete SKILL.md instructions, at most 64 KiB UTF-8 (action=skill)"
|
|
2143
|
+
},
|
|
2144
|
+
name: {
|
|
2145
|
+
type: "string",
|
|
2146
|
+
minLength: 1,
|
|
2147
|
+
maxLength: 64,
|
|
2148
|
+
pattern: "^(?!.*(?:anthropic|claude))(?!-)(?!.*--)[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
2149
|
+
description: "Stable skill identifier using lowercase a-z, digits, and hyphens (for action=skill)"
|
|
2150
|
+
},
|
|
2151
|
+
description: {
|
|
2152
|
+
type: "string",
|
|
2153
|
+
minLength: 1,
|
|
2154
|
+
maxLength: 1024,
|
|
2155
|
+
description: "When the skill should be used; XML tags are rejected (for action=skill)"
|
|
2156
|
+
},
|
|
2157
|
+
license: {
|
|
2158
|
+
type: "string",
|
|
2159
|
+
description: "Optional skill license (for action=skill)"
|
|
2020
2160
|
},
|
|
2021
2161
|
session_uid: {
|
|
2022
2162
|
type: "string",
|
|
2023
|
-
|
|
2163
|
+
minLength: 1,
|
|
2164
|
+
description: "Session UID that produced a lesson or skill"
|
|
2024
2165
|
},
|
|
2025
2166
|
work_uid: {
|
|
2026
2167
|
type: "string",
|
|
@@ -2041,7 +2182,8 @@ var TOOLS = [
|
|
|
2041
2182
|
summarizes_uids: {
|
|
2042
2183
|
type: "array",
|
|
2043
2184
|
items: { type: "string" },
|
|
2044
|
-
|
|
2185
|
+
minItems: 1,
|
|
2186
|
+
description: "Source node UIDs the lesson or skill was learned from"
|
|
2045
2187
|
},
|
|
2046
2188
|
entity_type: {
|
|
2047
2189
|
type: "string",
|
|
@@ -2108,7 +2250,24 @@ var TOOLS = [
|
|
|
2108
2250
|
description: "Agent identity"
|
|
2109
2251
|
}
|
|
2110
2252
|
},
|
|
2111
|
-
required: ["action", "label"]
|
|
2253
|
+
required: ["action", "label"],
|
|
2254
|
+
allOf: [
|
|
2255
|
+
{
|
|
2256
|
+
if: {
|
|
2257
|
+
properties: { action: { const: "skill" } },
|
|
2258
|
+
required: ["action"]
|
|
2259
|
+
},
|
|
2260
|
+
then: {
|
|
2261
|
+
required: ["name", "description", "content"],
|
|
2262
|
+
anyOf: [
|
|
2263
|
+
{ required: ["session_uid"] },
|
|
2264
|
+
{ required: ["work_uid"] },
|
|
2265
|
+
{ required: ["execution_uid"] },
|
|
2266
|
+
{ required: ["summarizes_uids"] }
|
|
2267
|
+
]
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
]
|
|
2112
2271
|
}
|
|
2113
2272
|
},
|
|
2114
2273
|
{
|
|
@@ -2694,6 +2853,42 @@ var TOOLS = [
|
|
|
2694
2853
|
required: ["action"]
|
|
2695
2854
|
}
|
|
2696
2855
|
},
|
|
2856
|
+
{
|
|
2857
|
+
name: "mindgraph_series_query",
|
|
2858
|
+
description: "Read dense numeric time series without placing raw points in general retrieval context. Use list_for_entity to discover series, latest for cached statistics, window for [from,to) keyset pages, and aggregate for bounded calendar buckets.",
|
|
2859
|
+
inputSchema: {
|
|
2860
|
+
type: "object",
|
|
2861
|
+
additionalProperties: false,
|
|
2862
|
+
properties: {
|
|
2863
|
+
action: {
|
|
2864
|
+
type: "string",
|
|
2865
|
+
enum: ["list_for_entity", "latest", "window", "aggregate"]
|
|
2866
|
+
},
|
|
2867
|
+
entity_uid: { type: "string" },
|
|
2868
|
+
series_uid: { type: "string" },
|
|
2869
|
+
from: { type: "number" },
|
|
2870
|
+
to: { type: "number" },
|
|
2871
|
+
cursor: { type: "number" },
|
|
2872
|
+
limit: { type: "number", minimum: 1, maximum: 1e4 },
|
|
2873
|
+
bucket: {
|
|
2874
|
+
type: "string",
|
|
2875
|
+
enum: ["year", "quarter", "month", "week", "day", "hour"]
|
|
2876
|
+
},
|
|
2877
|
+
agg: {
|
|
2878
|
+
type: "string",
|
|
2879
|
+
enum: ["sum", "mean", "min", "max", "count", "std_dev", "last"]
|
|
2880
|
+
},
|
|
2881
|
+
fill: { type: "string", enum: ["none", "null", "prev"] }
|
|
2882
|
+
},
|
|
2883
|
+
required: ["action"]
|
|
2884
|
+
},
|
|
2885
|
+
annotations: {
|
|
2886
|
+
readOnlyHint: true,
|
|
2887
|
+
destructiveHint: false,
|
|
2888
|
+
idempotentHint: true,
|
|
2889
|
+
openWorldHint: false
|
|
2890
|
+
}
|
|
2891
|
+
},
|
|
2697
2892
|
{
|
|
2698
2893
|
name: "mindgraph_ingest",
|
|
2699
2894
|
description: "Ingest long-form content (articles, transcripts, documents, meeting notes) into the knowledge graph. The pipeline automatically chunks the text, extracts entities/claims/relationships via LLM, and deduplicates against existing nodes. Use 'chunk' for a single passage (synchronous), 'document' for full documents (async \u2014 returns a job ID), 'session' for conversation transcripts. Use 'job_status' to check progress. For short individual facts, use the specific cognitive tools instead.",
|
|
@@ -3026,6 +3221,9 @@ async function handleTool(client2, name, args) {
|
|
|
3026
3221
|
case "mindgraph_retrieve":
|
|
3027
3222
|
result = await handleRetrieve(client2, args);
|
|
3028
3223
|
break;
|
|
3224
|
+
case "mindgraph_series_query":
|
|
3225
|
+
result = await handleSeriesQuery(client2, args);
|
|
3226
|
+
break;
|
|
3029
3227
|
case "mindgraph_ingest":
|
|
3030
3228
|
result = await handleIngest(client2, args);
|
|
3031
3229
|
break;
|
|
@@ -3070,12 +3268,36 @@ async function handleTool(client2, name, args) {
|
|
|
3070
3268
|
return err3(errorDetail(e));
|
|
3071
3269
|
}
|
|
3072
3270
|
}
|
|
3271
|
+
async function handleSeriesQuery(client2, args) {
|
|
3272
|
+
const action = typeof args.action === "string" ? args.action : "";
|
|
3273
|
+
if (!["list_for_entity", "latest", "window", "aggregate"].includes(action)) {
|
|
3274
|
+
return err3(`Unknown series query action: ${action}`);
|
|
3275
|
+
}
|
|
3276
|
+
if (action === "list_for_entity" && typeof args.entity_uid !== "string") {
|
|
3277
|
+
return err3("entity_uid is required for action='list_for_entity'");
|
|
3278
|
+
}
|
|
3279
|
+
if (action !== "list_for_entity" && typeof args.series_uid !== "string") {
|
|
3280
|
+
return err3(`series_uid is required for action='${action}'`);
|
|
3281
|
+
}
|
|
3282
|
+
if ((action === "window" || action === "aggregate") && (typeof args.from !== "number" || typeof args.to !== "number")) {
|
|
3283
|
+
return err3(`from and to are required for action='${action}'`);
|
|
3284
|
+
}
|
|
3285
|
+
if (action === "aggregate" && (typeof args.bucket !== "string" || typeof args.agg !== "string")) {
|
|
3286
|
+
return err3("bucket and agg are required for action='aggregate'");
|
|
3287
|
+
}
|
|
3288
|
+
return ok3(
|
|
3289
|
+
await client2.series(args)
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3073
3292
|
async function handleCapture(client2, args) {
|
|
3074
3293
|
const {
|
|
3075
3294
|
action,
|
|
3076
3295
|
label,
|
|
3077
3296
|
summary,
|
|
3078
3297
|
content,
|
|
3298
|
+
name,
|
|
3299
|
+
description,
|
|
3300
|
+
license,
|
|
3079
3301
|
session_uid,
|
|
3080
3302
|
work_uid,
|
|
3081
3303
|
execution_uid,
|
|
@@ -3110,6 +3332,41 @@ async function handleCapture(client2, args) {
|
|
|
3110
3332
|
agent_id
|
|
3111
3333
|
})
|
|
3112
3334
|
);
|
|
3335
|
+
case "skill": {
|
|
3336
|
+
if (!name) return err3("name is required for action=skill");
|
|
3337
|
+
if (!description) return err3("description is required for action=skill");
|
|
3338
|
+
if (content === void 0) return err3("content is required for action=skill");
|
|
3339
|
+
const hasProvenance = [session_uid, work_uid, execution_uid].some(
|
|
3340
|
+
(uid) => typeof uid === "string" && uid.length > 0
|
|
3341
|
+
) || Array.isArray(summarizes_uids) && summarizes_uids.some((uid) => typeof uid === "string" && uid.length > 0);
|
|
3342
|
+
if (!hasProvenance) {
|
|
3343
|
+
return err3(
|
|
3344
|
+
"at least one of session_uid, work_uid, execution_uid, or summarizes_uids is required for action=skill"
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
return ok3(
|
|
3348
|
+
await client2.distill({
|
|
3349
|
+
label,
|
|
3350
|
+
output_type: "skill",
|
|
3351
|
+
summary,
|
|
3352
|
+
confidence,
|
|
3353
|
+
salience,
|
|
3354
|
+
session_uid,
|
|
3355
|
+
work_uid,
|
|
3356
|
+
execution_uid,
|
|
3357
|
+
idempotency_key,
|
|
3358
|
+
supersedes_uid,
|
|
3359
|
+
summarizes_uids,
|
|
3360
|
+
props: {
|
|
3361
|
+
name,
|
|
3362
|
+
description,
|
|
3363
|
+
content,
|
|
3364
|
+
...license ? { license } : {}
|
|
3365
|
+
},
|
|
3366
|
+
agent_id
|
|
3367
|
+
})
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3113
3370
|
case "journal": {
|
|
3114
3371
|
if (!content) return err3("content is required for action=journal");
|
|
3115
3372
|
const journalProps = { content };
|
|
@@ -4313,6 +4570,7 @@ var ONTOLOGY_READ_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
4313
4570
|
]);
|
|
4314
4571
|
function toolMutability(toolName, action) {
|
|
4315
4572
|
if (toolName === "mindgraph_retrieve") return "read";
|
|
4573
|
+
if (toolName === "mindgraph_series_query") return "read";
|
|
4316
4574
|
if (toolName === "mindgraph_plan") {
|
|
4317
4575
|
return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
|
|
4318
4576
|
}
|
|
@@ -4339,6 +4597,8 @@ function toolMutability(toolName, action) {
|
|
|
4339
4597
|
var UID_FIELDS = [
|
|
4340
4598
|
"uid",
|
|
4341
4599
|
"source_uid",
|
|
4600
|
+
"entity_uid",
|
|
4601
|
+
"series_uid",
|
|
4342
4602
|
"session_uid",
|
|
4343
4603
|
"work_uid",
|
|
4344
4604
|
"goal_uid",
|
|
@@ -4440,7 +4700,7 @@ async function checkMcpGovernance(toolName, args, config) {
|
|
|
4440
4700
|
if (!response) {
|
|
4441
4701
|
return {
|
|
4442
4702
|
allowed: false,
|
|
4443
|
-
message: `MindGraph
|
|
4703
|
+
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.`
|
|
4444
4704
|
};
|
|
4445
4705
|
}
|
|
4446
4706
|
if (!response.ok) {
|
|
@@ -4606,6 +4866,225 @@ async function readGovernedResource(client2, uri, governanceConfig) {
|
|
|
4606
4866
|
return readResource(client2, uri);
|
|
4607
4867
|
}
|
|
4608
4868
|
|
|
4869
|
+
// src/index.ts
|
|
4870
|
+
var import_node_path5 = __toESM(require("path"));
|
|
4871
|
+
|
|
4872
|
+
// src/hook-installer.ts
|
|
4873
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
4874
|
+
var import_node_os3 = __toESM(require("os"));
|
|
4875
|
+
var import_node_path4 = __toESM(require("path"));
|
|
4876
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
4877
|
+
var OWNER_MARKER = "--owner mindgraph";
|
|
4878
|
+
var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
4879
|
+
function command(harness) {
|
|
4880
|
+
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
4881
|
+
}
|
|
4882
|
+
function windowsCommand(harness) {
|
|
4883
|
+
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
4884
|
+
}
|
|
4885
|
+
var RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
|
|
4886
|
+
function installHookRunner(sourceFile, homeDir, version) {
|
|
4887
|
+
const target = import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_RELATIVE);
|
|
4888
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
|
|
4889
|
+
const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
4890
|
+
import_node_fs4.default.copyFileSync(sourceFile, temporary);
|
|
4891
|
+
import_node_fs4.default.renameSync(temporary, target);
|
|
4892
|
+
const versionPath = import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_VERSION_RELATIVE);
|
|
4893
|
+
if (version) {
|
|
4894
|
+
import_node_fs4.default.writeFileSync(versionPath, `${version}
|
|
4895
|
+
`);
|
|
4896
|
+
} else {
|
|
4897
|
+
import_node_fs4.default.rmSync(versionPath, { force: true });
|
|
4898
|
+
}
|
|
4899
|
+
return target;
|
|
4900
|
+
}
|
|
4901
|
+
function installedHookRunnerVersion(homeDir) {
|
|
4902
|
+
try {
|
|
4903
|
+
const raw = import_node_fs4.default.readFileSync(
|
|
4904
|
+
import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_VERSION_RELATIVE),
|
|
4905
|
+
"utf8"
|
|
4906
|
+
).trim();
|
|
4907
|
+
return raw || void 0;
|
|
4908
|
+
} catch {
|
|
4909
|
+
return void 0;
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
function parseSemver(value) {
|
|
4913
|
+
if (!value) return void 0;
|
|
4914
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
|
|
4915
|
+
if (!match) return void 0;
|
|
4916
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4917
|
+
}
|
|
4918
|
+
function semverNewer(a, b) {
|
|
4919
|
+
for (let index = 0; index < 3; index += 1) {
|
|
4920
|
+
if (a[index] !== b[index]) return a[index] > b[index];
|
|
4921
|
+
}
|
|
4922
|
+
return false;
|
|
4923
|
+
}
|
|
4924
|
+
function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
|
|
4925
|
+
try {
|
|
4926
|
+
if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
|
|
4927
|
+
return { refreshed: false, reason: "disabled" };
|
|
4928
|
+
}
|
|
4929
|
+
const home = homeDir || import_node_os3.default.homedir();
|
|
4930
|
+
if (!import_node_fs4.default.existsSync(import_node_path4.default.join(home, RUNNER_RELATIVE))) {
|
|
4931
|
+
return { refreshed: false, reason: "no_runner" };
|
|
4932
|
+
}
|
|
4933
|
+
if (!import_node_fs4.default.existsSync(sourceBundle)) {
|
|
4934
|
+
return { refreshed: false, reason: "no_source" };
|
|
4935
|
+
}
|
|
4936
|
+
const server2 = parseSemver(serverVersion);
|
|
4937
|
+
if (!server2) return { refreshed: false, reason: "unparseable" };
|
|
4938
|
+
const runnerVersion = installedHookRunnerVersion(home);
|
|
4939
|
+
if (runnerVersion !== void 0) {
|
|
4940
|
+
const pinned = parseSemver(runnerVersion);
|
|
4941
|
+
if (!pinned) {
|
|
4942
|
+
return { refreshed: false, reason: "unparseable", runnerVersion };
|
|
4943
|
+
}
|
|
4944
|
+
if (!semverNewer(server2, pinned)) {
|
|
4945
|
+
return {
|
|
4946
|
+
refreshed: false,
|
|
4947
|
+
reason: runnerVersion === serverVersion ? "current" : "downgrade",
|
|
4948
|
+
runnerVersion
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
installHookRunner(sourceBundle, home, serverVersion);
|
|
4953
|
+
return { refreshed: true, reason: "updated", runnerVersion };
|
|
4954
|
+
} catch {
|
|
4955
|
+
return { refreshed: false, reason: "error" };
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
|
|
4959
|
+
const refreshed = [];
|
|
4960
|
+
const scopes = ["user", "project"];
|
|
4961
|
+
for (const scope of scopes) {
|
|
4962
|
+
try {
|
|
4963
|
+
const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
|
|
4964
|
+
if (!import_node_fs4.default.existsSync(file)) continue;
|
|
4965
|
+
if (!import_node_fs4.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
|
|
4966
|
+
installHooks("claude-code", scope, projectDir, void 0, homeDir);
|
|
4967
|
+
refreshed.push(file);
|
|
4968
|
+
} catch {
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
return refreshed;
|
|
4972
|
+
}
|
|
4973
|
+
function versionSkewNote(serverVersion, homeDir) {
|
|
4974
|
+
const runnerVersion = installedHookRunnerVersion(homeDir);
|
|
4975
|
+
if (!runnerVersion || runnerVersion === serverVersion) return void 0;
|
|
4976
|
+
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).`;
|
|
4977
|
+
}
|
|
4978
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os3.default.homedir(), ".codex"), homeDir) {
|
|
4979
|
+
if (harness === "codex") {
|
|
4980
|
+
return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
|
|
4981
|
+
}
|
|
4982
|
+
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");
|
|
4983
|
+
}
|
|
4984
|
+
function readSettings(file) {
|
|
4985
|
+
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
4986
|
+
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
4987
|
+
}
|
|
4988
|
+
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
4989
|
+
return {
|
|
4990
|
+
type: "command",
|
|
4991
|
+
command: command(harness),
|
|
4992
|
+
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
4993
|
+
timeout,
|
|
4994
|
+
statusMessage: "Syncing MindGraph session context",
|
|
4995
|
+
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
4996
|
+
};
|
|
4997
|
+
}
|
|
4998
|
+
function desiredEntries(harness) {
|
|
4999
|
+
const common = {
|
|
5000
|
+
SessionStart: [
|
|
5001
|
+
{
|
|
5002
|
+
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
5003
|
+
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
5004
|
+
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
5005
|
+
// open, but no brief reached the model. The same installed runner
|
|
5006
|
+
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
5007
|
+
// The declared Codex context limit must not undercut the brief's own
|
|
5008
|
+
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
5009
|
+
// truncation the renderer knew nothing about.
|
|
5010
|
+
hooks: [
|
|
5011
|
+
ownedHook(
|
|
5012
|
+
harness,
|
|
5013
|
+
30,
|
|
5014
|
+
harness === "codex" ? 1e4 : void 0
|
|
5015
|
+
)
|
|
5016
|
+
]
|
|
5017
|
+
}
|
|
5018
|
+
],
|
|
5019
|
+
PreToolUse: [
|
|
5020
|
+
{
|
|
5021
|
+
matcher: "mcp__mindgraph__.*",
|
|
5022
|
+
// Repository resolution over a large multi-repo workspace plus three
|
|
5023
|
+
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
5024
|
+
// PreToolUse means the call goes through untagged.
|
|
5025
|
+
hooks: [ownedHook(harness, 5)]
|
|
5026
|
+
}
|
|
5027
|
+
],
|
|
5028
|
+
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
5029
|
+
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
5030
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
|
|
5031
|
+
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
5032
|
+
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
5033
|
+
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
5034
|
+
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
5035
|
+
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
5036
|
+
// map), so the codex adapter instead makes at most ONE call there.
|
|
5037
|
+
SessionEnd: [
|
|
5038
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
5039
|
+
]
|
|
5040
|
+
};
|
|
5041
|
+
return harness === "claude-code" ? {
|
|
5042
|
+
...common,
|
|
5043
|
+
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
5044
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
5045
|
+
} : common;
|
|
5046
|
+
}
|
|
5047
|
+
function hookCommand(value) {
|
|
5048
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
5049
|
+
const command2 = value.command;
|
|
5050
|
+
return typeof command2 === "string" ? command2 : void 0;
|
|
5051
|
+
}
|
|
5052
|
+
function entryHasOwnedHook(value) {
|
|
5053
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5054
|
+
const hooks = value.hooks;
|
|
5055
|
+
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
5056
|
+
}
|
|
5057
|
+
function writeSettings(file, settings) {
|
|
5058
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
5059
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
5060
|
+
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
5061
|
+
`, {
|
|
5062
|
+
mode: 384
|
|
5063
|
+
});
|
|
5064
|
+
import_node_fs4.default.renameSync(temporary, file);
|
|
5065
|
+
}
|
|
5066
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir) {
|
|
5067
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
|
|
5068
|
+
const settings = readSettings(file);
|
|
5069
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
5070
|
+
let added = 0;
|
|
5071
|
+
let updated = 0;
|
|
5072
|
+
for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
|
|
5073
|
+
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
5074
|
+
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
5075
|
+
const hadOwned = kept.length !== current.length;
|
|
5076
|
+
for (const entry of wanted) {
|
|
5077
|
+
kept.push(entry);
|
|
5078
|
+
if (hadOwned) updated += 1;
|
|
5079
|
+
else added += 1;
|
|
5080
|
+
}
|
|
5081
|
+
hooks[event] = kept;
|
|
5082
|
+
}
|
|
5083
|
+
settings.hooks = hooks;
|
|
5084
|
+
writeSettings(file, settings);
|
|
5085
|
+
return { path: file, added, updated };
|
|
5086
|
+
}
|
|
5087
|
+
|
|
4609
5088
|
// src/index.ts
|
|
4610
5089
|
var API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
4611
5090
|
var BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
@@ -4728,10 +5207,42 @@ if (PROFILE === "coding") {
|
|
|
4728
5207
|
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
4729
5208
|
).join("\n");
|
|
4730
5209
|
}
|
|
5210
|
+
var RUNNER_REFRESH = (() => {
|
|
5211
|
+
try {
|
|
5212
|
+
return refreshHookRunner("0.19.0", import_node_path5.default.join(__dirname, "cli.js"));
|
|
5213
|
+
} catch {
|
|
5214
|
+
return void 0;
|
|
5215
|
+
}
|
|
5216
|
+
})();
|
|
5217
|
+
if (RUNNER_REFRESH?.refreshed) {
|
|
5218
|
+
const refreshedSettings = (() => {
|
|
5219
|
+
try {
|
|
5220
|
+
return refreshOwnedClaudeHooks();
|
|
5221
|
+
} catch {
|
|
5222
|
+
return [];
|
|
5223
|
+
}
|
|
5224
|
+
})();
|
|
5225
|
+
console.error(
|
|
5226
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.19.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
5227
|
+
);
|
|
5228
|
+
}
|
|
5229
|
+
var HOOK_SKEW_NOTE = (() => {
|
|
5230
|
+
try {
|
|
5231
|
+
return versionSkewNote("0.19.0");
|
|
5232
|
+
} catch {
|
|
5233
|
+
return void 0;
|
|
5234
|
+
}
|
|
5235
|
+
})();
|
|
5236
|
+
if (HOOK_SKEW_NOTE) {
|
|
5237
|
+
INSTRUCTIONS += `
|
|
5238
|
+
|
|
5239
|
+
${HOOK_SKEW_NOTE}`;
|
|
5240
|
+
console.error(`[mindgraph-mcp] ${HOOK_SKEW_NOTE}`);
|
|
5241
|
+
}
|
|
4731
5242
|
var server = new import_server.Server(
|
|
4732
5243
|
{
|
|
4733
5244
|
name: "mindgraph",
|
|
4734
|
-
version: "0.
|
|
5245
|
+
version: "0.19.0"
|
|
4735
5246
|
},
|
|
4736
5247
|
{
|
|
4737
5248
|
capabilities: {
|
|
@@ -4945,7 +5456,7 @@ async function main() {
|
|
|
4945
5456
|
const transport = new import_stdio.StdioServerTransport();
|
|
4946
5457
|
await server.connect(transport);
|
|
4947
5458
|
console.error(
|
|
4948
|
-
`MindGraph MCP server v${"0.
|
|
5459
|
+
`MindGraph MCP server v${"0.19.0"} running on stdio (${BASE_URL})`
|
|
4949
5460
|
);
|
|
4950
5461
|
}
|
|
4951
5462
|
main().catch((error) => {
|