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/cli.js CHANGED
@@ -26,13 +26,1422 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
 
29
+ // src/codegraph.ts
30
+ function normalizePath(path6) {
31
+ return path6.replaceAll("\\", "/").replace(/^\.\//, "");
32
+ }
33
+ function normalizeSignature(signature) {
34
+ return signature.replace(/\s+/g, " ").trim();
35
+ }
36
+ function languageFromPath(path6) {
37
+ const extension = (0, import_node_path2.extname)(path6).toLowerCase();
38
+ return {
39
+ ".c": "c",
40
+ ".cc": "cpp",
41
+ ".cpp": "cpp",
42
+ ".go": "go",
43
+ ".java": "java",
44
+ ".js": "javascript",
45
+ ".jsx": "javascript",
46
+ ".kt": "kotlin",
47
+ ".py": "python",
48
+ ".rb": "ruby",
49
+ ".rs": "rust",
50
+ ".swift": "swift",
51
+ ".ts": "typescript",
52
+ ".tsx": "typescript"
53
+ }[extension] ?? "unknown";
54
+ }
55
+ function asNode(hit) {
56
+ return hit.node ?? hit;
57
+ }
58
+ function toCandidate(node) {
59
+ if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
60
+ return {
61
+ path: normalizePath(node.filePath),
62
+ language: node.language ?? languageFromPath(node.filePath),
63
+ kind: node.kind,
64
+ qualifiedName: node.qualifiedName ?? node.name ?? "",
65
+ signature: node.signature ?? null,
66
+ startLine: node.startLine ?? 1,
67
+ endLine: node.endLine ?? node.startLine ?? 1
68
+ };
69
+ }
70
+ function normalizeRemote(remote) {
71
+ const trimmed = remote.trim();
72
+ if (!trimmed) return null;
73
+ const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
74
+ if (scp && !trimmed.includes("://")) {
75
+ return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
76
+ }
77
+ try {
78
+ const url = new URL(trimmed);
79
+ const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
80
+ return `${url.host.toLowerCase()}${pathname}`;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function pathContains(root, candidate) {
86
+ const child = (0, import_node_path2.relative)(root, candidate);
87
+ return child === "" || child !== ".." && !child.startsWith(`..${import_node_path2.sep}`) && !(0, import_node_path2.isAbsolute)(child);
88
+ }
89
+ function nearestExistingDirectory(candidate) {
90
+ let current = (0, import_node_path2.resolve)(candidate);
91
+ for (; ; ) {
92
+ if ((0, import_node_fs2.existsSync)(current)) {
93
+ try {
94
+ return (0, import_node_fs2.statSync)(current).isDirectory() ? current : (0, import_node_path2.dirname)(current);
95
+ } catch {
96
+ }
97
+ }
98
+ const parent = (0, import_node_path2.dirname)(current);
99
+ if (parent === current) return current;
100
+ current = parent;
101
+ }
102
+ }
103
+ function findWorkspaceFile(start, explicit) {
104
+ if (explicit) return (0, import_node_path2.resolve)(start, explicit);
105
+ let current = (0, import_node_path2.resolve)(start);
106
+ for (; ; ) {
107
+ const candidate = (0, import_node_path2.join)(current, ".mindgraph", "workspace.json");
108
+ if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
109
+ const parent = (0, import_node_path2.dirname)(current);
110
+ if (parent === current) return void 0;
111
+ current = parent;
112
+ }
113
+ }
114
+ async function runProcess(executable, args, timeoutMs, env, cwd) {
115
+ return await new Promise((resolvePromise, rejectPromise) => {
116
+ const child = (0, import_node_child_process.spawn)(executable, args, {
117
+ cwd,
118
+ env,
119
+ stdio: ["ignore", "pipe", "pipe"],
120
+ windowsHide: true
121
+ });
122
+ const stdout = [];
123
+ const stderr = [];
124
+ let outputBytes = 0;
125
+ let timedOut = false;
126
+ const timer = setTimeout(() => {
127
+ timedOut = true;
128
+ child.kill("SIGTERM");
129
+ setTimeout(() => child.kill("SIGKILL"), 250).unref();
130
+ }, timeoutMs);
131
+ timer.unref();
132
+ const collect = (target) => (chunk) => {
133
+ outputBytes += chunk.length;
134
+ if (outputBytes > MAX_OUTPUT_BYTES) {
135
+ child.kill("SIGTERM");
136
+ return;
137
+ }
138
+ target.push(chunk);
139
+ };
140
+ child.stdout.on("data", collect(stdout));
141
+ child.stderr.on("data", collect(stderr));
142
+ child.on("error", (cause) => {
143
+ clearTimeout(timer);
144
+ const error = new Error(cause.message);
145
+ error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
146
+ rejectPromise(error);
147
+ });
148
+ child.on("close", (code) => {
149
+ clearTimeout(timer);
150
+ if (timedOut) {
151
+ const error = new Error(`command timed out after ${timeoutMs}ms`);
152
+ error.kind = "timeout";
153
+ rejectPromise(error);
154
+ return;
155
+ }
156
+ if (outputBytes > MAX_OUTPUT_BYTES) {
157
+ const error = new Error("codegraph output exceeded 5 MiB");
158
+ error.kind = "command_failed";
159
+ rejectPromise(error);
160
+ return;
161
+ }
162
+ if (code !== 0) {
163
+ const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
164
+ error.kind = "command_failed";
165
+ error.exitCode = code;
166
+ rejectPromise(error);
167
+ return;
168
+ }
169
+ resolvePromise(Buffer.concat(stdout).toString("utf8"));
170
+ });
171
+ });
172
+ }
173
+ function repositoryIdentityKey(repository) {
174
+ return { v: 1, kind: "repository", repo_id: repository.repoId };
175
+ }
176
+ function codeRefIdentityKey(ref) {
177
+ if (ref.kind === "file") {
178
+ return {
179
+ v: 1,
180
+ kind: "file",
181
+ repo_id: ref.repoId,
182
+ path: ref.path
183
+ };
184
+ }
185
+ return {
186
+ v: 1,
187
+ kind: "symbol",
188
+ repo_id: ref.repoId,
189
+ language: ref.language,
190
+ symbol_kind: ref.kind,
191
+ qualified_name: ref.qualifiedName,
192
+ disambiguator: {
193
+ signature: ref.signature ?? "",
194
+ path: ref.path,
195
+ start_line: ref.startLine,
196
+ end_line: ref.endLine
197
+ }
198
+ };
199
+ }
200
+ var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
201
+ var init_codegraph = __esm({
202
+ "src/codegraph.ts"() {
203
+ "use strict";
204
+ import_node_child_process = require("child_process");
205
+ import_node_fs2 = require("fs");
206
+ import_promises = require("fs/promises");
207
+ import_node_os2 = require("os");
208
+ import_node_path2 = require("path");
209
+ import_node_crypto = require("crypto");
210
+ DEFAULT_TIMEOUT_MS = 5e3;
211
+ DEFAULT_STATUS_TTL_MS = 6e4;
212
+ MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
213
+ CodegraphAdapter = class {
214
+ timeoutMs;
215
+ statusTtlMs;
216
+ env;
217
+ cwd;
218
+ workspaceFile;
219
+ now;
220
+ identityCache = /* @__PURE__ */ new Map();
221
+ statusCache = /* @__PURE__ */ new Map();
222
+ constructor(options = {}) {
223
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
224
+ this.statusTtlMs = options.statusTtlMs ?? DEFAULT_STATUS_TTL_MS;
225
+ this.env = options.env ?? process.env;
226
+ this.cwd = options.cwd ?? process.cwd();
227
+ this.workspaceFile = findWorkspaceFile(
228
+ this.cwd,
229
+ options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
230
+ );
231
+ this.now = options.now ?? Date.now;
232
+ }
233
+ async resolveRepository(repo, fallbackSpaceUid) {
234
+ const configured = await this.resolveRepositories(fallbackSpaceUid);
235
+ const selected = repo && configured.find((candidate) => candidate.repoId === repo) || repo && configured.find(
236
+ (candidate) => candidate.repoRoot === repo || (0, import_node_path2.basename)(candidate.repoRoot) === repo
237
+ ) || (!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);
238
+ if (selected) {
239
+ return !repo && this.env.MINDGRAPH_REPO_ID ? this.resolveRepositoryIdentity(
240
+ selected.repoRoot,
241
+ this.env.MINDGRAPH_REPO_ID,
242
+ selected.spaceUid
243
+ ) : selected;
244
+ }
245
+ const requestedRoot = repo || this.env.MINDGRAPH_REPO_ROOT || this.cwd;
246
+ const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
247
+ () => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
248
+ );
249
+ const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
250
+ return this.resolveRepositoryIdentity(
251
+ repoRoot,
252
+ this.env.MINDGRAPH_REPO_ID,
253
+ this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
254
+ );
255
+ }
256
+ async resolveRepositories(fallbackSpaceUid) {
257
+ return Promise.all(
258
+ this.configuredRepositories().map(
259
+ (repository) => this.resolveRepositoryIdentity(
260
+ repository.repoRoot,
261
+ repository.repoId,
262
+ repository.spaceUid || this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
263
+ )
264
+ )
265
+ );
266
+ }
267
+ async resolveRepositoryIdentity(root, explicitRepoId, spaceUid) {
268
+ const repoRoot = await (0, import_promises.realpath)(root).catch(() => (0, import_node_path2.resolve)(root));
269
+ const cacheKey = `${repoRoot}\0${explicitRepoId ?? ""}\0${spaceUid ?? ""}`;
270
+ const cached = this.identityCache.get(cacheKey);
271
+ if (cached) return cached;
272
+ const pending = this.loadRepositoryIdentity(repoRoot, explicitRepoId, spaceUid);
273
+ this.identityCache.set(cacheKey, pending);
274
+ return pending;
275
+ }
276
+ async loadRepositoryIdentity(repoRoot, explicitRepoId, spaceUid) {
277
+ const remote = await runProcess(
278
+ "git",
279
+ ["-C", repoRoot, "config", "--get", "remote.origin.url"],
280
+ Math.min(this.timeoutMs, 2e3),
281
+ this.env
282
+ ).catch(() => "");
283
+ const repoId = explicitRepoId || normalizeRemote(remote) || await this.persistedRepositoryId(repoRoot);
284
+ return {
285
+ repoId,
286
+ repoRoot,
287
+ spaceUid
288
+ };
289
+ }
290
+ async persistedRepositoryId(repoRoot) {
291
+ const rawCommonDir = await runProcess(
292
+ "git",
293
+ ["-C", repoRoot, "rev-parse", "--git-common-dir"],
294
+ Math.min(this.timeoutMs, 2e3),
295
+ this.env
296
+ ).catch(() => "");
297
+ if (!rawCommonDir.trim()) {
298
+ return `local:${(0, import_node_path2.basename)(repoRoot)}`;
299
+ }
300
+ const commonDir = (0, import_node_path2.resolve)(repoRoot, rawCommonDir.trim());
301
+ const identityDir = (0, import_node_path2.join)(commonDir, "mindgraph");
302
+ const identityFile = (0, import_node_path2.join)(identityDir, "repository-id");
303
+ try {
304
+ const existing = (0, import_node_fs2.readFileSync)(identityFile, "utf8").trim();
305
+ if (existing) return existing;
306
+ } catch {
307
+ }
308
+ (0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
309
+ const generated = (0, import_node_crypto.randomUUID)();
310
+ try {
311
+ (0, import_node_fs2.writeFileSync)(identityFile, `${generated}
312
+ `, {
313
+ encoding: "utf8",
314
+ flag: "wx",
315
+ mode: 384
316
+ });
317
+ return generated;
318
+ } catch (cause) {
319
+ if (cause.code !== "EEXIST") throw cause;
320
+ const winner = (0, import_node_fs2.readFileSync)(identityFile, "utf8").trim();
321
+ if (!winner) throw new Error(`empty repository identity file: ${identityFile}`);
322
+ return winner;
323
+ }
324
+ }
325
+ async gitRoot(candidate) {
326
+ const start = nearestExistingDirectory(candidate);
327
+ const raw = await runProcess(
328
+ "git",
329
+ ["-C", start, "rev-parse", "--show-toplevel"],
330
+ Math.min(this.timeoutMs, 2e3),
331
+ this.env
332
+ ).catch(() => "");
333
+ return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
334
+ }
335
+ async availability(repository) {
336
+ const cached = this.statusCache.get(repository.repoRoot);
337
+ if (cached && cached.expiresAt > this.now()) return cached.availability;
338
+ try {
339
+ const status = await this.runJson([
340
+ "status",
341
+ repository.repoRoot,
342
+ "-j"
343
+ ]);
344
+ const caveats = [];
345
+ let availability;
346
+ if (status.worktreeMismatch != null) {
347
+ availability = {
348
+ available: false,
349
+ reason: "wrong_index",
350
+ stale: false,
351
+ caveats: ["the index belongs to a different worktree"],
352
+ lastIndexed: status.lastIndexed
353
+ };
354
+ } else if (!status.initialized || status.index?.state !== "complete") {
355
+ availability = {
356
+ available: false,
357
+ reason: "index_incomplete",
358
+ stale: false,
359
+ caveats: [
360
+ !status.initialized ? "codegraph is not initialized for this repository" : `codegraph index state is ${status.index?.state ?? "unknown"}`
361
+ ],
362
+ lastIndexed: status.lastIndexed
363
+ };
364
+ } else {
365
+ const pending = (status.pendingChanges?.added ?? 0) + (status.pendingChanges?.modified ?? 0) + (status.pendingChanges?.removed ?? 0);
366
+ if (pending > 0) caveats.push(`${pending} worktree changes are not indexed`);
367
+ if ((status.index?.pendingRefs ?? 0) > 0) {
368
+ caveats.push(`${status.index?.pendingRefs} references remain pending`);
369
+ }
370
+ if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
371
+ availability = {
372
+ available: true,
373
+ stale: caveats.length > 0,
374
+ caveats,
375
+ lastIndexed: status.lastIndexed
376
+ };
377
+ }
378
+ this.statusCache.set(repository.repoRoot, {
379
+ status,
380
+ availability,
381
+ expiresAt: this.now() + this.statusTtlMs
382
+ });
383
+ return availability;
384
+ } catch (cause) {
385
+ const failure = cause;
386
+ return {
387
+ available: false,
388
+ reason: failure.kind ?? "command_failed",
389
+ stale: false,
390
+ caveats: [failure.message || "codegraph status failed"]
391
+ };
392
+ }
393
+ }
394
+ async resolveRefs(refs, options = {}) {
395
+ const normalized = refs.map(
396
+ (ref) => typeof ref === "string" ? { symbol: ref } : ref
397
+ );
398
+ const results = await Promise.all(
399
+ normalized.map(async (ref) => {
400
+ const repository2 = await this.repositoryForRef(
401
+ ref,
402
+ options.repo,
403
+ options.fallbackSpaceUid
404
+ );
405
+ const availability2 = await this.availability(repository2);
406
+ if (!availability2.available) {
407
+ return {
408
+ ref,
409
+ repository: repository2,
410
+ availability: availability2,
411
+ candidates: [],
412
+ error: availability2.reason
413
+ };
414
+ }
415
+ return {
416
+ ...await this.resolveOne(ref, repository2),
417
+ repository: repository2,
418
+ availability: availability2
419
+ };
420
+ })
421
+ );
422
+ const repositories = [
423
+ ...new Map(
424
+ results.flatMap(
425
+ (result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
426
+ )
427
+ ).values()
428
+ ];
429
+ const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
430
+ if (repositories.length === 0) repositories.push(repository);
431
+ const availabilities = results.flatMap(
432
+ (result) => result.availability ? [result.availability] : []
433
+ );
434
+ const availability = availabilities.length === 0 ? await this.availability(repository) : {
435
+ available: availabilities.every((item) => item.available),
436
+ reason: availabilities.find((item) => !item.available)?.reason,
437
+ stale: availabilities.some((item) => item.stale),
438
+ caveats: [
439
+ ...new Set(availabilities.flatMap((item) => item.caveats))
440
+ ]
441
+ };
442
+ return { repository, repositories, availability, results };
443
+ }
444
+ async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
445
+ if (ref.repo || defaultRepo) {
446
+ return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
447
+ }
448
+ if (ref.path) {
449
+ const requested = (0, import_node_path2.isAbsolute)(ref.path) ? (0, import_node_path2.resolve)(ref.path) : (0, import_node_path2.resolve)(this.cwd, ref.path);
450
+ const absolute = await (0, import_promises.realpath)(requested).catch(() => requested);
451
+ const configured = await this.resolveRepositories(fallbackSpaceUid);
452
+ const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
453
+ if (defining) {
454
+ return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
455
+ defining.repoRoot,
456
+ this.env.MINDGRAPH_REPO_ID,
457
+ defining.spaceUid
458
+ ) : defining;
459
+ }
460
+ const gitRoot = await this.gitRoot(absolute);
461
+ if (gitRoot) {
462
+ return this.resolveRepositoryIdentity(
463
+ gitRoot,
464
+ this.env.MINDGRAPH_REPO_ID,
465
+ this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
466
+ );
467
+ }
468
+ }
469
+ return this.resolveRepository(void 0, fallbackSpaceUid);
470
+ }
471
+ async expand(resolved, limit = 20) {
472
+ const repository = {
473
+ repoId: resolved.repoId,
474
+ repoRoot: resolved.repoRoot
475
+ };
476
+ const availability = await this.availability(repository);
477
+ if (!availability.available) {
478
+ return { availability, callers: [], callees: [], truncated: false };
479
+ }
480
+ const cap = Math.min(Math.max(limit, 1), 20);
481
+ const [callers, callees] = await Promise.all([
482
+ this.related("callers", resolved, cap + 1),
483
+ this.related("callees", resolved, cap + 1)
484
+ ]);
485
+ return {
486
+ availability,
487
+ callers: callers.slice(0, cap),
488
+ callees: callees.slice(0, cap),
489
+ truncated: callers.length > cap || callees.length > cap
490
+ };
491
+ }
492
+ async affected(resolved, limit = 50) {
493
+ const repository = {
494
+ repoId: resolved.repoId,
495
+ repoRoot: resolved.repoRoot
496
+ };
497
+ const availability = await this.availability(repository);
498
+ if (!availability.available) {
499
+ return { availability, affected: [], truncated: false };
500
+ }
501
+ const cap = Math.min(Math.max(limit, 1), 50);
502
+ const raw = await this.runJson([
503
+ "impact",
504
+ "-p",
505
+ resolved.repoRoot,
506
+ "-j",
507
+ "-d",
508
+ "2",
509
+ resolved.qualifiedName
510
+ ]);
511
+ const refs = (raw.affected ?? []).map((node) => ({
512
+ path: node.filePath,
513
+ line: node.startLine,
514
+ symbol: node.qualifiedName ?? node.name,
515
+ kind: node.kind
516
+ }));
517
+ const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
518
+ return {
519
+ availability,
520
+ affected: resolvedRefs.slice(0, cap),
521
+ truncated: refs.length > cap
522
+ };
523
+ }
524
+ configuredRepositories() {
525
+ const configured = [];
526
+ if (this.workspaceFile && (0, import_node_fs2.existsSync)(this.workspaceFile)) {
527
+ try {
528
+ const parsed = JSON.parse(
529
+ (0, import_node_fs2.readFileSync)(this.workspaceFile, "utf8")
530
+ );
531
+ if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
532
+ const configDirectory = (0, import_node_path2.dirname)(this.workspaceFile);
533
+ const base = (0, import_node_path2.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path2.dirname)(configDirectory) : configDirectory;
534
+ for (const repository of parsed.repositories) {
535
+ if (!repository || typeof repository.root !== "string") continue;
536
+ configured.push({
537
+ repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
538
+ repoRoot: (0, import_node_path2.resolve)(base, repository.root),
539
+ spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
540
+ });
541
+ }
542
+ }
543
+ } catch {
544
+ }
545
+ }
546
+ const raw = this.env.MINDGRAPH_CODE_REPOS;
547
+ if (raw) {
548
+ try {
549
+ const parsed = JSON.parse(raw);
550
+ for (const [repoId, config] of Object.entries(parsed)) {
551
+ configured.push({
552
+ repoId,
553
+ repoRoot: (0, import_node_path2.resolve)(
554
+ this.cwd,
555
+ typeof config === "string" ? config : config.root
556
+ ),
557
+ spaceUid: typeof config === "string" ? void 0 : config.space_uid
558
+ });
559
+ }
560
+ } catch {
561
+ }
562
+ }
563
+ return [
564
+ ...new Map(
565
+ configured.map((repository) => [repository.repoRoot, repository])
566
+ ).values()
567
+ ];
568
+ }
569
+ async resolveOne(ref, repository) {
570
+ const search = ref.symbol || ref.path;
571
+ if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
572
+ return { ref, candidates: [], error: "invalid_ref" };
573
+ }
574
+ try {
575
+ const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
576
+ if (ref.kind) args.push("-k", ref.kind);
577
+ args.push(search);
578
+ const hits = await this.runJson(args);
579
+ let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
580
+ if (ref.path) {
581
+ const absolutePath = (0, import_node_path2.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
582
+ const wanted = normalizePath(
583
+ absolutePath ? (0, import_node_path2.relative)(repository.repoRoot, absolutePath) : ref.path
584
+ );
585
+ candidates = candidates.filter((candidate) => candidate.path === wanted);
586
+ }
587
+ if (ref.language) {
588
+ candidates = candidates.filter(
589
+ (candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
590
+ );
591
+ }
592
+ if (ref.kind) {
593
+ candidates = candidates.filter(
594
+ (candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
595
+ );
596
+ }
597
+ if (ref.symbol) {
598
+ const exact = candidates.filter((candidate) => {
599
+ const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
600
+ return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
601
+ });
602
+ if (exact.length > 0) candidates = exact;
603
+ }
604
+ if (ref.signature) {
605
+ const signature = normalizeSignature(ref.signature);
606
+ candidates = candidates.filter(
607
+ (candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
608
+ );
609
+ }
610
+ if (ref.line !== void 0) {
611
+ candidates = candidates.filter(
612
+ (candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
613
+ );
614
+ if (candidates.length > 1) {
615
+ const narrowest = Math.min(
616
+ ...candidates.map((candidate) => candidate.endLine - candidate.startLine)
617
+ );
618
+ const narrowed = candidates.filter(
619
+ (candidate) => candidate.endLine - candidate.startLine === narrowest
620
+ );
621
+ if (narrowed.length === 1) candidates = narrowed;
622
+ }
623
+ }
624
+ if (candidates.length !== 1) {
625
+ return {
626
+ ref,
627
+ candidates,
628
+ error: candidates.length === 0 ? "not_found" : "ambiguous"
629
+ };
630
+ }
631
+ return {
632
+ ref,
633
+ candidates,
634
+ resolved: {
635
+ repoId: repository.repoId,
636
+ repoRoot: repository.repoRoot,
637
+ ...candidates[0]
638
+ }
639
+ };
640
+ } catch (cause) {
641
+ const failure = cause;
642
+ return {
643
+ ref,
644
+ candidates: [],
645
+ error: failure.kind ?? "command_failed"
646
+ };
647
+ }
648
+ }
649
+ async related(action, resolved, limit) {
650
+ const raw = await this.runJson([
651
+ action,
652
+ "-p",
653
+ resolved.repoRoot,
654
+ "-j",
655
+ "-l",
656
+ String(limit),
657
+ resolved.qualifiedName
658
+ ]);
659
+ const refs = (raw[action] ?? []).map((node) => ({
660
+ path: node.filePath,
661
+ line: node.startLine,
662
+ symbol: node.qualifiedName ?? node.name,
663
+ kind: node.kind
664
+ }));
665
+ return this.resolveRelatedRefs(refs, {
666
+ repoId: resolved.repoId,
667
+ repoRoot: resolved.repoRoot
668
+ });
669
+ }
670
+ async resolveRelatedRefs(refs, repository) {
671
+ const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
672
+ const seen = /* @__PURE__ */ new Set();
673
+ return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
674
+ const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
675
+ if (seen.has(key)) return false;
676
+ seen.add(key);
677
+ return true;
678
+ });
679
+ }
680
+ async runJson(args) {
681
+ const candidates = [
682
+ this.env.MINDGRAPH_CODEGRAPH_BIN,
683
+ "codegraph",
684
+ (0, import_node_path2.resolve)((0, import_node_os2.homedir)(), ".local", "bin", "codegraph")
685
+ ].filter((candidate, index, all) => {
686
+ return Boolean(candidate) && all.indexOf(candidate) === index;
687
+ });
688
+ let lastFailure;
689
+ for (const executable of candidates) {
690
+ try {
691
+ const stdout = await runProcess(
692
+ executable,
693
+ args,
694
+ this.timeoutMs,
695
+ this.env,
696
+ this.cwd
697
+ );
698
+ return JSON.parse(stdout);
699
+ } catch (cause) {
700
+ const failure = cause;
701
+ lastFailure = failure;
702
+ if (failure.kind !== "absent") throw failure;
703
+ }
704
+ }
705
+ throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
706
+ kind: "absent"
707
+ });
708
+ }
709
+ };
710
+ }
711
+ });
712
+
713
+ // src/code-tool.ts
714
+ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
715
+ const codeClient = client2;
716
+ const action = args.action;
717
+ const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
718
+ const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
719
+ const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
720
+ const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : `space:agent:${agentId}`;
721
+ const options = {
722
+ repo: typeof args.repo === "string" ? args.repo : void 0,
723
+ fallbackSpaceUid
724
+ };
725
+ const limit = typeof args.limit === "number" ? args.limit : void 0;
726
+ try {
727
+ switch (action) {
728
+ case "anchor": {
729
+ if (refs.length === 0) return err("missing_code_refs", "code_refs is required for anchor");
730
+ const resolution = await adapter.resolveRefs(refs, options);
731
+ const resolved = resolution.results.flatMap(
732
+ (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
733
+ );
734
+ const unresolved = resolution.results.filter((result) => !result.resolved);
735
+ if (resolved.length === 0) {
736
+ return ok({ ...resolution, anchors: [], wrote: false });
737
+ }
738
+ const missingSpace = resolved.find(
739
+ ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
740
+ );
741
+ if (missingSpace) {
742
+ return err(
743
+ "missing_repository_space",
744
+ `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
745
+ );
746
+ }
747
+ const repositoryAnchors = /* @__PURE__ */ new Map();
748
+ for (const { repository } of resolved) {
749
+ if (repositoryAnchors.has(repository.repoRoot)) continue;
750
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
751
+ const anchored = await anchorRepository(
752
+ codeClient,
753
+ repository,
754
+ spaceUid,
755
+ agentId
756
+ );
757
+ repositoryAnchors.set(repository.repoRoot, {
758
+ ...anchored,
759
+ repository
760
+ });
761
+ }
762
+ const anchors = [];
763
+ for (const { ref, repository } of resolved) {
764
+ const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
765
+ if (!repositoryAnchor.uid) continue;
766
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
767
+ const anchor = await anchorCodeRef(
768
+ codeClient,
769
+ ref,
770
+ repositoryAnchor.uid,
771
+ spaceUid,
772
+ agentId
773
+ );
774
+ anchors.push(anchor);
775
+ }
776
+ return ok({
777
+ availability: resolution.availability,
778
+ repository: repositoryAnchors.values().next().value,
779
+ repositories: [...repositoryAnchors.values()],
780
+ anchors,
781
+ unresolved,
782
+ wrote: anchors.some((anchor) => anchor.created)
783
+ });
784
+ }
785
+ case "recall": {
786
+ const materialized = new Set(anchorUids);
787
+ let resolution;
788
+ if (refs.length > 0) {
789
+ resolution = await adapter.resolveRefs(refs, options);
790
+ for (const result of resolution.results) {
791
+ if (!result.resolved) continue;
792
+ const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
793
+ if (uid) materialized.add(uid);
794
+ }
795
+ }
796
+ if (materialized.size === 0) {
797
+ return ok({
798
+ availability: resolution?.availability,
799
+ anchors: [],
800
+ context: [],
801
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
802
+ });
803
+ }
804
+ const context = await Promise.all(
805
+ [...materialized].map(async (uid) => ({
806
+ anchor_uid: uid,
807
+ graph: await codeClient.traverse({
808
+ action: "neighborhood",
809
+ start_uid: uid,
810
+ depth: 2,
811
+ max_nodes: Math.min(limit ?? 20, 20)
812
+ })
813
+ }))
814
+ );
815
+ return ok({
816
+ availability: resolution?.availability,
817
+ anchors: [...materialized],
818
+ context,
819
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
820
+ });
821
+ }
822
+ case "expand": {
823
+ const target = await resolveSingleTarget(
824
+ codeClient,
825
+ adapter,
826
+ refs,
827
+ anchorUids,
828
+ options
829
+ );
830
+ if ("error" in target) return target.error;
831
+ const expanded = await adapter.expand(target.ref, limit);
832
+ const joined = await joinMaterialized(
833
+ codeClient,
834
+ [...expanded.callers, ...expanded.callees],
835
+ agentId,
836
+ Math.min(limit ?? 20, 20)
837
+ );
838
+ return ok({ anchor: target.ref, ...expanded, materialized: joined });
839
+ }
840
+ case "affected": {
841
+ const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
842
+ let graphContext;
843
+ const graphRefs = [];
844
+ if (targetUid) {
845
+ graphContext = await codeClient.traverse({
846
+ action: "neighborhood",
847
+ start_uid: targetUid,
848
+ depth: 2,
849
+ max_nodes: 50
850
+ });
851
+ graphRefs.push(
852
+ ...await codeRefsFromGraphContext(codeClient, adapter, graphContext)
853
+ );
854
+ }
855
+ if (refs.length > 0 || anchorUids.length > 0) {
856
+ const target = await resolveSingleTarget(
857
+ codeClient,
858
+ adapter,
859
+ refs,
860
+ anchorUids,
861
+ options
862
+ );
863
+ if ("error" in target) return target.error;
864
+ graphRefs.unshift(target.ref);
865
+ }
866
+ const unique = uniqueRefs(graphRefs);
867
+ if (unique.length === 0) {
868
+ return ok({
869
+ target_uid: targetUid,
870
+ graph_context: graphContext,
871
+ affected: [],
872
+ degradation: "no materialized code anchors were attached"
873
+ });
874
+ }
875
+ const impacts = await Promise.all(
876
+ unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
877
+ );
878
+ const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
879
+ const joined = await joinMaterialized(
880
+ codeClient,
881
+ structural,
882
+ agentId,
883
+ Math.min(limit ?? 50, 50)
884
+ );
885
+ return ok({
886
+ target_uid: targetUid,
887
+ graph_context: graphContext,
888
+ impacts,
889
+ materialized: joined
890
+ });
891
+ }
892
+ default:
893
+ return err("unknown_action", `unknown mindgraph_code action: ${action}`);
894
+ }
895
+ } catch (cause) {
896
+ return err(
897
+ "code_tool_failed",
898
+ cause instanceof Error ? cause.message : String(cause)
899
+ );
900
+ }
901
+ }
902
+ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
903
+ if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
904
+ return result;
905
+ }
906
+ const original = JSON.parse(result.content[0]?.text ?? "null");
907
+ const targetUids = primaryUids(original);
908
+ if (targetUids.length === 0) {
909
+ return ok({
910
+ result: original,
911
+ code_anchors: {
912
+ wrote: false,
913
+ caveat: "the write response exposed no primary UID to attach"
914
+ }
915
+ });
916
+ }
917
+ const anchorResult = await handleCodeTool(
918
+ client2,
919
+ {
920
+ action: "anchor",
921
+ code_refs: args.code_refs,
922
+ repo: args.repo,
923
+ repo_space_uid: args.repo_space_uid,
924
+ agent_id: args.agent_id
925
+ },
926
+ adapter
927
+ );
928
+ const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
929
+ if (!anchorResult.isError) {
930
+ const codeClient = client2;
931
+ const anchorUids = (anchors.anchors ?? []).flatMap(
932
+ (anchor) => anchor.uid ? [anchor.uid] : []
933
+ );
934
+ for (const targetUid of targetUids) {
935
+ for (const anchorUid of anchorUids) {
936
+ await codeClient.entity({
937
+ action: "relate",
938
+ source_uid: targetUid,
939
+ target_uid: anchorUid,
940
+ edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
941
+ agent_id: args.agent_id
942
+ });
943
+ }
944
+ }
945
+ }
946
+ return ok({ result: original, code_anchors: anchors });
947
+ }
948
+ async function anchorRepository(client2, repository, spaceUid, agentId) {
949
+ return client2.entity({
950
+ action: "create",
951
+ label: repository.repoId,
952
+ props: {
953
+ entity_type: "repository",
954
+ canonical_name: repository.repoId,
955
+ identifiers: { repo_id: repository.repoId }
956
+ },
957
+ identity: {
958
+ namespace: "external.code",
959
+ key_version: 1,
960
+ key: repositoryIdentityKey(repository)
961
+ },
962
+ identity_space_uid: spaceUid,
963
+ agent_id: agentId
964
+ });
965
+ }
966
+ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
967
+ const response = await client2.entity({
968
+ action: "create",
969
+ label: ref.qualifiedName,
970
+ props: {
971
+ entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
972
+ canonical_name: ref.qualifiedName,
973
+ identifiers: {
974
+ repo_id: ref.repoId,
975
+ path: ref.path,
976
+ qualified_name: ref.qualifiedName
977
+ },
978
+ attributes: { code_ref: storedCodeRef(ref) }
979
+ },
980
+ identity: {
981
+ namespace: "external.code",
982
+ key_version: 1,
983
+ key: codeRefIdentityKey(ref)
984
+ },
985
+ identity_space_uid: spaceUid,
986
+ agent_id: agentId
987
+ });
988
+ if (response.uid) {
989
+ await client2.entity({
990
+ action: "relate",
991
+ source_uid: response.uid,
992
+ target_uid: repositoryUid,
993
+ edge_type: "PART_OF",
994
+ agent_id: agentId
995
+ });
996
+ }
997
+ return { ...response, ref };
998
+ }
999
+ async function resolveMaterializedUid(client2, ref, agentId) {
1000
+ const response = await client2.entity({
1001
+ action: "resolve_identity",
1002
+ identity: {
1003
+ namespace: "external.code",
1004
+ key_version: 1,
1005
+ key: codeRefIdentityKey(ref)
1006
+ },
1007
+ agent_id: agentId
1008
+ });
1009
+ return response.status === "existing" ? response.uid : void 0;
1010
+ }
1011
+ async function codeRefFromAnchor(client2, adapter, uid) {
1012
+ const node = await client2.getNode(uid);
1013
+ const attributes = node.props?.attributes;
1014
+ if (!attributes || typeof attributes !== "object") return void 0;
1015
+ const codeRef = attributes.code_ref;
1016
+ if (!isStoredCodeRef(codeRef)) return void 0;
1017
+ const repository = await adapter.resolveRepository(codeRef.repoId);
1018
+ return { ...codeRef, repoRoot: repository.repoRoot };
1019
+ }
1020
+ async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
1021
+ if (refs.length > 1 || anchorUids.length > 1) {
1022
+ return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
1023
+ }
1024
+ if (anchorUids.length === 1) {
1025
+ const ref = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
1026
+ return ref ? { ref } : { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
1027
+ }
1028
+ if (refs.length !== 1) {
1029
+ return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
1030
+ }
1031
+ const resolution = await adapter.resolveRefs(refs, options);
1032
+ const result = resolution.results[0];
1033
+ if (!result.resolved) {
1034
+ return {
1035
+ error: ok({
1036
+ availability: resolution.availability,
1037
+ result,
1038
+ wrote: false
1039
+ })
1040
+ };
1041
+ }
1042
+ return { ref: result.resolved };
1043
+ }
1044
+ async function joinMaterialized(client2, refs, agentId, limit) {
1045
+ const joined = [];
1046
+ for (const ref of uniqueRefs(refs).slice(0, limit)) {
1047
+ const uid = await resolveMaterializedUid(client2, ref, agentId);
1048
+ if (!uid) continue;
1049
+ joined.push({
1050
+ ref,
1051
+ anchor_uid: uid,
1052
+ graph: await client2.traverse({
1053
+ action: "neighborhood",
1054
+ start_uid: uid,
1055
+ depth: 2,
1056
+ max_nodes: 20
1057
+ })
1058
+ });
1059
+ }
1060
+ return joined;
1061
+ }
1062
+ async function codeRefsFromGraphContext(client2, adapter, context) {
1063
+ const uids = /* @__PURE__ */ new Set();
1064
+ collectUids(context, uids);
1065
+ if (uids.size === 0) return [];
1066
+ const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
1067
+ const stored = nodes.flatMap((node) => {
1068
+ if (!node) return [];
1069
+ const attributes = node.props?.attributes;
1070
+ if (!attributes || typeof attributes !== "object") return [];
1071
+ const codeRef = attributes.code_ref;
1072
+ return isStoredCodeRef(codeRef) ? [codeRef] : [];
1073
+ });
1074
+ return Promise.all(
1075
+ stored.map(async (ref) => {
1076
+ const repository = await adapter.resolveRepository(ref.repoId);
1077
+ return { ...ref, repoRoot: repository.repoRoot };
1078
+ })
1079
+ );
1080
+ }
1081
+ function collectUids(value, uids) {
1082
+ if (Array.isArray(value)) {
1083
+ value.forEach((item) => collectUids(item, uids));
1084
+ return;
1085
+ }
1086
+ if (!value || typeof value !== "object") return;
1087
+ for (const [key, child] of Object.entries(value)) {
1088
+ if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
1089
+ uids.add(child);
1090
+ } else {
1091
+ collectUids(child, uids);
1092
+ }
1093
+ }
1094
+ }
1095
+ function isStoredCodeRef(value) {
1096
+ if (!value || typeof value !== "object") return false;
1097
+ const ref = value;
1098
+ 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";
1099
+ }
1100
+ function storedCodeRef(ref) {
1101
+ const { repoRoot: _localOnly, ...stored } = ref;
1102
+ return stored;
1103
+ }
1104
+ function uniqueRefs(refs) {
1105
+ const seen = /* @__PURE__ */ new Set();
1106
+ return refs.filter((ref) => {
1107
+ const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
1108
+ if (seen.has(key)) return false;
1109
+ seen.add(key);
1110
+ return true;
1111
+ });
1112
+ }
1113
+ function primaryUids(value) {
1114
+ if (!value || typeof value !== "object") return [];
1115
+ const object2 = value;
1116
+ const keys = [
1117
+ "uid",
1118
+ "claim_uid",
1119
+ "goal_uid",
1120
+ "project_uid",
1121
+ "milestone_uid",
1122
+ "decision_uid",
1123
+ "task_uid",
1124
+ "plan_uid",
1125
+ "step_uid",
1126
+ "execution_uid",
1127
+ "lesson_uid"
1128
+ ];
1129
+ return [
1130
+ ...new Set(
1131
+ keys.flatMap((key) => typeof object2[key] === "string" ? [object2[key]] : [])
1132
+ )
1133
+ ];
1134
+ }
1135
+ var ok, err, CODE_TOOL;
1136
+ var init_code_tool = __esm({
1137
+ "src/code-tool.ts"() {
1138
+ "use strict";
1139
+ init_codegraph();
1140
+ ok = (data) => ({
1141
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1142
+ });
1143
+ err = (code, message, details) => ({
1144
+ content: [
1145
+ {
1146
+ type: "text",
1147
+ text: JSON.stringify({ error: message, code, details }, null, 2)
1148
+ }
1149
+ ],
1150
+ isError: true
1151
+ });
1152
+ CODE_TOOL = {
1153
+ name: "mindgraph_code",
1154
+ 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.",
1155
+ inputSchema: {
1156
+ type: "object",
1157
+ properties: {
1158
+ action: {
1159
+ type: "string",
1160
+ enum: ["anchor", "recall", "expand", "affected"]
1161
+ },
1162
+ code_refs: {
1163
+ type: "array",
1164
+ maxItems: 50,
1165
+ items: {
1166
+ oneOf: [
1167
+ { type: "string" },
1168
+ {
1169
+ type: "object",
1170
+ properties: {
1171
+ repo: { type: "string" },
1172
+ path: { type: "string" },
1173
+ line: { type: "integer", minimum: 1 },
1174
+ symbol: { type: "string" },
1175
+ kind: { type: "string" },
1176
+ language: { type: "string" },
1177
+ signature: { type: "string" }
1178
+ },
1179
+ additionalProperties: false
1180
+ }
1181
+ ]
1182
+ }
1183
+ },
1184
+ anchor_uids: {
1185
+ type: "array",
1186
+ maxItems: 50,
1187
+ items: { type: "string" },
1188
+ description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
1189
+ },
1190
+ repo: {
1191
+ type: "string",
1192
+ description: "Configured repository id or repository root"
1193
+ },
1194
+ repo_space_uid: {
1195
+ type: "string",
1196
+ description: "Writable shared Project/repository Space for new anchors"
1197
+ },
1198
+ task_uid: { type: "string" },
1199
+ execution_uid: { type: "string" },
1200
+ limit: { type: "integer", minimum: 1, maximum: 50 },
1201
+ agent_id: { type: "string" }
1202
+ },
1203
+ required: ["action"]
1204
+ },
1205
+ annotations: {
1206
+ readOnlyHint: false,
1207
+ destructiveHint: false,
1208
+ idempotentHint: true,
1209
+ openWorldHint: true
1210
+ }
1211
+ };
1212
+ }
1213
+ });
1214
+
1215
+ // src/sync-tool.ts
1216
+ function safePath(root, candidate) {
1217
+ const absolute = import_node_path5.default.resolve(root, candidate);
1218
+ const relative2 = import_node_path5.default.relative(root, absolute);
1219
+ if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
1220
+ throw new Error(`path escapes repository root: ${candidate}`);
1221
+ }
1222
+ return absolute;
1223
+ }
1224
+ function markdownFiles(target) {
1225
+ if (!import_node_fs5.default.existsSync(target)) return [];
1226
+ const stat = import_node_fs5.default.statSync(target);
1227
+ if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
1228
+ if (!stat.isDirectory()) return [];
1229
+ return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name)));
1230
+ }
1231
+ function fileDescriptor(root, absolute) {
1232
+ const content = import_node_fs5.default.readFileSync(absolute, "utf8");
1233
+ return {
1234
+ logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
1235
+ content_hash: import_node_crypto4.default.createHash("sha256").update(content).digest("hex"),
1236
+ bytes: Buffer.byteLength(content),
1237
+ content
1238
+ };
1239
+ }
1240
+ async function handleSyncTool(client2, args) {
1241
+ try {
1242
+ const action = String(args.action || "");
1243
+ const context = args.invocation_context || {};
1244
+ const adapter = new CodegraphAdapter({
1245
+ cwd: context.cwd || process.cwd()
1246
+ });
1247
+ const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
1248
+ const repository = await adapter.resolveRepository(requestedRepo);
1249
+ const root = repository.repoRoot;
1250
+ const repoId = repository.repoId;
1251
+ const provider = String(args.provider || "claude_project");
1252
+ const remote = client2;
1253
+ if (action === "scan") {
1254
+ const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
1255
+ const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
1256
+ const scans = [];
1257
+ for (const selected of repositories) {
1258
+ scans.push(
1259
+ await scanRepository(
1260
+ remote,
1261
+ selected,
1262
+ requested,
1263
+ provider,
1264
+ args.agent_id
1265
+ )
1266
+ );
1267
+ }
1268
+ if (args.workspace === true) {
1269
+ return ok2({
1270
+ provider,
1271
+ workspace: true,
1272
+ repositories: scans,
1273
+ one_way: "files_to_graph"
1274
+ });
1275
+ }
1276
+ return ok2(scans[0]);
1277
+ }
1278
+ const request = {
1279
+ ...args,
1280
+ provider,
1281
+ repo_id: repoId
1282
+ };
1283
+ delete request.invocation_context;
1284
+ delete request.paths;
1285
+ delete request.repo;
1286
+ delete request.workspace;
1287
+ if (action === "begin" && !request.content) {
1288
+ if (!request.logical_path) {
1289
+ return err2("missing_field", "logical_path is required for begin");
1290
+ }
1291
+ const file = fileDescriptor(
1292
+ root,
1293
+ safePath(root, String(request.logical_path))
1294
+ );
1295
+ request.content = file.content;
1296
+ request.content_hash ||= file.content_hash;
1297
+ }
1298
+ if (action === "status" && request.logical_path && request.file_present !== false) {
1299
+ const absolute = safePath(root, String(request.logical_path));
1300
+ request.file_present = import_node_fs5.default.existsSync(absolute);
1301
+ if (request.file_present && !request.content_hash) {
1302
+ request.content_hash = fileDescriptor(root, absolute).content_hash;
1303
+ }
1304
+ }
1305
+ return ok2(await remote.memorySync(request));
1306
+ } catch (error) {
1307
+ return err2(
1308
+ "sync_failed",
1309
+ error instanceof Error ? error.message : String(error)
1310
+ );
1311
+ }
1312
+ }
1313
+ async function scanRepository(remote, repository, requested, provider, agentId) {
1314
+ const files = [
1315
+ ...new Set(
1316
+ requested.flatMap(
1317
+ (candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
1318
+ )
1319
+ )
1320
+ ].sort();
1321
+ const results = [];
1322
+ for (const absolute of files) {
1323
+ const file = fileDescriptor(repository.repoRoot, absolute);
1324
+ const status = await remote.memorySync({
1325
+ action: "scan",
1326
+ provider,
1327
+ repo_id: repository.repoId,
1328
+ logical_path: file.logical_path,
1329
+ content_hash: file.content_hash,
1330
+ file_present: true,
1331
+ agent_id: agentId
1332
+ });
1333
+ results.push({
1334
+ logical_path: file.logical_path,
1335
+ content_hash: file.content_hash,
1336
+ bytes: file.bytes,
1337
+ status
1338
+ });
1339
+ }
1340
+ return {
1341
+ repo_id: repository.repoId,
1342
+ root: ".",
1343
+ files: results
1344
+ };
1345
+ }
1346
+ var import_node_crypto4, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
1347
+ var init_sync_tool = __esm({
1348
+ "src/sync-tool.ts"() {
1349
+ "use strict";
1350
+ import_node_crypto4 = __toESM(require("crypto"));
1351
+ import_node_fs5 = __toESM(require("fs"));
1352
+ import_node_path5 = __toESM(require("path"));
1353
+ init_codegraph();
1354
+ ok2 = (data) => ({
1355
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1356
+ });
1357
+ err2 = (code, message) => ({
1358
+ content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
1359
+ isError: true
1360
+ });
1361
+ SYNC_TOOL = {
1362
+ name: "mindgraph_sync",
1363
+ 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.",
1364
+ annotations: {
1365
+ readOnlyHint: false,
1366
+ destructiveHint: false,
1367
+ idempotentHint: true,
1368
+ openWorldHint: true
1369
+ },
1370
+ inputSchema: {
1371
+ type: "object",
1372
+ properties: {
1373
+ action: {
1374
+ type: "string",
1375
+ enum: ["scan", "begin", "record", "finalize", "status", "abandon"]
1376
+ },
1377
+ paths: { type: "array", items: { type: "string" } },
1378
+ workspace: {
1379
+ type: "boolean",
1380
+ description: "For scan only: fan out across repositories explicitly listed in .mindgraph/workspace.json"
1381
+ },
1382
+ repo: {
1383
+ type: "string",
1384
+ description: "Configured repository id, name, or root"
1385
+ },
1386
+ logical_path: { type: "string" },
1387
+ content: { type: "string" },
1388
+ content_hash: { type: "string" },
1389
+ source_uid: { type: "string" },
1390
+ execution_uid: { type: "string" },
1391
+ assertion_fingerprint: { type: "string" },
1392
+ planned_fingerprints: { type: "array", items: { type: "string" } },
1393
+ current_fingerprints: { type: "array", items: { type: "string" } },
1394
+ source_selector: { type: "object" },
1395
+ capture_type: {
1396
+ type: "string",
1397
+ enum: ["lesson", "journal", "claim", "decision", "risk"]
1398
+ },
1399
+ label: { type: "string" },
1400
+ summary: { type: "string" },
1401
+ props: { type: "object" },
1402
+ expected_execution_version: { type: "integer", minimum: 1 },
1403
+ baseline_version: { type: "integer", minimum: 1 },
1404
+ repo_space_uid: { type: "string" },
1405
+ repo_id: { type: "string" },
1406
+ provider: { type: "string", enum: ["claude_project", "explicit"] },
1407
+ file_present: { type: "boolean" },
1408
+ agent_id: { type: "string" },
1409
+ invocation_context: {
1410
+ type: "object",
1411
+ description: "Adapter-owned; hooks replace forged or absent values.",
1412
+ properties: {
1413
+ harness: { type: "string" },
1414
+ harnessSessionId: { type: "string" },
1415
+ harnessTurnId: { type: "string" },
1416
+ cwd: { type: "string" },
1417
+ repoId: { type: "string" },
1418
+ branch: { type: "string" },
1419
+ commit: { type: "string" },
1420
+ worktreeState: { type: "string" },
1421
+ model: { type: "string" },
1422
+ injectedBy: { type: "string" }
1423
+ }
1424
+ }
1425
+ },
1426
+ required: ["action"]
1427
+ }
1428
+ };
1429
+ }
1430
+ });
1431
+
29
1432
  // src/tools.ts
30
- function ok(data) {
1433
+ function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
1434
+ if (profile === "coding") return TOOLS;
1435
+ return TOOLS.filter(
1436
+ (tool) => tool.name !== "mindgraph_code" && tool.name !== "mindgraph_sync"
1437
+ );
1438
+ }
1439
+ function ok3(data) {
31
1440
  return {
32
1441
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
33
1442
  };
34
1443
  }
35
- function err(message) {
1444
+ function err3(message) {
36
1445
  return {
37
1446
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
38
1447
  isError: true
@@ -40,29 +1449,58 @@ function err(message) {
40
1449
  }
41
1450
  async function handleTool(client2, name, args) {
42
1451
  try {
1452
+ let result;
43
1453
  switch (name) {
44
1454
  case "mindgraph_capture":
45
- return await handleCapture(client2, args);
1455
+ result = await handleCapture(client2, args);
1456
+ break;
46
1457
  case "mindgraph_reason":
47
- return await handleReason(client2, args);
1458
+ result = await handleReason(client2, args);
1459
+ break;
48
1460
  case "mindgraph_commit":
49
- return await handleCommit(client2, args);
1461
+ result = await handleCommit(client2, args);
1462
+ break;
50
1463
  case "mindgraph_plan":
51
- return await handlePlan(client2, args);
1464
+ result = await handlePlan(client2, args);
1465
+ break;
52
1466
  case "mindgraph_retrieve":
53
- return await handleRetrieve(client2, args);
1467
+ result = await handleRetrieve(client2, args);
1468
+ break;
54
1469
  case "mindgraph_ingest":
55
- return await handleIngest(client2, args);
1470
+ result = await handleIngest(client2, args);
1471
+ break;
56
1472
  case "mindgraph_synthesize":
57
- return await handleSynthesize(client2, args);
1473
+ result = await handleSynthesize(client2, args);
1474
+ break;
58
1475
  case "mindgraph_ontology":
59
- return await handleOntology(client2, args);
1476
+ result = await handleOntology(client2, args);
1477
+ break;
1478
+ case "mindgraph_code":
1479
+ result = await handleCodeTool(client2, args);
1480
+ break;
1481
+ case "mindgraph_sync":
1482
+ result = await handleSyncTool(client2, args);
1483
+ break;
60
1484
  default:
61
- return err(`Unknown tool: ${name}`);
1485
+ result = err3(`Unknown tool: ${name}`);
62
1486
  }
1487
+ const action = typeof args.action === "string" ? args.action : "";
1488
+ const attach = name === "mindgraph_capture" || name === "mindgraph_commit" && action !== "get_open_decisions" || name === "mindgraph_plan" && ![
1489
+ "get_plan",
1490
+ "get_executions",
1491
+ "get_pending",
1492
+ "get_assessments",
1493
+ "resume_work",
1494
+ "heartbeat"
1495
+ ].includes(action);
1496
+ return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
63
1497
  } catch (e) {
1498
+ if (e instanceof import_mindgraph.MindGraphError && e.body !== void 0) {
1499
+ const detail = typeof e.body === "string" ? e.body : JSON.stringify(e.body);
1500
+ return err3(`${e.message} \u2014 ${detail}`);
1501
+ }
64
1502
  const message = e instanceof Error ? e.message : String(e);
65
- return err(message);
1503
+ return err3(message);
66
1504
  }
67
1505
  }
68
1506
  async function handleCapture(client2, args) {
@@ -71,6 +1509,12 @@ async function handleCapture(client2, args) {
71
1509
  label,
72
1510
  summary,
73
1511
  content,
1512
+ session_uid,
1513
+ work_uid,
1514
+ execution_uid,
1515
+ idempotency_key,
1516
+ supersedes_uid,
1517
+ summarizes_uids,
74
1518
  entity_type,
75
1519
  source_uid,
76
1520
  mood,
@@ -81,12 +1525,30 @@ async function handleCapture(client2, args) {
81
1525
  agent_id
82
1526
  } = args;
83
1527
  switch (action) {
1528
+ case "lesson":
1529
+ return ok3(
1530
+ await client2.distill({
1531
+ label,
1532
+ output_type: "lesson",
1533
+ summary,
1534
+ confidence,
1535
+ salience,
1536
+ session_uid,
1537
+ work_uid,
1538
+ execution_uid,
1539
+ idempotency_key,
1540
+ supersedes_uid,
1541
+ summarizes_uids,
1542
+ props,
1543
+ agent_id
1544
+ })
1545
+ );
84
1546
  case "journal": {
85
- if (!content) return err("content is required for action=journal");
1547
+ if (!content) return err3("content is required for action=journal");
86
1548
  const journalProps = { content };
87
1549
  if (mood) journalProps.mood = mood;
88
1550
  if (tags) journalProps.tags = tags;
89
- return ok(await client2.journal(label, journalProps, { agent_id }));
1551
+ return ok3(await client2.journal(label, journalProps, { agent_id }));
90
1552
  }
91
1553
  case "entity": {
92
1554
  const type = entity_type || "concept";
@@ -94,22 +1556,22 @@ async function handleCapture(client2, args) {
94
1556
  if (summary) entityProps.description = summary;
95
1557
  switch (type) {
96
1558
  case "person":
97
- return ok(await client2.findOrCreatePerson(label, entityProps, agent_id));
1559
+ return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
98
1560
  case "organization":
99
- return ok(await client2.findOrCreateOrganization(label, entityProps, agent_id));
1561
+ return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
100
1562
  case "nation":
101
- return ok(await client2.findOrCreateNation(label, entityProps, agent_id));
1563
+ return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
102
1564
  case "event":
103
- return ok(await client2.findOrCreateEvent(label, entityProps, agent_id));
1565
+ return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
104
1566
  case "place":
105
- return ok(await client2.findOrCreatePlace(label, entityProps, agent_id));
1567
+ return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
106
1568
  case "concept":
107
1569
  default:
108
- return ok(await client2.findOrCreateConcept(label, entityProps, agent_id));
1570
+ return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
109
1571
  }
110
1572
  }
111
1573
  case "observation":
112
- return ok(
1574
+ return ok3(
113
1575
  await client2.capture({
114
1576
  action: "observation",
115
1577
  label,
@@ -121,7 +1583,7 @@ async function handleCapture(client2, args) {
121
1583
  })
122
1584
  );
123
1585
  case "source":
124
- return ok(
1586
+ return ok3(
125
1587
  await client2.capture({
126
1588
  action: "source",
127
1589
  label,
@@ -133,7 +1595,7 @@ async function handleCapture(client2, args) {
133
1595
  })
134
1596
  );
135
1597
  case "snippet":
136
- return ok(
1598
+ return ok3(
137
1599
  await client2.capture({
138
1600
  action: "snippet",
139
1601
  label,
@@ -146,7 +1608,7 @@ async function handleCapture(client2, args) {
146
1608
  })
147
1609
  );
148
1610
  case "concept":
149
- return ok(
1611
+ return ok3(
150
1612
  await client2.structure({
151
1613
  action: "concept",
152
1614
  label,
@@ -158,7 +1620,7 @@ async function handleCapture(client2, args) {
158
1620
  })
159
1621
  );
160
1622
  default:
161
- return err(`Unknown capture action: ${action}`);
1623
+ return err3(`Unknown capture action: ${action}`);
162
1624
  }
163
1625
  }
164
1626
  async function handleReason(client2, args) {
@@ -177,8 +1639,8 @@ async function handleReason(client2, args) {
177
1639
  } = args;
178
1640
  if (action === "claim") {
179
1641
  const claimObj = claim || (label ? { label, confidence } : null);
180
- if (!claimObj) return err("claim object or label is required for action=claim");
181
- return ok(
1642
+ if (!claimObj) return err3("claim object or label is required for action=claim");
1643
+ return ok3(
182
1644
  await client2.argue({
183
1645
  claim: claimObj,
184
1646
  evidence,
@@ -188,8 +1650,8 @@ async function handleReason(client2, args) {
188
1650
  })
189
1651
  );
190
1652
  }
191
- if (!label) return err(`label is required for action=${action}`);
192
- return ok(
1653
+ if (!label) return err3(`label is required for action=${action}`);
1654
+ return ok3(
193
1655
  await client2.inquire({
194
1656
  action,
195
1657
  label,
@@ -222,8 +1684,8 @@ async function handleCommit(client2, args) {
222
1684
  case "goal":
223
1685
  case "project":
224
1686
  case "milestone":
225
- if (!label) return err(`label is required for action=${action}`);
226
- return ok(
1687
+ if (!label) return err3(`label is required for action=${action}`);
1688
+ return ok3(
227
1689
  await client2.commit({
228
1690
  action,
229
1691
  label,
@@ -236,15 +1698,15 @@ async function handleCommit(client2, args) {
236
1698
  })
237
1699
  );
238
1700
  case "open_decision":
239
- if (!label) return err("label is required for open_decision");
240
- return ok(await client2.openDecision(label, { summary, props, agent_id }));
1701
+ if (!label) return err3("label is required for open_decision");
1702
+ return ok3(await client2.openDecision(label, { summary, props, agent_id }));
241
1703
  case "add_option":
242
- if (!decision_uid) return err("decision_uid is required for add_option");
243
- if (!label) return err("label is required for add_option");
244
- return ok(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
1704
+ if (!decision_uid) return err3("decision_uid is required for add_option");
1705
+ if (!label) return err3("label is required for add_option");
1706
+ return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
245
1707
  case "add_constraint":
246
- if (!decision_uid) return err("decision_uid is required for add_constraint");
247
- return ok(
1708
+ if (!decision_uid) return err3("decision_uid is required for add_constraint");
1709
+ return ok3(
248
1710
  await client2.deliberate({
249
1711
  action: "add_constraint",
250
1712
  label,
@@ -255,9 +1717,9 @@ async function handleCommit(client2, args) {
255
1717
  })
256
1718
  );
257
1719
  case "resolve_decision":
258
- if (!decision_uid) return err("decision_uid is required for resolve_decision");
259
- if (!chosen_option_uid) return err("chosen_option_uid is required for resolve_decision");
260
- return ok(
1720
+ if (!decision_uid) return err3("decision_uid is required for resolve_decision");
1721
+ if (!chosen_option_uid) return err3("chosen_option_uid is required for resolve_decision");
1722
+ return ok3(
261
1723
  await client2.resolveDecision(decision_uid, chosen_option_uid, {
262
1724
  summary,
263
1725
  props,
@@ -269,9 +1731,9 @@ async function handleCommit(client2, args) {
269
1731
  })
270
1732
  );
271
1733
  case "get_open_decisions":
272
- return ok(await client2.getOpenDecisions());
1734
+ return ok3(await client2.getOpenDecisions());
273
1735
  default:
274
- return err(`Unknown commit action: ${action}`);
1736
+ return err3(`Unknown commit action: ${action}`);
275
1737
  }
276
1738
  }
277
1739
  async function handlePlan(client2, args) {
@@ -281,8 +1743,41 @@ async function handlePlan(client2, args) {
281
1743
  summary,
282
1744
  plan_uid,
283
1745
  task_uid,
1746
+ step_uid,
1747
+ session_uid,
1748
+ goal_uid,
284
1749
  target_uid,
1750
+ execution_uid,
1751
+ executor_uid,
1752
+ affordance_uid,
1753
+ produces_node_uid,
1754
+ filter_plan_uid,
1755
+ depends_on_uids,
1756
+ related_uids,
285
1757
  status,
1758
+ task_status,
1759
+ step_status,
1760
+ execution_status,
1761
+ expected_version,
1762
+ lease_epoch,
1763
+ lease_ttl_secs,
1764
+ idempotency_key,
1765
+ allow_takeover,
1766
+ release_lease,
1767
+ override_reason,
1768
+ scope_uids,
1769
+ limit,
1770
+ confidence,
1771
+ salience,
1772
+ input_snapshot,
1773
+ output_snapshot,
1774
+ side_effects,
1775
+ outcome,
1776
+ error,
1777
+ next_action,
1778
+ checkpoint_summary,
1779
+ test_summary,
1780
+ produces_node_uids,
286
1781
  props,
287
1782
  agent_id
288
1783
  } = args;
@@ -293,25 +1788,120 @@ async function handlePlan(client2, args) {
293
1788
  case "add_step":
294
1789
  case "update_status":
295
1790
  case "get_plan":
296
- return ok(
1791
+ case "resume_work":
1792
+ case "claim_task":
1793
+ case "heartbeat":
1794
+ case "start_iteration":
1795
+ case "checkpoint_iteration":
1796
+ case "block_task":
1797
+ case "complete_task":
1798
+ case "abandon_iteration":
1799
+ return ok3(
297
1800
  await client2.plan({
298
1801
  action,
299
1802
  label,
300
1803
  summary,
1804
+ confidence,
1805
+ salience,
1806
+ goal_uid,
301
1807
  plan_uid,
302
1808
  task_uid,
1809
+ step_uid,
1810
+ session_uid,
1811
+ target_uid,
1812
+ depends_on_uids,
1813
+ related_uids,
303
1814
  status,
1815
+ task_status,
1816
+ step_status,
1817
+ execution_status,
1818
+ execution_uid,
1819
+ expected_version,
1820
+ lease_epoch,
1821
+ lease_ttl_secs,
1822
+ idempotency_key,
1823
+ allow_takeover,
1824
+ release_lease,
1825
+ override_reason,
1826
+ scope_uids,
1827
+ limit,
1828
+ input_snapshot,
1829
+ output_snapshot,
1830
+ side_effects,
1831
+ outcome,
1832
+ error,
1833
+ next_action,
1834
+ checkpoint_summary,
1835
+ test_summary,
1836
+ produces_node_uids,
304
1837
  props,
305
1838
  agent_id
306
1839
  })
307
1840
  );
308
1841
  // Execution tracking
309
1842
  case "start_execution":
310
- return ok(await client2.execution({ action: "start", task_uid, agent_id }));
1843
+ return ok3(
1844
+ await client2.execution({
1845
+ action: "start",
1846
+ label,
1847
+ summary,
1848
+ confidence,
1849
+ salience,
1850
+ plan_uid,
1851
+ executor_uid,
1852
+ affordance_uid,
1853
+ related_uids,
1854
+ input_snapshot,
1855
+ props,
1856
+ agent_id
1857
+ })
1858
+ );
311
1859
  case "complete_execution":
312
- return ok(await client2.execution({ action: "complete", task_uid, agent_id }));
1860
+ return ok3(
1861
+ await client2.execution({
1862
+ action: "complete",
1863
+ execution_uid,
1864
+ produces_node_uid,
1865
+ output_snapshot,
1866
+ side_effects,
1867
+ outcome,
1868
+ props,
1869
+ agent_id
1870
+ })
1871
+ );
313
1872
  case "fail_execution":
314
- return ok(await client2.execution({ action: "fail", task_uid, agent_id }));
1873
+ return ok3(
1874
+ await client2.execution({
1875
+ action: "fail",
1876
+ execution_uid,
1877
+ output_snapshot,
1878
+ side_effects,
1879
+ outcome,
1880
+ error,
1881
+ props,
1882
+ agent_id
1883
+ })
1884
+ );
1885
+ case "register_agent":
1886
+ return ok3(
1887
+ await client2.execution({
1888
+ action: "register_agent",
1889
+ label,
1890
+ summary,
1891
+ confidence,
1892
+ salience,
1893
+ props,
1894
+ agent_id
1895
+ })
1896
+ );
1897
+ case "get_executions":
1898
+ return ok3(
1899
+ await client2.execution({
1900
+ action: "get_executions",
1901
+ filter_plan_uid,
1902
+ agent_id
1903
+ })
1904
+ );
315
1905
  // Governance — read-only from MCP by design (C33). The write actions were
316
1906
  // removed rather than repaired: `resolve_approval` here also sent
317
1907
  // `task_uid` where the server requires `approval_uid`, and never sent
@@ -319,12 +1909,12 @@ async function handlePlan(client2, args) {
319
1909
  // DENIED every approval an agent tried to resolve. Wiring that correctly
320
1910
  // would have meant building a working self-approval path — the hole itself.
321
1911
  case "get_pending":
322
- return ok(await client2.governance({ action: "get_pending", agent_id }));
1912
+ return ok3(await client2.governance({ action: "get_pending", agent_id }));
323
1913
  // Procedures (merged from mindgraph_action)
324
1914
  case "create_flow":
325
1915
  case "add_affordance":
326
1916
  case "add_control":
327
- return ok(
1917
+ return ok3(
328
1918
  await client2.procedure({
329
1919
  action,
330
1920
  label,
@@ -335,7 +1925,7 @@ async function handlePlan(client2, args) {
335
1925
  })
336
1926
  );
337
1927
  case "add_procedure_step":
338
- return ok(
1928
+ return ok3(
339
1929
  await client2.procedure({
340
1930
  action: "add_step",
341
1931
  label,
@@ -347,7 +1937,7 @@ async function handlePlan(client2, args) {
347
1937
  );
348
1938
  // Risk assessment (merged from mindgraph_action)
349
1939
  case "assess_risk":
350
- return ok(
1940
+ return ok3(
351
1941
  await client2.risk({
352
1942
  action: "assess",
353
1943
  label,
@@ -358,7 +1948,7 @@ async function handlePlan(client2, args) {
358
1948
  })
359
1949
  );
360
1950
  case "get_assessments":
361
- return ok(
1951
+ return ok3(
362
1952
  await client2.risk({
363
1953
  action: "get_assessments",
364
1954
  target_uid,
@@ -366,7 +1956,7 @@ async function handlePlan(client2, args) {
366
1956
  })
367
1957
  );
368
1958
  default:
369
- return err(`Unknown plan action: ${action}`);
1959
+ return err3(`Unknown plan action: ${action}`);
370
1960
  }
371
1961
  }
372
1962
  async function handleRetrieve(client2, args) {
@@ -395,8 +1985,8 @@ async function handleRetrieve(client2, args) {
395
1985
  } = args;
396
1986
  switch (action) {
397
1987
  case "context":
398
- if (!query) return err("query is required for context retrieval");
399
- return ok(
1988
+ if (!query) return err3("query is required for context retrieval");
1989
+ return ok3(
400
1990
  await client2.retrieveContext({
401
1991
  query,
402
1992
  node_limit: limit,
@@ -409,8 +1999,8 @@ async function handleRetrieve(client2, args) {
409
1999
  })
410
2000
  );
411
2001
  case "text":
412
- if (!query) return err("query is required for text search");
413
- return ok(
2002
+ if (!query) return err3("query is required for text search");
2003
+ return ok3(
414
2004
  await client2.retrieve({
415
2005
  action: "text",
416
2006
  query,
@@ -421,8 +2011,8 @@ async function handleRetrieve(client2, args) {
421
2011
  })
422
2012
  );
423
2013
  case "semantic":
424
- if (!query) return err("query is required for semantic search");
425
- return ok(
2014
+ if (!query) return err3("query is required for semantic search");
2015
+ return ok3(
426
2016
  await client2.retrieve({
427
2017
  action: "semantic",
428
2018
  query,
@@ -433,8 +2023,8 @@ async function handleRetrieve(client2, args) {
433
2023
  })
434
2024
  );
435
2025
  case "hybrid":
436
- if (!query) return err("query is required for hybrid search");
437
- return ok(
2026
+ if (!query) return err3("query is required for hybrid search");
2027
+ return ok3(
438
2028
  await client2.retrieve({
439
2029
  action: "hybrid",
440
2030
  query,
@@ -446,17 +2036,17 @@ async function handleRetrieve(client2, args) {
446
2036
  );
447
2037
  // Structured queries
448
2038
  case "active_goals":
449
- return ok(await client2.getGoals());
2039
+ return ok3(await client2.getGoals());
450
2040
  case "open_questions":
451
- return ok(await client2.getOpenQuestions());
2041
+ return ok3(await client2.getOpenQuestions());
452
2042
  case "weak_claims":
453
- return ok(await client2.getWeakClaims());
2043
+ return ok3(await client2.getWeakClaims());
454
2044
  case "pending_approvals":
455
- return ok(await client2.getPendingApprovals());
2045
+ return ok3(await client2.getPendingApprovals());
456
2046
  case "unresolved_contradictions":
457
- return ok(await client2.getContradictions());
2047
+ return ok3(await client2.getContradictions());
458
2048
  case "stale_derivations":
459
- return ok(
2049
+ return ok3(
460
2050
  await client2.retrieve({
461
2051
  action: "stale_derivations",
462
2052
  limit,
@@ -464,7 +2054,7 @@ async function handleRetrieve(client2, args) {
464
2054
  })
465
2055
  );
466
2056
  case "preferences":
467
- return ok(
2057
+ return ok3(
468
2058
  await client2.retrieve({
469
2059
  action: "preferences",
470
2060
  ...query ? { query, k: limit } : {},
@@ -473,8 +2063,8 @@ async function handleRetrieve(client2, args) {
473
2063
  })
474
2064
  );
475
2065
  case "layer":
476
- if (!layer) return err("layer is required for layer retrieval");
477
- return ok(
2066
+ if (!layer) return err3("layer is required for layer retrieval");
2067
+ return ok3(
478
2068
  await client2.retrieve({
479
2069
  action: "layer",
480
2070
  layer,
@@ -483,7 +2073,7 @@ async function handleRetrieve(client2, args) {
483
2073
  })
484
2074
  );
485
2075
  case "recent":
486
- return ok(
2076
+ return ok3(
487
2077
  await client2.retrieve({
488
2078
  action: "recent",
489
2079
  limit,
@@ -495,7 +2085,7 @@ async function handleRetrieve(client2, args) {
495
2085
  );
496
2086
  // Document inventory
497
2087
  case "document_index":
498
- return ok(
2088
+ return ok3(
499
2089
  await client2.getNodes({
500
2090
  node_type: "Document",
501
2091
  limit: limit ?? 100
@@ -503,8 +2093,8 @@ async function handleRetrieve(client2, args) {
503
2093
  );
504
2094
  // Graph traversal
505
2095
  case "chain":
506
- if (!start_uid) return err("start_uid is required for chain traversal");
507
- return ok(
2096
+ if (!start_uid) return err3("start_uid is required for chain traversal");
2097
+ return ok3(
508
2098
  await client2.traverse({
509
2099
  action: "chain",
510
2100
  start_uid,
@@ -515,8 +2105,8 @@ async function handleRetrieve(client2, args) {
515
2105
  })
516
2106
  );
517
2107
  case "neighborhood":
518
- if (!start_uid) return err("start_uid is required for neighborhood traversal");
519
- return ok(
2108
+ if (!start_uid) return err3("start_uid is required for neighborhood traversal");
2109
+ return ok3(
520
2110
  await client2.traverse({
521
2111
  action: "neighborhood",
522
2112
  start_uid,
@@ -529,9 +2119,9 @@ async function handleRetrieve(client2, args) {
529
2119
  })
530
2120
  );
531
2121
  case "path":
532
- if (!start_uid) return err("start_uid is required for path traversal");
533
- if (!end_uid) return err("end_uid is required for path traversal");
534
- return ok(
2122
+ if (!start_uid) return err3("start_uid is required for path traversal");
2123
+ if (!end_uid) return err3("end_uid is required for path traversal");
2124
+ return ok3(
535
2125
  await client2.traverse({
536
2126
  action: "path",
537
2127
  start_uid,
@@ -545,8 +2135,8 @@ async function handleRetrieve(client2, args) {
545
2135
  })
546
2136
  );
547
2137
  case "subgraph":
548
- if (!start_uid) return err("start_uid is required for subgraph traversal");
549
- return ok(
2138
+ if (!start_uid) return err3("start_uid is required for subgraph traversal");
2139
+ return ok3(
550
2140
  await client2.traverse({
551
2141
  action: "subgraph",
552
2142
  start_uid,
@@ -559,7 +2149,7 @@ async function handleRetrieve(client2, args) {
559
2149
  })
560
2150
  );
561
2151
  default:
562
- return err(`Unknown retrieve action: ${action}`);
2152
+ return err3(`Unknown retrieve action: ${action}`);
563
2153
  }
564
2154
  }
565
2155
  async function handleIngest(client2, args) {
@@ -588,7 +2178,7 @@ async function handleIngest(client2, args) {
588
2178
  const targetsOntology = Array.isArray(layers) && layers.includes("ontology");
589
2179
  const cognitiveLayers = targetsOntology ? layers.filter((layer) => layer !== "ontology") : layers;
590
2180
  if (targetsOntology && !ontology_schema_id) {
591
- return err(
2181
+ return err3(
592
2182
  "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."
593
2183
  );
594
2184
  }
@@ -596,8 +2186,8 @@ async function handleIngest(client2, args) {
596
2186
  const ontologyOnly = targetsOntology && forwardedLayers === void 0;
597
2187
  switch (action) {
598
2188
  case "chunk":
599
- if (!content) return err("content is required for chunk ingestion");
600
- return ok(
2189
+ if (!content) return err3("content is required for chunk ingestion");
2190
+ return ok3(
601
2191
  await client2.ingestChunk({
602
2192
  content,
603
2193
  label: title,
@@ -611,8 +2201,8 @@ async function handleIngest(client2, args) {
611
2201
  })
612
2202
  );
613
2203
  case "document":
614
- if (!content) return err("content is required for document ingestion");
615
- return ok(
2204
+ if (!content) return err3("content is required for document ingestion");
2205
+ return ok3(
616
2206
  await client2.ingestDocument({
617
2207
  content,
618
2208
  title,
@@ -631,8 +2221,8 @@ async function handleIngest(client2, args) {
631
2221
  })
632
2222
  );
633
2223
  case "session":
634
- if (!content) return err("content is required for session ingestion");
635
- return ok(
2224
+ if (!content) return err3("content is required for session ingestion");
2225
+ return ok3(
636
2226
  await client2.ingestSession({
637
2227
  content,
638
2228
  title,
@@ -649,11 +2239,11 @@ async function handleIngest(client2, args) {
649
2239
  );
650
2240
  case "job_status":
651
2241
  if (job_id) {
652
- return ok(await client2.getJob(job_id));
2242
+ return ok3(await client2.getJob(job_id));
653
2243
  }
654
- return ok(await client2.listJobs());
2244
+ return ok3(await client2.listJobs());
655
2245
  default:
656
- return err(`Unknown ingest action: ${action}`);
2246
+ return err3(`Unknown ingest action: ${action}`);
657
2247
  }
658
2248
  }
659
2249
  async function handleSynthesize(client2, args) {
@@ -661,19 +2251,19 @@ async function handleSynthesize(client2, args) {
661
2251
  switch (action) {
662
2252
  case "signals":
663
2253
  if (!project_uid)
664
- return err("project_uid is required for action='signals'");
665
- return ok(
2254
+ return err3("project_uid is required for action='signals'");
2255
+ return ok3(
666
2256
  await client2.signals(project_uid, { signals, target_types })
667
2257
  );
668
2258
  case "run":
669
2259
  if (!project_uid)
670
- return err("project_uid is required for action='run'");
671
- return ok(await client2.runSynthesis(project_uid));
2260
+ return err3("project_uid is required for action='run'");
2261
+ return ok3(await client2.runSynthesis(project_uid));
672
2262
  case "job_status":
673
- if (!job_id) return err("job_id is required for action='job_status'");
674
- return ok(await client2.getJob(job_id));
2263
+ if (!job_id) return err3("job_id is required for action='job_status'");
2264
+ return ok3(await client2.getJob(job_id));
675
2265
  default:
676
- return err(`Unknown synthesize action: ${action}`);
2266
+ return err3(`Unknown synthesize action: ${action}`);
677
2267
  }
678
2268
  }
679
2269
  async function handleOntology(client2, args) {
@@ -701,18 +2291,18 @@ async function handleOntology(client2, args) {
701
2291
  } = args;
702
2292
  switch (action) {
703
2293
  case "schemas":
704
- return ok(await client2.listOntologySchemas());
2294
+ return ok3(await client2.listOntologySchemas());
705
2295
  case "schema":
706
- if (!schema_id) return err("schema_id is required for action='schema'");
707
- return ok(await client2.getOntologySchema(schema_id));
2296
+ if (!schema_id) return err3("schema_id is required for action='schema'");
2297
+ return ok3(await client2.getOntologySchema(schema_id));
708
2298
  case "query": {
709
- if (!query) return err("query is required for action='query'");
2299
+ if (!query) return err3("query is required for action='query'");
710
2300
  if (!schema_id) {
711
- return err(
2301
+ return err3(
712
2302
  "schema_id is required for action='query' (use action='schemas' to find the active schema)"
713
2303
  );
714
2304
  }
715
- return ok(
2305
+ return ok3(
716
2306
  await client2.queryOntology({
717
2307
  query,
718
2308
  schema_id,
@@ -725,8 +2315,8 @@ async function handleOntology(client2, args) {
725
2315
  );
726
2316
  }
727
2317
  case "search":
728
- if (!query) return err("query is required for action='search'");
729
- return ok(
2318
+ if (!query) return err3("query is required for action='search'");
2319
+ return ok3(
730
2320
  await client2.searchDomainObjects(query, {
731
2321
  schema_id,
732
2322
  object_types,
@@ -734,18 +2324,18 @@ async function handleOntology(client2, args) {
734
2324
  })
735
2325
  );
736
2326
  case "objects":
737
- if (!schema_id) return err("schema_id is required for action='objects'");
738
- return ok(
2327
+ if (!schema_id) return err3("schema_id is required for action='objects'");
2328
+ return ok3(
739
2329
  await client2.listDomainObjects({ schema_id, object_type, limit })
740
2330
  );
741
2331
  case "object":
742
- if (!uid) return err("uid is required for action='object'");
743
- return ok(await client2.getDomainObject(uid));
2332
+ if (!uid) return err3("uid is required for action='object'");
2333
+ return ok3(await client2.getDomainObject(uid));
744
2334
  case "object_context":
745
- if (!uid) return err("uid is required for action='object_context'");
746
- return ok(await client2.getDomainObjectContext(uid, depth));
2335
+ if (!uid) return err3("uid is required for action='object_context'");
2336
+ return ok3(await client2.getDomainObjectContext(uid, depth));
747
2337
  case "proposals":
748
- return ok(
2338
+ return ok3(
749
2339
  await client2.listOntologyProposals({
750
2340
  status: status ?? "pending",
751
2341
  schema_id,
@@ -754,21 +2344,21 @@ async function handleOntology(client2, args) {
754
2344
  })
755
2345
  );
756
2346
  case "proposal":
757
- if (!proposal_id) return err("proposal_id is required for action='proposal'");
758
- return ok(await client2.getOntologyProposal(proposal_id));
2347
+ if (!proposal_id) return err3("proposal_id is required for action='proposal'");
2348
+ return ok3(await client2.getOntologyProposal(proposal_id));
759
2349
  case "approve":
760
- if (!proposal_id) return err("proposal_id is required for action='approve'");
761
- return ok(await client2.approveOntologyProposal(proposal_id, { feedback }));
2350
+ if (!proposal_id) return err3("proposal_id is required for action='approve'");
2351
+ return ok3(await client2.approveOntologyProposal(proposal_id, { feedback }));
762
2352
  case "reject":
763
- if (!proposal_id) return err("proposal_id is required for action='reject'");
764
- return ok(await client2.rejectOntologyProposal(proposal_id, reason));
2353
+ if (!proposal_id) return err3("proposal_id is required for action='reject'");
2354
+ return ok3(await client2.rejectOntologyProposal(proposal_id, reason));
765
2355
  case "link": {
766
2356
  if (!from_uid || !to_uid || !relation_type) {
767
- return err(
2357
+ return err3(
768
2358
  "from_uid, to_uid, and relation_type are required for action='link'"
769
2359
  );
770
2360
  }
771
- return ok(
2361
+ return ok3(
772
2362
  await client2.linkDomainObjects({
773
2363
  from_uid,
774
2364
  to_uid,
@@ -779,11 +2369,11 @@ async function handleOntology(client2, args) {
779
2369
  );
780
2370
  }
781
2371
  case "extract": {
782
- if (!schema_id) return err("schema_id is required for action='extract'");
2372
+ if (!schema_id) return err3("schema_id is required for action='extract'");
783
2373
  if (!source_uids?.length) {
784
- return err("source_uids is required for action='extract'");
2374
+ return err3("source_uids is required for action='extract'");
785
2375
  }
786
- return ok(
2376
+ return ok3(
787
2377
  await client2.extractOntology({
788
2378
  ontology_schema_id: schema_id,
789
2379
  source_uids
@@ -791,23 +2381,34 @@ async function handleOntology(client2, args) {
791
2381
  );
792
2382
  }
793
2383
  default:
794
- return err(`Unknown ontology action: ${action}`);
2384
+ return err3(`Unknown ontology action: ${action}`);
795
2385
  }
796
2386
  }
797
- var TOOLS;
2387
+ var import_mindgraph, TOOLS, INVOCATION_CONTEXT_SCHEMA;
798
2388
  var init_tools = __esm({
799
2389
  "src/tools.ts"() {
800
2390
  "use strict";
2391
+ import_mindgraph = require("mindgraph");
2392
+ init_code_tool();
2393
+ init_sync_tool();
801
2394
  TOOLS = [
802
2395
  {
803
2396
  name: "mindgraph_capture",
804
- 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.",
2397
+ 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.",
805
2398
  inputSchema: {
806
2399
  type: "object",
807
2400
  properties: {
808
2401
  action: {
809
2402
  type: "string",
810
- enum: ["entity", "observation", "source", "snippet", "concept", "journal"],
2403
+ enum: [
2404
+ "entity",
2405
+ "observation",
2406
+ "source",
2407
+ "snippet",
2408
+ "concept",
2409
+ "journal",
2410
+ "lesson"
2411
+ ],
811
2412
  description: "Type of knowledge to capture"
812
2413
  },
813
2414
  label: {
@@ -822,6 +2423,31 @@ var init_tools = __esm({
822
2423
  type: "string",
823
2424
  description: "Journal content/body text (for action=journal)"
824
2425
  },
2426
+ session_uid: {
2427
+ type: "string",
2428
+ description: "Session UID that produced a lesson (for action=lesson)"
2429
+ },
2430
+ work_uid: {
2431
+ type: "string",
2432
+ description: "Durable Task/work UID this capture is relevant to"
2433
+ },
2434
+ execution_uid: {
2435
+ type: "string",
2436
+ description: "Material Execution that produced the lesson"
2437
+ },
2438
+ idempotency_key: {
2439
+ type: "string",
2440
+ description: "Stable retry key for an intentional capture"
2441
+ },
2442
+ supersedes_uid: {
2443
+ type: "string",
2444
+ description: "Earlier capture corrected or superseded by this one"
2445
+ },
2446
+ summarizes_uids: {
2447
+ type: "array",
2448
+ items: { type: "string" },
2449
+ description: "Source node UIDs the lesson was learned from (for action=lesson)"
2450
+ },
825
2451
  entity_type: {
826
2452
  type: "string",
827
2453
  enum: ["person", "organization", "place", "event", "nation", "concept"],
@@ -856,6 +2482,32 @@ var init_tools = __esm({
856
2482
  type: "object",
857
2483
  description: "Type-specific properties (e.g. birth_date for person, coordinates for place)"
858
2484
  },
2485
+ code_refs: {
2486
+ type: "array",
2487
+ items: {
2488
+ oneOf: [
2489
+ { type: "string" },
2490
+ {
2491
+ type: "object",
2492
+ properties: {
2493
+ repo: { type: "string" },
2494
+ path: { type: "string" },
2495
+ line: { type: "integer", minimum: 1 },
2496
+ symbol: { type: "string" },
2497
+ kind: { type: "string" },
2498
+ language: { type: "string" },
2499
+ signature: { type: "string" }
2500
+ }
2501
+ }
2502
+ ]
2503
+ },
2504
+ description: "Typed code targets to anchor and attach to the captured node"
2505
+ },
2506
+ repo: { type: "string", description: "Configured repository id or root" },
2507
+ repo_space_uid: {
2508
+ type: "string",
2509
+ description: "Writable shared repository/Project Space for new code anchors"
2510
+ },
859
2511
  agent_id: {
860
2512
  type: "string",
861
2513
  description: "Agent identity"
@@ -1003,17 +2655,250 @@ var init_tools = __esm({
1003
2655
  items: { type: "string" },
1004
2656
  description: "Decision-time context UIDs to link with dated INFORMS edges"
1005
2657
  },
1006
- as_of_date: {
2658
+ as_of_date: {
2659
+ type: "string",
2660
+ description: "ISO 8601 date when the decision context was available"
2661
+ },
2662
+ session_id: {
2663
+ type: "string",
2664
+ description: "Session whose retrieval context informed the decision"
2665
+ },
2666
+ retrieval_trace_id: {
2667
+ type: "string",
2668
+ description: "Identifier of the decision-time retrieval trace"
2669
+ },
2670
+ confidence: {
2671
+ type: "number",
2672
+ minimum: 0,
2673
+ maximum: 1
2674
+ },
2675
+ salience: {
2676
+ type: "number",
2677
+ minimum: 0,
2678
+ maximum: 1
2679
+ },
2680
+ props: {
2681
+ type: "object",
2682
+ description: "Type-specific properties (e.g. status, priority, deadline, horizon for goals)"
2683
+ },
2684
+ code_refs: {
2685
+ type: "array",
2686
+ items: { oneOf: [{ type: "string" }, { type: "object" }] },
2687
+ description: "Typed code targets to anchor and attach to the created commitment"
2688
+ },
2689
+ repo: { type: "string", description: "Configured repository id or root" },
2690
+ repo_space_uid: {
2691
+ type: "string",
2692
+ description: "Writable shared repository/Project Space for new code anchors"
2693
+ },
2694
+ agent_id: {
2695
+ type: "string",
2696
+ description: "Agent identity"
2697
+ }
2698
+ },
2699
+ required: ["action"]
2700
+ }
2701
+ },
2702
+ {
2703
+ name: "mindgraph_plan",
2704
+ 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.",
2705
+ inputSchema: {
2706
+ type: "object",
2707
+ properties: {
2708
+ action: {
2709
+ type: "string",
2710
+ enum: [
2711
+ "create_task",
2712
+ "create_plan",
2713
+ "add_step",
2714
+ "update_status",
2715
+ "get_plan",
2716
+ "resume_work",
2717
+ "claim_task",
2718
+ "heartbeat",
2719
+ "start_iteration",
2720
+ "checkpoint_iteration",
2721
+ "block_task",
2722
+ "complete_task",
2723
+ "abandon_iteration",
2724
+ "start_execution",
2725
+ "complete_execution",
2726
+ "fail_execution",
2727
+ "register_agent",
2728
+ "get_executions",
2729
+ // C33: governance WRITES are deliberately not exposed here.
2730
+ // `create_policy` let a model author the rules it is judged by;
2731
+ // `request_approval` + `resolve_approval` let it raise and then
2732
+ // clear its own gate, which defeats the human-in-the-loop control
2733
+ // entirely. Authoring and resolving happen in the dashboard, by a
2734
+ // person. `get_pending` stays — reading the queue is safe and is
2735
+ // what an agent needs in order to wait correctly.
2736
+ "get_pending",
2737
+ "create_flow",
2738
+ "add_procedure_step",
2739
+ "add_affordance",
2740
+ "add_control",
2741
+ "assess_risk",
2742
+ "get_assessments"
2743
+ ],
2744
+ description: "Planning, procedure, or governance action"
2745
+ },
2746
+ label: {
2747
+ type: "string",
2748
+ description: "Task, plan, step, flow, policy, or risk label"
2749
+ },
2750
+ summary: {
2751
+ type: "string",
2752
+ description: "Description"
2753
+ },
2754
+ plan_uid: {
2755
+ type: "string",
2756
+ description: "Plan UID (for create_plan execution link, add_step, get_plan)"
2757
+ },
2758
+ task_uid: {
2759
+ type: "string",
2760
+ description: "Task UID (for create_plan and graph-managed work actions)"
2761
+ },
2762
+ step_uid: {
2763
+ type: "string",
2764
+ description: "PlanStep UID for an iteration/checkpoint"
2765
+ },
2766
+ session_uid: {
2767
+ type: "string",
2768
+ description: "Durable Session UID that owns the task lease"
2769
+ },
2770
+ goal_uid: {
2771
+ type: "string",
2772
+ description: "Goal UID targeted by a task or plan"
2773
+ },
2774
+ target_uid: {
2775
+ type: "string",
2776
+ description: "Task/plan/step UID for update_status, or parent/target node for procedure and risk actions"
2777
+ },
2778
+ execution_uid: {
2779
+ type: "string",
2780
+ description: "Execution UID (required for complete_execution/fail_execution)"
2781
+ },
2782
+ executor_uid: {
2783
+ type: "string",
2784
+ description: "Agent node UID that executed the work"
2785
+ },
2786
+ affordance_uid: {
2787
+ type: "string",
2788
+ description: "Affordance invoked by start_execution"
2789
+ },
2790
+ produces_node_uid: {
2791
+ type: "string",
2792
+ description: "Node produced by a completed execution"
2793
+ },
2794
+ filter_plan_uid: {
2795
+ type: "string",
2796
+ description: "Restrict get_executions to one plan"
2797
+ },
2798
+ depends_on_uids: {
2799
+ type: "array",
2800
+ items: { type: "string" },
2801
+ description: "Plan-step UIDs that an added step depends on"
2802
+ },
2803
+ related_uids: {
2804
+ type: "array",
2805
+ items: { type: "string" },
2806
+ description: "Target node UIDs to link to created work or an execution"
2807
+ },
2808
+ status: {
2809
+ type: "string",
2810
+ description: "New status value (for update_status)"
2811
+ },
2812
+ task_status: {
2813
+ type: "string",
2814
+ description: "Task status requested by a checkpoint or abandon action"
2815
+ },
2816
+ step_status: {
2817
+ type: "string",
2818
+ description: "PlanStep status requested by a checkpoint"
2819
+ },
2820
+ execution_status: {
2821
+ type: "string",
2822
+ enum: ["completed", "failed", "abandoned"],
2823
+ description: "Terminal Execution status for checkpoint_iteration"
2824
+ },
2825
+ expected_version: {
2826
+ type: "number",
2827
+ description: "Current Task node version used as the optimistic fence"
2828
+ },
2829
+ lease_epoch: {
2830
+ type: "number",
2831
+ description: "Current monotonic task-lease epoch"
2832
+ },
2833
+ lease_ttl_secs: {
2834
+ type: "number",
2835
+ minimum: 1,
2836
+ description: "Requested server-time lease lifetime (default 300s)"
2837
+ },
2838
+ idempotency_key: {
2839
+ type: "string",
2840
+ description: "Caller-stable key required for every composite work mutation"
2841
+ },
2842
+ allow_takeover: {
2843
+ type: "boolean",
2844
+ description: "Governed claim override for a live lease owned by another agent"
2845
+ },
2846
+ release_lease: {
2847
+ type: "boolean",
2848
+ description: "Explicitly release or retain the lease after block/abandon/checkpoint"
2849
+ },
2850
+ override_reason: {
2851
+ type: "string",
2852
+ description: "Explicit completion reason when no terminal Execution evidence exists"
2853
+ },
2854
+ scope_uids: {
2855
+ type: "array",
2856
+ items: { type: "string" },
2857
+ description: "Ordinary target UIDs that bound deterministic resume selection"
2858
+ },
2859
+ limit: {
2860
+ type: "number",
2861
+ minimum: 1,
2862
+ maximum: 1,
2863
+ description: "Resume returns one authoritative Task"
2864
+ },
2865
+ input_snapshot: {
2866
+ type: "object",
2867
+ description: "Structured inputs captured when an execution starts"
2868
+ },
2869
+ output_snapshot: {
2870
+ type: "object",
2871
+ description: "Structured outputs captured when an execution completes or fails"
2872
+ },
2873
+ outcome: {
2874
+ type: "string",
2875
+ description: "Execution outcome"
2876
+ },
2877
+ error: {
2878
+ type: "string",
2879
+ description: "Execution error detail (for fail_execution)"
2880
+ },
2881
+ side_effects: {
2882
+ type: "array",
2883
+ items: { type: "string" },
2884
+ description: "Observable side effects of the execution"
2885
+ },
2886
+ next_action: {
1007
2887
  type: "string",
1008
- description: "ISO 8601 date when the decision context was available"
2888
+ description: "Recommended next action retained on terminal Execution evidence"
1009
2889
  },
1010
- session_id: {
2890
+ checkpoint_summary: {
1011
2891
  type: "string",
1012
- description: "Session whose retrieval context informed the decision"
2892
+ description: "Bounded material-attempt checkpoint summary"
1013
2893
  },
1014
- retrieval_trace_id: {
2894
+ test_summary: {
1015
2895
  type: "string",
1016
- description: "Identifier of the decision-time retrieval trace"
2896
+ description: "Test commands/results retained on the Execution"
2897
+ },
2898
+ produces_node_uids: {
2899
+ type: "array",
2900
+ items: { type: "string" },
2901
+ description: "Nodes produced by checkpoint_iteration"
1017
2902
  },
1018
2903
  confidence: {
1019
2904
  type: "number",
@@ -1027,77 +2912,17 @@ var init_tools = __esm({
1027
2912
  },
1028
2913
  props: {
1029
2914
  type: "object",
1030
- description: "Type-specific properties (e.g. status, priority, deadline, horizon for goals)"
1031
- },
1032
- agent_id: {
1033
- type: "string",
1034
- description: "Agent identity"
1035
- }
1036
- },
1037
- required: ["action"]
1038
- }
1039
- },
1040
- {
1041
- name: "mindgraph_plan",
1042
- 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.",
1043
- inputSchema: {
1044
- type: "object",
1045
- properties: {
1046
- action: {
1047
- type: "string",
1048
- enum: [
1049
- "create_task",
1050
- "create_plan",
1051
- "add_step",
1052
- "update_status",
1053
- "get_plan",
1054
- "start_execution",
1055
- "complete_execution",
1056
- "fail_execution",
1057
- // C33: governance WRITES are deliberately not exposed here.
1058
- // `create_policy` let a model author the rules it is judged by;
1059
- // `request_approval` + `resolve_approval` let it raise and then
1060
- // clear its own gate, which defeats the human-in-the-loop control
1061
- // entirely. Authoring and resolving happen in the dashboard, by a
1062
- // person. `get_pending` stays — reading the queue is safe and is
1063
- // what an agent needs in order to wait correctly.
1064
- "get_pending",
1065
- "create_flow",
1066
- "add_procedure_step",
1067
- "add_affordance",
1068
- "add_control",
1069
- "assess_risk",
1070
- "get_assessments"
1071
- ],
1072
- description: "Planning, procedure, or governance action"
1073
- },
1074
- label: {
1075
- type: "string",
1076
- description: "Task, plan, step, flow, policy, or risk label"
1077
- },
1078
- summary: {
1079
- type: "string",
1080
- description: "Description"
1081
- },
1082
- plan_uid: {
1083
- type: "string",
1084
- description: "Plan UID (for add_step, get_plan)"
1085
- },
1086
- task_uid: {
1087
- type: "string",
1088
- description: "Task/step UID (for update_status, execution actions)"
2915
+ description: "Additional properties (e.g. likelihood, impact, mitigation for risks)"
1089
2916
  },
1090
- target_uid: {
1091
- type: "string",
1092
- description: "Parent flow/step UID or target node (for procedure/risk actions)"
2917
+ code_refs: {
2918
+ type: "array",
2919
+ items: { oneOf: [{ type: "string" }, { type: "object" }] },
2920
+ description: "Typed code targets to anchor and attach to created work or execution"
1093
2921
  },
1094
- status: {
2922
+ repo: { type: "string", description: "Configured repository id or root" },
2923
+ repo_space_uid: {
1095
2924
  type: "string",
1096
- description: "New status value (for update_status)"
1097
- },
1098
- props: {
1099
- type: "object",
1100
- description: "Additional properties (e.g. likelihood, impact, mitigation for risks)"
2925
+ description: "Writable shared repository/Project Space for new code anchors"
1101
2926
  },
1102
2927
  agent_id: {
1103
2928
  type: "string",
@@ -1105,6 +2930,12 @@ var init_tools = __esm({
1105
2930
  }
1106
2931
  },
1107
2932
  required: ["action"]
2933
+ },
2934
+ annotations: {
2935
+ readOnlyHint: false,
2936
+ destructiveHint: false,
2937
+ idempotentHint: false,
2938
+ openWorldHint: true
1108
2939
  }
1109
2940
  },
1110
2941
  {
@@ -1479,16 +3310,45 @@ var init_tools = __esm({
1479
3310
  },
1480
3311
  required: ["action"]
1481
3312
  }
1482
- }
3313
+ },
3314
+ SYNC_TOOL,
3315
+ CODE_TOOL
1483
3316
  ];
3317
+ INVOCATION_CONTEXT_SCHEMA = {
3318
+ type: "object",
3319
+ 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.",
3320
+ properties: {
3321
+ harness: { type: "string", enum: ["claude-code", "codex", "generic"] },
3322
+ harnessSessionId: { type: "string" },
3323
+ harnessTurnId: { type: "string" },
3324
+ cwd: { type: "string" },
3325
+ repoId: { type: "string" },
3326
+ branch: { type: "string" },
3327
+ commit: { type: "string" },
3328
+ worktreeState: { type: "string", enum: ["clean", "dirty"] },
3329
+ model: { type: "string" },
3330
+ injectedBy: { type: "string", enum: ["hook", "mcp-process", "caller"] }
3331
+ }
3332
+ };
3333
+ for (const tool of TOOLS) {
3334
+ if (tool.name === "mindgraph_retrieve") continue;
3335
+ const schema = tool.inputSchema;
3336
+ if (schema.properties && !schema.properties.invocation_context) {
3337
+ schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
3338
+ }
3339
+ }
1484
3340
  }
1485
3341
  });
1486
3342
 
1487
3343
  // src/generated-tools.ts
1488
3344
  async function getGeneratedTools(client2) {
1489
- if (cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
3345
+ const now = Date.now();
3346
+ if (cache && now - cache.fetchedAt < CACHE_TTL_MS) {
1490
3347
  return { tools: cache.tools, byName: cache.byName };
1491
3348
  }
3349
+ if (now < retryNotBefore) {
3350
+ return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
3351
+ }
1492
3352
  try {
1493
3353
  const c = client2;
1494
3354
  if (typeof c.listOntologyTools !== "function") {
@@ -1512,11 +3372,12 @@ async function getGeneratedTools(client2) {
1512
3372
  });
1513
3373
  }
1514
3374
  cache = { tools, byName, fetchedAt: Date.now() };
3375
+ retryNotBefore = 0;
1515
3376
  return { tools, byName };
1516
3377
  } catch (error) {
1517
3378
  if (error instanceof DuplicateGeneratedToolError) throw error;
1518
- cache = { tools: [], byName: /* @__PURE__ */ new Map(), fetchedAt: Date.now() };
1519
- return { tools: cache.tools, byName: cache.byName };
3379
+ retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
3380
+ return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
1520
3381
  }
1521
3382
  }
1522
3383
  async function handleGeneratedTool(client2, desc, args) {
@@ -1525,10 +3386,10 @@ async function handleGeneratedTool(client2, desc, args) {
1525
3386
  switch (desc.maps_to) {
1526
3387
  case "search": {
1527
3388
  const query = args.query;
1528
- if (!query) return err2(`query is required for ${desc.name}`);
3389
+ if (!query) return err4(`query is required for ${desc.name}`);
1529
3390
  const limit = typeof args.limit === "number" ? args.limit : void 0;
1530
3391
  const filters = Array.isArray(args.filters) ? args.filters : void 0;
1531
- return ok2(
3392
+ return ok4(
1532
3393
  await ontologyClient.searchDomainObjects(query, {
1533
3394
  schema_id: desc.schema_id,
1534
3395
  object_types: [desc.object_type],
@@ -1539,33 +3400,33 @@ async function handleGeneratedTool(client2, desc, args) {
1539
3400
  }
1540
3401
  case "object": {
1541
3402
  const uid = args.uid;
1542
- if (!uid) return err2(`uid is required for ${desc.name}`);
1543
- return ok2(await ontologyClient.getDomainObject(uid, {
3403
+ if (!uid) return err4(`uid is required for ${desc.name}`);
3404
+ return ok4(await ontologyClient.getDomainObject(uid, {
1544
3405
  schema_id: desc.schema_id,
1545
3406
  object_type: desc.object_type
1546
3407
  }));
1547
3408
  }
1548
3409
  case "object_context": {
1549
3410
  const uid = args.uid;
1550
- if (!uid) return err2(`uid is required for ${desc.name}`);
3411
+ if (!uid) return err4(`uid is required for ${desc.name}`);
1551
3412
  const depth = typeof args.depth === "number" ? args.depth : void 0;
1552
- return ok2(await ontologyClient.getDomainObjectContext(uid, depth, {
3413
+ return ok4(await ontologyClient.getDomainObjectContext(uid, depth, {
1553
3414
  schema_id: desc.schema_id,
1554
3415
  object_type: desc.object_type
1555
3416
  }));
1556
3417
  }
1557
3418
  case "related": {
1558
3419
  const uid = args.uid;
1559
- if (!uid) return err2(`uid is required for ${desc.name}`);
3420
+ if (!uid) return err4(`uid is required for ${desc.name}`);
1560
3421
  if (!desc.relation || !desc.entry_role || !desc.far_type) {
1561
- return err2(`invalid related descriptor: ${desc.name}`);
3422
+ return err4(`invalid related descriptor: ${desc.name}`);
1562
3423
  }
1563
3424
  if (!ontologyClient.queryRelatedDomainObjects) {
1564
- return err2(
3425
+ return err4(
1565
3426
  "related ontology tools require a graph-aware version of the mindgraph SDK"
1566
3427
  );
1567
3428
  }
1568
- return ok2(await ontologyClient.queryRelatedDomainObjects({
3429
+ return ok4(await ontologyClient.queryRelatedDomainObjects({
1569
3430
  schema_id: desc.schema_id,
1570
3431
  entry_type: desc.object_type,
1571
3432
  uid,
@@ -1580,44 +3441,67 @@ async function handleGeneratedTool(client2, desc, args) {
1580
3441
  }
1581
3442
  case "structured_query": {
1582
3443
  const select = args.select;
1583
- if (!select) return err2(`select is required for ${desc.name}`);
3444
+ if (!select) return err4(`select is required for ${desc.name}`);
1584
3445
  if (!ontologyClient.queryDomainStructured) {
1585
- return err2(
3446
+ return err4(
1586
3447
  "structured ontology tools require a graph-aware version of the mindgraph SDK"
1587
3448
  );
1588
3449
  }
1589
- return ok2(await ontologyClient.queryDomainStructured({
1590
- ...args,
3450
+ const request = {
1591
3451
  schema_id: desc.schema_id,
1592
3452
  select
1593
- }));
3453
+ };
3454
+ for (const field of STRUCTURED_QUERY_PASSTHROUGH) {
3455
+ if (args[field] !== void 0) request[field] = args[field];
3456
+ }
3457
+ return ok4(await ontologyClient.queryDomainStructured(request));
1594
3458
  }
1595
3459
  default:
1596
- return err2(`unknown generated tool mapping: ${desc.maps_to}`);
3460
+ return err4(`unknown generated tool mapping: ${desc.maps_to}`);
1597
3461
  }
1598
3462
  } catch (e) {
1599
- return err2(e instanceof Error ? e.message : String(e));
3463
+ return err4(e instanceof Error ? e.message : String(e));
1600
3464
  }
1601
3465
  }
1602
- var ok2, err2, CACHE_TTL_MS, cache, DuplicateGeneratedToolError;
3466
+ var ok4, err4, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
1603
3467
  var init_generated_tools = __esm({
1604
3468
  "src/generated-tools.ts"() {
1605
3469
  "use strict";
1606
- ok2 = (data) => ({
3470
+ ok4 = (data) => ({
1607
3471
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1608
3472
  });
1609
- err2 = (message) => ({
3473
+ err4 = (message) => ({
1610
3474
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
1611
3475
  isError: true
1612
3476
  });
1613
3477
  CACHE_TTL_MS = 5 * 60 * 1e3;
3478
+ FAILURE_COOLDOWN_MS = 15 * 1e3;
1614
3479
  cache = null;
3480
+ retryNotBefore = 0;
3481
+ EMPTY = {
3482
+ tools: [],
3483
+ byName: /* @__PURE__ */ new Map()
3484
+ };
1615
3485
  DuplicateGeneratedToolError = class extends Error {
1616
3486
  };
3487
+ STRUCTURED_QUERY_PASSTHROUGH = [
3488
+ "anchor",
3489
+ "path",
3490
+ "where",
3491
+ "include",
3492
+ "page",
3493
+ "aggregate"
3494
+ ];
1617
3495
  }
1618
3496
  });
1619
3497
 
1620
3498
  // src/governance.ts
3499
+ function isTransientStatus(status) {
3500
+ return status === 408 || status === 425 || status === 429 || status >= 500;
3501
+ }
3502
+ function sleep(ms) {
3503
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
3504
+ }
1621
3505
  function headers(apiKey, orgId) {
1622
3506
  const value = {
1623
3507
  authorization: `Bearer ${apiKey}`,
@@ -1632,42 +3516,128 @@ function unsupportedResponse(status, body) {
1632
3516
  const lower = body.toLowerCase();
1633
3517
  return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
1634
3518
  }
3519
+ function toolMutability(toolName, action) {
3520
+ if (toolName === "mindgraph_retrieve") return "read";
3521
+ if (toolName === "mindgraph_plan") {
3522
+ return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
3523
+ }
3524
+ if (toolName === "mindgraph_ingest") {
3525
+ return action === "job_status" ? "read" : "write";
3526
+ }
3527
+ if (toolName === "mindgraph_synthesize") {
3528
+ return action === "signals" ? "read" : "write";
3529
+ }
3530
+ if (toolName === "mindgraph_code") {
3531
+ return action === "anchor" ? "write" : "read";
3532
+ }
3533
+ if (toolName === "mindgraph_sync") {
3534
+ return action === "scan" || action === "status" ? "read" : "write";
3535
+ }
3536
+ if (toolName === "mindgraph_ontology") {
3537
+ return action && ONTOLOGY_READ_ACTIONS.has(action) ? "read" : "write";
3538
+ }
3539
+ if (/_(search|get|context|related|query)$/.test(toolName) && toolName !== "mindgraph_capture") {
3540
+ return "read";
3541
+ }
3542
+ return "write";
3543
+ }
3544
+ function governanceTarget(toolName, args) {
3545
+ const action = typeof args.action === "string" ? args.action : void 0;
3546
+ const targetUids = /* @__PURE__ */ new Set();
3547
+ for (const field of UID_FIELDS) {
3548
+ const value = args[field];
3549
+ if (typeof value === "string" && value.length > 0) targetUids.add(value);
3550
+ }
3551
+ for (const field of UID_ARRAY_FIELDS) {
3552
+ const value = args[field];
3553
+ if (!Array.isArray(value)) continue;
3554
+ for (const uid of value) {
3555
+ if (typeof uid === "string" && uid.length > 0) targetUids.add(uid);
3556
+ }
3557
+ }
3558
+ return {
3559
+ tool_name: toolName,
3560
+ ...action ? { action } : {},
3561
+ mutability: toolMutability(toolName, action),
3562
+ target_uids: [...targetUids]
3563
+ };
3564
+ }
1635
3565
  async function checkMcpGovernance(toolName, args, config) {
1636
- if (config.disabled || capability === "unsupported") return { allowed: true };
3566
+ if (config.disabled) return { allowed: true };
3567
+ if (capability === "unsupported") {
3568
+ if (Date.now() < unsupportedUntil) return { allowed: true };
3569
+ capability = "unknown";
3570
+ }
1637
3571
  const fetchImpl = config.fetchImpl ?? fetch;
3572
+ const retryDelays = config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
1638
3573
  let response;
1639
- let body;
1640
- try {
1641
- response = await fetchImpl(`${config.baseUrl.replace(/\/$/, "")}/agent/governance`, {
1642
- method: "POST",
1643
- headers: headers(config.apiKey, config.orgId),
1644
- body: JSON.stringify({
1645
- action: "check",
1646
- act: "tool_invoke",
1647
- agent_id: String(args.agent_id || config.agentId),
1648
- target: { tool_name: toolName },
1649
- context: { adapter: "mindgraph-mcp" },
1650
- tier: "checkpoint"
1651
- })
1652
- });
1653
- body = await response.text();
1654
- } catch (error) {
3574
+ let body = "";
3575
+ let lastFailure = "";
3576
+ for (let attempt = 0; attempt <= retryDelays.length; attempt++) {
3577
+ try {
3578
+ const candidate = await fetchImpl(
3579
+ `${config.baseUrl.replace(/\/$/, "")}/agent/governance`,
3580
+ {
3581
+ method: "POST",
3582
+ headers: headers(config.apiKey, config.orgId),
3583
+ body: JSON.stringify({
3584
+ action: "check",
3585
+ act: "tool_invoke",
3586
+ agent_id: String(args.agent_id || config.agentId),
3587
+ target: governanceTarget(toolName, args),
3588
+ context: { adapter: "mindgraph-mcp", ...config.context },
3589
+ tier: "checkpoint"
3590
+ })
3591
+ }
3592
+ );
3593
+ const candidateBody = await candidate.text();
3594
+ if (isTransientStatus(candidate.status) && attempt < retryDelays.length) {
3595
+ lastFailure = `HTTP ${candidate.status}`;
3596
+ await sleep(retryDelays[attempt]);
3597
+ continue;
3598
+ }
3599
+ response = candidate;
3600
+ body = candidateBody;
3601
+ break;
3602
+ } catch (error) {
3603
+ lastFailure = error instanceof Error ? error.message : String(error);
3604
+ if (attempt < retryDelays.length) {
3605
+ await sleep(retryDelays[attempt]);
3606
+ continue;
3607
+ }
3608
+ }
3609
+ }
3610
+ if (!response) {
1655
3611
  return {
1656
3612
  allowed: false,
1657
- message: `MindGraph governance could not establish permission: ${error instanceof Error ? error.message : String(error)}`
3613
+ message: `MindGraph governance could not establish permission after ${retryDelays.length + 1} attempts (${lastFailure}). Set MINDGRAPH_GOVERNANCE=off to run this adapter ungoverned.`
1658
3614
  };
1659
3615
  }
1660
3616
  if (!response.ok) {
1661
3617
  if (capability === "unknown" && unsupportedResponse(response.status, body)) {
1662
3618
  capability = "unsupported";
3619
+ unsupportedUntil = Date.now() + UNSUPPORTED_LEASE_MS;
3620
+ if (!unsupportedLogged) {
3621
+ unsupportedLogged = true;
3622
+ console.error(
3623
+ `[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.`
3624
+ );
3625
+ }
1663
3626
  return { allowed: true };
1664
3627
  }
3628
+ if (response.status === 401 || response.status === 403) {
3629
+ return {
3630
+ allowed: false,
3631
+ 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.`
3632
+ };
3633
+ }
1665
3634
  return {
1666
3635
  allowed: false,
1667
3636
  message: `MindGraph governance check failed (${response.status})`
1668
3637
  };
1669
3638
  }
1670
3639
  capability = "supported";
3640
+ unsupportedLogged = false;
1671
3641
  let decision;
1672
3642
  try {
1673
3643
  decision = JSON.parse(body);
@@ -1701,12 +3671,180 @@ async function checkMcpGovernance(toolName, args, config) {
1701
3671
  message: verdict === "conditional" ? `Approval is required${policy ? ` by policy ${policy}` : ""}${approvalUid ? ` (approval ${approvalUid})` : ""}` : `Denied by MindGraph governance${policy ? ` policy ${policy}` : ""}`
1702
3672
  };
1703
3673
  }
1704
- var capability, KNOWN_DUTIES;
3674
+ var capability, UNSUPPORTED_LEASE_MS, unsupportedUntil, unsupportedLogged, DEFAULT_RETRY_DELAYS_MS, KNOWN_DUTIES, PLAN_READ_ACTIONS, ONTOLOGY_READ_ACTIONS, UID_FIELDS, UID_ARRAY_FIELDS;
1705
3675
  var init_governance = __esm({
1706
3676
  "src/governance.ts"() {
1707
3677
  "use strict";
1708
3678
  capability = "unknown";
3679
+ UNSUPPORTED_LEASE_MS = 5 * 60 * 1e3;
3680
+ unsupportedUntil = 0;
3681
+ unsupportedLogged = false;
3682
+ DEFAULT_RETRY_DELAYS_MS = [150, 600];
1709
3683
  KNOWN_DUTIES = /* @__PURE__ */ new Set(["log", "require_approval"]);
3684
+ PLAN_READ_ACTIONS = /* @__PURE__ */ new Set([
3685
+ "get_plan",
3686
+ "get_executions",
3687
+ "get_pending",
3688
+ "get_assessments",
3689
+ "resume_work"
3690
+ ]);
3691
+ ONTOLOGY_READ_ACTIONS = /* @__PURE__ */ new Set([
3692
+ "list_schemas",
3693
+ "get_schema",
3694
+ "query",
3695
+ "search_objects",
3696
+ "list_objects",
3697
+ "get_object",
3698
+ "get_context",
3699
+ "list_proposals",
3700
+ "get_proposal"
3701
+ ]);
3702
+ UID_FIELDS = [
3703
+ "uid",
3704
+ "source_uid",
3705
+ "session_uid",
3706
+ "work_uid",
3707
+ "goal_uid",
3708
+ "project_uid",
3709
+ "task_uid",
3710
+ "plan_uid",
3711
+ "step_uid",
3712
+ "target_uid",
3713
+ "execution_uid",
3714
+ "executor_uid",
3715
+ "affordance_uid",
3716
+ "produces_node_uid",
3717
+ "filter_plan_uid",
3718
+ "parent_uid",
3719
+ "decision_uid",
3720
+ "chosen_option_uid",
3721
+ "governed_uid",
3722
+ "approval_uid",
3723
+ "requires_plan_uid",
3724
+ "start_uid",
3725
+ "end_uid"
3726
+ ];
3727
+ UID_ARRAY_FIELDS = [
3728
+ "related_uids",
3729
+ "depends_on_uids",
3730
+ "summarizes_uids",
3731
+ "informs_uid",
3732
+ "target_uids",
3733
+ "anchor_uids",
3734
+ "scope_uids",
3735
+ "produces_node_uids"
3736
+ ];
3737
+ }
3738
+ });
3739
+
3740
+ // src/resources.ts
3741
+ function json(uri, data) {
3742
+ return {
3743
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
3744
+ };
3745
+ }
3746
+ async function readResource(client2, uri) {
3747
+ switch (uri) {
3748
+ case "mindgraph://stats":
3749
+ return json(uri, await client2.stats());
3750
+ case "mindgraph://goals":
3751
+ return json(uri, await client2.getGoals());
3752
+ case "mindgraph://questions":
3753
+ return json(uri, await client2.getOpenQuestions());
3754
+ case "mindgraph://contradictions":
3755
+ return json(uri, await client2.getContradictions());
3756
+ case "mindgraph://decisions":
3757
+ return json(uri, await client2.getOpenDecisions());
3758
+ }
3759
+ const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
3760
+ if (nodeMatch) {
3761
+ const uid = decodeURIComponent(nodeMatch[1]);
3762
+ const node = await client2.getNode(uid);
3763
+ const edges = await client2.getEdges({ from_uid: uid });
3764
+ const inEdges = await client2.getEdges({ to_uid: uid });
3765
+ return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
3766
+ }
3767
+ const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
3768
+ if (searchMatch) {
3769
+ const query = decodeURIComponent(searchMatch[1]);
3770
+ return json(uri, await client2.search(query, { limit: 20 }));
3771
+ }
3772
+ const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
3773
+ if (layerMatch) {
3774
+ const layer = decodeURIComponent(layerMatch[1]);
3775
+ return json(uri, await client2.getNodes({ layer, limit: 50 }));
3776
+ }
3777
+ throw new Error(`Unknown resource: ${uri}`);
3778
+ }
3779
+ async function readGovernedResource(client2, uri, governanceConfig) {
3780
+ const governance = await checkMcpGovernance(
3781
+ RESOURCE_GOVERNANCE_TOOL,
3782
+ {},
3783
+ { ...governanceConfig, context: { resource_uri: uri } }
3784
+ );
3785
+ if (!governance.allowed) {
3786
+ throw new Error(governance.message);
3787
+ }
3788
+ return readResource(client2, uri);
3789
+ }
3790
+ var RESOURCES, RESOURCE_TEMPLATES, RESOURCE_GOVERNANCE_TOOL;
3791
+ var init_resources = __esm({
3792
+ "src/resources.ts"() {
3793
+ "use strict";
3794
+ init_governance();
3795
+ RESOURCES = [
3796
+ {
3797
+ uri: "mindgraph://stats",
3798
+ name: "Graph Statistics",
3799
+ description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
3800
+ mimeType: "application/json"
3801
+ },
3802
+ {
3803
+ uri: "mindgraph://goals",
3804
+ name: "Active Goals",
3805
+ description: "All active goals in the knowledge graph",
3806
+ mimeType: "application/json"
3807
+ },
3808
+ {
3809
+ uri: "mindgraph://questions",
3810
+ name: "Open Questions",
3811
+ description: "All open questions in the knowledge graph",
3812
+ mimeType: "application/json"
3813
+ },
3814
+ {
3815
+ uri: "mindgraph://contradictions",
3816
+ name: "Contradictions",
3817
+ description: "Contradictory claims in the knowledge graph",
3818
+ mimeType: "application/json"
3819
+ },
3820
+ {
3821
+ uri: "mindgraph://decisions",
3822
+ name: "Open Decisions",
3823
+ description: "Unresolved decisions awaiting resolution",
3824
+ mimeType: "application/json"
3825
+ }
3826
+ ];
3827
+ RESOURCE_TEMPLATES = [
3828
+ {
3829
+ uriTemplate: "mindgraph://node/{uid}",
3830
+ name: "Graph Node",
3831
+ description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
3832
+ mimeType: "application/json"
3833
+ },
3834
+ {
3835
+ uriTemplate: "mindgraph://search/{query}",
3836
+ name: "Search Results",
3837
+ description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
3838
+ mimeType: "application/json"
3839
+ },
3840
+ {
3841
+ uriTemplate: "mindgraph://layer/{layer}",
3842
+ name: "Layer Nodes",
3843
+ description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
3844
+ mimeType: "application/json"
3845
+ }
3846
+ ];
3847
+ RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
1710
3848
  }
1711
3849
  });
1712
3850
 
@@ -1719,28 +3857,31 @@ async function main() {
1719
3857
  `MindGraph MCP server v0.7.1 running on stdio (${BASE_URL})`
1720
3858
  );
1721
3859
  }
1722
- var import_server, import_stdio, import_types, import_mindgraph, API_KEY, BASE_URL, AGENT_ID, ORG_ID, client, INSTRUCTIONS, server, PROMPTS;
3860
+ var import_server, import_stdio, import_types, import_mindgraph2, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
1723
3861
  var init_src = __esm({
1724
3862
  "src/index.ts"() {
1725
3863
  "use strict";
1726
3864
  import_server = require("@modelcontextprotocol/sdk/server/index.js");
1727
3865
  import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
1728
3866
  import_types = require("@modelcontextprotocol/sdk/types.js");
1729
- import_mindgraph = require("mindgraph");
3867
+ import_mindgraph2 = require("mindgraph");
1730
3868
  init_tools();
1731
3869
  init_generated_tools();
1732
3870
  init_governance();
3871
+ init_resources();
1733
3872
  API_KEY = process.env.MINDGRAPH_API_KEY;
1734
3873
  BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
1735
3874
  AGENT_ID = process.env.MINDGRAPH_AGENT_ID || "mcp";
1736
3875
  ORG_ID = process.env.MINDGRAPH_ORG_ID;
3876
+ PROFILE = process.env.MINDGRAPH_PROFILE || "general";
3877
+ HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
1737
3878
  if (!API_KEY) {
1738
3879
  console.error(
1739
3880
  "Error: MINDGRAPH_API_KEY environment variable is required.\nGet your API key at https://mindgraph.cloud/dashboard/keys\nOr run: npx mindgraph-mcp init"
1740
3881
  );
1741
3882
  process.exit(1);
1742
3883
  }
1743
- client = new import_mindgraph.MindGraph({
3884
+ client = new import_mindgraph2.MindGraph({
1744
3885
  baseUrl: BASE_URL,
1745
3886
  apiKey: API_KEY,
1746
3887
  orgId: ORG_ID,
@@ -1749,7 +3890,7 @@ var init_src = __esm({
1749
3890
  // window where npm may still resolve the preceding SDK declaration file.
1750
3891
  telemetrySurface: "mcp"
1751
3892
  });
1752
- INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph (7 tools).
3893
+ INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
1753
3894
 
1754
3895
  ## #1 RULE: Go straight to the user's topic
1755
3896
 
@@ -1812,22 +3953,42 @@ Capture knowledge when the user shares something worth remembering:
1812
3953
  - Questions to track \u2192 \`mindgraph_reason\` action "open_question"
1813
3954
  - Goals or projects \u2192 \`mindgraph_commit\` action "goal"/"project"
1814
3955
  - Decisions \u2192 \`mindgraph_commit\` action "open_decision"
3956
+ - Durable engineering work that must survive sessions/harnesses \u2192 \`mindgraph_plan\`
3957
+ - Typed code anchors, attached memory/work, callers/callees, or impact \u2192 \`mindgraph_code\`
3958
+ - Reusable learning from a material attempt \u2192 \`mindgraph_capture\` action "lesson"
1815
3959
  - Long-form content \u2192 \`mindgraph_ingest\`
1816
3960
  - 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)
1817
3961
 
1818
3962
  ## Tool tips
1819
3963
 
3964
+ - 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.
3965
+ - 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.
1820
3966
  - \`capture\`: "entity"/"observation" for facts, "journal" for personal/informal
1821
3967
  - \`reason\`: "claim" when evidence exists, "open_question"/"hypothesis" otherwise
1822
3968
  - \`retrieve\`: "context" returns graph nodes only by default \u2014 set include_chunks=true for source text
1823
3969
  - \`retrieve\`: "context" (FTS default) \u2192 "semantic" (conceptual fallback) \u2192 "hybrid" (both)
1824
3970
  - \`retrieve\`: "neighborhood"/"chain"/"path"/"subgraph" for graph exploration
3971
+ - \`code\`: pass path + symbol + signature when names are overloaded; ambiguity never writes
1825
3972
  - Confidence: 0.9+ for facts, 0.5-0.8 for uncertain, <0.5 for speculation
1826
3973
  - Don't narrate tool usage \u2014 capture naturally as part of conversation`;
3974
+ if (PROFILE === "coding") {
3975
+ INSTRUCTIONS += `
3976
+
3977
+ ## Coding profile
3978
+
3979
+ - 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.
3980
+ - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
3981
+ - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
3982
+ - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
3983
+ } else {
3984
+ INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
3985
+ (line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
3986
+ ).join("\n");
3987
+ }
1827
3988
  server = new import_server.Server(
1828
3989
  {
1829
3990
  name: "mindgraph",
1830
- version: "0.13.4"
3991
+ version: "0.14.0"
1831
3992
  },
1832
3993
  {
1833
3994
  capabilities: {
@@ -1840,21 +4001,25 @@ Capture knowledge when the user shares something worth remembering:
1840
4001
  );
1841
4002
  server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
1842
4003
  const generated = await getGeneratedTools(client);
1843
- return { tools: [...TOOLS, ...generated.tools] };
4004
+ return { tools: [...toolsForProfile(PROFILE), ...generated.tools] };
1844
4005
  });
4006
+ GOVERNANCE_CONFIG = {
4007
+ baseUrl: BASE_URL,
4008
+ apiKey: API_KEY,
4009
+ orgId: ORG_ID,
4010
+ agentId: AGENT_ID,
4011
+ get disabled() {
4012
+ return process.env.MINDGRAPH_GOVERNANCE === "off";
4013
+ }
4014
+ };
1845
4015
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
1846
4016
  const { name, arguments: args } = request.params;
1847
- const toolArgs = { ...args || {} };
4017
+ const rawArgs = { ...args || {} };
4018
+ const toolArgs = { ...rawArgs };
1848
4019
  if (!toolArgs.agent_id) {
1849
4020
  toolArgs.agent_id = AGENT_ID;
1850
4021
  }
1851
- const governance = await checkMcpGovernance(name, toolArgs, {
1852
- baseUrl: BASE_URL,
1853
- apiKey: API_KEY,
1854
- orgId: ORG_ID,
1855
- agentId: AGENT_ID,
1856
- disabled: process.env.MINDGRAPH_GOVERNANCE === "off"
1857
- });
4022
+ const governance = await checkMcpGovernance(name, toolArgs, GOVERNANCE_CONFIG);
1858
4023
  if (!governance.allowed) {
1859
4024
  return {
1860
4025
  content: [{ type: "text", text: JSON.stringify({ error: governance.message, ...governance }) }],
@@ -1864,7 +4029,7 @@ Capture knowledge when the user shares something worth remembering:
1864
4029
  const generated = await getGeneratedTools(client);
1865
4030
  const desc = generated.byName.get(name);
1866
4031
  if (desc) {
1867
- return handleGeneratedTool(client, desc, toolArgs);
4032
+ return handleGeneratedTool(client, desc, rawArgs);
1868
4033
  }
1869
4034
  return handleTool(client, name, toolArgs);
1870
4035
  });
@@ -2014,189 +4179,24 @@ Capture knowledge when the user shares something worth remembering:
2014
4179
  - Any hypotheses or theories discussed \u2192 mindgraph_reason action "hypothesis"/"theory"
2015
4180
 
2016
4181
  Conversation:
2017
- ${conversation}`
2018
- }
2019
- }
2020
- ]
2021
- };
2022
- }
2023
- default:
2024
- throw new Error(`Unknown prompt: ${name}`);
2025
- }
2026
- });
2027
- server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
2028
- return {
2029
- resources: [
2030
- {
2031
- uri: "mindgraph://stats",
2032
- name: "Graph Statistics",
2033
- description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
2034
- mimeType: "application/json"
2035
- },
2036
- {
2037
- uri: "mindgraph://goals",
2038
- name: "Active Goals",
2039
- description: "All active goals in the knowledge graph",
2040
- mimeType: "application/json"
2041
- },
2042
- {
2043
- uri: "mindgraph://questions",
2044
- name: "Open Questions",
2045
- description: "All open questions in the knowledge graph",
2046
- mimeType: "application/json"
2047
- },
2048
- {
2049
- uri: "mindgraph://contradictions",
2050
- name: "Contradictions",
2051
- description: "Contradictory claims in the knowledge graph",
2052
- mimeType: "application/json"
2053
- },
2054
- {
2055
- uri: "mindgraph://decisions",
2056
- name: "Open Decisions",
2057
- description: "Unresolved decisions awaiting resolution",
2058
- mimeType: "application/json"
2059
- }
2060
- ]
2061
- };
2062
- });
2063
- server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => {
2064
- return {
2065
- resourceTemplates: [
2066
- {
2067
- uriTemplate: "mindgraph://node/{uid}",
2068
- name: "Graph Node",
2069
- description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
2070
- mimeType: "application/json"
2071
- },
2072
- {
2073
- uriTemplate: "mindgraph://search/{query}",
2074
- name: "Search Results",
2075
- description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
2076
- mimeType: "application/json"
2077
- },
2078
- {
2079
- uriTemplate: "mindgraph://layer/{layer}",
2080
- name: "Layer Nodes",
2081
- description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
2082
- mimeType: "application/json"
2083
- }
2084
- ]
2085
- };
2086
- });
2087
- server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
2088
- const { uri } = request.params;
2089
- switch (uri) {
2090
- case "mindgraph://stats": {
2091
- const stats = await client.stats();
2092
- return {
2093
- contents: [
2094
- {
2095
- uri,
2096
- mimeType: "application/json",
2097
- text: JSON.stringify(stats, null, 2)
2098
- }
2099
- ]
2100
- };
2101
- }
2102
- case "mindgraph://goals": {
2103
- const goals = await client.getGoals();
2104
- return {
2105
- contents: [
2106
- {
2107
- uri,
2108
- mimeType: "application/json",
2109
- text: JSON.stringify(goals, null, 2)
2110
- }
2111
- ]
2112
- };
2113
- }
2114
- case "mindgraph://questions": {
2115
- const questions = await client.getOpenQuestions();
2116
- return {
2117
- contents: [
2118
- {
2119
- uri,
2120
- mimeType: "application/json",
2121
- text: JSON.stringify(questions, null, 2)
2122
- }
2123
- ]
2124
- };
2125
- }
2126
- case "mindgraph://contradictions": {
2127
- const contradictions = await client.getContradictions();
2128
- return {
2129
- contents: [
2130
- {
2131
- uri,
2132
- mimeType: "application/json",
2133
- text: JSON.stringify(contradictions, null, 2)
2134
- }
2135
- ]
2136
- };
2137
- }
2138
- case "mindgraph://decisions": {
2139
- const decisions = await client.getOpenDecisions();
2140
- return {
2141
- contents: [
2142
- {
2143
- uri,
2144
- mimeType: "application/json",
2145
- text: JSON.stringify(decisions, null, 2)
2146
- }
2147
- ]
2148
- };
2149
- }
2150
- }
2151
- const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
2152
- if (nodeMatch) {
2153
- const uid = decodeURIComponent(nodeMatch[1]);
2154
- const node = await client.getNode(uid);
2155
- const edges = await client.getEdges({ from_uid: uid });
2156
- const inEdges = await client.getEdges({ to_uid: uid });
2157
- return {
2158
- contents: [
2159
- {
2160
- uri,
2161
- mimeType: "application/json",
2162
- text: JSON.stringify(
2163
- { node, outgoing_edges: edges, incoming_edges: inEdges },
2164
- null,
2165
- 2
2166
- )
2167
- }
2168
- ]
2169
- };
2170
- }
2171
- const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
2172
- if (searchMatch) {
2173
- const query = decodeURIComponent(searchMatch[1]);
2174
- const results = await client.search(query, { limit: 20 });
2175
- return {
2176
- contents: [
2177
- {
2178
- uri,
2179
- mimeType: "application/json",
2180
- text: JSON.stringify(results, null, 2)
2181
- }
2182
- ]
2183
- };
2184
- }
2185
- const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
2186
- if (layerMatch) {
2187
- const layer = decodeURIComponent(layerMatch[1]);
2188
- const nodes = await client.getNodes({ layer, limit: 50 });
2189
- return {
2190
- contents: [
2191
- {
2192
- uri,
2193
- mimeType: "application/json",
2194
- text: JSON.stringify(nodes, null, 2)
2195
- }
2196
- ]
2197
- };
4182
+ ${conversation}`
4183
+ }
4184
+ }
4185
+ ]
4186
+ };
4187
+ }
4188
+ default:
4189
+ throw new Error(`Unknown prompt: ${name}`);
2198
4190
  }
2199
- throw new Error(`Unknown resource: ${uri}`);
4191
+ });
4192
+ server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
4193
+ return { resources: RESOURCES };
4194
+ });
4195
+ server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => {
4196
+ return { resourceTemplates: RESOURCE_TEMPLATES };
4197
+ });
4198
+ server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
4199
+ return readGovernedResource(client, request.params.uri, GOVERNANCE_CONFIG);
2200
4200
  });
2201
4201
  main().catch((error) => {
2202
4202
  console.error("Fatal error:", error);
@@ -2206,30 +4206,670 @@ ${conversation}`
2206
4206
  });
