mindgraph-mcp 0.13.4 → 0.14.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.
Files changed (5) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +71 -3
  3. package/dist/cli.js +3207 -494
  4. package/dist/index.js +2353 -359
  5. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,22 +1,1438 @@
1
1
  "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
2
24
 
3
25
  // src/index.ts
4
26
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
5
27
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
6
28
  var import_types = require("@modelcontextprotocol/sdk/types.js");
29
+ var import_mindgraph2 = require("mindgraph");
30
+
31
+ // src/tools.ts
7
32
  var import_mindgraph = require("mindgraph");
8
33
 
34
+ // src/codegraph.ts
35
+ var import_node_child_process = require("child_process");
36
+ var import_node_fs = require("fs");
37
+ var import_promises = require("fs/promises");
38
+ var import_node_os = require("os");
39
+ var import_node_path = require("path");
40
+ var import_node_crypto = require("crypto");
41
+ var DEFAULT_TIMEOUT_MS = 5e3;
42
+ var DEFAULT_STATUS_TTL_MS = 6e4;
43
+ var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
44
+ function normalizePath(path2) {
45
+ return path2.replaceAll("\\", "/").replace(/^\.\//, "");
46
+ }
47
+ function normalizeSignature(signature) {
48
+ return signature.replace(/\s+/g, " ").trim();
49
+ }
50
+ function languageFromPath(path2) {
51
+ const extension = (0, import_node_path.extname)(path2).toLowerCase();
52
+ return {
53
+ ".c": "c",
54
+ ".cc": "cpp",
55
+ ".cpp": "cpp",
56
+ ".go": "go",
57
+ ".java": "java",
58
+ ".js": "javascript",
59
+ ".jsx": "javascript",
60
+ ".kt": "kotlin",
61
+ ".py": "python",
62
+ ".rb": "ruby",
63
+ ".rs": "rust",
64
+ ".swift": "swift",
65
+ ".ts": "typescript",
66
+ ".tsx": "typescript"
67
+ }[extension] ?? "unknown";
68
+ }
69
+ function asNode(hit) {
70
+ return hit.node ?? hit;
71
+ }
72
+ function toCandidate(node) {
73
+ if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
74
+ return {
75
+ path: normalizePath(node.filePath),
76
+ language: node.language ?? languageFromPath(node.filePath),
77
+ kind: node.kind,
78
+ qualifiedName: node.qualifiedName ?? node.name ?? "",
79
+ signature: node.signature ?? null,
80
+ startLine: node.startLine ?? 1,
81
+ endLine: node.endLine ?? node.startLine ?? 1
82
+ };
83
+ }
84
+ function normalizeRemote(remote) {
85
+ const trimmed = remote.trim();
86
+ if (!trimmed) return null;
87
+ const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
88
+ if (scp && !trimmed.includes("://")) {
89
+ return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
90
+ }
91
+ try {
92
+ const url = new URL(trimmed);
93
+ const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
94
+ return `${url.host.toLowerCase()}${pathname}`;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ function pathContains(root, candidate) {
100
+ const child = (0, import_node_path.relative)(root, candidate);
101
+ return child === "" || child !== ".." && !child.startsWith(`..${import_node_path.sep}`) && !(0, import_node_path.isAbsolute)(child);
102
+ }
103
+ function nearestExistingDirectory(candidate) {
104
+ let current = (0, import_node_path.resolve)(candidate);
105
+ for (; ; ) {
106
+ if ((0, import_node_fs.existsSync)(current)) {
107
+ try {
108
+ return (0, import_node_fs.statSync)(current).isDirectory() ? current : (0, import_node_path.dirname)(current);
109
+ } catch {
110
+ }
111
+ }
112
+ const parent = (0, import_node_path.dirname)(current);
113
+ if (parent === current) return current;
114
+ current = parent;
115
+ }
116
+ }
117
+ function findWorkspaceFile(start, explicit) {
118
+ if (explicit) return (0, import_node_path.resolve)(start, explicit);
119
+ let current = (0, import_node_path.resolve)(start);
120
+ for (; ; ) {
121
+ const candidate = (0, import_node_path.join)(current, ".mindgraph", "workspace.json");
122
+ if ((0, import_node_fs.existsSync)(candidate)) return candidate;
123
+ const parent = (0, import_node_path.dirname)(current);
124
+ if (parent === current) return void 0;
125
+ current = parent;
126
+ }
127
+ }
128
+ async function runProcess(executable, args, timeoutMs, env, cwd) {
129
+ return await new Promise((resolvePromise, rejectPromise) => {
130
+ const child = (0, import_node_child_process.spawn)(executable, args, {
131
+ cwd,
132
+ env,
133
+ stdio: ["ignore", "pipe", "pipe"],
134
+ windowsHide: true
135
+ });
136
+ const stdout = [];
137
+ const stderr = [];
138
+ let outputBytes = 0;
139
+ let timedOut = false;
140
+ const timer = setTimeout(() => {
141
+ timedOut = true;
142
+ child.kill("SIGTERM");
143
+ setTimeout(() => child.kill("SIGKILL"), 250).unref();
144
+ }, timeoutMs);
145
+ timer.unref();
146
+ const collect = (target) => (chunk) => {
147
+ outputBytes += chunk.length;
148
+ if (outputBytes > MAX_OUTPUT_BYTES) {
149
+ child.kill("SIGTERM");
150
+ return;
151
+ }
152
+ target.push(chunk);
153
+ };
154
+ child.stdout.on("data", collect(stdout));
155
+ child.stderr.on("data", collect(stderr));
156
+ child.on("error", (cause) => {
157
+ clearTimeout(timer);
158
+ const error = new Error(cause.message);
159
+ error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
160
+ rejectPromise(error);
161
+ });
162
+ child.on("close", (code) => {
163
+ clearTimeout(timer);
164
+ if (timedOut) {
165
+ const error = new Error(`command timed out after ${timeoutMs}ms`);
166
+ error.kind = "timeout";
167
+ rejectPromise(error);
168
+ return;
169
+ }
170
+ if (outputBytes > MAX_OUTPUT_BYTES) {
171
+ const error = new Error("codegraph output exceeded 5 MiB");
172
+ error.kind = "command_failed";
173
+ rejectPromise(error);
174
+ return;
175
+ }
176
+ if (code !== 0) {
177
+ const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
178
+ error.kind = "command_failed";
179
+ error.exitCode = code;
180
+ rejectPromise(error);
181
+ return;
182
+ }
183
+ resolvePromise(Buffer.concat(stdout).toString("utf8"));
184
+ });
185
+ });
186
+ }
187
+ var CodegraphAdapter = class {
188
+ timeoutMs;
189
+ statusTtlMs;
190
+ env;
191
+ cwd;
192
+ workspaceFile;
193
+ now;
194
+ identityCache = /* @__PURE__ */ new Map();
195
+ statusCache = /* @__PURE__ */ new Map();
196
+ constructor(options = {}) {
197
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
198
+ this.statusTtlMs = options.statusTtlMs ?? DEFAULT_STATUS_TTL_MS;
199
+ this.env = options.env ?? process.env;
200
+ this.cwd = options.cwd ?? process.cwd();
201
+ this.workspaceFile = findWorkspaceFile(
202
+ this.cwd,
203
+ options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
204
+ );
205
+ this.now = options.now ?? Date.now;
206
+ }
207
+ async resolveRepository(repo, fallbackSpaceUid) {
208
+ const configured = await this.resolveRepositories(fallbackSpaceUid);
209
+ const selected = repo && configured.find((candidate) => candidate.repoId === repo) || repo && configured.find(
210
+ (candidate) => candidate.repoRoot === repo || (0, import_node_path.basename)(candidate.repoRoot) === repo
211
+ ) || (!repo ? [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, this.cwd)) : void 0) || (!repo && configured.length === 1 ? configured[0] : void 0);
212
+ if (selected) {
213
+ return !repo && this.env.MINDGRAPH_REPO_ID ? this.resolveRepositoryIdentity(
214
+ selected.repoRoot,
215
+ this.env.MINDGRAPH_REPO_ID,
216
+ selected.spaceUid
217
+ ) : selected;
218
+ }
219
+ const requestedRoot = repo || this.env.MINDGRAPH_REPO_ROOT || this.cwd;
220
+ const requestedPath = await (0, import_promises.realpath)((0, import_node_path.resolve)(this.cwd, requestedRoot)).catch(
221
+ () => (0, import_node_path.resolve)(this.cwd, requestedRoot)
222
+ );
223
+ const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
224
+ return this.resolveRepositoryIdentity(
225
+ repoRoot,
226
+ this.env.MINDGRAPH_REPO_ID,
227
+ this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
228
+ );
229
+ }
230
+ async resolveRepositories(fallbackSpaceUid) {
231
+ return Promise.all(
232
+ this.configuredRepositories().map(
233
+ (repository) => this.resolveRepositoryIdentity(
234
+ repository.repoRoot,
235
+ repository.repoId,
236
+ repository.spaceUid || this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
237
+ )
238
+ )
239
+ );
240
+ }
241
+ async resolveRepositoryIdentity(root, explicitRepoId, spaceUid) {
242
+ const repoRoot = await (0, import_promises.realpath)(root).catch(() => (0, import_node_path.resolve)(root));
243
+ const cacheKey = `${repoRoot}\0${explicitRepoId ?? ""}\0${spaceUid ?? ""}`;
244
+ const cached = this.identityCache.get(cacheKey);
245
+ if (cached) return cached;
246
+ const pending = this.loadRepositoryIdentity(repoRoot, explicitRepoId, spaceUid);
247
+ this.identityCache.set(cacheKey, pending);
248
+ return pending;
249
+ }
250
+ async loadRepositoryIdentity(repoRoot, explicitRepoId, spaceUid) {
251
+ const remote = await runProcess(
252
+ "git",
253
+ ["-C", repoRoot, "config", "--get", "remote.origin.url"],
254
+ Math.min(this.timeoutMs, 2e3),
255
+ this.env
256
+ ).catch(() => "");
257
+ const repoId = explicitRepoId || normalizeRemote(remote) || await this.persistedRepositoryId(repoRoot);
258
+ return {
259
+ repoId,
260
+ repoRoot,
261
+ spaceUid
262
+ };
263
+ }
264
+ async persistedRepositoryId(repoRoot) {
265
+ const rawCommonDir = await runProcess(
266
+ "git",
267
+ ["-C", repoRoot, "rev-parse", "--git-common-dir"],
268
+ Math.min(this.timeoutMs, 2e3),
269
+ this.env
270
+ ).catch(() => "");
271
+ if (!rawCommonDir.trim()) {
272
+ return `local:${(0, import_node_path.basename)(repoRoot)}`;
273
+ }
274
+ const commonDir = (0, import_node_path.resolve)(repoRoot, rawCommonDir.trim());
275
+ const identityDir = (0, import_node_path.join)(commonDir, "mindgraph");
276
+ const identityFile = (0, import_node_path.join)(identityDir, "repository-id");
277
+ try {
278
+ const existing = (0, import_node_fs.readFileSync)(identityFile, "utf8").trim();
279
+ if (existing) return existing;
280
+ } catch {
281
+ }
282
+ (0, import_node_fs.mkdirSync)(identityDir, { recursive: true, mode: 448 });
283
+ const generated = (0, import_node_crypto.randomUUID)();
284
+ try {
285
+ (0, import_node_fs.writeFileSync)(identityFile, `${generated}
286
+ `, {
287
+ encoding: "utf8",
288
+ flag: "wx",
289
+ mode: 384
290
+ });
291
+ return generated;
292
+ } catch (cause) {
293
+ if (cause.code !== "EEXIST") throw cause;
294
+ const winner = (0, import_node_fs.readFileSync)(identityFile, "utf8").trim();
295
+ if (!winner) throw new Error(`empty repository identity file: ${identityFile}`);
296
+ return winner;
297
+ }
298
+ }
299
+ async gitRoot(candidate) {
300
+ const start = nearestExistingDirectory(candidate);
301
+ const raw = await runProcess(
302
+ "git",
303
+ ["-C", start, "rev-parse", "--show-toplevel"],
304
+ Math.min(this.timeoutMs, 2e3),
305
+ this.env
306
+ ).catch(() => "");
307
+ return raw.trim() ? (0, import_node_path.resolve)(start, raw.trim()) : void 0;
308
+ }
309
+ async availability(repository) {
310
+ const cached = this.statusCache.get(repository.repoRoot);
311
+ if (cached && cached.expiresAt > this.now()) return cached.availability;
312
+ try {
313
+ const status = await this.runJson([
314
+ "status",
315
+ repository.repoRoot,
316
+ "-j"
317
+ ]);
318
+ const caveats = [];
319
+ let availability;
320
+ if (status.worktreeMismatch != null) {
321
+ availability = {
322
+ available: false,
323
+ reason: "wrong_index",
324
+ stale: false,
325
+ caveats: ["the index belongs to a different worktree"],
326
+ lastIndexed: status.lastIndexed
327
+ };
328
+ } else if (!status.initialized || status.index?.state !== "complete") {
329
+ availability = {
330
+ available: false,
331
+ reason: "index_incomplete",
332
+ stale: false,
333
+ caveats: [
334
+ !status.initialized ? "codegraph is not initialized for this repository" : `codegraph index state is ${status.index?.state ?? "unknown"}`
335
+ ],
336
+ lastIndexed: status.lastIndexed
337
+ };
338
+ } else {
339
+ const pending = (status.pendingChanges?.added ?? 0) + (status.pendingChanges?.modified ?? 0) + (status.pendingChanges?.removed ?? 0);
340
+ if (pending > 0) caveats.push(`${pending} worktree changes are not indexed`);
341
+ if ((status.index?.pendingRefs ?? 0) > 0) {
342
+ caveats.push(`${status.index?.pendingRefs} references remain pending`);
343
+ }
344
+ if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
345
+ availability = {
346
+ available: true,
347
+ stale: caveats.length > 0,
348
+ caveats,
349
+ lastIndexed: status.lastIndexed
350
+ };
351
+ }
352
+ this.statusCache.set(repository.repoRoot, {
353
+ status,
354
+ availability,
355
+ expiresAt: this.now() + this.statusTtlMs
356
+ });
357
+ return availability;
358
+ } catch (cause) {
359
+ const failure = cause;
360
+ return {
361
+ available: false,
362
+ reason: failure.kind ?? "command_failed",
363
+ stale: false,
364
+ caveats: [failure.message || "codegraph status failed"]
365
+ };
366
+ }
367
+ }
368
+ async resolveRefs(refs, options = {}) {
369
+ const normalized = refs.map(
370
+ (ref) => typeof ref === "string" ? { symbol: ref } : ref
371
+ );
372
+ const results = await Promise.all(
373
+ normalized.map(async (ref) => {
374
+ const repository2 = await this.repositoryForRef(
375
+ ref,
376
+ options.repo,
377
+ options.fallbackSpaceUid
378
+ );
379
+ const availability2 = await this.availability(repository2);
380
+ if (!availability2.available) {
381
+ return {
382
+ ref,
383
+ repository: repository2,
384
+ availability: availability2,
385
+ candidates: [],
386
+ error: availability2.reason
387
+ };
388
+ }
389
+ return {
390
+ ...await this.resolveOne(ref, repository2),
391
+ repository: repository2,
392
+ availability: availability2
393
+ };
394
+ })
395
+ );
396
+ const repositories = [
397
+ ...new Map(
398
+ results.flatMap(
399
+ (result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
400
+ )
401
+ ).values()
402
+ ];
403
+ const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
404
+ if (repositories.length === 0) repositories.push(repository);
405
+ const availabilities = results.flatMap(
406
+ (result) => result.availability ? [result.availability] : []
407
+ );
408
+ const availability = availabilities.length === 0 ? await this.availability(repository) : {
409
+ available: availabilities.every((item) => item.available),
410
+ reason: availabilities.find((item) => !item.available)?.reason,
411
+ stale: availabilities.some((item) => item.stale),
412
+ caveats: [
413
+ ...new Set(availabilities.flatMap((item) => item.caveats))
414
+ ]
415
+ };
416
+ return { repository, repositories, availability, results };
417
+ }
418
+ async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
419
+ if (ref.repo || defaultRepo) {
420
+ return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
421
+ }
422
+ if (ref.path) {
423
+ const requested = (0, import_node_path.isAbsolute)(ref.path) ? (0, import_node_path.resolve)(ref.path) : (0, import_node_path.resolve)(this.cwd, ref.path);
424
+ const absolute = await (0, import_promises.realpath)(requested).catch(() => requested);
425
+ const configured = await this.resolveRepositories(fallbackSpaceUid);
426
+ const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
427
+ if (defining) {
428
+ return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
429
+ defining.repoRoot,
430
+ this.env.MINDGRAPH_REPO_ID,
431
+ defining.spaceUid
432
+ ) : defining;
433
+ }
434
+ const gitRoot = await this.gitRoot(absolute);
435
+ if (gitRoot) {
436
+ return this.resolveRepositoryIdentity(
437
+ gitRoot,
438
+ this.env.MINDGRAPH_REPO_ID,
439
+ this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
440
+ );
441
+ }
442
+ }
443
+ return this.resolveRepository(void 0, fallbackSpaceUid);
444
+ }
445
+ async expand(resolved, limit = 20) {
446
+ const repository = {
447
+ repoId: resolved.repoId,
448
+ repoRoot: resolved.repoRoot
449
+ };
450
+ const availability = await this.availability(repository);
451
+ if (!availability.available) {
452
+ return { availability, callers: [], callees: [], truncated: false };
453
+ }
454
+ const cap = Math.min(Math.max(limit, 1), 20);
455
+ const [callers, callees] = await Promise.all([
456
+ this.related("callers", resolved, cap + 1),
457
+ this.related("callees", resolved, cap + 1)
458
+ ]);
459
+ return {
460
+ availability,
461
+ callers: callers.slice(0, cap),
462
+ callees: callees.slice(0, cap),
463
+ truncated: callers.length > cap || callees.length > cap
464
+ };
465
+ }
466
+ async affected(resolved, limit = 50) {
467
+ const repository = {
468
+ repoId: resolved.repoId,
469
+ repoRoot: resolved.repoRoot
470
+ };
471
+ const availability = await this.availability(repository);
472
+ if (!availability.available) {
473
+ return { availability, affected: [], truncated: false };
474
+ }
475
+ const cap = Math.min(Math.max(limit, 1), 50);
476
+ const raw = await this.runJson([
477
+ "impact",
478
+ "-p",
479
+ resolved.repoRoot,
480
+ "-j",
481
+ "-d",
482
+ "2",
483
+ resolved.qualifiedName
484
+ ]);
485
+ const refs = (raw.affected ?? []).map((node) => ({
486
+ path: node.filePath,
487
+ line: node.startLine,
488
+ symbol: node.qualifiedName ?? node.name,
489
+ kind: node.kind
490
+ }));
491
+ const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
492
+ return {
493
+ availability,
494
+ affected: resolvedRefs.slice(0, cap),
495
+ truncated: refs.length > cap
496
+ };
497
+ }
498
+ configuredRepositories() {
499
+ const configured = [];
500
+ if (this.workspaceFile && (0, import_node_fs.existsSync)(this.workspaceFile)) {
501
+ try {
502
+ const parsed = JSON.parse(
503
+ (0, import_node_fs.readFileSync)(this.workspaceFile, "utf8")
504
+ );
505
+ if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
506
+ const configDirectory = (0, import_node_path.dirname)(this.workspaceFile);
507
+ const base = (0, import_node_path.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path.dirname)(configDirectory) : configDirectory;
508
+ for (const repository of parsed.repositories) {
509
+ if (!repository || typeof repository.root !== "string") continue;
510
+ configured.push({
511
+ repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
512
+ repoRoot: (0, import_node_path.resolve)(base, repository.root),
513
+ spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
514
+ });
515
+ }
516
+ }
517
+ } catch {
518
+ }
519
+ }
520
+ const raw = this.env.MINDGRAPH_CODE_REPOS;
521
+ if (raw) {
522
+ try {
523
+ const parsed = JSON.parse(raw);
524
+ for (const [repoId, config] of Object.entries(parsed)) {
525
+ configured.push({
526
+ repoId,
527
+ repoRoot: (0, import_node_path.resolve)(
528
+ this.cwd,
529
+ typeof config === "string" ? config : config.root
530
+ ),
531
+ spaceUid: typeof config === "string" ? void 0 : config.space_uid
532
+ });
533
+ }
534
+ } catch {
535
+ }
536
+ }
537
+ return [
538
+ ...new Map(
539
+ configured.map((repository) => [repository.repoRoot, repository])
540
+ ).values()
541
+ ];
542
+ }
543
+ async resolveOne(ref, repository) {
544
+ const search = ref.symbol || ref.path;
545
+ if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
546
+ return { ref, candidates: [], error: "invalid_ref" };
547
+ }
548
+ try {
549
+ const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
550
+ if (ref.kind) args.push("-k", ref.kind);
551
+ args.push(search);
552
+ const hits = await this.runJson(args);
553
+ let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
554
+ if (ref.path) {
555
+ const absolutePath = (0, import_node_path.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
556
+ const wanted = normalizePath(
557
+ absolutePath ? (0, import_node_path.relative)(repository.repoRoot, absolutePath) : ref.path
558
+ );
559
+ candidates = candidates.filter((candidate) => candidate.path === wanted);
560
+ }
561
+ if (ref.language) {
562
+ candidates = candidates.filter(
563
+ (candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
564
+ );
565
+ }
566
+ if (ref.kind) {
567
+ candidates = candidates.filter(
568
+ (candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
569
+ );
570
+ }
571
+ if (ref.symbol) {
572
+ const exact = candidates.filter((candidate) => {
573
+ const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
574
+ return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
575
+ });
576
+ if (exact.length > 0) candidates = exact;
577
+ }
578
+ if (ref.signature) {
579
+ const signature = normalizeSignature(ref.signature);
580
+ candidates = candidates.filter(
581
+ (candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
582
+ );
583
+ }
584
+ if (ref.line !== void 0) {
585
+ candidates = candidates.filter(
586
+ (candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
587
+ );
588
+ if (candidates.length > 1) {
589
+ const narrowest = Math.min(
590
+ ...candidates.map((candidate) => candidate.endLine - candidate.startLine)
591
+ );
592
+ const narrowed = candidates.filter(
593
+ (candidate) => candidate.endLine - candidate.startLine === narrowest
594
+ );
595
+ if (narrowed.length === 1) candidates = narrowed;
596
+ }
597
+ }
598
+ if (candidates.length !== 1) {
599
+ return {
600
+ ref,
601
+ candidates,
602
+ error: candidates.length === 0 ? "not_found" : "ambiguous"
603
+ };
604
+ }
605
+ return {
606
+ ref,
607
+ candidates,
608
+ resolved: {
609
+ repoId: repository.repoId,
610
+ repoRoot: repository.repoRoot,
611
+ ...candidates[0]
612
+ }
613
+ };
614
+ } catch (cause) {
615
+ const failure = cause;
616
+ return {
617
+ ref,
618
+ candidates: [],
619
+ error: failure.kind ?? "command_failed"
620
+ };
621
+ }
622
+ }
623
+ async related(action, resolved, limit) {
624
+ const raw = await this.runJson([
625
+ action,
626
+ "-p",
627
+ resolved.repoRoot,
628
+ "-j",
629
+ "-l",
630
+ String(limit),
631
+ resolved.qualifiedName
632
+ ]);
633
+ const refs = (raw[action] ?? []).map((node) => ({
634
+ path: node.filePath,
635
+ line: node.startLine,
636
+ symbol: node.qualifiedName ?? node.name,
637
+ kind: node.kind
638
+ }));
639
+ return this.resolveRelatedRefs(refs, {
640
+ repoId: resolved.repoId,
641
+ repoRoot: resolved.repoRoot
642
+ });
643
+ }
644
+ async resolveRelatedRefs(refs, repository) {
645
+ const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
646
+ const seen = /* @__PURE__ */ new Set();
647
+ return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
648
+ const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
649
+ if (seen.has(key)) return false;
650
+ seen.add(key);
651
+ return true;
652
+ });
653
+ }
654
+ async runJson(args) {
655
+ const candidates = [
656
+ this.env.MINDGRAPH_CODEGRAPH_BIN,
657
+ "codegraph",
658
+ (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".local", "bin", "codegraph")
659
+ ].filter((candidate, index, all) => {
660
+ return Boolean(candidate) && all.indexOf(candidate) === index;
661
+ });
662
+ let lastFailure;
663
+ for (const executable of candidates) {
664
+ try {
665
+ const stdout = await runProcess(
666
+ executable,
667
+ args,
668
+ this.timeoutMs,
669
+ this.env,
670
+ this.cwd
671
+ );
672
+ return JSON.parse(stdout);
673
+ } catch (cause) {
674
+ const failure = cause;
675
+ lastFailure = failure;
676
+ if (failure.kind !== "absent") throw failure;
677
+ }
678
+ }
679
+ throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
680
+ kind: "absent"
681
+ });
682
+ }
683
+ };
684
+ function repositoryIdentityKey(repository) {
685
+ return { v: 1, kind: "repository", repo_id: repository.repoId };
686
+ }
687
+ function codeRefIdentityKey(ref) {
688
+ if (ref.kind === "file") {
689
+ return {
690
+ v: 1,
691
+ kind: "file",
692
+ repo_id: ref.repoId,
693
+ path: ref.path
694
+ };
695
+ }
696
+ return {
697
+ v: 1,
698
+ kind: "symbol",
699
+ repo_id: ref.repoId,
700
+ language: ref.language,
701
+ symbol_kind: ref.kind,
702
+ qualified_name: ref.qualifiedName,
703
+ disambiguator: {
704
+ signature: ref.signature ?? "",
705
+ path: ref.path,
706
+ start_line: ref.startLine,
707
+ end_line: ref.endLine
708
+ }
709
+ };
710
+ }
711
+
712
+ // src/code-tool.ts
713
+ var ok = (data) => ({
714
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
715
+ });
716
+ var err = (code, message, details) => ({
717
+ content: [
718
+ {
719
+ type: "text",
720
+ text: JSON.stringify({ error: message, code, details }, null, 2)
721
+ }
722
+ ],
723
+ isError: true
724
+ });
725
+ var CODE_TOOL = {
726
+ name: "mindgraph_code",
727
+ 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.",
728
+ inputSchema: {
729
+ type: "object",
730
+ properties: {
731
+ action: {
732
+ type: "string",
733
+ enum: ["anchor", "recall", "expand", "affected"]
734
+ },
735
+ code_refs: {
736
+ type: "array",
737
+ maxItems: 50,
738
+ items: {
739
+ oneOf: [
740
+ { type: "string" },
741
+ {
742
+ type: "object",
743
+ properties: {
744
+ repo: { type: "string" },
745
+ path: { type: "string" },
746
+ line: { type: "integer", minimum: 1 },
747
+ symbol: { type: "string" },
748
+ kind: { type: "string" },
749
+ language: { type: "string" },
750
+ signature: { type: "string" }
751
+ },
752
+ additionalProperties: false
753
+ }
754
+ ]
755
+ }
756
+ },
757
+ anchor_uids: {
758
+ type: "array",
759
+ maxItems: 50,
760
+ items: { type: "string" },
761
+ description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
762
+ },
763
+ repo: {
764
+ type: "string",
765
+ description: "Configured repository id or repository root"
766
+ },
767
+ repo_space_uid: {
768
+ type: "string",
769
+ description: "Writable shared Project/repository Space for new anchors"
770
+ },
771
+ task_uid: { type: "string" },
772
+ execution_uid: { type: "string" },
773
+ limit: { type: "integer", minimum: 1, maximum: 50 },
774
+ agent_id: { type: "string" }
775
+ },
776
+ required: ["action"]
777
+ },
778
+ annotations: {
779
+ readOnlyHint: false,
780
+ destructiveHint: false,
781
+ idempotentHint: true,
782
+ openWorldHint: true
783
+ }
784
+ };
785
+ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
786
+ const codeClient = client2;
787
+ const action = args.action;
788
+ const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
789
+ const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
790
+ const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
791
+ const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : `space:agent:${agentId}`;
792
+ const options = {
793
+ repo: typeof args.repo === "string" ? args.repo : void 0,
794
+ fallbackSpaceUid
795
+ };
796
+ const limit = typeof args.limit === "number" ? args.limit : void 0;
797
+ try {
798
+ switch (action) {
799
+ case "anchor": {
800
+ if (refs.length === 0) return err("missing_code_refs", "code_refs is required for anchor");
801
+ const resolution = await adapter.resolveRefs(refs, options);
802
+ const resolved = resolution.results.flatMap(
803
+ (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
804
+ );
805
+ const unresolved = resolution.results.filter((result) => !result.resolved);
806
+ if (resolved.length === 0) {
807
+ return ok({ ...resolution, anchors: [], wrote: false });
808
+ }
809
+ const missingSpace = resolved.find(
810
+ ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
811
+ );
812
+ if (missingSpace) {
813
+ return err(
814
+ "missing_repository_space",
815
+ `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
816
+ );
817
+ }
818
+ const repositoryAnchors = /* @__PURE__ */ new Map();
819
+ for (const { repository } of resolved) {
820
+ if (repositoryAnchors.has(repository.repoRoot)) continue;
821
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
822
+ const anchored = await anchorRepository(
823
+ codeClient,
824
+ repository,
825
+ spaceUid,
826
+ agentId
827
+ );
828
+ repositoryAnchors.set(repository.repoRoot, {
829
+ ...anchored,
830
+ repository
831
+ });
832
+ }
833
+ const anchors = [];
834
+ for (const { ref, repository } of resolved) {
835
+ const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
836
+ if (!repositoryAnchor.uid) continue;
837
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
838
+ const anchor = await anchorCodeRef(
839
+ codeClient,
840
+ ref,
841
+ repositoryAnchor.uid,
842
+ spaceUid,
843
+ agentId
844
+ );
845
+ anchors.push(anchor);
846
+ }
847
+ return ok({
848
+ availability: resolution.availability,
849
+ repository: repositoryAnchors.values().next().value,
850
+ repositories: [...repositoryAnchors.values()],
851
+ anchors,
852
+ unresolved,
853
+ wrote: anchors.some((anchor) => anchor.created)
854
+ });
855
+ }
856
+ case "recall": {
857
+ const materialized = new Set(anchorUids);
858
+ let resolution;
859
+ if (refs.length > 0) {
860
+ resolution = await adapter.resolveRefs(refs, options);
861
+ for (const result of resolution.results) {
862
+ if (!result.resolved) continue;
863
+ const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
864
+ if (uid) materialized.add(uid);
865
+ }
866
+ }
867
+ if (materialized.size === 0) {
868
+ return ok({
869
+ availability: resolution?.availability,
870
+ anchors: [],
871
+ context: [],
872
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
873
+ });
874
+ }
875
+ const context = await Promise.all(
876
+ [...materialized].map(async (uid) => ({
877
+ anchor_uid: uid,
878
+ graph: await codeClient.traverse({
879
+ action: "neighborhood",
880
+ start_uid: uid,
881
+ depth: 2,
882
+ max_nodes: Math.min(limit ?? 20, 20)
883
+ })
884
+ }))
885
+ );
886
+ return ok({
887
+ availability: resolution?.availability,
888
+ anchors: [...materialized],
889
+ context,
890
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
891
+ });
892
+ }
893
+ case "expand": {
894
+ const target = await resolveSingleTarget(
895
+ codeClient,
896
+ adapter,
897
+ refs,
898
+ anchorUids,
899
+ options
900
+ );
901
+ if ("error" in target) return target.error;
902
+ const expanded = await adapter.expand(target.ref, limit);
903
+ const joined = await joinMaterialized(
904
+ codeClient,
905
+ [...expanded.callers, ...expanded.callees],
906
+ agentId,
907
+ Math.min(limit ?? 20, 20)
908
+ );
909
+ return ok({ anchor: target.ref, ...expanded, materialized: joined });
910
+ }
911
+ case "affected": {
912
+ const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
913
+ let graphContext;
914
+ const graphRefs = [];
915
+ if (targetUid) {
916
+ graphContext = await codeClient.traverse({
917
+ action: "neighborhood",
918
+ start_uid: targetUid,
919
+ depth: 2,
920
+ max_nodes: 50
921
+ });
922
+ graphRefs.push(
923
+ ...await codeRefsFromGraphContext(codeClient, adapter, graphContext)
924
+ );
925
+ }
926
+ if (refs.length > 0 || anchorUids.length > 0) {
927
+ const target = await resolveSingleTarget(
928
+ codeClient,
929
+ adapter,
930
+ refs,
931
+ anchorUids,
932
+ options
933
+ );
934
+ if ("error" in target) return target.error;
935
+ graphRefs.unshift(target.ref);
936
+ }
937
+ const unique = uniqueRefs(graphRefs);
938
+ if (unique.length === 0) {
939
+ return ok({
940
+ target_uid: targetUid,
941
+ graph_context: graphContext,
942
+ affected: [],
943
+ degradation: "no materialized code anchors were attached"
944
+ });
945
+ }
946
+ const impacts = await Promise.all(
947
+ unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
948
+ );
949
+ const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
950
+ const joined = await joinMaterialized(
951
+ codeClient,
952
+ structural,
953
+ agentId,
954
+ Math.min(limit ?? 50, 50)
955
+ );
956
+ return ok({
957
+ target_uid: targetUid,
958
+ graph_context: graphContext,
959
+ impacts,
960
+ materialized: joined
961
+ });
962
+ }
963
+ default:
964
+ return err("unknown_action", `unknown mindgraph_code action: ${action}`);
965
+ }
966
+ } catch (cause) {
967
+ return err(
968
+ "code_tool_failed",
969
+ cause instanceof Error ? cause.message : String(cause)
970
+ );
971
+ }
972
+ }
973
+ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
974
+ if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
975
+ return result;
976
+ }
977
+ const original = JSON.parse(result.content[0]?.text ?? "null");
978
+ const targetUids = primaryUids(original);
979
+ if (targetUids.length === 0) {
980
+ return ok({
981
+ result: original,
982
+ code_anchors: {
983
+ wrote: false,
984
+ caveat: "the write response exposed no primary UID to attach"
985
+ }
986
+ });
987
+ }
988
+ const anchorResult = await handleCodeTool(
989
+ client2,
990
+ {
991
+ action: "anchor",
992
+ code_refs: args.code_refs,
993
+ repo: args.repo,
994
+ repo_space_uid: args.repo_space_uid,
995
+ agent_id: args.agent_id
996
+ },
997
+ adapter
998
+ );
999
+ const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
1000
+ if (!anchorResult.isError) {
1001
+ const codeClient = client2;
1002
+ const anchorUids = (anchors.anchors ?? []).flatMap(
1003
+ (anchor) => anchor.uid ? [anchor.uid] : []
1004
+ );
1005
+ for (const targetUid of targetUids) {
1006
+ for (const anchorUid of anchorUids) {
1007
+ await codeClient.entity({
1008
+ action: "relate",
1009
+ source_uid: targetUid,
1010
+ target_uid: anchorUid,
1011
+ edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
1012
+ agent_id: args.agent_id
1013
+ });
1014
+ }
1015
+ }
1016
+ }
1017
+ return ok({ result: original, code_anchors: anchors });
1018
+ }
1019
+ async function anchorRepository(client2, repository, spaceUid, agentId) {
1020
+ return client2.entity({
1021
+ action: "create",
1022
+ label: repository.repoId,
1023
+ props: {
1024
+ entity_type: "repository",
1025
+ canonical_name: repository.repoId,
1026
+ identifiers: { repo_id: repository.repoId }
1027
+ },
1028
+ identity: {
1029
+ namespace: "external.code",
1030
+ key_version: 1,
1031
+ key: repositoryIdentityKey(repository)
1032
+ },
1033
+ identity_space_uid: spaceUid,
1034
+ agent_id: agentId
1035
+ });
1036
+ }
1037
+ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
1038
+ const response = await client2.entity({
1039
+ action: "create",
1040
+ label: ref.qualifiedName,
1041
+ props: {
1042
+ entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
1043
+ canonical_name: ref.qualifiedName,
1044
+ identifiers: {
1045
+ repo_id: ref.repoId,
1046
+ path: ref.path,
1047
+ qualified_name: ref.qualifiedName
1048
+ },
1049
+ attributes: { code_ref: storedCodeRef(ref) }
1050
+ },
1051
+ identity: {
1052
+ namespace: "external.code",
1053
+ key_version: 1,
1054
+ key: codeRefIdentityKey(ref)
1055
+ },
1056
+ identity_space_uid: spaceUid,
1057
+ agent_id: agentId
1058
+ });
1059
+ if (response.uid) {
1060
+ await client2.entity({
1061
+ action: "relate",
1062
+ source_uid: response.uid,
1063
+ target_uid: repositoryUid,
1064
+ edge_type: "PART_OF",
1065
+ agent_id: agentId
1066
+ });
1067
+ }
1068
+ return { ...response, ref };
1069
+ }
1070
+ async function resolveMaterializedUid(client2, ref, agentId) {
1071
+ const response = await client2.entity({
1072
+ action: "resolve_identity",
1073
+ identity: {
1074
+ namespace: "external.code",
1075
+ key_version: 1,
1076
+ key: codeRefIdentityKey(ref)
1077
+ },
1078
+ agent_id: agentId
1079
+ });
1080
+ return response.status === "existing" ? response.uid : void 0;
1081
+ }
1082
+ async function codeRefFromAnchor(client2, adapter, uid) {
1083
+ const node = await client2.getNode(uid);
1084
+ const attributes = node.props?.attributes;
1085
+ if (!attributes || typeof attributes !== "object") return void 0;
1086
+ const codeRef = attributes.code_ref;
1087
+ if (!isStoredCodeRef(codeRef)) return void 0;
1088
+ const repository = await adapter.resolveRepository(codeRef.repoId);
1089
+ return { ...codeRef, repoRoot: repository.repoRoot };
1090
+ }
1091
+ async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
1092
+ if (refs.length > 1 || anchorUids.length > 1) {
1093
+ return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
1094
+ }
1095
+ if (anchorUids.length === 1) {
1096
+ const ref = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
1097
+ return ref ? { ref } : { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
1098
+ }
1099
+ if (refs.length !== 1) {
1100
+ return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
1101
+ }
1102
+ const resolution = await adapter.resolveRefs(refs, options);
1103
+ const result = resolution.results[0];
1104
+ if (!result.resolved) {
1105
+ return {
1106
+ error: ok({
1107
+ availability: resolution.availability,
1108
+ result,
1109
+ wrote: false
1110
+ })
1111
+ };
1112
+ }
1113
+ return { ref: result.resolved };
1114
+ }
1115
+ async function joinMaterialized(client2, refs, agentId, limit) {
1116
+ const joined = [];
1117
+ for (const ref of uniqueRefs(refs).slice(0, limit)) {
1118
+ const uid = await resolveMaterializedUid(client2, ref, agentId);
1119
+ if (!uid) continue;
1120
+ joined.push({
1121
+ ref,
1122
+ anchor_uid: uid,
1123
+ graph: await client2.traverse({
1124
+ action: "neighborhood",
1125
+ start_uid: uid,
1126
+ depth: 2,
1127
+ max_nodes: 20
1128
+ })
1129
+ });
1130
+ }
1131
+ return joined;
1132
+ }
1133
+ async function codeRefsFromGraphContext(client2, adapter, context) {
1134
+ const uids = /* @__PURE__ */ new Set();
1135
+ collectUids(context, uids);
1136
+ if (uids.size === 0) return [];
1137
+ const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
1138
+ const stored = nodes.flatMap((node) => {
1139
+ if (!node) return [];
1140
+ const attributes = node.props?.attributes;
1141
+ if (!attributes || typeof attributes !== "object") return [];
1142
+ const codeRef = attributes.code_ref;
1143
+ return isStoredCodeRef(codeRef) ? [codeRef] : [];
1144
+ });
1145
+ return Promise.all(
1146
+ stored.map(async (ref) => {
1147
+ const repository = await adapter.resolveRepository(ref.repoId);
1148
+ return { ...ref, repoRoot: repository.repoRoot };
1149
+ })
1150
+ );
1151
+ }
1152
+ function collectUids(value, uids) {
1153
+ if (Array.isArray(value)) {
1154
+ value.forEach((item) => collectUids(item, uids));
1155
+ return;
1156
+ }
1157
+ if (!value || typeof value !== "object") return;
1158
+ for (const [key, child] of Object.entries(value)) {
1159
+ if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
1160
+ uids.add(child);
1161
+ } else {
1162
+ collectUids(child, uids);
1163
+ }
1164
+ }
1165
+ }
1166
+ function isStoredCodeRef(value) {
1167
+ if (!value || typeof value !== "object") return false;
1168
+ const ref = value;
1169
+ return typeof ref.repoId === "string" && typeof ref.path === "string" && typeof ref.language === "string" && typeof ref.kind === "string" && typeof ref.qualifiedName === "string" && (typeof ref.signature === "string" || ref.signature === null) && typeof ref.startLine === "number" && typeof ref.endLine === "number";
1170
+ }
1171
+ function storedCodeRef(ref) {
1172
+ const { repoRoot: _localOnly, ...stored } = ref;
1173
+ return stored;
1174
+ }
1175
+ function uniqueRefs(refs) {
1176
+ const seen = /* @__PURE__ */ new Set();
1177
+ return refs.filter((ref) => {
1178
+ const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
1179
+ if (seen.has(key)) return false;
1180
+ seen.add(key);
1181
+ return true;
1182
+ });
1183
+ }
1184
+ function primaryUids(value) {
1185
+ if (!value || typeof value !== "object") return [];
1186
+ const object = value;
1187
+ const keys = [
1188
+ "uid",
1189
+ "claim_uid",
1190
+ "goal_uid",
1191
+ "project_uid",
1192
+ "milestone_uid",
1193
+ "decision_uid",
1194
+ "task_uid",
1195
+ "plan_uid",
1196
+ "step_uid",
1197
+ "execution_uid",
1198
+ "lesson_uid"
1199
+ ];
1200
+ return [
1201
+ ...new Set(
1202
+ keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
1203
+ )
1204
+ ];
1205
+ }
1206
+
1207
+ // src/sync-tool.ts
1208
+ var import_node_crypto2 = __toESM(require("crypto"));
1209
+ var import_node_fs2 = __toESM(require("fs"));
1210
+ var import_node_path2 = __toESM(require("path"));
1211
+ var ok2 = (data) => ({
1212
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1213
+ });
1214
+ var err2 = (code, message) => ({
1215
+ content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
1216
+ isError: true
1217
+ });
1218
+ var SYNC_TOOL = {
1219
+ name: "mindgraph_sync",
1220
+ description: "Transactional one-way project memory-file sync. scan/status are reads; begin/record/finalize/abandon are writes. Discovery and hashing are automatic; semantic assertions are deliberately supplied through record. This never writes graph state back to files.",
1221
+ annotations: {
1222
+ readOnlyHint: false,
1223
+ destructiveHint: false,
1224
+ idempotentHint: true,
1225
+ openWorldHint: true
1226
+ },
1227
+ inputSchema: {
1228
+ type: "object",
1229
+ properties: {
1230
+ action: {
1231
+ type: "string",
1232
+ enum: ["scan", "begin", "record", "finalize", "status", "abandon"]
1233
+ },
1234
+ paths: { type: "array", items: { type: "string" } },
1235
+ workspace: {
1236
+ type: "boolean",
1237
+ description: "For scan only: fan out across repositories explicitly listed in .mindgraph/workspace.json"
1238
+ },
1239
+ repo: {
1240
+ type: "string",
1241
+ description: "Configured repository id, name, or root"
1242
+ },
1243
+ logical_path: { type: "string" },
1244
+ content: { type: "string" },
1245
+ content_hash: { type: "string" },
1246
+ source_uid: { type: "string" },
1247
+ execution_uid: { type: "string" },
1248
+ assertion_fingerprint: { type: "string" },
1249
+ planned_fingerprints: { type: "array", items: { type: "string" } },
1250
+ current_fingerprints: { type: "array", items: { type: "string" } },
1251
+ source_selector: { type: "object" },
1252
+ capture_type: {
1253
+ type: "string",
1254
+ enum: ["lesson", "journal", "claim", "decision", "risk"]
1255
+ },
1256
+ label: { type: "string" },
1257
+ summary: { type: "string" },
1258
+ props: { type: "object" },
1259
+ expected_execution_version: { type: "integer", minimum: 1 },
1260
+ baseline_version: { type: "integer", minimum: 1 },
1261
+ repo_space_uid: { type: "string" },
1262
+ repo_id: { type: "string" },
1263
+ provider: { type: "string", enum: ["claude_project", "explicit"] },
1264
+ file_present: { type: "boolean" },
1265
+ agent_id: { type: "string" },
1266
+ invocation_context: {
1267
+ type: "object",
1268
+ description: "Adapter-owned; hooks replace forged or absent values.",
1269
+ properties: {
1270
+ harness: { type: "string" },
1271
+ harnessSessionId: { type: "string" },
1272
+ harnessTurnId: { type: "string" },
1273
+ cwd: { type: "string" },
1274
+ repoId: { type: "string" },
1275
+ branch: { type: "string" },
1276
+ commit: { type: "string" },
1277
+ worktreeState: { type: "string" },
1278
+ model: { type: "string" },
1279
+ injectedBy: { type: "string" }
1280
+ }
1281
+ }
1282
+ },
1283
+ required: ["action"]
1284
+ }
1285
+ };
1286
+ function safePath(root, candidate) {
1287
+ const absolute = import_node_path2.default.resolve(root, candidate);
1288
+ const relative2 = import_node_path2.default.relative(root, absolute);
1289
+ if (relative2.startsWith("..") || import_node_path2.default.isAbsolute(relative2)) {
1290
+ throw new Error(`path escapes repository root: ${candidate}`);
1291
+ }
1292
+ return absolute;
1293
+ }
1294
+ function markdownFiles(target) {
1295
+ if (!import_node_fs2.default.existsSync(target)) return [];
1296
+ const stat = import_node_fs2.default.statSync(target);
1297
+ if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
1298
+ if (!stat.isDirectory()) return [];
1299
+ return import_node_fs2.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path2.default.join(target, entry.name)));
1300
+ }
1301
+ function fileDescriptor(root, absolute) {
1302
+ const content = import_node_fs2.default.readFileSync(absolute, "utf8");
1303
+ return {
1304
+ logical_path: import_node_path2.default.relative(root, absolute).split(import_node_path2.default.sep).join("/"),
1305
+ content_hash: import_node_crypto2.default.createHash("sha256").update(content).digest("hex"),
1306
+ bytes: Buffer.byteLength(content),
1307
+ content
1308
+ };
1309
+ }
1310
+ async function handleSyncTool(client2, args) {
1311
+ try {
1312
+ const action = String(args.action || "");
1313
+ const context = args.invocation_context || {};
1314
+ const adapter = new CodegraphAdapter({
1315
+ cwd: context.cwd || process.cwd()
1316
+ });
1317
+ const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
1318
+ const repository = await adapter.resolveRepository(requestedRepo);
1319
+ const root = repository.repoRoot;
1320
+ const repoId = repository.repoId;
1321
+ const provider = String(args.provider || "claude_project");
1322
+ const remote = client2;
1323
+ if (action === "scan") {
1324
+ const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
1325
+ const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
1326
+ const scans = [];
1327
+ for (const selected of repositories) {
1328
+ scans.push(
1329
+ await scanRepository(
1330
+ remote,
1331
+ selected,
1332
+ requested,
1333
+ provider,
1334
+ args.agent_id
1335
+ )
1336
+ );
1337
+ }
1338
+ if (args.workspace === true) {
1339
+ return ok2({
1340
+ provider,
1341
+ workspace: true,
1342
+ repositories: scans,
1343
+ one_way: "files_to_graph"
1344
+ });
1345
+ }
1346
+ return ok2(scans[0]);
1347
+ }
1348
+ const request = {
1349
+ ...args,
1350
+ provider,
1351
+ repo_id: repoId
1352
+ };
1353
+ delete request.invocation_context;
1354
+ delete request.paths;
1355
+ delete request.repo;
1356
+ delete request.workspace;
1357
+ if (action === "begin" && !request.content) {
1358
+ if (!request.logical_path) {
1359
+ return err2("missing_field", "logical_path is required for begin");
1360
+ }
1361
+ const file = fileDescriptor(
1362
+ root,
1363
+ safePath(root, String(request.logical_path))
1364
+ );
1365
+ request.content = file.content;
1366
+ request.content_hash ||= file.content_hash;
1367
+ }
1368
+ if (action === "status" && request.logical_path && request.file_present !== false) {
1369
+ const absolute = safePath(root, String(request.logical_path));
1370
+ request.file_present = import_node_fs2.default.existsSync(absolute);
1371
+ if (request.file_present && !request.content_hash) {
1372
+ request.content_hash = fileDescriptor(root, absolute).content_hash;
1373
+ }
1374
+ }
1375
+ return ok2(await remote.memorySync(request));
1376
+ } catch (error) {
1377
+ return err2(
1378
+ "sync_failed",
1379
+ error instanceof Error ? error.message : String(error)
1380
+ );
1381
+ }
1382
+ }
1383
+ async function scanRepository(remote, repository, requested, provider, agentId) {
1384
+ const files = [
1385
+ ...new Set(
1386
+ requested.flatMap(
1387
+ (candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
1388
+ )
1389
+ )
1390
+ ].sort();
1391
+ const results = [];
1392
+ for (const absolute of files) {
1393
+ const file = fileDescriptor(repository.repoRoot, absolute);
1394
+ const status = await remote.memorySync({
1395
+ action: "scan",
1396
+ provider,
1397
+ repo_id: repository.repoId,
1398
+ logical_path: file.logical_path,
1399
+ content_hash: file.content_hash,
1400
+ file_present: true,
1401
+ agent_id: agentId
1402
+ });
1403
+ results.push({
1404
+ logical_path: file.logical_path,
1405
+ content_hash: file.content_hash,
1406
+ bytes: file.bytes,
1407
+ status
1408
+ });
1409
+ }
1410
+ return {
1411
+ repo_id: repository.repoId,
1412
+ root: ".",
1413
+ files: results
1414
+ };
1415
+ }
1416
+
9
1417
  // src/tools.ts
10
1418
  var TOOLS = [
11
1419
  {
12
1420
  name: "mindgraph_capture",
13
- 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, 'concept' for abstract ideas. Use 'journal' for quick personal notes, preferences, reflections, moods \u2014 anything subjective or informal. Prefer 'entity'/'observation' for objective facts, 'journal' for personal notes.",
1421
+ 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 and 'lesson' for a durable learning attributable to a session or source nodes. Prefer 'entity'/'observation' for objective facts, 'journal' for informal notes, and 'lesson' only for reusable knowledge.",
14
1422
  inputSchema: {
15
1423
  type: "object",
16
1424
  properties: {
17
1425
  action: {
18
1426
  type: "string",
19
- enum: ["entity", "observation", "source", "snippet", "concept", "journal"],
1427
+ enum: [
1428
+ "entity",
1429
+ "observation",
1430
+ "source",
1431
+ "snippet",
1432
+ "concept",
1433
+ "journal",
1434
+ "lesson"
1435
+ ],
20
1436
  description: "Type of knowledge to capture"
21
1437
  },
22
1438
  label: {
@@ -31,6 +1447,31 @@ var TOOLS = [
31
1447
  type: "string",
32
1448
  description: "Journal content/body text (for action=journal)"
33
1449
  },
1450
+ session_uid: {
1451
+ type: "string",
1452
+ description: "Session UID that produced a lesson (for action=lesson)"
1453
+ },
1454
+ work_uid: {
1455
+ type: "string",
1456
+ description: "Durable Task/work UID this capture is relevant to"
1457
+ },
1458
+ execution_uid: {
1459
+ type: "string",
1460
+ description: "Material Execution that produced the lesson"
1461
+ },
1462
+ idempotency_key: {
1463
+ type: "string",
1464
+ description: "Stable retry key for an intentional capture"
1465
+ },
1466
+ supersedes_uid: {
1467
+ type: "string",
1468
+ description: "Earlier capture corrected or superseded by this one"
1469
+ },
1470
+ summarizes_uids: {
1471
+ type: "array",
1472
+ items: { type: "string" },
1473
+ description: "Source node UIDs the lesson was learned from (for action=lesson)"
1474
+ },
34
1475
  entity_type: {
35
1476
  type: "string",
36
1477
  enum: ["person", "organization", "place", "event", "nation", "concept"],
@@ -65,6 +1506,32 @@ var TOOLS = [
65
1506
  type: "object",
66
1507
  description: "Type-specific properties (e.g. birth_date for person, coordinates for place)"
67
1508
  },
1509
+ code_refs: {
1510
+ type: "array",
1511
+ items: {
1512
+ oneOf: [
1513
+ { type: "string" },
1514
+ {
1515
+ type: "object",
1516
+ properties: {
1517
+ repo: { type: "string" },
1518
+ path: { type: "string" },
1519
+ line: { type: "integer", minimum: 1 },
1520
+ symbol: { type: "string" },
1521
+ kind: { type: "string" },
1522
+ language: { type: "string" },
1523
+ signature: { type: "string" }
1524
+ }
1525
+ }
1526
+ ]
1527
+ },
1528
+ description: "Typed code targets to anchor and attach to the captured node"
1529
+ },
1530
+ repo: { type: "string", description: "Configured repository id or root" },
1531
+ repo_space_uid: {
1532
+ type: "string",
1533
+ description: "Writable shared repository/Project Space for new code anchors"
1534
+ },
68
1535
  agent_id: {
69
1536
  type: "string",
70
1537
  description: "Agent identity"
@@ -238,6 +1705,16 @@ var TOOLS = [
238
1705
  type: "object",
239
1706
  description: "Type-specific properties (e.g. status, priority, deadline, horizon for goals)"
240
1707
  },
1708
+ code_refs: {
1709
+ type: "array",
1710
+ items: { oneOf: [{ type: "string" }, { type: "object" }] },
1711
+ description: "Typed code targets to anchor and attach to the created commitment"
1712
+ },
1713
+ repo: { type: "string", description: "Configured repository id or root" },
1714
+ repo_space_uid: {
1715
+ type: "string",
1716
+ description: "Writable shared repository/Project Space for new code anchors"
1717
+ },
241
1718
  agent_id: {
242
1719
  type: "string",
243
1720
  description: "Agent identity"
@@ -248,7 +1725,7 @@ var TOOLS = [
248
1725
  },
249
1726
  {
250
1727
  name: "mindgraph_plan",
251
- description: "Agent-level task management, governance, and procedural knowledge. Use 'create_plan'/'create_task'/'add_step' for task breakdowns, 'update_status' for progress, 'get_plan' to review. Use 'create_flow'/'add_procedure_step'/'add_affordance'/'add_control' for workflows and procedures. Use 'assess_risk'/'get_assessments' for risk analysis. Use 'get_pending' to read the governance approval queue \u2014 approvals are granted by a person in the MindGraph dashboard, never from here, so wait for one rather than trying to clear it. Prefer mindgraph_commit for user goals/projects; use this for agent-managed execution.",
1728
+ description: "Authoritative durable engineering work and execution evidence. Use create_task/create_plan/add_step for work that must survive sessions; resume_work for the deterministic bounded brief; claim_task + heartbeat for the fenced lease; and start_iteration/checkpoint_iteration/block_task/complete_task/abandon_iteration for idempotent material attempts. Every composite write needs task version, Session, lease epoch, and an idempotency key. Use the harness-native todo list only for local coordination; never maintain the same authoritative Task in both systems.",
252
1729
  inputSchema: {
253
1730
  type: "object",
254
1731
  properties: {
@@ -260,9 +1737,19 @@ var TOOLS = [
260
1737
  "add_step",
261
1738
  "update_status",
262
1739
  "get_plan",
1740
+ "resume_work",
1741
+ "claim_task",
1742
+ "heartbeat",
1743
+ "start_iteration",
1744
+ "checkpoint_iteration",
1745
+ "block_task",
1746
+ "complete_task",
1747
+ "abandon_iteration",
263
1748
  "start_execution",
264
1749
  "complete_execution",
265
1750
  "fail_execution",
1751
+ "register_agent",
1752
+ "get_executions",
266
1753
  // C33: governance WRITES are deliberately not exposed here.
267
1754
  // `create_policy` let a model author the rules it is judged by;
268
1755
  // `request_approval` + `resolve_approval` let it raise and then
@@ -288,32 +1775,191 @@ var TOOLS = [
288
1775
  type: "string",
289
1776
  description: "Description"
290
1777
  },
291
- plan_uid: {
1778
+ plan_uid: {
1779
+ type: "string",
1780
+ description: "Plan UID (for create_plan execution link, add_step, get_plan)"
1781
+ },
1782
+ task_uid: {
1783
+ type: "string",
1784
+ description: "Task UID (for create_plan and graph-managed work actions)"
1785
+ },
1786
+ step_uid: {
1787
+ type: "string",
1788
+ description: "PlanStep UID for an iteration/checkpoint"
1789
+ },
1790
+ session_uid: {
1791
+ type: "string",
1792
+ description: "Durable Session UID that owns the task lease"
1793
+ },
1794
+ goal_uid: {
1795
+ type: "string",
1796
+ description: "Goal UID targeted by a task or plan"
1797
+ },
1798
+ target_uid: {
1799
+ type: "string",
1800
+ description: "Task/plan/step UID for update_status, or parent/target node for procedure and risk actions"
1801
+ },
1802
+ execution_uid: {
1803
+ type: "string",
1804
+ description: "Execution UID (required for complete_execution/fail_execution)"
1805
+ },
1806
+ executor_uid: {
1807
+ type: "string",
1808
+ description: "Agent node UID that executed the work"
1809
+ },
1810
+ affordance_uid: {
1811
+ type: "string",
1812
+ description: "Affordance invoked by start_execution"
1813
+ },
1814
+ produces_node_uid: {
1815
+ type: "string",
1816
+ description: "Node produced by a completed execution"
1817
+ },
1818
+ filter_plan_uid: {
1819
+ type: "string",
1820
+ description: "Restrict get_executions to one plan"
1821
+ },
1822
+ depends_on_uids: {
1823
+ type: "array",
1824
+ items: { type: "string" },
1825
+ description: "Plan-step UIDs that an added step depends on"
1826
+ },
1827
+ related_uids: {
1828
+ type: "array",
1829
+ items: { type: "string" },
1830
+ description: "Target node UIDs to link to created work or an execution"
1831
+ },
1832
+ status: {
1833
+ type: "string",
1834
+ description: "New status value (for update_status)"
1835
+ },
1836
+ task_status: {
1837
+ type: "string",
1838
+ description: "Task status requested by a checkpoint or abandon action"
1839
+ },
1840
+ step_status: {
1841
+ type: "string",
1842
+ description: "PlanStep status requested by a checkpoint"
1843
+ },
1844
+ execution_status: {
1845
+ type: "string",
1846
+ enum: ["completed", "failed", "abandoned"],
1847
+ description: "Terminal Execution status for checkpoint_iteration"
1848
+ },
1849
+ expected_version: {
1850
+ type: "number",
1851
+ description: "Current Task node version used as the optimistic fence"
1852
+ },
1853
+ lease_epoch: {
1854
+ type: "number",
1855
+ description: "Current monotonic task-lease epoch"
1856
+ },
1857
+ lease_ttl_secs: {
1858
+ type: "number",
1859
+ minimum: 1,
1860
+ description: "Requested server-time lease lifetime (default 300s)"
1861
+ },
1862
+ idempotency_key: {
1863
+ type: "string",
1864
+ description: "Caller-stable key required for every composite work mutation"
1865
+ },
1866
+ allow_takeover: {
1867
+ type: "boolean",
1868
+ description: "Governed claim override for a live lease owned by another agent"
1869
+ },
1870
+ release_lease: {
1871
+ type: "boolean",
1872
+ description: "Explicitly release or retain the lease after block/abandon/checkpoint"
1873
+ },
1874
+ override_reason: {
1875
+ type: "string",
1876
+ description: "Explicit completion reason when no terminal Execution evidence exists"
1877
+ },
1878
+ scope_uids: {
1879
+ type: "array",
1880
+ items: { type: "string" },
1881
+ description: "Ordinary target UIDs that bound deterministic resume selection"
1882
+ },
1883
+ limit: {
1884
+ type: "number",
1885
+ minimum: 1,
1886
+ maximum: 1,
1887
+ description: "Resume returns one authoritative Task"
1888
+ },
1889
+ input_snapshot: {
1890
+ type: "object",
1891
+ description: "Structured inputs captured when an execution starts"
1892
+ },
1893
+ output_snapshot: {
1894
+ type: "object",
1895
+ description: "Structured outputs captured when an execution completes or fails"
1896
+ },
1897
+ outcome: {
1898
+ type: "string",
1899
+ description: "Execution outcome"
1900
+ },
1901
+ error: {
292
1902
  type: "string",
293
- description: "Plan UID (for add_step, get_plan)"
1903
+ description: "Execution error detail (for fail_execution)"
294
1904
  },
295
- task_uid: {
1905
+ side_effects: {
1906
+ type: "array",
1907
+ items: { type: "string" },
1908
+ description: "Observable side effects of the execution"
1909
+ },
1910
+ next_action: {
296
1911
  type: "string",
297
- description: "Task/step UID (for update_status, execution actions)"
1912
+ description: "Recommended next action retained on terminal Execution evidence"
298
1913
  },
299
- target_uid: {
1914
+ checkpoint_summary: {
300
1915
  type: "string",
301
- description: "Parent flow/step UID or target node (for procedure/risk actions)"
1916
+ description: "Bounded material-attempt checkpoint summary"
302
1917
  },
303
- status: {
1918
+ test_summary: {
304
1919
  type: "string",
305
- description: "New status value (for update_status)"
1920
+ description: "Test commands/results retained on the Execution"
1921
+ },
1922
+ produces_node_uids: {
1923
+ type: "array",
1924
+ items: { type: "string" },
1925
+ description: "Nodes produced by checkpoint_iteration"
1926
+ },
1927
+ confidence: {
1928
+ type: "number",
1929
+ minimum: 0,
1930
+ maximum: 1
1931
+ },
1932
+ salience: {
1933
+ type: "number",
1934
+ minimum: 0,
1935
+ maximum: 1
306
1936
  },
307
1937
  props: {
308
1938
  type: "object",
309
1939
  description: "Additional properties (e.g. likelihood, impact, mitigation for risks)"
310
1940
  },
1941
+ code_refs: {
1942
+ type: "array",
1943
+ items: { oneOf: [{ type: "string" }, { type: "object" }] },
1944
+ description: "Typed code targets to anchor and attach to created work or execution"
1945
+ },
1946
+ repo: { type: "string", description: "Configured repository id or root" },
1947
+ repo_space_uid: {
1948
+ type: "string",
1949
+ description: "Writable shared repository/Project Space for new code anchors"
1950
+ },
311
1951
  agent_id: {
312
1952
  type: "string",
313
1953
  description: "Agent identity"
314
1954
  }
315
1955
  },
316
1956
  required: ["action"]
1957
+ },
1958
+ annotations: {
1959
+ readOnlyHint: false,
1960
+ destructiveHint: false,
1961
+ idempotentHint: false,
1962
+ openWorldHint: true
317
1963
  }
318
1964
  },
319
1965
  {
@@ -688,14 +2334,45 @@ var TOOLS = [
688
2334
  },
689
2335
  required: ["action"]
690
2336
  }
691
- }
2337
+ },
2338
+ SYNC_TOOL,
2339
+ CODE_TOOL
692
2340
  ];
693
- function ok(data) {
2341
+ var INVOCATION_CONTEXT_SCHEMA = {
2342
+ type: "object",
2343
+ description: "Adapter-owned invocation context. Models should omit it; compatible hooks replace forged or absent values. cwd is consumed locally and never sent to the API.",
2344
+ properties: {
2345
+ harness: { type: "string", enum: ["claude-code", "codex", "generic"] },
2346
+ harnessSessionId: { type: "string" },
2347
+ harnessTurnId: { type: "string" },
2348
+ cwd: { type: "string" },
2349
+ repoId: { type: "string" },
2350
+ branch: { type: "string" },
2351
+ commit: { type: "string" },
2352
+ worktreeState: { type: "string", enum: ["clean", "dirty"] },
2353
+ model: { type: "string" },
2354
+ injectedBy: { type: "string", enum: ["hook", "mcp-process", "caller"] }
2355
+ }
2356
+ };
2357
+ for (const tool of TOOLS) {
2358
+ if (tool.name === "mindgraph_retrieve") continue;
2359
+ const schema = tool.inputSchema;
2360
+ if (schema.properties && !schema.properties.invocation_context) {
2361
+ schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
2362
+ }
2363
+ }
2364
+ function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
2365
+ if (profile === "coding") return TOOLS;
2366
+ return TOOLS.filter(
2367
+ (tool) => tool.name !== "mindgraph_code" && tool.name !== "mindgraph_sync"
2368
+ );
2369
+ }
2370
+ function ok3(data) {
694
2371
  return {
695
2372
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
696
2373
  };
697
2374
  }
698
- function err(message) {
2375
+ function err3(message) {
699
2376
  return {
700
2377
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
701
2378
  isError: true
@@ -703,29 +2380,58 @@ function err(message) {
703
2380
  }
704
2381
  async function handleTool(client2, name, args) {
705
2382
  try {
2383
+ let result;
706
2384
  switch (name) {
707
2385
  case "mindgraph_capture":
708
- return await handleCapture(client2, args);
2386
+ result = await handleCapture(client2, args);
2387
+ break;
709
2388
  case "mindgraph_reason":
710
- return await handleReason(client2, args);
2389
+ result = await handleReason(client2, args);
2390
+ break;
711
2391
  case "mindgraph_commit":
712
- return await handleCommit(client2, args);
2392
+ result = await handleCommit(client2, args);
2393
+ break;
713
2394
  case "mindgraph_plan":
714
- return await handlePlan(client2, args);
2395
+ result = await handlePlan(client2, args);
2396
+ break;
715
2397
  case "mindgraph_retrieve":
716
- return await handleRetrieve(client2, args);
2398
+ result = await handleRetrieve(client2, args);
2399
+ break;
717
2400
  case "mindgraph_ingest":
718
- return await handleIngest(client2, args);
2401
+ result = await handleIngest(client2, args);
2402
+ break;
719
2403
  case "mindgraph_synthesize":
720
- return await handleSynthesize(client2, args);
2404
+ result = await handleSynthesize(client2, args);
2405
+ break;
721
2406
  case "mindgraph_ontology":
722
- return await handleOntology(client2, args);
2407
+ result = await handleOntology(client2, args);
2408
+ break;
2409
+ case "mindgraph_code":
2410
+ result = await handleCodeTool(client2, args);
2411
+ break;
2412
+ case "mindgraph_sync":
2413
+ result = await handleSyncTool(client2, args);
2414
+ break;
723
2415
  default:
724
- return err(`Unknown tool: ${name}`);
2416
+ result = err3(`Unknown tool: ${name}`);
725
2417
  }
2418
+ const action = typeof args.action === "string" ? args.action : "";
2419
+ const attach = name === "mindgraph_capture" || name === "mindgraph_commit" && action !== "get_open_decisions" || name === "mindgraph_plan" && ![
2420
+ "get_plan",
2421
+ "get_executions",
2422
+ "get_pending",
2423
+ "get_assessments",
2424
+ "resume_work",
2425
+ "heartbeat"
2426
+ ].includes(action);
2427
+ return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
726
2428
  } catch (e) {
2429
+ if (e instanceof import_mindgraph.MindGraphError && e.body !== void 0) {
2430
+ const detail = typeof e.body === "string" ? e.body : JSON.stringify(e.body);
2431
+ return err3(`${e.message} \u2014 ${detail}`);
2432
+ }
727
2433
  const message = e instanceof Error ? e.message : String(e);
728
- return err(message);
2434
+ return err3(message);
729
2435
  }
730
2436
  }
731
2437
  async function handleCapture(client2, args) {
@@ -734,6 +2440,12 @@ async function handleCapture(client2, args) {
734
2440
  label,
735
2441
  summary,
736
2442
  content,
2443
+ session_uid,
2444
+ work_uid,
2445
+ execution_uid,
2446
+ idempotency_key,
2447
+ supersedes_uid,
2448
+ summarizes_uids,
737
2449
  entity_type,
738
2450
  source_uid,
739
2451
  mood,
@@ -744,12 +2456,30 @@ async function handleCapture(client2, args) {
744
2456
  agent_id
745
2457
  } = args;
746
2458
  switch (action) {
2459
+ case "lesson":
2460
+ return ok3(
2461
+ await client2.distill({
2462
+ label,
2463
+ output_type: "lesson",
2464
+ summary,
2465
+ confidence,
2466
+ salience,
2467
+ session_uid,
2468
+ work_uid,
2469
+ execution_uid,
2470
+ idempotency_key,
2471
+ supersedes_uid,
2472
+ summarizes_uids,
2473
+ props,
2474
+ agent_id
2475
+ })
2476
+ );
747
2477
  case "journal": {
748
- if (!content) return err("content is required for action=journal");
2478
+ if (!content) return err3("content is required for action=journal");
749
2479
  const journalProps = { content };
750
2480
  if (mood) journalProps.mood = mood;
751
2481
  if (tags) journalProps.tags = tags;
752
- return ok(await client2.journal(label, journalProps, { agent_id }));
2482
+ return ok3(await client2.journal(label, journalProps, { agent_id }));
753
2483
  }
754
2484
  case "entity": {
755
2485
  const type = entity_type || "concept";
@@ -757,22 +2487,22 @@ async function handleCapture(client2, args) {
757
2487
  if (summary) entityProps.description = summary;
758
2488
  switch (type) {
759
2489
  case "person":
760
- return ok(await client2.findOrCreatePerson(label, entityProps, agent_id));
2490
+ return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
761
2491
  case "organization":
762
- return ok(await client2.findOrCreateOrganization(label, entityProps, agent_id));
2492
+ return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
763
2493
  case "nation":
764
- return ok(await client2.findOrCreateNation(label, entityProps, agent_id));
2494
+ return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
765
2495
  case "event":
766
- return ok(await client2.findOrCreateEvent(label, entityProps, agent_id));
2496
+ return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
767
2497
  case "place":
768
- return ok(await client2.findOrCreatePlace(label, entityProps, agent_id));
2498
+ return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
769
2499
  case "concept":
770
2500
  default:
771
- return ok(await client2.findOrCreateConcept(label, entityProps, agent_id));
2501
+ return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
772
2502
  }
773
2503
  }
774
2504
  case "observation":
775
- return ok(
2505
+ return ok3(
776
2506
  await client2.capture({
777
2507
  action: "observation",
778
2508
  label,
@@ -784,7 +2514,7 @@ async function handleCapture(client2, args) {
784
2514
  })
785
2515
  );
786
2516
  case "source":
787
- return ok(
2517
+ return ok3(
788
2518
  await client2.capture({
789
2519
  action: "source",
790
2520
  label,
@@ -796,7 +2526,7 @@ async function handleCapture(client2, args) {
796
2526
  })
797
2527
  );
798
2528
  case "snippet":
799
- return ok(
2529
+ return ok3(
800
2530
  await client2.capture({
801
2531
  action: "snippet",
802
2532
  label,
@@ -809,7 +2539,7 @@ async function handleCapture(client2, args) {
809
2539
  })
810
2540
  );
811
2541
  case "concept":
812
- return ok(
2542
+ return ok3(
813
2543
  await client2.structure({
814
2544
  action: "concept",
815
2545
  label,
@@ -821,7 +2551,7 @@ async function handleCapture(client2, args) {
821
2551
  })
822
2552
  );
823
2553
  default:
824
- return err(`Unknown capture action: ${action}`);
2554
+ return err3(`Unknown capture action: ${action}`);
825
2555
  }
826
2556
  }
827
2557
  async function handleReason(client2, args) {
@@ -840,8 +2570,8 @@ async function handleReason(client2, args) {
840
2570
  } = args;
841
2571
  if (action === "claim") {
842
2572
  const claimObj = claim || (label ? { label, confidence } : null);
843
- if (!claimObj) return err("claim object or label is required for action=claim");
844
- return ok(
2573
+ if (!claimObj) return err3("claim object or label is required for action=claim");
2574
+ return ok3(
845
2575
  await client2.argue({
846
2576
  claim: claimObj,
847
2577
  evidence,
@@ -851,8 +2581,8 @@ async function handleReason(client2, args) {
851
2581
  })
852
2582
  );
853
2583
  }
854
- if (!label) return err(`label is required for action=${action}`);
855
- return ok(
2584
+ if (!label) return err3(`label is required for action=${action}`);
2585
+ return ok3(
856
2586
  await client2.inquire({
857
2587
  action,
858
2588
  label,
@@ -885,8 +2615,8 @@ async function handleCommit(client2, args) {
885
2615
  case "goal":
886
2616
  case "project":
887
2617
  case "milestone":
888
- if (!label) return err(`label is required for action=${action}`);
889
- return ok(
2618
+ if (!label) return err3(`label is required for action=${action}`);
2619
+ return ok3(
890
2620
  await client2.commit({
891
2621
  action,
892
2622
  label,
@@ -899,15 +2629,15 @@ async function handleCommit(client2, args) {
899
2629
  })
900
2630
  );
901
2631
  case "open_decision":
902
- if (!label) return err("label is required for open_decision");
903
- return ok(await client2.openDecision(label, { summary, props, agent_id }));
2632
+ if (!label) return err3("label is required for open_decision");
2633
+ return ok3(await client2.openDecision(label, { summary, props, agent_id }));
904
2634
  case "add_option":
905
- if (!decision_uid) return err("decision_uid is required for add_option");
906
- if (!label) return err("label is required for add_option");
907
- return ok(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
2635
+ if (!decision_uid) return err3("decision_uid is required for add_option");
2636
+ if (!label) return err3("label is required for add_option");
2637
+ return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
908
2638
  case "add_constraint":
909
- if (!decision_uid) return err("decision_uid is required for add_constraint");
910
- return ok(
2639
+ if (!decision_uid) return err3("decision_uid is required for add_constraint");
2640
+ return ok3(
911
2641
  await client2.deliberate({
912
2642
  action: "add_constraint",
913
2643
  label,
@@ -918,9 +2648,9 @@ async function handleCommit(client2, args) {
918
2648
  })
919
2649
  );
920
2650
  case "resolve_decision":
921
- if (!decision_uid) return err("decision_uid is required for resolve_decision");
922
- if (!chosen_option_uid) return err("chosen_option_uid is required for resolve_decision");
923
- return ok(
2651
+ if (!decision_uid) return err3("decision_uid is required for resolve_decision");
2652
+ if (!chosen_option_uid) return err3("chosen_option_uid is required for resolve_decision");
2653
+ return ok3(
924
2654
  await client2.resolveDecision(decision_uid, chosen_option_uid, {
925
2655
  summary,
926
2656
  props,
@@ -932,9 +2662,9 @@ async function handleCommit(client2, args) {
932
2662
  })
933
2663
  );
934
2664
  case "get_open_decisions":
935
- return ok(await client2.getOpenDecisions());
2665
+ return ok3(await client2.getOpenDecisions());
936
2666
  default:
937
- return err(`Unknown commit action: ${action}`);
2667
+ return err3(`Unknown commit action: ${action}`);
938
2668
  }
939
2669
  }
940
2670
  async function handlePlan(client2, args) {
@@ -944,8 +2674,41 @@ async function handlePlan(client2, args) {
944
2674
  summary,
945
2675
  plan_uid,
946
2676
  task_uid,
2677
+ step_uid,
2678
+ session_uid,
2679
+ goal_uid,
947
2680
  target_uid,
2681
+ execution_uid,
2682
+ executor_uid,
2683
+ affordance_uid,
2684
+ produces_node_uid,
2685
+ filter_plan_uid,
2686
+ depends_on_uids,
2687
+ related_uids,
948
2688
  status,
2689
+ task_status,
2690
+ step_status,
2691
+ execution_status,
2692
+ expected_version,
2693
+ lease_epoch,
2694
+ lease_ttl_secs,
2695
+ idempotency_key,
2696
+ allow_takeover,
2697
+ release_lease,
2698
+ override_reason,
2699
+ scope_uids,
2700
+ limit,
2701
+ confidence,
2702
+ salience,
2703
+ input_snapshot,
2704
+ output_snapshot,
2705
+ side_effects,
2706
+ outcome,
2707
+ error,
2708
+ next_action,
2709
+ checkpoint_summary,
2710
+ test_summary,
2711
+ produces_node_uids,
949
2712
  props,
950
2713
  agent_id
951
2714
  } = args;
@@ -956,25 +2719,120 @@ async function handlePlan(client2, args) {
956
2719
  case "add_step":
957
2720
  case "update_status":
958
2721
  case "get_plan":
959
- return ok(
2722
+ case "resume_work":
2723
+ case "claim_task":
2724
+ case "heartbeat":
2725
+ case "start_iteration":
2726
+ case "checkpoint_iteration":
2727
+ case "block_task":
2728
+ case "complete_task":
2729
+ case "abandon_iteration":
2730
+ return ok3(
960
2731
  await client2.plan({
961
2732
  action,
962
2733
  label,
963
2734
  summary,
2735
+ confidence,
2736
+ salience,
2737
+ goal_uid,
964
2738
  plan_uid,
965
2739
  task_uid,
2740
+ step_uid,
2741
+ session_uid,
2742
+ target_uid,
2743
+ depends_on_uids,
2744
+ related_uids,
966
2745
  status,
2746
+ task_status,
2747
+ step_status,
2748
+ execution_status,
2749
+ execution_uid,
2750
+ expected_version,
2751
+ lease_epoch,
2752
+ lease_ttl_secs,
2753
+ idempotency_key,
2754
+ allow_takeover,
2755
+ release_lease,
2756
+ override_reason,
2757
+ scope_uids,
2758
+ limit,
2759
+ input_snapshot,
2760
+ output_snapshot,
2761
+ side_effects,
2762
+ outcome,
2763
+ error,
2764
+ next_action,
2765
+ checkpoint_summary,
2766
+ test_summary,
2767
+ produces_node_uids,
967
2768
  props,
968
2769
  agent_id
969
2770
  })
970
2771
  );
971
2772
  // Execution tracking
972
2773
  case "start_execution":
973
- return ok(await client2.execution({ action: "start", task_uid, agent_id }));
2774
+ return ok3(
2775
+ await client2.execution({
2776
+ action: "start",
2777
+ label,
2778
+ summary,
2779
+ confidence,
2780
+ salience,
2781
+ plan_uid,
2782
+ executor_uid,
2783
+ affordance_uid,
2784
+ related_uids,
2785
+ input_snapshot,
2786
+ props,
2787
+ agent_id
2788
+ })
2789
+ );
974
2790
  case "complete_execution":
975
- return ok(await client2.execution({ action: "complete", task_uid, agent_id }));
2791
+ return ok3(
2792
+ await client2.execution({
2793
+ action: "complete",
2794
+ execution_uid,
2795
+ produces_node_uid,
2796
+ output_snapshot,
2797
+ side_effects,
2798
+ outcome,
2799
+ props,
2800
+ agent_id
2801
+ })
2802
+ );
976
2803
  case "fail_execution":
977
- return ok(await client2.execution({ action: "fail", task_uid, agent_id }));
2804
+ return ok3(
2805
+ await client2.execution({
2806
+ action: "fail",
2807
+ execution_uid,
2808
+ output_snapshot,
2809
+ side_effects,
2810
+ outcome,
2811
+ error,
2812
+ props,
2813
+ agent_id
2814
+ })
2815
+ );
2816
+ case "register_agent":
2817
+ return ok3(
2818
+ await client2.execution({
2819
+ action: "register_agent",
2820
+ label,
2821
+ summary,
2822
+ confidence,
2823
+ salience,
2824
+ props,
2825
+ agent_id
2826
+ })
2827
+ );
2828
+ case "get_executions":
2829
+ return ok3(
2830
+ await client2.execution({
2831
+ action: "get_executions",
2832
+ filter_plan_uid,
2833
+ agent_id
2834
+ })
2835
+ );
978
2836
  // Governance — read-only from MCP by design (C33). The write actions were
979
2837
  // removed rather than repaired: `resolve_approval` here also sent
980
2838
  // `task_uid` where the server requires `approval_uid`, and never sent
@@ -982,12 +2840,12 @@ async function handlePlan(client2, args) {
982
2840
  // DENIED every approval an agent tried to resolve. Wiring that correctly
983
2841
  // would have meant building a working self-approval path — the hole itself.
984
2842
  case "get_pending":
985
- return ok(await client2.governance({ action: "get_pending", agent_id }));
2843
+ return ok3(await client2.governance({ action: "get_pending", agent_id }));
986
2844
  // Procedures (merged from mindgraph_action)
987
2845
  case "create_flow":
988
2846
  case "add_affordance":
989
2847
  case "add_control":
990
- return ok(
2848
+ return ok3(
991
2849
  await client2.procedure({
992
2850
  action,
993
2851
  label,
@@ -998,7 +2856,7 @@ async function handlePlan(client2, args) {
998
2856
  })
999
2857
  );
1000
2858
  case "add_procedure_step":
1001
- return ok(
2859
+ return ok3(
1002
2860
  await client2.procedure({
1003
2861
  action: "add_step",
1004
2862
  label,
@@ -1010,7 +2868,7 @@ async function handlePlan(client2, args) {
1010
2868
  );
1011
2869
  // Risk assessment (merged from mindgraph_action)
1012
2870
  case "assess_risk":
1013
- return ok(
2871
+ return ok3(
1014
2872
  await client2.risk({
1015
2873
  action: "assess",
1016
2874
  label,
@@ -1021,7 +2879,7 @@ async function handlePlan(client2, args) {
1021
2879
  })
1022
2880
  );
1023
2881
  case "get_assessments":
1024
- return ok(
2882
+ return ok3(
1025
2883
  await client2.risk({
1026
2884
  action: "get_assessments",
1027
2885
  target_uid,
@@ -1029,7 +2887,7 @@ async function handlePlan(client2, args) {
1029
2887
  })
1030
2888
  );
1031
2889
  default:
1032
- return err(`Unknown plan action: ${action}`);
2890
+ return err3(`Unknown plan action: ${action}`);
1033
2891
  }
1034
2892
  }
1035
2893
  async function handleRetrieve(client2, args) {
@@ -1058,8 +2916,8 @@ async function handleRetrieve(client2, args) {
1058
2916
  } = args;
1059
2917
  switch (action) {
1060
2918
  case "context":
1061
- if (!query) return err("query is required for context retrieval");
1062
- return ok(
2919
+ if (!query) return err3("query is required for context retrieval");
2920
+ return ok3(
1063
2921
  await client2.retrieveContext({
1064
2922
  query,
1065
2923
  node_limit: limit,
@@ -1072,8 +2930,8 @@ async function handleRetrieve(client2, args) {
1072
2930
  })
1073
2931
  );
1074
2932
  case "text":
1075
- if (!query) return err("query is required for text search");
1076
- return ok(
2933
+ if (!query) return err3("query is required for text search");
2934
+ return ok3(
1077
2935
  await client2.retrieve({
1078
2936
  action: "text",
1079
2937
  query,
@@ -1084,8 +2942,8 @@ async function handleRetrieve(client2, args) {
1084
2942
  })
1085
2943
  );
1086
2944
  case "semantic":
1087
- if (!query) return err("query is required for semantic search");
1088
- return ok(
2945
+ if (!query) return err3("query is required for semantic search");
2946
+ return ok3(
1089
2947
  await client2.retrieve({
1090
2948
  action: "semantic",
1091
2949
  query,
@@ -1096,8 +2954,8 @@ async function handleRetrieve(client2, args) {
1096
2954
  })
1097
2955
  );
1098
2956
  case "hybrid":
1099
- if (!query) return err("query is required for hybrid search");
1100
- return ok(
2957
+ if (!query) return err3("query is required for hybrid search");
2958
+ return ok3(
1101
2959
  await client2.retrieve({
1102
2960
  action: "hybrid",
1103
2961
  query,
@@ -1109,17 +2967,17 @@ async function handleRetrieve(client2, args) {
1109
2967
  );
1110
2968
  // Structured queries
1111
2969
  case "active_goals":
1112
- return ok(await client2.getGoals());
2970
+ return ok3(await client2.getGoals());
1113
2971
  case "open_questions":
1114
- return ok(await client2.getOpenQuestions());
2972
+ return ok3(await client2.getOpenQuestions());
1115
2973
  case "weak_claims":
1116
- return ok(await client2.getWeakClaims());
2974
+ return ok3(await client2.getWeakClaims());
1117
2975
  case "pending_approvals":
1118
- return ok(await client2.getPendingApprovals());
2976
+ return ok3(await client2.getPendingApprovals());
1119
2977
  case "unresolved_contradictions":
1120
- return ok(await client2.getContradictions());
2978
+ return ok3(await client2.getContradictions());
1121
2979
  case "stale_derivations":
1122
- return ok(
2980
+ return ok3(
1123
2981
  await client2.retrieve({
1124
2982
  action: "stale_derivations",
1125
2983
  limit,
@@ -1127,7 +2985,7 @@ async function handleRetrieve(client2, args) {
1127
2985
  })
1128
2986
  );
1129
2987
  case "preferences":
1130
- return ok(
2988
+ return ok3(
1131
2989
  await client2.retrieve({
1132
2990
  action: "preferences",
1133
2991
  ...query ? { query, k: limit } : {},
@@ -1136,8 +2994,8 @@ async function handleRetrieve(client2, args) {
1136
2994
  })
1137
2995
  );
1138
2996
  case "layer":
1139
- if (!layer) return err("layer is required for layer retrieval");
1140
- return ok(
2997
+ if (!layer) return err3("layer is required for layer retrieval");
2998
+ return ok3(
1141
2999
  await client2.retrieve({
1142
3000
  action: "layer",
1143
3001
  layer,
@@ -1146,7 +3004,7 @@ async function handleRetrieve(client2, args) {
1146
3004
  })
1147
3005
  );
1148
3006
  case "recent":
1149
- return ok(
3007
+ return ok3(
1150
3008
  await client2.retrieve({
1151
3009
  action: "recent",
1152
3010
  limit,
@@ -1158,7 +3016,7 @@ async function handleRetrieve(client2, args) {
1158
3016
  );
1159
3017
  // Document inventory
1160
3018
  case "document_index":
1161
- return ok(
3019
+ return ok3(
1162
3020
  await client2.getNodes({
1163
3021
  node_type: "Document",
1164
3022
  limit: limit ?? 100
@@ -1166,8 +3024,8 @@ async function handleRetrieve(client2, args) {
1166
3024
  );
1167
3025
  // Graph traversal
1168
3026
  case "chain":
1169
- if (!start_uid) return err("start_uid is required for chain traversal");
1170
- return ok(
3027
+ if (!start_uid) return err3("start_uid is required for chain traversal");
3028
+ return ok3(
1171
3029
  await client2.traverse({
1172
3030
  action: "chain",
1173
3031
  start_uid,
@@ -1178,8 +3036,8 @@ async function handleRetrieve(client2, args) {
1178
3036
  })
1179
3037
  );
1180
3038
  case "neighborhood":
1181
- if (!start_uid) return err("start_uid is required for neighborhood traversal");
1182
- return ok(
3039
+ if (!start_uid) return err3("start_uid is required for neighborhood traversal");
3040
+ return ok3(
1183
3041
  await client2.traverse({
1184
3042
  action: "neighborhood",
1185
3043
  start_uid,
@@ -1192,9 +3050,9 @@ async function handleRetrieve(client2, args) {
1192
3050
  })
1193
3051
  );
1194
3052
  case "path":
1195
- if (!start_uid) return err("start_uid is required for path traversal");
1196
- if (!end_uid) return err("end_uid is required for path traversal");
1197
- return ok(
3053
+ if (!start_uid) return err3("start_uid is required for path traversal");
3054
+ if (!end_uid) return err3("end_uid is required for path traversal");
3055
+ return ok3(
1198
3056
  await client2.traverse({
1199
3057
  action: "path",
1200
3058
  start_uid,
@@ -1208,8 +3066,8 @@ async function handleRetrieve(client2, args) {
1208
3066
  })
1209
3067
  );
1210
3068
  case "subgraph":
1211
- if (!start_uid) return err("start_uid is required for subgraph traversal");
1212
- return ok(
3069
+ if (!start_uid) return err3("start_uid is required for subgraph traversal");
3070
+ return ok3(
1213
3071
  await client2.traverse({
1214
3072
  action: "subgraph",
1215
3073
  start_uid,
@@ -1222,7 +3080,7 @@ async function handleRetrieve(client2, args) {
1222
3080
  })
1223
3081
  );
1224
3082
  default:
1225
- return err(`Unknown retrieve action: ${action}`);
3083
+ return err3(`Unknown retrieve action: ${action}`);
1226
3084
  }
1227
3085
  }
1228
3086
  async function handleIngest(client2, args) {
@@ -1251,7 +3109,7 @@ async function handleIngest(client2, args) {
1251
3109
  const targetsOntology = Array.isArray(layers) && layers.includes("ontology");
1252
3110
  const cognitiveLayers = targetsOntology ? layers.filter((layer) => layer !== "ontology") : layers;
1253
3111
  if (targetsOntology && !ontology_schema_id) {
1254
- return err(
3112
+ return err3(
1255
3113
  "layers included 'ontology' but no ontology_schema_id was supplied. Typed Layer-7 extraction is driven by ontology_schema_id, not by the layers list \u2014 pass the schema id, or drop 'ontology' from layers."
1256
3114
  );
1257
3115
  }
@@ -1259,8 +3117,8 @@ async function handleIngest(client2, args) {
1259
3117
  const ontologyOnly = targetsOntology && forwardedLayers === void 0;
1260
3118
  switch (action) {
1261
3119
  case "chunk":
1262
- if (!content) return err("content is required for chunk ingestion");
1263
- return ok(
3120
+ if (!content) return err3("content is required for chunk ingestion");
3121
+ return ok3(
1264
3122
  await client2.ingestChunk({
1265
3123
  content,
1266
3124
  label: title,
@@ -1274,8 +3132,8 @@ async function handleIngest(client2, args) {
1274
3132
  })
1275
3133
  );
1276
3134
  case "document":
1277
- if (!content) return err("content is required for document ingestion");
1278
- return ok(
3135
+ if (!content) return err3("content is required for document ingestion");
3136
+ return ok3(
1279
3137
  await client2.ingestDocument({
1280
3138
  content,
1281
3139
  title,
@@ -1294,8 +3152,8 @@ async function handleIngest(client2, args) {
1294
3152
  })
1295
3153
  );
1296
3154
  case "session":
1297
- if (!content) return err("content is required for session ingestion");
1298
- return ok(
3155
+ if (!content) return err3("content is required for session ingestion");
3156
+ return ok3(
1299
3157
  await client2.ingestSession({
1300
3158
  content,
1301
3159
  title,
@@ -1312,11 +3170,11 @@ async function handleIngest(client2, args) {
1312
3170
  );
1313
3171
  case "job_status":
1314
3172
  if (job_id) {
1315
- return ok(await client2.getJob(job_id));
3173
+ return ok3(await client2.getJob(job_id));
1316
3174
  }
1317
- return ok(await client2.listJobs());
3175
+ return ok3(await client2.listJobs());
1318
3176
  default:
1319
- return err(`Unknown ingest action: ${action}`);
3177
+ return err3(`Unknown ingest action: ${action}`);
1320
3178
  }
1321
3179
  }
1322
3180
  async function handleSynthesize(client2, args) {
@@ -1324,19 +3182,19 @@ async function handleSynthesize(client2, args) {
1324
3182
  switch (action) {
1325
3183
  case "signals":
1326
3184
  if (!project_uid)
1327
- return err("project_uid is required for action='signals'");
1328
- return ok(
3185
+ return err3("project_uid is required for action='signals'");
3186
+ return ok3(
1329
3187
  await client2.signals(project_uid, { signals, target_types })
1330
3188
  );
1331
3189
  case "run":
1332
3190
  if (!project_uid)
1333
- return err("project_uid is required for action='run'");
1334
- return ok(await client2.runSynthesis(project_uid));
3191
+ return err3("project_uid is required for action='run'");
3192
+ return ok3(await client2.runSynthesis(project_uid));
1335
3193
  case "job_status":
1336
- if (!job_id) return err("job_id is required for action='job_status'");
1337
- return ok(await client2.getJob(job_id));
3194
+ if (!job_id) return err3("job_id is required for action='job_status'");
3195
+ return ok3(await client2.getJob(job_id));
1338
3196
  default:
1339
- return err(`Unknown synthesize action: ${action}`);
3197
+ return err3(`Unknown synthesize action: ${action}`);
1340
3198
  }
1341
3199
  }
1342
3200
  async function handleOntology(client2, args) {
@@ -1364,18 +3222,18 @@ async function handleOntology(client2, args) {
1364
3222
  } = args;
1365
3223
  switch (action) {
1366
3224
  case "schemas":
1367
- return ok(await client2.listOntologySchemas());
3225
+ return ok3(await client2.listOntologySchemas());
1368
3226
  case "schema":
1369
- if (!schema_id) return err("schema_id is required for action='schema'");
1370
- return ok(await client2.getOntologySchema(schema_id));
3227
+ if (!schema_id) return err3("schema_id is required for action='schema'");
3228
+ return ok3(await client2.getOntologySchema(schema_id));
1371
3229
  case "query": {
1372
- if (!query) return err("query is required for action='query'");
3230
+ if (!query) return err3("query is required for action='query'");
1373
3231
  if (!schema_id) {
1374
- return err(
3232
+ return err3(
1375
3233
  "schema_id is required for action='query' (use action='schemas' to find the active schema)"
1376
3234
  );
1377
3235
  }
1378
- return ok(
3236
+ return ok3(
1379
3237
  await client2.queryOntology({
1380
3238
  query,
1381
3239
  schema_id,
@@ -1388,8 +3246,8 @@ async function handleOntology(client2, args) {
1388
3246
  );
1389
3247
  }
1390
3248
  case "search":
1391
- if (!query) return err("query is required for action='search'");
1392
- return ok(
3249
+ if (!query) return err3("query is required for action='search'");
3250
+ return ok3(
1393
3251
  await client2.searchDomainObjects(query, {
1394
3252
  schema_id,
1395
3253
  object_types,
@@ -1397,18 +3255,18 @@ async function handleOntology(client2, args) {
1397
3255
  })
1398
3256
  );
1399
3257
  case "objects":
1400
- if (!schema_id) return err("schema_id is required for action='objects'");
1401
- return ok(
3258
+ if (!schema_id) return err3("schema_id is required for action='objects'");
3259
+ return ok3(
1402
3260
  await client2.listDomainObjects({ schema_id, object_type, limit })
1403
3261
  );
1404
3262
  case "object":
1405
- if (!uid) return err("uid is required for action='object'");
1406
- return ok(await client2.getDomainObject(uid));
3263
+ if (!uid) return err3("uid is required for action='object'");
3264
+ return ok3(await client2.getDomainObject(uid));
1407
3265
  case "object_context":
1408
- if (!uid) return err("uid is required for action='object_context'");
1409
- return ok(await client2.getDomainObjectContext(uid, depth));
3266
+ if (!uid) return err3("uid is required for action='object_context'");
3267
+ return ok3(await client2.getDomainObjectContext(uid, depth));
1410
3268
  case "proposals":
1411
- return ok(
3269
+ return ok3(
1412
3270
  await client2.listOntologyProposals({
1413
3271
  status: status ?? "pending",
1414
3272
  schema_id,
@@ -1417,21 +3275,21 @@ async function handleOntology(client2, args) {
1417
3275
  })
1418
3276
  );
1419
3277
  case "proposal":
1420
- if (!proposal_id) return err("proposal_id is required for action='proposal'");
1421
- return ok(await client2.getOntologyProposal(proposal_id));
3278
+ if (!proposal_id) return err3("proposal_id is required for action='proposal'");
3279
+ return ok3(await client2.getOntologyProposal(proposal_id));
1422
3280
  case "approve":
1423
- if (!proposal_id) return err("proposal_id is required for action='approve'");
1424
- return ok(await client2.approveOntologyProposal(proposal_id, { feedback }));
3281
+ if (!proposal_id) return err3("proposal_id is required for action='approve'");
3282
+ return ok3(await client2.approveOntologyProposal(proposal_id, { feedback }));
1425
3283
  case "reject":
1426
- if (!proposal_id) return err("proposal_id is required for action='reject'");
1427
- return ok(await client2.rejectOntologyProposal(proposal_id, reason));
3284
+ if (!proposal_id) return err3("proposal_id is required for action='reject'");
3285
+ return ok3(await client2.rejectOntologyProposal(proposal_id, reason));
1428
3286
  case "link": {
1429
3287
  if (!from_uid || !to_uid || !relation_type) {
1430
- return err(
3288
+ return err3(
1431
3289
  "from_uid, to_uid, and relation_type are required for action='link'"
1432
3290
  );
1433
3291
  }
1434
- return ok(
3292
+ return ok3(
1435
3293
  await client2.linkDomainObjects({
1436
3294
  from_uid,
1437
3295
  to_uid,
@@ -1442,11 +3300,11 @@ async function handleOntology(client2, args) {
1442
3300
  );
1443
3301
  }
1444
3302
  case "extract": {
1445
- if (!schema_id) return err("schema_id is required for action='extract'");
3303
+ if (!schema_id) return err3("schema_id is required for action='extract'");
1446
3304
  if (!source_uids?.length) {
1447
- return err("source_uids is required for action='extract'");
3305
+ return err3("source_uids is required for action='extract'");
1448
3306
  }
1449
- return ok(
3307
+ return ok3(
1450
3308
  await client2.extractOntology({
1451
3309
  ontology_schema_id: schema_id,
1452
3310
  source_uids
@@ -1454,26 +3312,36 @@ async function handleOntology(client2, args) {
1454
3312
  );
1455
3313
  }
1456
3314
  default:
1457
- return err(`Unknown ontology action: ${action}`);
3315
+ return err3(`Unknown ontology action: ${action}`);
1458
3316
  }
1459
3317
  }
1460
3318
 
1461
3319
  // src/generated-tools.ts
1462
- var ok2 = (data) => ({
3320
+ var ok4 = (data) => ({
1463
3321
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1464
3322
  });
1465
- var err2 = (message) => ({
3323
+ var err4 = (message) => ({
1466
3324
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
1467
3325
  isError: true
1468
3326
  });
1469
3327
  var CACHE_TTL_MS = 5 * 60 * 1e3;
3328
+ var FAILURE_COOLDOWN_MS = 15 * 1e3;
1470
3329
  var cache = null;
3330
+ var retryNotBefore = 0;
3331
+ var EMPTY = {
3332
+ tools: [],
3333
+ byName: /* @__PURE__ */ new Map()
3334
+ };
1471
3335
  var DuplicateGeneratedToolError = class extends Error {
1472
3336
  };
1473
3337
  async function getGeneratedTools(client2) {
1474
- if (cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
3338
+ const now = Date.now();
3339
+ if (cache && now - cache.fetchedAt < CACHE_TTL_MS) {
1475
3340
  return { tools: cache.tools, byName: cache.byName };
1476
3341
  }
3342
+ if (now < retryNotBefore) {
3343
+ return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
3344
+ }
1477
3345
  try {
1478
3346
  const c = client2;
1479
3347
  if (typeof c.listOntologyTools !== "function") {
@@ -1497,23 +3365,32 @@ async function getGeneratedTools(client2) {
1497
3365
  });
1498
3366
  }
1499
3367
  cache = { tools, byName, fetchedAt: Date.now() };
3368
+ retryNotBefore = 0;
1500
3369
  return { tools, byName };
1501
3370
  } catch (error) {
1502
3371
  if (error instanceof DuplicateGeneratedToolError) throw error;
1503
- cache = { tools: [], byName: /* @__PURE__ */ new Map(), fetchedAt: Date.now() };
1504
- return { tools: cache.tools, byName: cache.byName };
3372
+ retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
3373
+ return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
1505
3374
  }
1506
3375
  }
3376
+ var STRUCTURED_QUERY_PASSTHROUGH = [
3377
+ "anchor",
3378
+ "path",
3379
+ "where",
3380
+ "include",
3381
+ "page",
3382
+ "aggregate"
3383
+ ];
1507
3384
  async function handleGeneratedTool(client2, desc, args) {
1508
3385
  try {
1509
3386
  const ontologyClient = client2;
1510
3387
  switch (desc.maps_to) {
1511
3388
  case "search": {
1512
3389
  const query = args.query;
1513
- if (!query) return err2(`query is required for ${desc.name}`);
3390
+ if (!query) return err4(`query is required for ${desc.name}`);
1514
3391
  const limit = typeof args.limit === "number" ? args.limit : void 0;
1515
3392
  const filters = Array.isArray(args.filters) ? args.filters : void 0;
1516
- return ok2(
3393
+ return ok4(
1517
3394
  await ontologyClient.searchDomainObjects(query, {
1518
3395
  schema_id: desc.schema_id,
1519
3396
  object_types: [desc.object_type],
@@ -1524,33 +3401,33 @@ async function handleGeneratedTool(client2, desc, args) {
1524
3401
  }
1525
3402
  case "object": {
1526
3403
  const uid = args.uid;
1527
- if (!uid) return err2(`uid is required for ${desc.name}`);
1528
- return ok2(await ontologyClient.getDomainObject(uid, {
3404
+ if (!uid) return err4(`uid is required for ${desc.name}`);
3405
+ return ok4(await ontologyClient.getDomainObject(uid, {
1529
3406
  schema_id: desc.schema_id,
1530
3407
  object_type: desc.object_type
1531
3408
  }));
1532
3409
  }
1533
3410
  case "object_context": {
1534
3411
  const uid = args.uid;
1535
- if (!uid) return err2(`uid is required for ${desc.name}`);
3412
+ if (!uid) return err4(`uid is required for ${desc.name}`);
1536
3413
  const depth = typeof args.depth === "number" ? args.depth : void 0;
1537
- return ok2(await ontologyClient.getDomainObjectContext(uid, depth, {
3414
+ return ok4(await ontologyClient.getDomainObjectContext(uid, depth, {
1538
3415
  schema_id: desc.schema_id,
1539
3416
  object_type: desc.object_type
1540
3417
  }));
1541
3418
  }
1542
3419
  case "related": {
1543
3420
  const uid = args.uid;
1544
- if (!uid) return err2(`uid is required for ${desc.name}`);
3421
+ if (!uid) return err4(`uid is required for ${desc.name}`);
1545
3422
  if (!desc.relation || !desc.entry_role || !desc.far_type) {
1546
- return err2(`invalid related descriptor: ${desc.name}`);
3423
+ return err4(`invalid related descriptor: ${desc.name}`);
1547
3424
  }
1548
3425
  if (!ontologyClient.queryRelatedDomainObjects) {
1549
- return err2(
3426
+ return err4(
1550
3427
  "related ontology tools require a graph-aware version of the mindgraph SDK"
1551
3428
  );
1552
3429
  }
1553
- return ok2(await ontologyClient.queryRelatedDomainObjects({
3430
+ return ok4(await ontologyClient.queryRelatedDomainObjects({
1554
3431
  schema_id: desc.schema_id,
1555
3432
  entry_type: desc.object_type,
1556
3433
  uid,
@@ -1565,28 +3442,41 @@ async function handleGeneratedTool(client2, desc, args) {
1565
3442
  }
1566
3443
  case "structured_query": {
1567
3444
  const select = args.select;
1568
- if (!select) return err2(`select is required for ${desc.name}`);
3445
+ if (!select) return err4(`select is required for ${desc.name}`);
1569
3446
  if (!ontologyClient.queryDomainStructured) {
1570
- return err2(
3447
+ return err4(
1571
3448
  "structured ontology tools require a graph-aware version of the mindgraph SDK"
1572
3449
  );
1573
3450
  }
1574
- return ok2(await ontologyClient.queryDomainStructured({
1575
- ...args,
3451
+ const request = {
1576
3452
  schema_id: desc.schema_id,
1577
3453
  select
1578
- }));
3454
+ };
3455
+ for (const field of STRUCTURED_QUERY_PASSTHROUGH) {
3456
+ if (args[field] !== void 0) request[field] = args[field];
3457
+ }
3458
+ return ok4(await ontologyClient.queryDomainStructured(request));
1579
3459
  }
1580
3460
  default:
1581
- return err2(`unknown generated tool mapping: ${desc.maps_to}`);
3461
+ return err4(`unknown generated tool mapping: ${desc.maps_to}`);
1582
3462
  }
1583
3463
  } catch (e) {
1584
- return err2(e instanceof Error ? e.message : String(e));
3464
+ return err4(e instanceof Error ? e.message : String(e));
1585
3465
  }
1586
3466
  }
1587
3467
 
1588
3468
  // src/governance.ts
1589
3469
  var capability = "unknown";
3470
+ var UNSUPPORTED_LEASE_MS = 5 * 60 * 1e3;
3471
+ var unsupportedUntil = 0;
3472
+ var unsupportedLogged = false;
3473
+ var DEFAULT_RETRY_DELAYS_MS = [150, 600];
3474
+ function isTransientStatus(status) {
3475
+ return status === 408 || status === 425 || status === 429 || status >= 500;
3476
+ }
3477
+ function sleep(ms) {
3478
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
3479
+ }
1590
3480
  var KNOWN_DUTIES = /* @__PURE__ */ new Set(["log", "require_approval"]);
1591
3481
  function headers(apiKey, orgId) {
1592
3482
  const value = {
@@ -1602,42 +3492,181 @@ function unsupportedResponse(status, body) {
1602
3492
  const lower = body.toLowerCase();
1603
3493
  return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
1604
3494
  }
3495
+ var PLAN_READ_ACTIONS = /* @__PURE__ */ new Set([
3496
+ "get_plan",
3497
+ "get_executions",
3498
+ "get_pending",
3499
+ "get_assessments",
3500
+ "resume_work"
3501
+ ]);
3502
+ var ONTOLOGY_READ_ACTIONS = /* @__PURE__ */ new Set([
3503
+ "list_schemas",
3504
+ "get_schema",
3505
+ "query",
3506
+ "search_objects",
3507
+ "list_objects",
3508
+ "get_object",
3509
+ "get_context",
3510
+ "list_proposals",
3511
+ "get_proposal"
3512
+ ]);
3513
+ function toolMutability(toolName, action) {
3514
+ if (toolName === "mindgraph_retrieve") return "read";
3515
+ if (toolName === "mindgraph_plan") {
3516
+ return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
3517
+ }
3518
+ if (toolName === "mindgraph_ingest") {
3519
+ return action === "job_status" ? "read" : "write";
3520
+ }
3521
+ if (toolName === "mindgraph_synthesize") {
3522
+ return action === "signals" ? "read" : "write";
3523
+ }
3524
+ if (toolName === "mindgraph_code") {
3525
+ return action === "anchor" ? "write" : "read";
3526
+ }
3527
+ if (toolName === "mindgraph_sync") {
3528
+ return action === "scan" || action === "status" ? "read" : "write";
3529
+ }
3530
+ if (toolName === "mindgraph_ontology") {
3531
+ return action && ONTOLOGY_READ_ACTIONS.has(action) ? "read" : "write";
3532
+ }
3533
+ if (/_(search|get|context|related|query)$/.test(toolName) && toolName !== "mindgraph_capture") {
3534
+ return "read";
3535
+ }
3536
+ return "write";
3537
+ }
3538
+ var UID_FIELDS = [
3539
+ "uid",
3540
+ "source_uid",
3541
+ "session_uid",
3542
+ "work_uid",
3543
+ "goal_uid",
3544
+ "project_uid",
3545
+ "task_uid",
3546
+ "plan_uid",
3547
+ "step_uid",
3548
+ "target_uid",
3549
+ "execution_uid",
3550
+ "executor_uid",
3551
+ "affordance_uid",
3552
+ "produces_node_uid",
3553
+ "filter_plan_uid",
3554
+ "parent_uid",
3555
+ "decision_uid",
3556
+ "chosen_option_uid",
3557
+ "governed_uid",
3558
+ "approval_uid",
3559
+ "requires_plan_uid",
3560
+ "start_uid",
3561
+ "end_uid"
3562
+ ];
3563
+ var UID_ARRAY_FIELDS = [
3564
+ "related_uids",
3565
+ "depends_on_uids",
3566
+ "summarizes_uids",
3567
+ "informs_uid",
3568
+ "target_uids",
3569
+ "anchor_uids",
3570
+ "scope_uids",
3571
+ "produces_node_uids"
3572
+ ];
3573
+ function governanceTarget(toolName, args) {
3574
+ const action = typeof args.action === "string" ? args.action : void 0;
3575
+ const targetUids = /* @__PURE__ */ new Set();
3576
+ for (const field of UID_FIELDS) {
3577
+ const value = args[field];
3578
+ if (typeof value === "string" && value.length > 0) targetUids.add(value);
3579
+ }
3580
+ for (const field of UID_ARRAY_FIELDS) {
3581
+ const value = args[field];
3582
+ if (!Array.isArray(value)) continue;
3583
+ for (const uid of value) {
3584
+ if (typeof uid === "string" && uid.length > 0) targetUids.add(uid);
3585
+ }
3586
+ }
3587
+ return {
3588
+ tool_name: toolName,
3589
+ ...action ? { action } : {},
3590
+ mutability: toolMutability(toolName, action),
3591
+ target_uids: [...targetUids]
3592
+ };
3593
+ }
1605
3594
  async function checkMcpGovernance(toolName, args, config) {
1606
- if (config.disabled || capability === "unsupported") return { allowed: true };
3595
+ if (config.disabled) return { allowed: true };
3596
+ if (capability === "unsupported") {
3597
+ if (Date.now() < unsupportedUntil) return { allowed: true };
3598
+ capability = "unknown";
3599
+ }
1607
3600
  const fetchImpl = config.fetchImpl ?? fetch;
3601
+ const retryDelays = config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
1608
3602
  let response;
1609
- let body;
1610
- try {
1611
- response = await fetchImpl(`${config.baseUrl.replace(/\/$/, "")}/agent/governance`, {
1612
- method: "POST",
1613
- headers: headers(config.apiKey, config.orgId),
1614
- body: JSON.stringify({
1615
- action: "check",
1616
- act: "tool_invoke",
1617
- agent_id: String(args.agent_id || config.agentId),
1618
- target: { tool_name: toolName },
1619
- context: { adapter: "mindgraph-mcp" },
1620
- tier: "checkpoint"
1621
- })
1622
- });
1623
- body = await response.text();
1624
- } catch (error) {
3603
+ let body = "";
3604
+ let lastFailure = "";
3605
+ for (let attempt = 0; attempt <= retryDelays.length; attempt++) {
3606
+ try {
3607
+ const candidate = await fetchImpl(
3608
+ `${config.baseUrl.replace(/\/$/, "")}/agent/governance`,
3609
+ {
3610
+ method: "POST",
3611
+ headers: headers(config.apiKey, config.orgId),
3612
+ body: JSON.stringify({
3613
+ action: "check",
3614
+ act: "tool_invoke",
3615
+ agent_id: String(args.agent_id || config.agentId),
3616
+ target: governanceTarget(toolName, args),
3617
+ context: { adapter: "mindgraph-mcp", ...config.context },
3618
+ tier: "checkpoint"
3619
+ })
3620
+ }
3621
+ );
3622
+ const candidateBody = await candidate.text();
3623
+ if (isTransientStatus(candidate.status) && attempt < retryDelays.length) {
3624
+ lastFailure = `HTTP ${candidate.status}`;
3625
+ await sleep(retryDelays[attempt]);
3626
+ continue;
3627
+ }
3628
+ response = candidate;
3629
+ body = candidateBody;
3630
+ break;
3631
+ } catch (error) {
3632
+ lastFailure = error instanceof Error ? error.message : String(error);
3633
+ if (attempt < retryDelays.length) {
3634
+ await sleep(retryDelays[attempt]);
3635
+ continue;
3636
+ }
3637
+ }
3638
+ }
3639
+ if (!response) {
1625
3640
  return {
1626
3641
  allowed: false,
1627
- message: `MindGraph governance could not establish permission: ${error instanceof Error ? error.message : String(error)}`
3642
+ message: `MindGraph governance could not establish permission after ${retryDelays.length + 1} attempts (${lastFailure}). Set MINDGRAPH_GOVERNANCE=off to run this adapter ungoverned.`
1628
3643
  };
1629
3644
  }
1630
3645
  if (!response.ok) {
1631
3646
  if (capability === "unknown" && unsupportedResponse(response.status, body)) {
1632
3647
  capability = "unsupported";
3648
+ unsupportedUntil = Date.now() + UNSUPPORTED_LEASE_MS;
3649
+ if (!unsupportedLogged) {
3650
+ unsupportedLogged = true;
3651
+ console.error(
3652
+ `[mindgraph-mcp] This MindGraph server does not implement the governance checkpoint (HTTP ${response.status}); tool calls proceed ungoverned. Re-probing every ${Math.round(UNSUPPORTED_LEASE_MS / 6e4)} minutes.`
3653
+ );
3654
+ }
1633
3655
  return { allowed: true };
1634
3656
  }
3657
+ if (response.status === 401 || response.status === 403) {
3658
+ return {
3659
+ allowed: false,
3660
+ message: `This credential is not permitted to evaluate MindGraph governance (HTTP ${response.status}). Use a user-scoped API key, or set MINDGRAPH_GOVERNANCE=off to run this adapter ungoverned.`
3661
+ };
3662
+ }
1635
3663
  return {
1636
3664
  allowed: false,
1637
3665
  message: `MindGraph governance check failed (${response.status})`
1638
3666
  };
1639
3667
  }
1640
3668
  capability = "supported";
3669
+ unsupportedLogged = false;
1641
3670
  let decision;
1642
3671
  try {
1643
3672
  decision = JSON.parse(body);
@@ -1672,18 +3701,124 @@ async function checkMcpGovernance(toolName, args, config) {
1672
3701
  };
1673
3702
  }
1674
3703
 
3704
+ // src/resources.ts
3705
+ var RESOURCES = [
3706
+ {
3707
+ uri: "mindgraph://stats",
3708
+ name: "Graph Statistics",
3709
+ description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
3710
+ mimeType: "application/json"
3711
+ },
3712
+ {
3713
+ uri: "mindgraph://goals",
3714
+ name: "Active Goals",
3715
+ description: "All active goals in the knowledge graph",
3716
+ mimeType: "application/json"
3717
+ },
3718
+ {
3719
+ uri: "mindgraph://questions",
3720
+ name: "Open Questions",
3721
+ description: "All open questions in the knowledge graph",
3722
+ mimeType: "application/json"
3723
+ },
3724
+ {
3725
+ uri: "mindgraph://contradictions",
3726
+ name: "Contradictions",
3727
+ description: "Contradictory claims in the knowledge graph",
3728
+ mimeType: "application/json"
3729
+ },
3730
+ {
3731
+ uri: "mindgraph://decisions",
3732
+ name: "Open Decisions",
3733
+ description: "Unresolved decisions awaiting resolution",
3734
+ mimeType: "application/json"
3735
+ }
3736
+ ];
3737
+ var RESOURCE_TEMPLATES = [
3738
+ {
3739
+ uriTemplate: "mindgraph://node/{uid}",
3740
+ name: "Graph Node",
3741
+ description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
3742
+ mimeType: "application/json"
3743
+ },
3744
+ {
3745
+ uriTemplate: "mindgraph://search/{query}",
3746
+ name: "Search Results",
3747
+ description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
3748
+ mimeType: "application/json"
3749
+ },
3750
+ {
3751
+ uriTemplate: "mindgraph://layer/{layer}",
3752
+ name: "Layer Nodes",
3753
+ description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
3754
+ mimeType: "application/json"
3755
+ }
3756
+ ];
3757
+ var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
3758
+ function json(uri, data) {
3759
+ return {
3760
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
3761
+ };
3762
+ }
3763
+ async function readResource(client2, uri) {
3764
+ switch (uri) {
3765
+ case "mindgraph://stats":
3766
+ return json(uri, await client2.stats());
3767
+ case "mindgraph://goals":
3768
+ return json(uri, await client2.getGoals());
3769
+ case "mindgraph://questions":
3770
+ return json(uri, await client2.getOpenQuestions());
3771
+ case "mindgraph://contradictions":
3772
+ return json(uri, await client2.getContradictions());
3773
+ case "mindgraph://decisions":
3774
+ return json(uri, await client2.getOpenDecisions());
3775
+ }
3776
+ const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
3777
+ if (nodeMatch) {
3778
+ const uid = decodeURIComponent(nodeMatch[1]);
3779
+ const node = await client2.getNode(uid);
3780
+ const edges = await client2.getEdges({ from_uid: uid });
3781
+ const inEdges = await client2.getEdges({ to_uid: uid });
3782
+ return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
3783
+ }
3784
+ const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
3785
+ if (searchMatch) {
3786
+ const query = decodeURIComponent(searchMatch[1]);
3787
+ return json(uri, await client2.search(query, { limit: 20 }));
3788
+ }
3789
+ const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
3790
+ if (layerMatch) {
3791
+ const layer = decodeURIComponent(layerMatch[1]);
3792
+ return json(uri, await client2.getNodes({ layer, limit: 50 }));
3793
+ }
3794
+ throw new Error(`Unknown resource: ${uri}`);
3795
+ }
3796
+ async function readGovernedResource(client2, uri, governanceConfig) {
3797
+ const governance = await checkMcpGovernance(
3798
+ RESOURCE_GOVERNANCE_TOOL,
3799
+ {},
3800
+ { ...governanceConfig, context: { resource_uri: uri } }
3801
+ );
3802
+ if (!governance.allowed) {
3803
+ throw new Error(governance.message);
3804
+ }
3805
+ return readResource(client2, uri);
3806
+ }
3807
+
1675
3808
  // src/index.ts
1676
3809
  var API_KEY = process.env.MINDGRAPH_API_KEY;
1677
3810
  var BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
1678
3811
  var AGENT_ID = process.env.MINDGRAPH_AGENT_ID || "mcp";
1679
3812
  var ORG_ID = process.env.MINDGRAPH_ORG_ID;
3813
+ var PROFILE = process.env.MINDGRAPH_PROFILE || "general";
3814
+ var HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
1680
3815
  if (!API_KEY) {
1681
3816
  console.error(
1682
3817
  "Error: MINDGRAPH_API_KEY environment variable is required.\nGet your API key at https://mindgraph.cloud/dashboard/keys\nOr run: npx mindgraph-mcp init"
1683
3818
  );
1684
3819
  process.exit(1);
1685
3820
  }
1686
- var client = new import_mindgraph.MindGraph({
3821
+ var client = new import_mindgraph2.MindGraph({
1687
3822
  baseUrl: BASE_URL,
1688
3823
  apiKey: API_KEY,
1689
3824
  orgId: ORG_ID,
@@ -1692,7 +3827,7 @@ var client = new import_mindgraph.MindGraph({
1692
3827
  // window where npm may still resolve the preceding SDK declaration file.
1693
3828
  telemetrySurface: "mcp"
1694
3829
  });
1695
- var INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph (7 tools).
3830
+ var INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
1696
3831
 
1697
3832
  ## #1 RULE: Go straight to the user's topic
1698
3833
 
@@ -1755,22 +3890,42 @@ Capture knowledge when the user shares something worth remembering:
1755
3890
  - Questions to track \u2192 \`mindgraph_reason\` action "open_question"
1756
3891
  - Goals or projects \u2192 \`mindgraph_commit\` action "goal"/"project"
1757
3892
  - Decisions \u2192 \`mindgraph_commit\` action "open_decision"
3893
+ - Durable engineering work that must survive sessions/harnesses \u2192 \`mindgraph_plan\`
3894
+ - Typed code anchors, attached memory/work, callers/callees, or impact \u2192 \`mindgraph_code\`
3895
+ - Reusable learning from a material attempt \u2192 \`mindgraph_capture\` action "lesson"
1758
3896
  - Long-form content \u2192 \`mindgraph_ingest\`
1759
3897
  - Cross-document synthesis over a project corpus \u2192 \`mindgraph_synthesize\` (action "signals" for structural signals; action "run" to spawn a synthesis job that writes Article nodes)
1760
3898
 
1761
3899
  ## Tool tips
1762
3900
 
3901
+ - Use the harness todo list to organize only the current harness's local execution. Use \`mindgraph_plan\` when work is authoritative across sessions/harnesses, needs graph relationships or evidence, or must be visible to other agents. Do not maintain the same item in both.
3902
+ - If a local harness task remains unfinished and should become authoritative outside that local list, promote it once with a stable idempotency key when the graph-managed composite actions are available; otherwise do not copy it.
1763
3903
  - \`capture\`: "entity"/"observation" for facts, "journal" for personal/informal
1764
3904
  - \`reason\`: "claim" when evidence exists, "open_question"/"hypothesis" otherwise
1765
3905
  - \`retrieve\`: "context" returns graph nodes only by default \u2014 set include_chunks=true for source text
1766
3906
  - \`retrieve\`: "context" (FTS default) \u2192 "semantic" (conceptual fallback) \u2192 "hybrid" (both)
1767
3907
  - \`retrieve\`: "neighborhood"/"chain"/"path"/"subgraph" for graph exploration
3908
+ - \`code\`: pass path + symbol + signature when names are overloaded; ambiguity never writes
1768
3909
  - Confidence: 0.9+ for facts, 0.5-0.8 for uncertain, <0.5 for speculation
1769
3910
  - Don't narrate tool usage \u2014 capture naturally as part of conversation`;
3911
+ if (PROFILE === "coding") {
3912
+ INSTRUCTIONS += `
3913
+
3914
+ ## Coding profile
3915
+
3916
+ - Use mindgraph_sync for explicit, one-way project memory-file imports. It discovers and hashes files, but you deliberately map durable assertions with record; it never writes graph state back to files.
3917
+ - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
3918
+ - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
3919
+ - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
3920
+ } else {
3921
+ INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
3922
+ (line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
3923
+ ).join("\n");
3924
+ }
1770
3925
  var server = new import_server.Server(
1771
3926
  {
1772
3927
  name: "mindgraph",
1773
- version: "0.13.4"
3928
+ version: "0.14.0"
1774
3929
  },
1775
3930
  {
1776
3931
  capabilities: {
@@ -1783,21 +3938,25 @@ var server = new import_server.Server(
1783
3938
  );
1784
3939
  server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
1785
3940
  const generated = await getGeneratedTools(client);
1786
- return { tools: [...TOOLS, ...generated.tools] };
3941
+ return { tools: [...toolsForProfile(PROFILE), ...generated.tools] };
1787
3942
  });
3943
+ var GOVERNANCE_CONFIG = {
3944
+ baseUrl: BASE_URL,
3945
+ apiKey: API_KEY,
3946
+ orgId: ORG_ID,
3947
+ agentId: AGENT_ID,
3948
+ get disabled() {
3949
+ return process.env.MINDGRAPH_GOVERNANCE === "off";
3950
+ }
3951
+ };
1788
3952
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
1789
3953
  const { name, arguments: args } = request.params;
1790
- const toolArgs = { ...args || {} };
3954
+ const rawArgs = { ...args || {} };
3955
+ const toolArgs = { ...rawArgs };
1791
3956
  if (!toolArgs.agent_id) {
1792
3957
  toolArgs.agent_id = AGENT_ID;
1793
3958
  }
1794
- const governance = await checkMcpGovernance(name, toolArgs, {
1795
- baseUrl: BASE_URL,
1796
- apiKey: API_KEY,
1797
- orgId: ORG_ID,
1798
- agentId: AGENT_ID,
1799
- disabled: process.env.MINDGRAPH_GOVERNANCE === "off"
1800
- });
3959
+ const governance = await checkMcpGovernance(name, toolArgs, GOVERNANCE_CONFIG);
1801
3960
  if (!governance.allowed) {
1802
3961
  return {
1803
3962
  content: [{ type: "text", text: JSON.stringify({ error: governance.message, ...governance }) }],
@@ -1807,7 +3966,7 @@ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) =>
1807
3966
  const generated = await getGeneratedTools(client);
1808
3967
  const desc = generated.byName.get(name);
1809
3968
  if (desc) {
1810
- return handleGeneratedTool(client, desc, toolArgs);
3969
+ return handleGeneratedTool(client, desc, rawArgs);
1811
3970
  }
1812
3971
  return handleTool(client, name, toolArgs);
1813
3972
  });
@@ -1968,178 +4127,13 @@ ${conversation}`
1968
4127
  }
1969
4128
  });
1970
4129
  server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
1971
- return {
1972
- resources: [
1973
- {
1974
- uri: "mindgraph://stats",
1975
- name: "Graph Statistics",
1976
- description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
1977
- mimeType: "application/json"
1978
- },
1979
- {
1980
- uri: "mindgraph://goals",
1981
- name: "Active Goals",
1982
- description: "All active goals in the knowledge graph",
1983
- mimeType: "application/json"
1984
- },
1985
- {
1986
- uri: "mindgraph://questions",
1987
- name: "Open Questions",
1988
- description: "All open questions in the knowledge graph",
1989
- mimeType: "application/json"
1990
- },
1991
- {
1992
- uri: "mindgraph://contradictions",
1993
- name: "Contradictions",
1994
- description: "Contradictory claims in the knowledge graph",
1995
- mimeType: "application/json"
1996
- },
1997
- {
1998
- uri: "mindgraph://decisions",
1999
- name: "Open Decisions",
2000
- description: "Unresolved decisions awaiting resolution",
2001
- mimeType: "application/json"
2002
- }
2003
- ]
2004
- };
4130
+ return { resources: RESOURCES };
2005
4131
  });
2006
4132
  server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => {
2007
- return {
2008
- resourceTemplates: [
2009
- {
2010
- uriTemplate: "mindgraph://node/{uid}",
2011
- name: "Graph Node",
2012
- description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
2013
- mimeType: "application/json"
2014
- },
2015
- {
2016
- uriTemplate: "mindgraph://search/{query}",
2017
- name: "Search Results",
2018
- description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
2019
- mimeType: "application/json"
2020
- },
2021
- {
2022
- uriTemplate: "mindgraph://layer/{layer}",
2023
- name: "Layer Nodes",
2024
- description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
2025
- mimeType: "application/json"
2026
- }
2027
- ]
2028
- };
4133
+ return { resourceTemplates: RESOURCE_TEMPLATES };
2029
4134
  });
2030
4135
  server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
2031
- const { uri } = request.params;
2032
- switch (uri) {
2033
- case "mindgraph://stats": {
2034
- const stats = await client.stats();
2035
- return {
2036
- contents: [
2037
- {
2038
- uri,
2039
- mimeType: "application/json",
2040
- text: JSON.stringify(stats, null, 2)
2041
- }
2042
- ]
2043
- };
2044
- }
2045
- case "mindgraph://goals": {
2046
- const goals = await client.getGoals();
2047
- return {
2048
- contents: [
2049
- {
2050
- uri,
2051
- mimeType: "application/json",
2052
- text: JSON.stringify(goals, null, 2)
2053
- }
2054
- ]
2055
- };
2056
- }
2057
- case "mindgraph://questions": {
2058
- const questions = await client.getOpenQuestions();
2059
- return {
2060
- contents: [
2061
- {
2062
- uri,
2063
- mimeType: "application/json",
2064
- text: JSON.stringify(questions, null, 2)
2065
- }
2066
- ]
2067
- };
2068
- }
2069
- case "mindgraph://contradictions": {
2070
- const contradictions = await client.getContradictions();
2071
- return {
2072
- contents: [
2073
- {
2074
- uri,
2075
- mimeType: "application/json",
2076
- text: JSON.stringify(contradictions, null, 2)
2077
- }
2078
- ]
2079
- };
2080
- }
2081
- case "mindgraph://decisions": {
2082
- const decisions = await client.getOpenDecisions();
2083
- return {
2084
- contents: [
2085
- {
2086
- uri,
2087
- mimeType: "application/json",
2088
- text: JSON.stringify(decisions, null, 2)
2089
- }
2090
- ]
2091
- };
2092
- }
2093
- }
2094
- const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
2095
- if (nodeMatch) {
2096
- const uid = decodeURIComponent(nodeMatch[1]);
2097
- const node = await client.getNode(uid);
2098
- const edges = await client.getEdges({ from_uid: uid });
2099
- const inEdges = await client.getEdges({ to_uid: uid });
2100
- return {
2101
- contents: [
2102
- {
2103
- uri,
2104
- mimeType: "application/json",
2105
- text: JSON.stringify(
2106
- { node, outgoing_edges: edges, incoming_edges: inEdges },
2107
- null,
2108
- 2
2109
- )
2110
- }
2111
- ]
2112
- };
2113
- }
2114
- const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
2115
- if (searchMatch) {
2116
- const query = decodeURIComponent(searchMatch[1]);
2117
- const results = await client.search(query, { limit: 20 });
2118
- return {
2119
- contents: [
2120
- {
2121
- uri,
2122
- mimeType: "application/json",
2123
- text: JSON.stringify(results, null, 2)
2124
- }
2125
- ]
2126
- };
2127
- }
2128
- const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
2129
- if (layerMatch) {
2130
- const layer = decodeURIComponent(layerMatch[1]);
2131
- const nodes = await client.getNodes({ layer, limit: 50 });
2132
- return {
2133
- contents: [
2134
- {
2135
- uri,
2136
- mimeType: "application/json",
2137
- text: JSON.stringify(nodes, null, 2)
2138
- }
2139
- ]
2140
- };
2141
- }
2142
- throw new Error(`Unknown resource: ${uri}`);
4136
+ return readGovernedResource(client, request.params.uri, GOVERNANCE_CONFIG);
2143
4137
  });
2144
4138
  async function main() {
2145
4139
  const transport = new import_stdio.StdioServerTransport();