2207
4207
 
2208
4208
  // src/cli.ts
2209
- var fs = __toESM(require("fs"));
2210
- var path = __toESM(require("path"));
2211
- var os = __toESM(require("os"));
2212
- var crypto = __toESM(require("crypto"));
4209
+ var fs5 = __toESM(require("fs"));
4210
+ var path5 = __toESM(require("path"));
4211
+ var os4 = __toESM(require("os"));
4212
+ var crypto4 = __toESM(require("crypto"));
2213
4213
  var import_child_process = require("child_process");
4214
+ var import_mindgraph3 = require("mindgraph");
4215
+
4216
+ // src/hook-env.ts
4217
+ var import_node_fs = __toESM(require("fs"));
4218
+ var import_node_os = __toESM(require("os"));
4219
+ var import_node_path = __toESM(require("path"));
4220
+ function hookEnvPath(dir) {
4221
+ return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
4222
+ }
4223
+ function loadHookEnv(dir) {
4224
+ try {
4225
+ const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
4226
+ const parsed = JSON.parse(raw);
4227
+ return {
4228
+ baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
4229
+ apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0
4230
+ };
4231
+ } catch {
4232
+ return {};
4233
+ }
4234
+ }
4235
+ function saveHookEnv(env, dir) {
4236
+ const file = hookEnvPath(dir);
4237
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
4238
+ const existing = loadHookEnv(dir);
4239
+ const merged = {
4240
+ baseUrl: env.baseUrl ?? existing.baseUrl,
4241
+ apiKey: env.apiKey ?? existing.apiKey
4242
+ };
4243
+ import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
4244
+ `, {
4245
+ mode: 384
4246
+ });
4247
+ import_node_fs.default.chmodSync(file, 384);
4248
+ return file;
4249
+ }
4250
+
4251
+ // src/cli-args.ts
4252
+ function parseArgs(argv) {
4253
+ const args = argv.slice(2);
4254
+ let command = "serve";
4255
+ let apiKey = process.env.MINDGRAPH_API_KEY;
4256
+ let baseUrl = process.env.MINDGRAPH_BASE_URL;
4257
+ let scope = "project";
4258
+ let projectDir;
4259
+ let harness;
4260
+ let hooks = false;
4261
+ for (let i = 0; i < args.length; i++) {
4262
+ const arg = args[i];
4263
+ switch (arg) {
4264
+ case "--api-key":
4265
+ apiKey = args[++i];
4266
+ break;
4267
+ case "--base-url":
4268
+ baseUrl = args[++i];
4269
+ break;
4270
+ case "--scope": {
4271
+ const value = args[++i];
4272
+ if (value !== "user" && value !== "project") {
4273
+ throw new Error("--scope must be user or project");
4274
+ }
4275
+ scope = value;
4276
+ break;
4277
+ }
4278
+ case "--project-dir":
4279
+ projectDir = args[++i];
4280
+ break;
4281
+ case "--harness":
4282
+ harness = args[++i];
4283
+ break;
4284
+ case "--hooks":
4285
+ hooks = true;
4286
+ break;
4287
+ case "--owner":
4288
+ ++i;
4289
+ break;
4290
+ case "--help":
4291
+ case "-h":
4292
+ command = "help";
4293
+ break;
4294
+ default:
4295
+ if (!arg.startsWith("-")) {
4296
+ command = arg;
4297
+ }
4298
+ break;
4299
+ }
4300
+ }
4301
+ return { command, apiKey, baseUrl, scope, projectDir, harness, hooks };
4302
+ }
4303
+
4304
+ // src/claude-hooks.ts
4305
+ var import_node_crypto2 = __toESM(require("crypto"));
4306
+ var import_node_fs3 = __toESM(require("fs"));
4307
+ var import_node_os3 = __toESM(require("os"));
4308
+ var import_node_path3 = __toESM(require("path"));
4309
+ var import_node_child_process2 = require("child_process");
4310
+ init_codegraph();
4311
+ function defaultLedger(sessionId) {
4312
+ return {
4313
+ harness: "claude-code",
4314
+ harnessSessionId: sessionId,
4315
+ materialToolCalls: 0,
4316
+ mutatingOperations: 0,
4317
+ memoryWritten: false,
4318
+ iterationCheckpointed: false,
4319
+ stopNudged: false,
4320
+ nativeTasksCreated: 0,
4321
+ nativeTasksTerminal: 0,
4322
+ baselineWorktree: "unknown",
4323
+ updatedAt: Date.now()
4324
+ };
4325
+ }
4326
+ function runtimeDir(options) {
4327
+ return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
4328
+ }
4329
+ function ledgerPath(sessionId, options) {
4330
+ const key = import_node_crypto2.default.createHash("sha256").update(`claude-code\0${sessionId}`).digest("hex");
4331
+ return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
4332
+ }
4333
+ function waitBriefly(ms) {
4334
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
4335
+ }
4336
+ function withLedger(sessionId, options, update) {
4337
+ const file = ledgerPath(sessionId, options);
4338
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true, mode: 448 });
4339
+ try {
4340
+ import_node_fs3.default.chmodSync(import_node_path3.default.dirname(file), 448);
4341
+ } catch {
4342
+ }
4343
+ const lock = `${file}.lock`;
4344
+ let lockFd;
4345
+ for (let attempt = 0; attempt < 50; attempt++) {
4346
+ try {
4347
+ lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
4348
+ break;
4349
+ } catch (error) {
4350
+ if (error.code !== "EEXIST") throw error;
4351
+ try {
4352
+ if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 1e4) import_node_fs3.default.unlinkSync(lock);
4353
+ } catch {
4354
+ }
4355
+ waitBriefly(10);
4356
+ }
4357
+ }
4358
+ if (lockFd === void 0) {
4359
+ return update(defaultLedger(sessionId));
4360
+ }
4361
+ try {
4362
+ let ledger = defaultLedger(sessionId);
4363
+ try {
4364
+ ledger = {
4365
+ ...ledger,
4366
+ ...JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"))
4367
+ };
4368
+ } catch {
4369
+ }
4370
+ const result = update(ledger);
4371
+ ledger.updatedAt = Date.now();
4372
+ const temporary = `${file}.${process.pid}.${import_node_crypto2.default.randomUUID()}.tmp`;
4373
+ import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
4374
+ `, { mode: 384 });
4375
+ import_node_fs3.default.renameSync(temporary, file);
4376
+ try {
4377
+ import_node_fs3.default.chmodSync(file, 384);
4378
+ } catch {
4379
+ }
4380
+ return result;
4381
+ } finally {
4382
+ import_node_fs3.default.closeSync(lockFd);
4383
+ try {
4384
+ import_node_fs3.default.unlinkSync(lock);
4385
+ } catch {
4386
+ }
4387
+ }
4388
+ }
4389
+ function readLedger(sessionId, options) {
4390
+ return withLedger(sessionId, options, (ledger) => ({ ...ledger }));
4391
+ }
4392
+ function gitValue(cwd, args) {
4393
+ try {
4394
+ return (0, import_node_child_process2.execFileSync)("git", ["-C", cwd, ...args], {
4395
+ encoding: "utf8",
4396
+ stdio: ["ignore", "pipe", "ignore"],
4397
+ timeout: 750
4398
+ }).trim();
4399
+ } catch {
4400
+ return void 0;
4401
+ }
4402
+ }
4403
+ function worktreeState(cwd) {
4404
+ const status = gitValue(cwd, ["status", "--porcelain"]);
4405
+ return status === void 0 ? "unknown" : status ? "dirty" : "clean";
4406
+ }
4407
+ async function invocationContext(input, options) {
4408
+ const repository = await new CodegraphAdapter({
4409
+ cwd: input.cwd,
4410
+ workspaceFile: options.workspaceFile
4411
+ }).resolveRepository();
4412
+ return {
4413
+ harness: "claude-code",
4414
+ harnessSessionId: input.session_id,
4415
+ cwd: input.cwd,
4416
+ repoId: repository.repoId,
4417
+ branch: gitValue(input.cwd, ["branch", "--show-current"]),
4418
+ commit: gitValue(input.cwd, ["rev-parse", "HEAD"]),
4419
+ worktreeState: worktreeState(input.cwd),
4420
+ model: input.model,
4421
+ injectedBy: "hook"
4422
+ };
4423
+ }
4424
+ function object(value) {
4425
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4426
+ }
4427
+ function responsePayload(value) {
4428
+ const direct = object(value);
4429
+ if (!direct) return void 0;
4430
+ if (Array.isArray(direct.content)) {
4431
+ for (const item of direct.content) {
4432
+ const block = object(item);
4433
+ if (block?.type === "text" && typeof block.text === "string") {
4434
+ try {
4435
+ const parsed = JSON.parse(block.text);
4436
+ const payload = object(parsed);
4437
+ if (payload) return payload;
4438
+ } catch {
4439
+ }
4440
+ }
4441
+ }
4442
+ }
4443
+ return direct;
4444
+ }
4445
+ function number(value) {
4446
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4447
+ }
4448
+ function string(value) {
4449
+ return typeof value === "string" && value ? value : void 0;
4450
+ }
4451
+ function boundedBrief(brief) {
4452
+ const encoded = JSON.stringify(brief, null, 2);
4453
+ const capped = encoded.length <= 9e3 ? encoded : `${encoded.slice(0, 8900)}
4454
+ \u2026[bounded work brief truncated]`;
4455
+ return [
4456
+ "MindGraph durable work brief (authoritative across sessions/harnesses):",
4457
+ capped,
4458
+ "Use the harness todo list only for local execution details; do not duplicate this durable Task there."
4459
+ ].join("\n");
4460
+ }
4461
+ async function repositoryScopeUids(input, client2, options, agentId) {
4462
+ if (!client2.entity) return [];
4463
+ const adapter = new CodegraphAdapter({
4464
+ cwd: input.cwd,
4465
+ workspaceFile: options.workspaceFile
4466
+ });
4467
+ const configured = await adapter.resolveRepositories();
4468
+ const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
4469
+ const resolved = await Promise.all(
4470
+ repositories.map(
4471
+ (repository) => client2.entity({
4472
+ action: "resolve_identity",
4473
+ identity: {
4474
+ namespace: "external.code",
4475
+ key_version: 1,
4476
+ key: repositoryIdentityKey(repository)
4477
+ },
4478
+ agent_id: agentId
4479
+ }).catch(() => void 0)
4480
+ )
4481
+ );
4482
+ return [
4483
+ ...new Set(
4484
+ resolved.flatMap((response) => {
4485
+ const payload = object(response);
4486
+ return payload?.status === "existing" && string(payload.uid) ? [string(payload.uid)] : [];
4487
+ })
4488
+ )
4489
+ ];
4490
+ }
4491
+ async function sessionStart(input, client2, options) {
4492
+ const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || "claude-code";
4493
+ const opened = object(
4494
+ await client2.session({
4495
+ action: "open",
4496
+ label: `Claude Code ${input.session_id}`,
4497
+ harness: "claude-code",
4498
+ harness_session_id: input.session_id,
4499
+ model: input.model,
4500
+ agent_id: agentId
4501
+ })
4502
+ );
4503
+ const sessionUid = string(opened?.uid);
4504
+ const scopeUids = await repositoryScopeUids(input, client2, options, agentId);
4505
+ let brief = object(
4506
+ await client2.plan({
4507
+ action: "resume_work",
4508
+ session_uid: sessionUid,
4509
+ ...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
4510
+ agent_id: agentId
4511
+ })
4512
+ );
4513
+ const task = object(brief?.task);
4514
+ const lease = object(brief?.lease);
4515
+ if (sessionUid && string(task?.uid) && number(task?.version)) {
4516
+ try {
4517
+ const claimed = object(
4518
+ await client2.plan({
4519
+ action: "claim_task",
4520
+ task_uid: task.uid,
4521
+ session_uid: sessionUid,
4522
+ expected_version: task.version,
4523
+ idempotency_key: `session-start:${input.session_id}:${task.uid}:${lease?.lease_epoch || 0}`,
4524
+ agent_id: agentId
4525
+ })
4526
+ );
4527
+ brief = object(
4528
+ await client2.plan({
4529
+ action: "resume_work",
4530
+ task_uid: task.uid,
4531
+ session_uid: sessionUid,
4532
+ ...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
4533
+ agent_id: agentId
4534
+ })
4535
+ );
4536
+ if (brief && claimed) brief.claim = claimed;
4537
+ } catch {
4538
+ }
4539
+ }
4540
+ withLedger(input.session_id, options, (ledger) => {
4541
+ ledger.sessionUid = sessionUid;
4542
+ ledger.baselineWorktree = worktreeState(input.cwd);
4543
+ const selectedTask = object(brief?.task);
4544
+ const selectedLease = object(brief?.lease) || object(brief?.claim);
4545
+ const executions = Array.isArray(brief?.recent_executions) ? brief?.recent_executions : [];
4546
+ const latest = object(executions[0]);
4547
+ ledger.taskUid = string(selectedTask?.uid);
4548
+ ledger.taskVersion = number(selectedTask?.version);
4549
+ ledger.leaseEpoch = number(selectedLease?.lease_epoch);
4550
+ ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
4551
+ ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
4552
+ return void 0;
4553
+ });
4554
+ return {
4555
+ hookSpecificOutput: {
4556
+ hookEventName: "SessionStart",
4557
+ additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
4558
+ }
4559
+ };
4560
+ }
4561
+ async function preTool(input, options) {
4562
+ const original = { ...input.tool_input || {} };
4563
+ const ledger = readLedger(input.session_id, options);
4564
+ const updatedInput = {
4565
+ ...original,
4566
+ invocation_context: await invocationContext(input, options)
4567
+ };
4568
+ if (ledger.sessionUid) updatedInput.session_uid = ledger.sessionUid;
4569
+ if (ledger.taskUid && updatedInput.task_uid === void 0) {
4570
+ updatedInput.task_uid = ledger.taskUid;
4571
+ }
4572
+ if (ledger.taskUid && input.tool_name?.includes("mindgraph_capture") && updatedInput.work_uid === void 0) {
4573
+ updatedInput.work_uid = ledger.taskUid;
4574
+ }
4575
+ if (ledger.executionUid && updatedInput.execution_uid === void 0) {
4576
+ updatedInput.execution_uid = ledger.executionUid;
4577
+ }
4578
+ if (ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
4579
+ updatedInput.lease_epoch = ledger.leaseEpoch;
4580
+ }
4581
+ if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
4582
+ updatedInput.expected_version = ledger.taskVersion;
4583
+ }
4584
+ return {
4585
+ hookSpecificOutput: {
4586
+ hookEventName: "PreToolUse",
4587
+ updatedInput
4588
+ }
4589
+ };
4590
+ }
4591
+ async function postTool(input, client2, options) {
4592
+ const name = input.tool_name || "";
4593
+ const args = input.tool_input || {};
4594
+ const action = string(args.action);
4595
+ const payload = responsePayload(input.tool_response);
4596
+ const nowSeconds = (options.now || (() => Date.now() / 1e3))();
4597
+ let heartbeat;
4598
+ withLedger(input.session_id, options, (ledger) => {
4599
+ ledger.materialToolCalls += 1;
4600
+ if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
4601
+ ledger.mutatingOperations += 1;
4602
+ }
4603
+ if (name.includes("mindgraph_capture") && !payload?.error) {
4604
+ ledger.memoryWritten = true;
4605
+ }
4606
+ if (name.includes("mindgraph_plan")) {
4607
+ if (action === "start_iteration" && !payload?.error) {
4608
+ ledger.executionUid = string(payload?.execution_uid) || ledger.executionUid;
4609
+ }
4610
+ if (["checkpoint_iteration", "block_task", "complete_task", "abandon_iteration"].includes(
4611
+ action || ""
4612
+ ) && !payload?.error) {
4613
+ ledger.iterationCheckpointed = true;
4614
+ if (action !== "block_task") ledger.executionUid = void 0;
4615
+ }
4616
+ ledger.taskVersion = number(payload?.task_version) || number(payload?.version) || ledger.taskVersion;
4617
+ ledger.leaseEpoch = number(payload?.lease_epoch) || ledger.leaseEpoch;
4618
+ ledger.leaseExpiresAt = number(payload?.lease_expires_at) || ledger.leaseExpiresAt;
4619
+ }
4620
+ if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 && ledger.leaseExpiresAt - nowSeconds <= 60) {
4621
+ heartbeat = {
4622
+ taskUid: ledger.taskUid,
4623
+ sessionUid: ledger.sessionUid,
4624
+ taskVersion: ledger.taskVersion,
4625
+ leaseEpoch: ledger.leaseEpoch
4626
+ };
4627
+ }
4628
+ return void 0;
4629
+ });
4630
+ if (heartbeat) {
4631
+ try {
4632
+ const response = object(
4633
+ await client2.plan({
4634
+ action: "heartbeat",
4635
+ task_uid: heartbeat.taskUid,
4636
+ session_uid: heartbeat.sessionUid,
4637
+ expected_version: heartbeat.taskVersion,
4638
+ lease_epoch: heartbeat.leaseEpoch,
4639
+ idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
4640
+ agent_id: options.agentId || "claude-code"
4641
+ })
4642
+ );
4643
+ withLedger(input.session_id, options, (ledger) => {
4644
+ ledger.leaseExpiresAt = number(response?.lease_expires_at) || ledger.leaseExpiresAt;
4645
+ return void 0;
4646
+ });
4647
+ } catch {
4648
+ }
4649
+ }
4650
+ return {};
4651
+ }
4652
+ function stop(input, options) {
4653
+ if (input.stop_hook_active) return {};
4654
+ return withLedger(input.session_id, options, (ledger) => {
4655
+ const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
4656
+ const substantial = Boolean(ledger.executionUid) || changed;
4657
+ if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
4658
+ return {};
4659
+ }
4660
+ ledger.stopNudged = true;
4661
+ return {
4662
+ decision: "block",
4663
+ reason: "Substantial work is still uncheckpointed. Checkpoint the active iteration, capture one durable lesson/decision/risk, or explicitly state that nothing durable changed. If a local harness task should become authoritative outside this list, promote it once with a stable idempotency key; otherwise do not copy it."
4664
+ };
4665
+ });
4666
+ }
4667
+ async function sessionEnd(input, client2, options) {
4668
+ const ledger = readLedger(input.session_id, options);
4669
+ if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
4670
+ try {
4671
+ await client2.plan({
4672
+ action: "abandon_iteration",
4673
+ task_uid: ledger.taskUid,
4674
+ execution_uid: ledger.executionUid,
4675
+ session_uid: ledger.sessionUid,
4676
+ expected_version: ledger.taskVersion,
4677
+ lease_epoch: ledger.leaseEpoch,
4678
+ release_lease: true,
4679
+ summary: `Claude Code SessionEnd: ${input.reason || "interrupted"}`,
4680
+ idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
4681
+ agent_id: options.agentId || "claude-code"
4682
+ });
4683
+ } catch {
4684
+ }
4685
+ }
4686
+ if (ledger.sessionUid) {
4687
+ try {
4688
+ await client2.session({
4689
+ action: "close",
4690
+ session_uid: ledger.sessionUid,
4691
+ agent_id: options.agentId || "claude-code"
4692
+ });
4693
+ } catch {
4694
+ }
4695
+ }
4696
+ return {};
4697
+ }
4698
+ async function runClaudeHook(input, client2, options = {}) {
4699
+ switch (input.hook_event_name) {
4700
+ case "SessionStart":
4701
+ return sessionStart(input, client2, options);
4702
+ case "PreToolUse":
4703
+ if (!input.tool_name?.startsWith("mcp__mindgraph__")) return {};
4704
+ return preTool(input, options);
4705
+ case "PostToolUse":
4706
+ return postTool(input, client2, options);
4707
+ case "TaskCreated":
4708
+ withLedger(input.session_id, options, (ledger) => {
4709
+ ledger.nativeTasksCreated += 1;
4710
+ return void 0;
4711
+ });
4712
+ return {};
4713
+ case "TaskCompleted":
4714
+ withLedger(input.session_id, options, (ledger) => {
4715
+ ledger.nativeTasksTerminal += 1;
4716
+ return void 0;
4717
+ });
4718
+ return {};
4719
+ case "Stop":
4720
+ return stop(input, options);
4721
+ case "SessionEnd":
4722
+ return sessionEnd(input, client2, options);
4723
+ default:
4724
+ return {};
4725
+ }
4726
+ }
4727
+ async function readHookInput(stream = process.stdin) {
4728
+ const chunks = [];
4729
+ for await (const chunk of stream) {
4730
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4731
+ }
4732
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
4733
+ }
4734
+
4735
+ // src/hook-installer.ts
4736
+ var import_node_fs4 = __toESM(require("fs"));
4737
+ var import_node_os4 = __toESM(require("os"));
4738
+ var import_node_path4 = __toESM(require("path"));
4739
+ var import_node_crypto3 = __toESM(require("crypto"));
4740
+ var OWNER_MARKER = "--owner mindgraph";
4741
+ var COMMAND = "npx -y mindgraph-mcp@latest hook --harness claude-code --owner mindgraph";
4742
+ function settingsPath(scope, projectDir = process.cwd()) {
4743
+ return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
4744
+ }
4745
+ function readSettings(file) {
4746
+ if (!import_node_fs4.default.existsSync(file)) return {};
4747
+ return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
4748
+ }
4749
+ function ownedHook(timeout) {
4750
+ return {
4751
+ type: "command",
4752
+ command: COMMAND,
4753
+ timeout,
4754
+ statusMessage: "Syncing MindGraph session context"
4755
+ };
4756
+ }
4757
+ function desiredEntries() {
4758
+ return {
4759
+ SessionStart: [
4760
+ {
4761
+ matcher: "startup|resume|clear|compact|fork",
4762
+ hooks: [ownedHook(8)]
4763
+ }
4764
+ ],
4765
+ PreToolUse: [
4766
+ {
4767
+ matcher: "mcp__mindgraph__.*",
4768
+ hooks: [ownedHook(3)]
4769
+ }
4770
+ ],
4771
+ PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
4772
+ TaskCreated: [{ hooks: [ownedHook(3)] }],
4773
+ TaskCompleted: [{ hooks: [ownedHook(3)] }],
4774
+ Stop: [{ hooks: [ownedHook(5)] }],
4775
+ SessionEnd: [{ hooks: [ownedHook(8)] }]
4776
+ };
4777
+ }
4778
+ function hookCommand(value) {
4779
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
4780
+ const command = value.command;
4781
+ return typeof command === "string" ? command : void 0;
4782
+ }
4783
+ function entryHasOwnedHook(value) {
4784
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4785
+ const hooks = value.hooks;
4786
+ return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
4787
+ }
4788
+ function writeSettings(file, settings) {
4789
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
4790
+ const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
4791
+ import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
4792
+ `, {
4793
+ mode: 384
4794
+ });
4795
+ import_node_fs4.default.renameSync(temporary, file);
4796
+ }
4797
+ function installClaudeHooks(scope, projectDir = process.cwd()) {
4798
+ const file = settingsPath(scope, projectDir);
4799
+ const settings = readSettings(file);
4800
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
4801
+ let added = 0;
4802
+ for (const [event, wanted] of Object.entries(desiredEntries())) {
4803
+ const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
4804
+ for (const entry of wanted) {
4805
+ if (!current.some(entryHasOwnedHook)) {
4806
+ current.push(entry);
4807
+ added += 1;
4808
+ }
4809
+ }
4810
+ hooks[event] = current;
4811
+ }
4812
+ settings.hooks = hooks;
4813
+ writeSettings(file, settings);
4814
+ return { path: file, added };
4815
+ }
4816
+ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
4817
+ const file = settingsPath(scope, projectDir);
4818
+ if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
4819
+ const settings = readSettings(file);
4820
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
4821
+ let removed = 0;
4822
+ for (const [event, value] of Object.entries(hooks)) {
4823
+ if (!Array.isArray(value)) continue;
4824
+ const retainedEntries = [];
4825
+ for (const rawEntry of value) {
4826
+ if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
4827
+ retainedEntries.push(rawEntry);
4828
+ continue;
4829
+ }
4830
+ const entry = { ...rawEntry };
4831
+ if (!Array.isArray(entry.hooks)) {
4832
+ retainedEntries.push(entry);
4833
+ continue;
4834
+ }
4835
+ const retainedHooks = entry.hooks.filter((hook) => {
4836
+ const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
4837
+ if (owned) removed += 1;
4838
+ return !owned;
4839
+ });
4840
+ if (retainedHooks.length > 0) {
4841
+ entry.hooks = retainedHooks;
4842
+ retainedEntries.push(entry);
4843
+ }
4844
+ }
4845
+ if (retainedEntries.length > 0) hooks[event] = retainedEntries;
4846
+ else delete hooks[event];
4847
+ }
4848
+ settings.hooks = hooks;
4849
+ writeSettings(file, settings);
4850
+ return { path: file, removed };
4851
+ }
4852
+
4853
+ // src/cli.ts
2214
4854
  function getClaudeDesktopConfigPath() {
2215
4855
  switch (process.platform) {
2216
4856
  case "darwin":
2217
- return path.join(
2218
- os.homedir(),
4857
+ return path5.join(
4858
+ os4.homedir(),
2219
4859
  "Library",
2220
4860
  "Application Support",
2221
4861
  "Claude",
2222
4862
  "claude_desktop_config.json"
2223
4863
  );
2224
4864
  case "win32":
2225
- return path.join(
2226
- process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"),
4865
+ return path5.join(
4866
+ process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"),
2227
4867
  "Claude",
2228
4868
  "claude_desktop_config.json"
2229
4869
  );
2230
4870
  case "linux":
2231
- return path.join(
2232
- os.homedir(),
4871
+ return path5.join(
4872
+ os4.homedir(),
2233
4873
  ".config",
2234
4874
  "Claude",
2235
4875
  "claude_desktop_config.json"
@@ -2239,16 +4879,16 @@ function getClaudeDesktopConfigPath() {
2239
4879
  }
2240
4880
  }
2241
4881
  function readJsonFile(filePath) {
2242
- if (!fs.existsSync(filePath)) return {};
2243
- const content = fs.readFileSync(filePath, "utf-8");
4882
+ if (!fs5.existsSync(filePath)) return {};
4883
+ const content = fs5.readFileSync(filePath, "utf-8");
2244
4884
  return JSON.parse(content);
2245
4885
  }
2246
4886
  function writeJsonFile(filePath, data) {
2247
- const dir = path.dirname(filePath);
2248
- if (!fs.existsSync(dir)) {
2249
- fs.mkdirSync(dir, { recursive: true });
4887
+ const dir = path5.dirname(filePath);
4888
+ if (!fs5.existsSync(dir)) {
4889
+ fs5.mkdirSync(dir, { recursive: true });
2250
4890
  }
2251
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
4891
+ fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
2252
4892
  }
2253
4893
  function getMcpServerEntry(apiKey, baseUrl) {
2254
4894
  const env = { MINDGRAPH_API_KEY: apiKey };
@@ -2278,7 +4918,16 @@ function installClaudeDesktop(apiKey, baseUrl) {
2278
4918
  console.log("Restart Claude Desktop to activate.");
2279
4919
  }
2280
4920
  function installClaudeCode(apiKey, baseUrl) {
2281
- const envArgs = [`--env`, `MINDGRAPH_API_KEY=${apiKey}`];
4921
+ const envArgs = [
4922
+ `--env`,
4923
+ `MINDGRAPH_API_KEY=${apiKey}`,
4924
+ "--env",
4925
+ "MINDGRAPH_PROFILE=coding",
4926
+ "--env",
4927
+ "MINDGRAPH_HARNESS=claude-code",
4928
+ "--env",
4929
+ "MINDGRAPH_AGENT_ID=claude-code"
4930
+ ];
2282
4931
  if (baseUrl) {
2283
4932
  envArgs.push("--env", `MINDGRAPH_BASE_URL=${baseUrl}`);
2284
4933
  }
@@ -2308,7 +4957,7 @@ function installClaudeCode(apiKey, baseUrl) {
2308
4957
  }
2309
4958
  function uninstallClaudeDesktop() {
2310
4959
  const configPath = getClaudeDesktopConfigPath();
2311
- if (!fs.existsSync(configPath)) {
4960
+ if (!fs5.existsSync(configPath)) {
2312
4961
  console.log("Claude Desktop config not found. Nothing to remove.");
2313
4962
  return;
2314
4963
  }
@@ -2335,7 +4984,7 @@ function uninstallClaudeCode() {
2335
4984
  }
2336
4985
  function printStatus() {
2337
4986
  const desktopPath = getClaudeDesktopConfigPath();
2338
- if (fs.existsSync(desktopPath)) {
4987
+ if (fs5.existsSync(desktopPath)) {
2339
4988
  const config = readJsonFile(desktopPath);
2340
4989
  const servers = config.mcpServers;
2341
4990
  if (servers && "mindgraph" in servers) {
@@ -2353,6 +5002,34 @@ function printStatus() {
2353
5002
  `MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
2354
5003
  );
2355
5004
  }
5005
+ function withHooksFlag() {
5006
+ return process.argv.includes("--hooks");
5007
+ }
5008
+ function finishCodeInstall(apiKey, baseUrl, withHooks) {
5009
+ if (withHooks) {
5010
+ const result = installClaudeHooks("user");
5011
+ console.log(
5012
+ `Installed ${result.added} MindGraph Claude Code hook entries in ${result.path}`
5013
+ );
5014
+ const envPath = saveHookEnv({ apiKey, baseUrl });
5015
+ console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
5016
+ } else {
5017
+ console.log(
5018
+ "Tip: run with --hooks (or run install-hooks) to add the Claude Code session hooks \u2014 work-brief injection, provenance, reflection checkpoint."
5019
+ );
5020
+ }
5021
+ printCodegraphStatus();
5022
+ }
5023
+ function printCodegraphStatus() {
5024
+ try {
5025
+ (0, import_child_process.execFileSync)("codegraph", ["--version"], { stdio: "pipe" });
5026
+ console.log("Code intelligence: codegraph detected \u2014 anchors and structural recall are enabled.");
5027
+ } catch {
5028
+ console.log(
5029
+ "Optional: enable code intelligence by installing codegraph (https://github.com/colbymchenry/codegraph) and running `codegraph init` in your repositories. Without it, memory and work tools still function; code anchoring degrades gracefully."
5030
+ );
5031
+ }
5032
+ }
2356
5033
  function printUsage() {
2357
5034
  console.log(`
2358
5035
  mindgraph-mcp \u2014 MCP server for MindGraph knowledge graphs
@@ -2361,14 +5038,19 @@ USAGE:
2361
5038
  mindgraph-mcp Start the MCP server (stdio transport)
2362
5039
  mindgraph-mcp init Interactive setup for Claude Desktop
2363
5040
  mindgraph-mcp install Install into Claude Desktop config
2364
- mindgraph-mcp install-code Install into Claude Code
5041
+ mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
2365
5042
  mindgraph-mcp uninstall Remove from Claude Desktop config
2366
5043
  mindgraph-mcp uninstall-code Remove from Claude Code
5044
+ mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
5045
+ mindgraph-mcp uninstall-hooks --harness claude-code [--scope user|project]
2367
5046
  mindgraph-mcp status Show installation status
2368
5047
 
2369
5048
  OPTIONS:
2370
5049
  --api-key <key> MindGraph API key (or set MINDGRAPH_API_KEY env var)
2371
5050
  --base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
5051
+ --scope <scope> Hook settings scope: project (default) or user
5052
+ --project-dir <dir> Project root for project-scoped hooks
5053
+ --harness <name> Hook harness (currently claude-code)
2372
5054
  --help, -h Show this help message
2373
5055
 
2374
5056
  EXAMPLES:
@@ -2384,38 +5066,11 @@ EXAMPLES:
2384
5066
  Get your API key at: https://mindgraph.cloud/dashboard/keys
2385
5067
  `);
2386
5068
  }
2387
- function parseArgs(argv) {
2388
- const args = argv.slice(2);
2389
- let command = "serve";
2390
- let apiKey = process.env.MINDGRAPH_API_KEY;
2391
- let baseUrl = process.env.MINDGRAPH_BASE_URL;
2392
- for (let i = 0; i < args.length; i++) {
2393
- const arg = args[i];
2394
- switch (arg) {
2395
- case "--api-key":
2396
- apiKey = args[++i];
2397
- break;
2398
- case "--base-url":
2399
- baseUrl = args[++i];
2400
- break;
2401
- case "--help":
2402
- case "-h":
2403
- command = "help";
2404
- break;
2405
- default:
2406
- if (!arg.startsWith("-")) {
2407
- command = arg;
2408
- }
2409
- break;
2410
- }
2411
- }
2412
- return { command, apiKey, baseUrl };
2413
- }
2414
5069
  var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cloud";
2415
5070
  var POLL_INTERVAL = 2e3;
2416
5071
  var POLL_TIMEOUT = 10 * 60 * 1e3;
2417
5072
  function generateCode() {
2418
- return crypto.randomBytes(32).toString("hex");
5073
+ return crypto4.randomBytes(32).toString("hex");
2419
5074
  }
2420
5075
  function openBrowser(url) {
2421
5076
  try {
@@ -2452,7 +5107,7 @@ async function pollForKey(code) {
2452
5107
  } catch (e) {
2453
5108
  if (e instanceof Error && e.message.includes("Session")) throw e;
2454
5109
  }
2455
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
5110
+ await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
2456
5111
  }
2457
5112
  throw new Error("Timed out waiting for authorization.");
2458
5113
  }
@@ -2462,7 +5117,7 @@ async function interactiveInit(baseUrl) {
2462
5117
  input: process.stdin,
2463
5118
  output: process.stdout
2464
5119
  });
2465
- const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
5120
+ const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
2466
5121
  console.log("\nMindGraph MCP Server Setup\n");
2467
5122
  console.log(" 1. Sign in via browser (recommended)");
2468
5123
  console.log(" 2. Enter API key manually");
@@ -2509,10 +5164,12 @@ async function interactiveInit(baseUrl) {
2509
5164
  break;
2510
5165
  case "2":
2511
5166
  installClaudeCode(apiKey, baseUrl);
5167
+ finishCodeInstall(apiKey, baseUrl, withHooksFlag());
2512
5168
  break;
2513
5169
  case "3":
2514
5170
  installClaudeDesktop(apiKey, baseUrl);
2515
5171
  installClaudeCode(apiKey, baseUrl);
5172
+ finishCodeInstall(apiKey, baseUrl, withHooksFlag());
2516
5173
  break;
2517
5174
  default:
2518
5175
  console.error("Invalid choice.");
@@ -2521,7 +5178,7 @@ async function interactiveInit(baseUrl) {
2521
5178
  console.log("\nDone! Your knowledge graph is now connected.");
2522
5179
  }
2523
5180
  async function main2() {
2524
- const { command, apiKey, baseUrl } = parseArgs(process.argv);
5181
+ const { command, apiKey, baseUrl, scope, projectDir, harness, hooks } = parseArgs(process.argv);
2525
5182
  switch (command) {
2526
5183
  case "help":
2527
5184
  printUsage();
@@ -2546,6 +5203,7 @@ async function main2() {
2546
5203
  process.exit(1);
2547
5204
  }
2548
5205
  installClaudeCode(apiKey, baseUrl);
5206
+ finishCodeInstall(apiKey, baseUrl, withHooksFlag());
2549
5207
  break;
2550
5208
  case "uninstall":
2551
5209
  uninstallClaudeDesktop();
@@ -2553,6 +5211,61 @@ async function main2() {
2553
5211
  case "uninstall-code":
2554
5212
  uninstallClaudeCode();
2555
5213
  break;
5214
+ case "install-hooks": {
5215
+ if ((harness || "claude-code") !== "claude-code") {
5216
+ throw new Error("only --harness claude-code is available in this release");
5217
+ }
5218
+ const result = installClaudeHooks(scope, projectDir);
5219
+ console.log(
5220
+ `Installed ${result.added} MindGraph Claude Code hook entries in ${result.path}`
5221
+ );
5222
+ if (apiKey || baseUrl) {
5223
+ const envPath = saveHookEnv({ apiKey, baseUrl });
5224
+ console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
5225
+ } else {
5226
+ console.log(
5227
+ "Note: hooks resolve MINDGRAPH_API_KEY/MINDGRAPH_BASE_URL from the environment or ~/.mindgraph/hooks.json \u2014 pass --api-key/--base-url to persist them now."
5228
+ );
5229
+ }
5230
+ break;
5231
+ }
5232
+ case "uninstall-hooks": {
5233
+ if ((harness || "claude-code") !== "claude-code") {
5234
+ throw new Error("only --harness claude-code is available in this release");
5235
+ }
5236
+ const result = uninstallClaudeHooks(scope, projectDir);
5237
+ console.log(
5238
+ `Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
5239
+ );
5240
+ break;
5241
+ }
5242
+ case "hook": {
5243
+ const stored = loadHookEnv();
5244
+ const hookApiKey = apiKey ?? stored.apiKey;
5245
+ const hookBaseUrl = baseUrl ?? stored.baseUrl;
5246
+ if (!hookApiKey) {
5247
+ process.stdout.write("{}\n");
5248
+ break;
5249
+ }
5250
+ try {
5251
+ const input = await readHookInput();
5252
+ const client2 = new import_mindgraph3.MindGraph({
5253
+ baseUrl: hookBaseUrl || "https://api.mindgraph.cloud",
5254
+ apiKey: hookApiKey,
5255
+ orgId: process.env.MINDGRAPH_ORG_ID,
5256
+ maxRetries: 0,
5257
+ telemetrySurface: "mcp"
5258
+ });
5259
+ const output = await runClaudeHook(input, client2, {
5260
+ agentId: process.env.MINDGRAPH_AGENT_ID || "claude-code"
5261
+ });
5262
+ process.stdout.write(`${JSON.stringify(output)}
5263
+ `);
5264
+ } catch {
5265
+ process.stdout.write("{}\n");
5266
+ }
5267
+ break;
5268
+ }
2556
5269
  case "status":
2557
5270
  printStatus();
2558
5271
  break;