mindgraph-mcp 0.13.5 → 0.14.1

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 +2941 -216
  4. package/dist/index.js +2145 -160
  5. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -26,13 +26,1434 @@ 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/error-detail.ts
714
+ function errorDetail(e) {
715
+ if (e instanceof import_mindgraph.MindGraphError && e.body !== void 0) {
716
+ const detail = typeof e.body === "string" ? e.body : JSON.stringify(e.body);
717
+ return `${e.message} \u2014 ${detail}`;
718
+ }
719
+ return e instanceof Error ? e.message : String(e);
720
+ }
721
+ var import_mindgraph;
722
+ var init_error_detail = __esm({
723
+ "src/error-detail.ts"() {
724
+ "use strict";
725
+ import_mindgraph = require("mindgraph");
726
+ }
727
+ });
728
+
729
+ // src/code-tool.ts
730
+ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
731
+ const codeClient = client2;
732
+ const action = args.action;
733
+ const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
734
+ const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
735
+ const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
736
+ const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : `space:agent:${agentId}`;
737
+ const options = {
738
+ repo: typeof args.repo === "string" ? args.repo : void 0,
739
+ fallbackSpaceUid
740
+ };
741
+ const limit = typeof args.limit === "number" ? args.limit : void 0;
742
+ try {
743
+ switch (action) {
744
+ case "anchor": {
745
+ if (refs.length === 0) return err("missing_code_refs", "code_refs is required for anchor");
746
+ const resolution = await adapter.resolveRefs(refs, options);
747
+ const resolved = resolution.results.flatMap(
748
+ (result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
749
+ );
750
+ const unresolved = resolution.results.filter((result) => !result.resolved);
751
+ if (resolved.length === 0) {
752
+ return ok({ ...resolution, anchors: [], wrote: false });
753
+ }
754
+ const missingSpace = resolved.find(
755
+ ({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
756
+ );
757
+ if (missingSpace) {
758
+ return err(
759
+ "missing_repository_space",
760
+ `repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
761
+ );
762
+ }
763
+ const repositoryAnchors = /* @__PURE__ */ new Map();
764
+ for (const { repository } of resolved) {
765
+ if (repositoryAnchors.has(repository.repoRoot)) continue;
766
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
767
+ const anchored = await anchorRepository(
768
+ codeClient,
769
+ repository,
770
+ spaceUid,
771
+ agentId
772
+ );
773
+ repositoryAnchors.set(repository.repoRoot, {
774
+ ...anchored,
775
+ repository
776
+ });
777
+ }
778
+ const anchors = [];
779
+ for (const { ref, repository } of resolved) {
780
+ const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
781
+ if (!repositoryAnchor.uid) continue;
782
+ const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
783
+ const anchor = await anchorCodeRef(
784
+ codeClient,
785
+ ref,
786
+ repositoryAnchor.uid,
787
+ spaceUid,
788
+ agentId
789
+ );
790
+ anchors.push(anchor);
791
+ }
792
+ return ok({
793
+ availability: resolution.availability,
794
+ repository: repositoryAnchors.values().next().value,
795
+ repositories: [...repositoryAnchors.values()],
796
+ anchors,
797
+ unresolved,
798
+ wrote: anchors.some((anchor) => anchor.created)
799
+ });
800
+ }
801
+ case "recall": {
802
+ const materialized = new Set(anchorUids);
803
+ let resolution;
804
+ if (refs.length > 0) {
805
+ resolution = await adapter.resolveRefs(refs, options);
806
+ for (const result of resolution.results) {
807
+ if (!result.resolved) continue;
808
+ const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
809
+ if (uid) materialized.add(uid);
810
+ }
811
+ }
812
+ if (materialized.size === 0) {
813
+ return ok({
814
+ availability: resolution?.availability,
815
+ anchors: [],
816
+ context: [],
817
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
818
+ });
819
+ }
820
+ const context = await Promise.all(
821
+ [...materialized].map(async (uid) => ({
822
+ anchor_uid: uid,
823
+ graph: await codeClient.traverse({
824
+ action: "neighborhood",
825
+ start_uid: uid,
826
+ depth: 2,
827
+ max_nodes: Math.min(limit ?? 20, 20)
828
+ })
829
+ }))
830
+ );
831
+ return ok({
832
+ availability: resolution?.availability,
833
+ anchors: [...materialized],
834
+ context,
835
+ unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
836
+ });
837
+ }
838
+ case "expand": {
839
+ const target = await resolveSingleTarget(
840
+ codeClient,
841
+ adapter,
842
+ refs,
843
+ anchorUids,
844
+ options
845
+ );
846
+ if ("error" in target) return target.error;
847
+ const expanded = await adapter.expand(target.ref, limit);
848
+ const joined = await joinMaterialized(
849
+ codeClient,
850
+ [...expanded.callers, ...expanded.callees],
851
+ agentId,
852
+ Math.min(limit ?? 20, 20)
853
+ );
854
+ return ok({ anchor: target.ref, ...expanded, materialized: joined });
855
+ }
856
+ case "affected": {
857
+ const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
858
+ let graphContext;
859
+ const graphRefs = [];
860
+ if (targetUid) {
861
+ graphContext = await codeClient.traverse({
862
+ action: "neighborhood",
863
+ start_uid: targetUid,
864
+ depth: 2,
865
+ max_nodes: 50
866
+ });
867
+ graphRefs.push(
868
+ ...await codeRefsFromGraphContext(codeClient, adapter, graphContext)
869
+ );
870
+ }
871
+ if (refs.length > 0 || anchorUids.length > 0) {
872
+ const target = await resolveSingleTarget(
873
+ codeClient,
874
+ adapter,
875
+ refs,
876
+ anchorUids,
877
+ options
878
+ );
879
+ if ("error" in target) return target.error;
880
+ graphRefs.unshift(target.ref);
881
+ }
882
+ const unique = uniqueRefs(graphRefs);
883
+ if (unique.length === 0) {
884
+ return ok({
885
+ target_uid: targetUid,
886
+ graph_context: graphContext,
887
+ affected: [],
888
+ degradation: "no materialized code anchors were attached"
889
+ });
890
+ }
891
+ const impacts = await Promise.all(
892
+ unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
893
+ );
894
+ const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
895
+ const joined = await joinMaterialized(
896
+ codeClient,
897
+ structural,
898
+ agentId,
899
+ Math.min(limit ?? 50, 50)
900
+ );
901
+ return ok({
902
+ target_uid: targetUid,
903
+ graph_context: graphContext,
904
+ impacts,
905
+ materialized: joined
906
+ });
907
+ }
908
+ default:
909
+ return err("unknown_action", `unknown mindgraph_code action: ${action}`);
910
+ }
911
+ } catch (cause) {
912
+ return err("code_tool_failed", errorDetail(cause));
913
+ }
914
+ }
915
+ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
916
+ if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
917
+ return result;
918
+ }
919
+ const original = JSON.parse(result.content[0]?.text ?? "null");
920
+ const targetUids = primaryUids(original);
921
+ if (targetUids.length === 0) {
922
+ return ok({
923
+ result: original,
924
+ code_anchors: {
925
+ wrote: false,
926
+ caveat: "the write response exposed no primary UID to attach"
927
+ }
928
+ });
929
+ }
930
+ const anchorResult = await handleCodeTool(
931
+ client2,
932
+ {
933
+ action: "anchor",
934
+ code_refs: args.code_refs,
935
+ repo: args.repo,
936
+ repo_space_uid: args.repo_space_uid,
937
+ agent_id: args.agent_id
938
+ },
939
+ adapter
940
+ );
941
+ const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
942
+ if (!anchorResult.isError) {
943
+ const codeClient = client2;
944
+ const anchorUids = (anchors.anchors ?? []).flatMap(
945
+ (anchor) => anchor.uid ? [anchor.uid] : []
946
+ );
947
+ for (const targetUid of targetUids) {
948
+ for (const anchorUid of anchorUids) {
949
+ await codeClient.entity({
950
+ action: "relate",
951
+ source_uid: targetUid,
952
+ target_uid: anchorUid,
953
+ edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
954
+ agent_id: args.agent_id
955
+ });
956
+ }
957
+ }
958
+ }
959
+ return ok({ result: original, code_anchors: anchors });
960
+ }
961
+ async function anchorRepository(client2, repository, spaceUid, agentId) {
962
+ return client2.entity({
963
+ action: "create",
964
+ label: repository.repoId,
965
+ props: {
966
+ entity_type: "repository",
967
+ canonical_name: repository.repoId,
968
+ identifiers: { repo_id: repository.repoId }
969
+ },
970
+ identity: {
971
+ namespace: "external.code",
972
+ key_version: 1,
973
+ key: repositoryIdentityKey(repository)
974
+ },
975
+ identity_space_uid: spaceUid,
976
+ agent_id: agentId
977
+ });
978
+ }
979
+ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
980
+ const response = await client2.entity({
981
+ action: "create",
982
+ label: ref.qualifiedName,
983
+ props: {
984
+ entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
985
+ canonical_name: ref.qualifiedName,
986
+ identifiers: {
987
+ repo_id: ref.repoId,
988
+ path: ref.path,
989
+ qualified_name: ref.qualifiedName
990
+ },
991
+ attributes: { code_ref: storedCodeRef(ref) }
992
+ },
993
+ identity: {
994
+ namespace: "external.code",
995
+ key_version: 1,
996
+ key: codeRefIdentityKey(ref)
997
+ },
998
+ identity_space_uid: spaceUid,
999
+ agent_id: agentId
1000
+ });
1001
+ if (response.uid) {
1002
+ await client2.entity({
1003
+ action: "relate",
1004
+ source_uid: response.uid,
1005
+ target_uid: repositoryUid,
1006
+ edge_type: "PART_OF",
1007
+ agent_id: agentId
1008
+ });
1009
+ }
1010
+ return { ...response, ref };
1011
+ }
1012
+ async function resolveMaterializedUid(client2, ref, agentId) {
1013
+ const response = await client2.entity({
1014
+ action: "resolve_identity",
1015
+ identity: {
1016
+ namespace: "external.code",
1017
+ key_version: 1,
1018
+ key: codeRefIdentityKey(ref)
1019
+ },
1020
+ agent_id: agentId
1021
+ });
1022
+ return response.status === "existing" ? response.uid : void 0;
1023
+ }
1024
+ async function codeRefFromAnchor(client2, adapter, uid) {
1025
+ const node = await client2.getNode(uid);
1026
+ const attributes = node.props?.attributes;
1027
+ if (!attributes || typeof attributes !== "object") return void 0;
1028
+ const codeRef = attributes.code_ref;
1029
+ if (!isStoredCodeRef(codeRef)) return void 0;
1030
+ const repository = await adapter.resolveRepository(codeRef.repoId);
1031
+ return { ...codeRef, repoRoot: repository.repoRoot };
1032
+ }
1033
+ async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
1034
+ if (refs.length > 1 || anchorUids.length > 1) {
1035
+ return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
1036
+ }
1037
+ if (anchorUids.length === 1) {
1038
+ const ref = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
1039
+ return ref ? { ref } : { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
1040
+ }
1041
+ if (refs.length !== 1) {
1042
+ return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
1043
+ }
1044
+ const resolution = await adapter.resolveRefs(refs, options);
1045
+ const result = resolution.results[0];
1046
+ if (!result.resolved) {
1047
+ return {
1048
+ error: ok({
1049
+ availability: resolution.availability,
1050
+ result,
1051
+ wrote: false
1052
+ })
1053
+ };
1054
+ }
1055
+ return { ref: result.resolved };
1056
+ }
1057
+ async function joinMaterialized(client2, refs, agentId, limit) {
1058
+ const joined = [];
1059
+ for (const ref of uniqueRefs(refs).slice(0, limit)) {
1060
+ const uid = await resolveMaterializedUid(client2, ref, agentId);
1061
+ if (!uid) continue;
1062
+ joined.push({
1063
+ ref,
1064
+ anchor_uid: uid,
1065
+ graph: await client2.traverse({
1066
+ action: "neighborhood",
1067
+ start_uid: uid,
1068
+ depth: 2,
1069
+ max_nodes: 20
1070
+ })
1071
+ });
1072
+ }
1073
+ return joined;
1074
+ }
1075
+ async function codeRefsFromGraphContext(client2, adapter, context) {
1076
+ const uids = /* @__PURE__ */ new Set();
1077
+ collectUids(context, uids);
1078
+ if (uids.size === 0) return [];
1079
+ const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
1080
+ const stored = nodes.flatMap((node) => {
1081
+ if (!node) return [];
1082
+ const attributes = node.props?.attributes;
1083
+ if (!attributes || typeof attributes !== "object") return [];
1084
+ const codeRef = attributes.code_ref;
1085
+ return isStoredCodeRef(codeRef) ? [codeRef] : [];
1086
+ });
1087
+ return Promise.all(
1088
+ stored.map(async (ref) => {
1089
+ const repository = await adapter.resolveRepository(ref.repoId);
1090
+ return { ...ref, repoRoot: repository.repoRoot };
1091
+ })
1092
+ );
1093
+ }
1094
+ function collectUids(value, uids) {
1095
+ if (Array.isArray(value)) {
1096
+ value.forEach((item) => collectUids(item, uids));
1097
+ return;
1098
+ }
1099
+ if (!value || typeof value !== "object") return;
1100
+ for (const [key, child] of Object.entries(value)) {
1101
+ if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
1102
+ uids.add(child);
1103
+ } else {
1104
+ collectUids(child, uids);
1105
+ }
1106
+ }
1107
+ }
1108
+ function isStoredCodeRef(value) {
1109
+ if (!value || typeof value !== "object") return false;
1110
+ const ref = value;
1111
+ 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";
1112
+ }
1113
+ function storedCodeRef(ref) {
1114
+ const { repoRoot: _localOnly, ...stored } = ref;
1115
+ return stored;
1116
+ }
1117
+ function uniqueRefs(refs) {
1118
+ const seen = /* @__PURE__ */ new Set();
1119
+ return refs.filter((ref) => {
1120
+ const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
1121
+ if (seen.has(key)) return false;
1122
+ seen.add(key);
1123
+ return true;
1124
+ });
1125
+ }
1126
+ function primaryUids(value) {
1127
+ if (!value || typeof value !== "object") return [];
1128
+ const object2 = value;
1129
+ const keys = [
1130
+ "uid",
1131
+ "claim_uid",
1132
+ "goal_uid",
1133
+ "project_uid",
1134
+ "milestone_uid",
1135
+ "decision_uid",
1136
+ "task_uid",
1137
+ "plan_uid",
1138
+ "step_uid",
1139
+ "execution_uid",
1140
+ "lesson_uid"
1141
+ ];
1142
+ return [
1143
+ ...new Set(
1144
+ keys.flatMap((key) => typeof object2[key] === "string" ? [object2[key]] : [])
1145
+ )
1146
+ ];
1147
+ }
1148
+ var ok, err, CODE_TOOL;
1149
+ var init_code_tool = __esm({
1150
+ "src/code-tool.ts"() {
1151
+ "use strict";
1152
+ init_error_detail();
1153
+ init_codegraph();
1154
+ ok = (data) => ({
1155
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1156
+ });
1157
+ err = (code, message, details) => ({
1158
+ content: [
1159
+ {
1160
+ type: "text",
1161
+ text: JSON.stringify({ error: message, code, details }, null, 2)
1162
+ }
1163
+ ],
1164
+ isError: true
1165
+ });
1166
+ CODE_TOOL = {
1167
+ name: "mindgraph_code",
1168
+ 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.",
1169
+ inputSchema: {
1170
+ type: "object",
1171
+ properties: {
1172
+ action: {
1173
+ type: "string",
1174
+ enum: ["anchor", "recall", "expand", "affected"]
1175
+ },
1176
+ code_refs: {
1177
+ type: "array",
1178
+ maxItems: 50,
1179
+ items: {
1180
+ oneOf: [
1181
+ { type: "string" },
1182
+ {
1183
+ type: "object",
1184
+ properties: {
1185
+ repo: { type: "string" },
1186
+ path: { type: "string" },
1187
+ line: { type: "integer", minimum: 1 },
1188
+ symbol: { type: "string" },
1189
+ kind: { type: "string" },
1190
+ language: { type: "string" },
1191
+ signature: { type: "string" }
1192
+ },
1193
+ additionalProperties: false
1194
+ }
1195
+ ]
1196
+ }
1197
+ },
1198
+ anchor_uids: {
1199
+ type: "array",
1200
+ maxItems: 50,
1201
+ items: { type: "string" },
1202
+ description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
1203
+ },
1204
+ repo: {
1205
+ type: "string",
1206
+ description: "Configured repository id or repository root"
1207
+ },
1208
+ repo_space_uid: {
1209
+ type: "string",
1210
+ description: "Writable shared Project/repository Space for new anchors"
1211
+ },
1212
+ task_uid: { type: "string" },
1213
+ execution_uid: { type: "string" },
1214
+ limit: { type: "integer", minimum: 1, maximum: 50 },
1215
+ agent_id: { type: "string" }
1216
+ },
1217
+ required: ["action"]
1218
+ },
1219
+ annotations: {
1220
+ readOnlyHint: false,
1221
+ destructiveHint: false,
1222
+ idempotentHint: true,
1223
+ openWorldHint: true
1224
+ }
1225
+ };
1226
+ }
1227
+ });
1228
+
1229
+ // src/sync-tool.ts
1230
+ function safePath(root, candidate) {
1231
+ const absolute = import_node_path5.default.resolve(root, candidate);
1232
+ const relative2 = import_node_path5.default.relative(root, absolute);
1233
+ if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
1234
+ throw new Error(`path escapes repository root: ${candidate}`);
1235
+ }
1236
+ return absolute;
1237
+ }
1238
+ function markdownFiles(target) {
1239
+ if (!import_node_fs5.default.existsSync(target)) return [];
1240
+ const stat = import_node_fs5.default.statSync(target);
1241
+ if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
1242
+ if (!stat.isDirectory()) return [];
1243
+ return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name)));
1244
+ }
1245
+ function fileDescriptor(root, absolute) {
1246
+ const content = import_node_fs5.default.readFileSync(absolute, "utf8");
1247
+ return {
1248
+ logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
1249
+ content_hash: import_node_crypto4.default.createHash("sha256").update(content).digest("hex"),
1250
+ bytes: Buffer.byteLength(content),
1251
+ content
1252
+ };
1253
+ }
1254
+ async function handleSyncTool(client2, args) {
1255
+ try {
1256
+ const action = String(args.action || "");
1257
+ const context = args.invocation_context || {};
1258
+ const adapter = new CodegraphAdapter({
1259
+ cwd: context.cwd || process.cwd()
1260
+ });
1261
+ const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
1262
+ const repository = await adapter.resolveRepository(requestedRepo);
1263
+ const root = repository.repoRoot;
1264
+ const repoId = repository.repoId;
1265
+ const provider = String(args.provider || "claude_project");
1266
+ const remote = client2;
1267
+ if (action === "scan") {
1268
+ const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
1269
+ const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
1270
+ const scans = [];
1271
+ for (const selected of repositories) {
1272
+ scans.push(
1273
+ await scanRepository(
1274
+ remote,
1275
+ selected,
1276
+ requested,
1277
+ provider,
1278
+ args.agent_id
1279
+ )
1280
+ );
1281
+ }
1282
+ if (args.workspace === true) {
1283
+ return ok2({
1284
+ provider,
1285
+ workspace: true,
1286
+ repositories: scans,
1287
+ one_way: "files_to_graph"
1288
+ });
1289
+ }
1290
+ return ok2(scans[0]);
1291
+ }
1292
+ const request = {
1293
+ ...args,
1294
+ provider,
1295
+ repo_id: repoId
1296
+ };
1297
+ delete request.invocation_context;
1298
+ delete request.paths;
1299
+ delete request.repo;
1300
+ delete request.workspace;
1301
+ if (action === "begin" && !request.content) {
1302
+ if (!request.logical_path) {
1303
+ return err2("missing_field", "logical_path is required for begin");
1304
+ }
1305
+ const file = fileDescriptor(
1306
+ root,
1307
+ safePath(root, String(request.logical_path))
1308
+ );
1309
+ request.content = file.content;
1310
+ request.content_hash ||= file.content_hash;
1311
+ }
1312
+ if (action === "status" && request.logical_path && request.file_present !== false) {
1313
+ const absolute = safePath(root, String(request.logical_path));
1314
+ request.file_present = import_node_fs5.default.existsSync(absolute);
1315
+ if (request.file_present && !request.content_hash) {
1316
+ request.content_hash = fileDescriptor(root, absolute).content_hash;
1317
+ }
1318
+ }
1319
+ return ok2(await remote.memorySync(request));
1320
+ } catch (error) {
1321
+ return err2("sync_failed", errorDetail(error));
1322
+ }
1323
+ }
1324
+ async function scanRepository(remote, repository, requested, provider, agentId) {
1325
+ const files = [
1326
+ ...new Set(
1327
+ requested.flatMap(
1328
+ (candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
1329
+ )
1330
+ )
1331
+ ].sort();
1332
+ const results = [];
1333
+ for (const absolute of files) {
1334
+ const file = fileDescriptor(repository.repoRoot, absolute);
1335
+ const status = await remote.memorySync({
1336
+ action: "scan",
1337
+ provider,
1338
+ repo_id: repository.repoId,
1339
+ logical_path: file.logical_path,
1340
+ content_hash: file.content_hash,
1341
+ file_present: true,
1342
+ agent_id: agentId
1343
+ });
1344
+ results.push({
1345
+ logical_path: file.logical_path,
1346
+ content_hash: file.content_hash,
1347
+ bytes: file.bytes,
1348
+ status
1349
+ });
1350
+ }
1351
+ return {
1352
+ repo_id: repository.repoId,
1353
+ root: ".",
1354
+ files: results
1355
+ };
1356
+ }
1357
+ var import_node_crypto4, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
1358
+ var init_sync_tool = __esm({
1359
+ "src/sync-tool.ts"() {
1360
+ "use strict";
1361
+ import_node_crypto4 = __toESM(require("crypto"));
1362
+ import_node_fs5 = __toESM(require("fs"));
1363
+ import_node_path5 = __toESM(require("path"));
1364
+ init_codegraph();
1365
+ init_error_detail();
1366
+ ok2 = (data) => ({
1367
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1368
+ });
1369
+ err2 = (code, message) => ({
1370
+ content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
1371
+ isError: true
1372
+ });
1373
+ SYNC_TOOL = {
1374
+ name: "mindgraph_sync",
1375
+ 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.",
1376
+ annotations: {
1377
+ readOnlyHint: false,
1378
+ destructiveHint: false,
1379
+ idempotentHint: true,
1380
+ openWorldHint: true
1381
+ },
1382
+ inputSchema: {
1383
+ type: "object",
1384
+ properties: {
1385
+ action: {
1386
+ type: "string",
1387
+ enum: ["scan", "begin", "record", "finalize", "status", "abandon"]
1388
+ },
1389
+ paths: { type: "array", items: { type: "string" } },
1390
+ workspace: {
1391
+ type: "boolean",
1392
+ description: "For scan only: fan out across repositories explicitly listed in .mindgraph/workspace.json"
1393
+ },
1394
+ repo: {
1395
+ type: "string",
1396
+ description: "Configured repository id, name, or root"
1397
+ },
1398
+ logical_path: { type: "string" },
1399
+ content: { type: "string" },
1400
+ content_hash: { type: "string" },
1401
+ source_uid: { type: "string" },
1402
+ execution_uid: { type: "string" },
1403
+ assertion_fingerprint: { type: "string" },
1404
+ planned_fingerprints: { type: "array", items: { type: "string" } },
1405
+ current_fingerprints: { type: "array", items: { type: "string" } },
1406
+ source_selector: { type: "object" },
1407
+ capture_type: {
1408
+ type: "string",
1409
+ enum: ["lesson", "journal", "claim", "decision", "risk"]
1410
+ },
1411
+ label: { type: "string" },
1412
+ summary: { type: "string" },
1413
+ props: { type: "object" },
1414
+ expected_execution_version: { type: "integer", minimum: 1 },
1415
+ baseline_version: { type: "integer", minimum: 1 },
1416
+ repo_space_uid: { type: "string" },
1417
+ repo_id: { type: "string" },
1418
+ provider: { type: "string", enum: ["claude_project", "explicit"] },
1419
+ file_present: { type: "boolean" },
1420
+ agent_id: { type: "string" },
1421
+ invocation_context: {
1422
+ type: "object",
1423
+ description: "Adapter-owned; hooks replace forged or absent values.",
1424
+ properties: {
1425
+ harness: { type: "string" },
1426
+ harnessSessionId: { type: "string" },
1427
+ harnessTurnId: { type: "string" },
1428
+ cwd: { type: "string" },
1429
+ repoId: { type: "string" },
1430
+ branch: { type: "string" },
1431
+ commit: { type: "string" },
1432
+ worktreeState: { type: "string" },
1433
+ model: { type: "string" },
1434
+ injectedBy: { type: "string" }
1435
+ }
1436
+ }
1437
+ },
1438
+ required: ["action"]
1439
+ }
1440
+ };
1441
+ }
1442
+ });
1443
+
29
1444
  // src/tools.ts
30
- function ok(data) {
1445
+ function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
1446
+ if (profile === "coding") return TOOLS;
1447
+ return TOOLS.filter(
1448
+ (tool) => tool.name !== "mindgraph_code" && tool.name !== "mindgraph_sync"
1449
+ );
1450
+ }
1451
+ function ok3(data) {
31
1452
  return {
32
1453
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
33
1454
  };
34
1455
  }
35
- function err(message) {
1456
+ function err3(message) {
36
1457
  return {
37
1458
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
38
1459
  isError: true
@@ -40,29 +1461,53 @@ function err(message) {
40
1461
  }
41
1462
  async function handleTool(client2, name, args) {
42
1463
  try {
1464
+ let result;
43
1465
  switch (name) {
44
1466
  case "mindgraph_capture":
45
- return await handleCapture(client2, args);
1467
+ result = await handleCapture(client2, args);
1468
+ break;
46
1469
  case "mindgraph_reason":
47
- return await handleReason(client2, args);
1470
+ result = await handleReason(client2, args);
1471
+ break;
48
1472
  case "mindgraph_commit":
49
- return await handleCommit(client2, args);
1473
+ result = await handleCommit(client2, args);
1474
+ break;
50
1475
  case "mindgraph_plan":
51
- return await handlePlan(client2, args);
1476
+ result = await handlePlan(client2, args);
1477
+ break;
52
1478
  case "mindgraph_retrieve":
53
- return await handleRetrieve(client2, args);
1479
+ result = await handleRetrieve(client2, args);
1480
+ break;
54
1481
  case "mindgraph_ingest":
55
- return await handleIngest(client2, args);
1482
+ result = await handleIngest(client2, args);
1483
+ break;
56
1484
  case "mindgraph_synthesize":
57
- return await handleSynthesize(client2, args);
1485
+ result = await handleSynthesize(client2, args);
1486
+ break;
58
1487
  case "mindgraph_ontology":
59
- return await handleOntology(client2, args);
1488
+ result = await handleOntology(client2, args);
1489
+ break;
1490
+ case "mindgraph_code":
1491
+ result = await handleCodeTool(client2, args);
1492
+ break;
1493
+ case "mindgraph_sync":
1494
+ result = await handleSyncTool(client2, args);
1495
+ break;
60
1496
  default:
61
- return err(`Unknown tool: ${name}`);
1497
+ result = err3(`Unknown tool: ${name}`);
62
1498
  }
1499
+ const action = typeof args.action === "string" ? args.action : "";
1500
+ const attach = name === "mindgraph_capture" || name === "mindgraph_commit" && action !== "get_open_decisions" || name === "mindgraph_plan" && ![
1501
+ "get_plan",
1502
+ "get_executions",
1503
+ "get_pending",
1504
+ "get_assessments",
1505
+ "resume_work",
1506
+ "heartbeat"
1507
+ ].includes(action);
1508
+ return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
63
1509
  } catch (e) {
64
- const message = e instanceof Error ? e.message : String(e);
65
- return err(message);
1510
+ return err3(errorDetail(e));
66
1511
  }
67
1512
  }
68
1513
  async function handleCapture(client2, args) {
@@ -71,6 +1516,12 @@ async function handleCapture(client2, args) {
71
1516
  label,
72
1517
  summary,
73
1518
  content,
1519
+ session_uid,
1520
+ work_uid,
1521
+ execution_uid,
1522
+ idempotency_key,
1523
+ supersedes_uid,
1524
+ summarizes_uids,
74
1525
  entity_type,
75
1526
  source_uid,
76
1527
  mood,
@@ -81,12 +1532,30 @@ async function handleCapture(client2, args) {
81
1532
  agent_id
82
1533
  } = args;
83
1534
  switch (action) {
1535
+ case "lesson":
1536
+ return ok3(
1537
+ await client2.distill({
1538
+ label,
1539
+ output_type: "lesson",
1540
+ summary,
1541
+ confidence,
1542
+ salience,
1543
+ session_uid,
1544
+ work_uid,
1545
+ execution_uid,
1546
+ idempotency_key,
1547
+ supersedes_uid,
1548
+ summarizes_uids,
1549
+ props,
1550
+ agent_id
1551
+ })
1552
+ );
84
1553
  case "journal": {
85
- if (!content) return err("content is required for action=journal");
1554
+ if (!content) return err3("content is required for action=journal");
86
1555
  const journalProps = { content };
87
1556
  if (mood) journalProps.mood = mood;
88
1557
  if (tags) journalProps.tags = tags;
89
- return ok(await client2.journal(label, journalProps, { agent_id }));
1558
+ return ok3(await client2.journal(label, journalProps, { agent_id }));
90
1559
  }
91
1560
  case "entity": {
92
1561
  const type = entity_type || "concept";
@@ -94,22 +1563,22 @@ async function handleCapture(client2, args) {
94
1563
  if (summary) entityProps.description = summary;
95
1564
  switch (type) {
96
1565
  case "person":
97
- return ok(await client2.findOrCreatePerson(label, entityProps, agent_id));
1566
+ return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
98
1567
  case "organization":
99
- return ok(await client2.findOrCreateOrganization(label, entityProps, agent_id));
1568
+ return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
100
1569
  case "nation":
101
- return ok(await client2.findOrCreateNation(label, entityProps, agent_id));
1570
+ return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
102
1571
  case "event":
103
- return ok(await client2.findOrCreateEvent(label, entityProps, agent_id));
1572
+ return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
104
1573
  case "place":
105
- return ok(await client2.findOrCreatePlace(label, entityProps, agent_id));
1574
+ return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
106
1575
  case "concept":
107
1576
  default:
108
- return ok(await client2.findOrCreateConcept(label, entityProps, agent_id));
1577
+ return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
109
1578
  }
110
1579
  }
111
1580
  case "observation":
112
- return ok(
1581
+ return ok3(
113
1582
  await client2.capture({
114
1583
  action: "observation",
115
1584
  label,
@@ -121,7 +1590,7 @@ async function handleCapture(client2, args) {
121
1590
  })
122
1591
  );
123
1592
  case "source":
124
- return ok(
1593
+ return ok3(
125
1594
  await client2.capture({
126
1595
  action: "source",
127
1596
  label,
@@ -133,7 +1602,7 @@ async function handleCapture(client2, args) {
133
1602
  })
134
1603
  );
135
1604
  case "snippet":
136
- return ok(
1605
+ return ok3(
137
1606
  await client2.capture({
138
1607
  action: "snippet",
139
1608
  label,
@@ -146,7 +1615,7 @@ async function handleCapture(client2, args) {
146
1615
  })
147
1616
  );
148
1617
  case "concept":
149
- return ok(
1618
+ return ok3(
150
1619
  await client2.structure({
151
1620
  action: "concept",
152
1621
  label,
@@ -158,7 +1627,7 @@ async function handleCapture(client2, args) {
158
1627
  })
159
1628
  );
160
1629
  default:
161
- return err(`Unknown capture action: ${action}`);
1630
+ return err3(`Unknown capture action: ${action}`);
162
1631
  }
163
1632
  }
164
1633
  async function handleReason(client2, args) {
@@ -176,20 +1645,30 @@ async function handleReason(client2, args) {
176
1645
  agent_id
177
1646
  } = args;
178
1647
  if (action === "claim") {
179
- const claimObj = claim || (label ? { label, confidence } : null);
180
- if (!claimObj) return err("claim object or label is required for action=claim");
181
- return ok(
1648
+ const claimObj = claim ? {
1649
+ label: claim.label,
1650
+ confidence: claim.confidence,
1651
+ ...claim.content ? { props: { content: claim.content } } : {}
1652
+ } : label ? { label, confidence } : null;
1653
+ if (!claimObj) return err3("claim object or label is required for action=claim");
1654
+ return ok3(
182
1655
  await client2.argue({
183
1656
  claim: claimObj,
184
- evidence,
185
- warrant,
1657
+ evidence: evidence?.map((e) => ({
1658
+ label: e.label,
1659
+ ...e.description ? { props: { description: e.description } } : {}
1660
+ })),
1661
+ warrant: warrant ? {
1662
+ label: warrant.label,
1663
+ ...warrant.content ? { props: { content: warrant.content } } : {}
1664
+ } : void 0,
186
1665
  argument,
187
1666
  agent_id
188
1667
  })
189
1668
  );
190
1669
  }
191
- if (!label) return err(`label is required for action=${action}`);
192
- return ok(
1670
+ if (!label) return err3(`label is required for action=${action}`);
1671
+ return ok3(
193
1672
  await client2.inquire({
194
1673
  action,
195
1674
  label,
@@ -222,8 +1701,8 @@ async function handleCommit(client2, args) {
222
1701
  case "goal":
223
1702
  case "project":
224
1703
  case "milestone":
225
- if (!label) return err(`label is required for action=${action}`);
226
- return ok(
1704
+ if (!label) return err3(`label is required for action=${action}`);
1705
+ return ok3(
227
1706
  await client2.commit({
228
1707
  action,
229
1708
  label,
@@ -236,15 +1715,15 @@ async function handleCommit(client2, args) {
236
1715
  })
237
1716
  );
238
1717
  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 }));
1718
+ if (!label) return err3("label is required for open_decision");
1719
+ return ok3(await client2.openDecision(label, { summary, props, agent_id }));
241
1720
  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 }));
1721
+ if (!decision_uid) return err3("decision_uid is required for add_option");
1722
+ if (!label) return err3("label is required for add_option");
1723
+ return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
245
1724
  case "add_constraint":
246
- if (!decision_uid) return err("decision_uid is required for add_constraint");
247
- return ok(
1725
+ if (!decision_uid) return err3("decision_uid is required for add_constraint");
1726
+ return ok3(
248
1727
  await client2.deliberate({
249
1728
  action: "add_constraint",
250
1729
  label,
@@ -255,9 +1734,9 @@ async function handleCommit(client2, args) {
255
1734
  })
256
1735
  );
257
1736
  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(
1737
+ if (!decision_uid) return err3("decision_uid is required for resolve_decision");
1738
+ if (!chosen_option_uid) return err3("chosen_option_uid is required for resolve_decision");
1739
+ return ok3(
261
1740
  await client2.resolveDecision(decision_uid, chosen_option_uid, {
262
1741
  summary,
263
1742
  props,
@@ -269,9 +1748,9 @@ async function handleCommit(client2, args) {
269
1748
  })
270
1749
  );
271
1750
  case "get_open_decisions":
272
- return ok(await client2.getOpenDecisions());
1751
+ return ok3(await client2.getOpenDecisions());
273
1752
  default:
274
- return err(`Unknown commit action: ${action}`);
1753
+ return err3(`Unknown commit action: ${action}`);
275
1754
  }
276
1755
  }
277
1756
  async function handlePlan(client2, args) {
@@ -281,8 +1760,41 @@ async function handlePlan(client2, args) {
281
1760
  summary,
282
1761
  plan_uid,
283
1762
  task_uid,
1763
+ step_uid,
1764
+ session_uid,
1765
+ goal_uid,
284
1766
  target_uid,
1767
+ execution_uid,
1768
+ executor_uid,
1769
+ affordance_uid,
1770
+ produces_node_uid,
1771
+ filter_plan_uid,
1772
+ depends_on_uids,
1773
+ related_uids,
285
1774
  status,
1775
+ task_status,
1776
+ step_status,
1777
+ execution_status,
1778
+ expected_version,
1779
+ lease_epoch,
1780
+ lease_ttl_secs,
1781
+ idempotency_key,
1782
+ allow_takeover,
1783
+ release_lease,
1784
+ override_reason,
1785
+ scope_uids,
1786
+ limit,
1787
+ confidence,
1788
+ salience,
1789
+ input_snapshot,
1790
+ output_snapshot,
1791
+ side_effects,
1792
+ outcome,
1793
+ error,
1794
+ next_action,
1795
+ checkpoint_summary,
1796
+ test_summary,
1797
+ produces_node_uids,
286
1798
  props,
287
1799
  agent_id
288
1800
  } = args;
@@ -293,25 +1805,120 @@ async function handlePlan(client2, args) {
293
1805
  case "add_step":
294
1806
  case "update_status":
295
1807
  case "get_plan":
296
- return ok(
1808
+ case "resume_work":
1809
+ case "claim_task":
1810
+ case "heartbeat":
1811
+ case "start_iteration":
1812
+ case "checkpoint_iteration":
1813
+ case "block_task":
1814
+ case "complete_task":
1815
+ case "abandon_iteration":
1816
+ return ok3(
297
1817
  await client2.plan({
298
1818
  action,
299
1819
  label,
300
1820
  summary,
1821
+ confidence,
1822
+ salience,
1823
+ goal_uid,
301
1824
  plan_uid,
302
1825
  task_uid,
1826
+ step_uid,
1827
+ session_uid,
1828
+ target_uid,
1829
+ depends_on_uids,
1830
+ related_uids,
303
1831
  status,
1832
+ task_status,
1833
+ step_status,
1834
+ execution_status,
1835
+ execution_uid,
1836
+ expected_version,
1837
+ lease_epoch,
1838
+ lease_ttl_secs,
1839
+ idempotency_key,
1840
+ allow_takeover,
1841
+ release_lease,
1842
+ override_reason,
1843
+ scope_uids,
1844
+ limit,
1845
+ input_snapshot,
1846
+ output_snapshot,
1847
+ side_effects,
1848
+ outcome,
1849
+ error,
1850
+ next_action,
1851
+ checkpoint_summary,
1852
+ test_summary,
1853
+ produces_node_uids,
304
1854
  props,
305
1855
  agent_id
306
1856
  })
307
1857
  );
308
1858
  // Execution tracking
309
1859
  case "start_execution":
310
- return ok(await client2.execution({ action: "start", task_uid, agent_id }));
1860
+ return ok3(
1861
+ await client2.execution({
1862
+ action: "start",
1863
+ label,
1864
+ summary,
1865
+ confidence,
1866
+ salience,
1867
+ plan_uid,
1868
+ executor_uid,
1869
+ affordance_uid,
1870
+ related_uids,
1871
+ input_snapshot,
1872
+ props,
1873
+ agent_id
1874
+ })
1875
+ );
311
1876
  case "complete_execution":
312
- return ok(await client2.execution({ action: "complete", task_uid, agent_id }));
1877
+ return ok3(
1878
+ await client2.execution({
1879
+ action: "complete",
1880
+ execution_uid,
1881
+ produces_node_uid,
1882
+ output_snapshot,
1883
+ side_effects,
1884
+ outcome,
1885
+ props,
1886
+ agent_id
1887
+ })
1888
+ );
313
1889
  case "fail_execution":
314
- return ok(await client2.execution({ action: "fail", task_uid, agent_id }));
1890
+ return ok3(
1891
+ await client2.execution({
1892
+ action: "fail",
1893
+ execution_uid,
1894
+ output_snapshot,
1895
+ side_effects,
1896
+ outcome,
1897
+ error,
1898
+ props,
1899
+ agent_id
1900
+ })
1901
+ );
1902
+ case "register_agent":
1903
+ return ok3(
1904
+ await client2.execution({
1905
+ action: "register_agent",
1906
+ label,
1907
+ summary,
1908
+ confidence,
1909
+ salience,
1910
+ props,
1911
+ agent_id
1912
+ })
1913
+ );
1914
+ case "get_executions":
1915
+ return ok3(
1916
+ await client2.execution({
1917
+ action: "get_executions",
1918
+ filter_plan_uid,
1919
+ agent_id
1920
+ })
1921
+ );
315
1922
  // Governance — read-only from MCP by design (C33). The write actions were
316
1923
  // removed rather than repaired: `resolve_approval` here also sent
317
1924
  // `task_uid` where the server requires `approval_uid`, and never sent
@@ -319,12 +1926,12 @@ async function handlePlan(client2, args) {
319
1926
  // DENIED every approval an agent tried to resolve. Wiring that correctly
320
1927
  // would have meant building a working self-approval path — the hole itself.
321
1928
  case "get_pending":
322
- return ok(await client2.governance({ action: "get_pending", agent_id }));
1929
+ return ok3(await client2.governance({ action: "get_pending", agent_id }));
323
1930
  // Procedures (merged from mindgraph_action)
324
1931
  case "create_flow":
325
1932
  case "add_affordance":
326
1933
  case "add_control":
327
- return ok(
1934
+ return ok3(
328
1935
  await client2.procedure({
329
1936
  action,
330
1937
  label,
@@ -335,7 +1942,7 @@ async function handlePlan(client2, args) {
335
1942
  })
336
1943
  );
337
1944
  case "add_procedure_step":
338
- return ok(
1945
+ return ok3(
339
1946
  await client2.procedure({
340
1947
  action: "add_step",
341
1948
  label,
@@ -347,7 +1954,7 @@ async function handlePlan(client2, args) {
347
1954
  );
348
1955
  // Risk assessment (merged from mindgraph_action)
349
1956
  case "assess_risk":
350
- return ok(
1957
+ return ok3(
351
1958
  await client2.risk({
352
1959
  action: "assess",
353
1960
  label,
@@ -358,7 +1965,7 @@ async function handlePlan(client2, args) {
358
1965
  })
359
1966
  );
360
1967
  case "get_assessments":
361
- return ok(
1968
+ return ok3(
362
1969
  await client2.risk({
363
1970
  action: "get_assessments",
364
1971
  target_uid,
@@ -366,7 +1973,7 @@ async function handlePlan(client2, args) {
366
1973
  })
367
1974
  );
368
1975
  default:
369
- return err(`Unknown plan action: ${action}`);
1976
+ return err3(`Unknown plan action: ${action}`);
370
1977
  }
371
1978
  }
372
1979
  async function handleRetrieve(client2, args) {
@@ -395,8 +2002,8 @@ async function handleRetrieve(client2, args) {
395
2002
  } = args;
396
2003
  switch (action) {
397
2004
  case "context":
398
- if (!query) return err("query is required for context retrieval");
399
- return ok(
2005
+ if (!query) return err3("query is required for context retrieval");
2006
+ return ok3(
400
2007
  await client2.retrieveContext({
401
2008
  query,
402
2009
  node_limit: limit,
@@ -409,8 +2016,8 @@ async function handleRetrieve(client2, args) {
409
2016
  })
410
2017
  );
411
2018
  case "text":
412
- if (!query) return err("query is required for text search");
413
- return ok(
2019
+ if (!query) return err3("query is required for text search");
2020
+ return ok3(
414
2021
  await client2.retrieve({
415
2022
  action: "text",
416
2023
  query,
@@ -421,8 +2028,8 @@ async function handleRetrieve(client2, args) {
421
2028
  })
422
2029
  );
423
2030
  case "semantic":
424
- if (!query) return err("query is required for semantic search");
425
- return ok(
2031
+ if (!query) return err3("query is required for semantic search");
2032
+ return ok3(
426
2033
  await client2.retrieve({
427
2034
  action: "semantic",
428
2035
  query,
@@ -433,8 +2040,8 @@ async function handleRetrieve(client2, args) {
433
2040
  })
434
2041
  );
435
2042
  case "hybrid":
436
- if (!query) return err("query is required for hybrid search");
437
- return ok(
2043
+ if (!query) return err3("query is required for hybrid search");
2044
+ return ok3(
438
2045
  await client2.retrieve({
439
2046
  action: "hybrid",
440
2047
  query,
@@ -446,17 +2053,17 @@ async function handleRetrieve(client2, args) {
446
2053
  );
447
2054
  // Structured queries
448
2055
  case "active_goals":
449
- return ok(await client2.getGoals());
2056
+ return ok3(await client2.getGoals());
450
2057
  case "open_questions":
451
- return ok(await client2.getOpenQuestions());
2058
+ return ok3(await client2.getOpenQuestions());
452
2059
  case "weak_claims":
453
- return ok(await client2.getWeakClaims());
2060
+ return ok3(await client2.getWeakClaims());
454
2061
  case "pending_approvals":
455
- return ok(await client2.getPendingApprovals());
2062
+ return ok3(await client2.getPendingApprovals());
456
2063
  case "unresolved_contradictions":
457
- return ok(await client2.getContradictions());
2064
+ return ok3(await client2.getContradictions());
458
2065
  case "stale_derivations":
459
- return ok(
2066
+ return ok3(
460
2067
  await client2.retrieve({
461
2068
  action: "stale_derivations",
462
2069
  limit,
@@ -464,7 +2071,7 @@ async function handleRetrieve(client2, args) {
464
2071
  })
465
2072
  );
466
2073
  case "preferences":
467
- return ok(
2074
+ return ok3(
468
2075
  await client2.retrieve({
469
2076
  action: "preferences",
470
2077
  ...query ? { query, k: limit } : {},
@@ -473,8 +2080,8 @@ async function handleRetrieve(client2, args) {
473
2080
  })
474
2081
  );
475
2082
  case "layer":
476
- if (!layer) return err("layer is required for layer retrieval");
477
- return ok(
2083
+ if (!layer) return err3("layer is required for layer retrieval");
2084
+ return ok3(
478
2085
  await client2.retrieve({
479
2086
  action: "layer",
480
2087
  layer,
@@ -483,7 +2090,7 @@ async function handleRetrieve(client2, args) {
483
2090
  })
484
2091
  );
485
2092
  case "recent":
486
- return ok(
2093
+ return ok3(
487
2094
  await client2.retrieve({
488
2095
  action: "recent",
489
2096
  limit,
@@ -495,7 +2102,7 @@ async function handleRetrieve(client2, args) {
495
2102
  );
496
2103
  // Document inventory
497
2104
  case "document_index":
498
- return ok(
2105
+ return ok3(
499
2106
  await client2.getNodes({
500
2107
  node_type: "Document",
501
2108
  limit: limit ?? 100
@@ -503,8 +2110,8 @@ async function handleRetrieve(client2, args) {
503
2110
  );
504
2111
  // Graph traversal
505
2112
  case "chain":
506
- if (!start_uid) return err("start_uid is required for chain traversal");
507
- return ok(
2113
+ if (!start_uid) return err3("start_uid is required for chain traversal");
2114
+ return ok3(
508
2115
  await client2.traverse({
509
2116
  action: "chain",
510
2117
  start_uid,
@@ -515,8 +2122,8 @@ async function handleRetrieve(client2, args) {
515
2122
  })
516
2123
  );
517
2124
  case "neighborhood":
518
- if (!start_uid) return err("start_uid is required for neighborhood traversal");
519
- return ok(
2125
+ if (!start_uid) return err3("start_uid is required for neighborhood traversal");
2126
+ return ok3(
520
2127
  await client2.traverse({
521
2128
  action: "neighborhood",
522
2129
  start_uid,
@@ -529,9 +2136,9 @@ async function handleRetrieve(client2, args) {
529
2136
  })
530
2137
  );
531
2138
  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(
2139
+ if (!start_uid) return err3("start_uid is required for path traversal");
2140
+ if (!end_uid) return err3("end_uid is required for path traversal");
2141
+ return ok3(
535
2142
  await client2.traverse({
536
2143
  action: "path",
537
2144
  start_uid,
@@ -545,8 +2152,8 @@ async function handleRetrieve(client2, args) {
545
2152
  })
546
2153
  );
547
2154
  case "subgraph":
548
- if (!start_uid) return err("start_uid is required for subgraph traversal");
549
- return ok(
2155
+ if (!start_uid) return err3("start_uid is required for subgraph traversal");
2156
+ return ok3(
550
2157
  await client2.traverse({
551
2158
  action: "subgraph",
552
2159
  start_uid,
@@ -559,7 +2166,7 @@ async function handleRetrieve(client2, args) {
559
2166
  })
560
2167
  );
561
2168
  default:
562
- return err(`Unknown retrieve action: ${action}`);
2169
+ return err3(`Unknown retrieve action: ${action}`);
563
2170
  }
564
2171
  }
565
2172
  async function handleIngest(client2, args) {
@@ -588,7 +2195,7 @@ async function handleIngest(client2, args) {
588
2195
  const targetsOntology = Array.isArray(layers) && layers.includes("ontology");
589
2196
  const cognitiveLayers = targetsOntology ? layers.filter((layer) => layer !== "ontology") : layers;
590
2197
  if (targetsOntology && !ontology_schema_id) {
591
- return err(
2198
+ return err3(
592
2199
  "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
2200
  );
594
2201
  }
@@ -596,8 +2203,8 @@ async function handleIngest(client2, args) {
596
2203
  const ontologyOnly = targetsOntology && forwardedLayers === void 0;
597
2204
  switch (action) {
598
2205
  case "chunk":
599
- if (!content) return err("content is required for chunk ingestion");
600
- return ok(
2206
+ if (!content) return err3("content is required for chunk ingestion");
2207
+ return ok3(
601
2208
  await client2.ingestChunk({
602
2209
  content,
603
2210
  label: title,
@@ -611,8 +2218,8 @@ async function handleIngest(client2, args) {
611
2218
  })
612
2219
  );
613
2220
  case "document":
614
- if (!content) return err("content is required for document ingestion");
615
- return ok(
2221
+ if (!content) return err3("content is required for document ingestion");
2222
+ return ok3(
616
2223
  await client2.ingestDocument({
617
2224
  content,
618
2225
  title,
@@ -631,8 +2238,8 @@ async function handleIngest(client2, args) {
631
2238
  })
632
2239
  );
633
2240
  case "session":
634
- if (!content) return err("content is required for session ingestion");
635
- return ok(
2241
+ if (!content) return err3("content is required for session ingestion");
2242
+ return ok3(
636
2243
  await client2.ingestSession({
637
2244
  content,
638
2245
  title,
@@ -649,11 +2256,11 @@ async function handleIngest(client2, args) {
649
2256
  );
650
2257
  case "job_status":
651
2258
  if (job_id) {
652
- return ok(await client2.getJob(job_id));
2259
+ return ok3(await client2.getJob(job_id));
653
2260
  }
654
- return ok(await client2.listJobs());
2261
+ return ok3(await client2.listJobs());
655
2262
  default:
656
- return err(`Unknown ingest action: ${action}`);
2263
+ return err3(`Unknown ingest action: ${action}`);
657
2264
  }
658
2265
  }
659
2266
  async function handleSynthesize(client2, args) {
@@ -661,19 +2268,19 @@ async function handleSynthesize(client2, args) {
661
2268
  switch (action) {
662
2269
  case "signals":
663
2270
  if (!project_uid)
664
- return err("project_uid is required for action='signals'");
665
- return ok(
2271
+ return err3("project_uid is required for action='signals'");
2272
+ return ok3(
666
2273
  await client2.signals(project_uid, { signals, target_types })
667
2274
  );
668
2275
  case "run":
669
2276
  if (!project_uid)
670
- return err("project_uid is required for action='run'");
671
- return ok(await client2.runSynthesis(project_uid));
2277
+ return err3("project_uid is required for action='run'");
2278
+ return ok3(await client2.runSynthesis(project_uid));
672
2279
  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));
2280
+ if (!job_id) return err3("job_id is required for action='job_status'");
2281
+ return ok3(await client2.getJob(job_id));
675
2282
  default:
676
- return err(`Unknown synthesize action: ${action}`);
2283
+ return err3(`Unknown synthesize action: ${action}`);
677
2284
  }
678
2285
  }
679
2286
  async function handleOntology(client2, args) {
@@ -701,18 +2308,18 @@ async function handleOntology(client2, args) {
701
2308
  } = args;
702
2309
  switch (action) {
703
2310
  case "schemas":
704
- return ok(await client2.listOntologySchemas());
2311
+ return ok3(await client2.listOntologySchemas());
705
2312
  case "schema":
706
- if (!schema_id) return err("schema_id is required for action='schema'");
707
- return ok(await client2.getOntologySchema(schema_id));
2313
+ if (!schema_id) return err3("schema_id is required for action='schema'");
2314
+ return ok3(await client2.getOntologySchema(schema_id));
708
2315
  case "query": {
709
- if (!query) return err("query is required for action='query'");
2316
+ if (!query) return err3("query is required for action='query'");
710
2317
  if (!schema_id) {
711
- return err(
2318
+ return err3(
712
2319
  "schema_id is required for action='query' (use action='schemas' to find the active schema)"
713
2320
  );
714
2321
  }
715
- return ok(
2322
+ return ok3(
716
2323
  await client2.queryOntology({
717
2324
  query,
718
2325
  schema_id,
@@ -725,8 +2332,8 @@ async function handleOntology(client2, args) {
725
2332
  );
726
2333
  }
727
2334
  case "search":
728
- if (!query) return err("query is required for action='search'");
729
- return ok(
2335
+ if (!query) return err3("query is required for action='search'");
2336
+ return ok3(
730
2337
  await client2.searchDomainObjects(query, {
731
2338
  schema_id,
732
2339
  object_types,
@@ -734,18 +2341,18 @@ async function handleOntology(client2, args) {
734
2341
  })
735
2342
  );
736
2343
  case "objects":
737
- if (!schema_id) return err("schema_id is required for action='objects'");
738
- return ok(
2344
+ if (!schema_id) return err3("schema_id is required for action='objects'");
2345
+ return ok3(
739
2346
  await client2.listDomainObjects({ schema_id, object_type, limit })
740
2347
  );
741
2348
  case "object":
742
- if (!uid) return err("uid is required for action='object'");
743
- return ok(await client2.getDomainObject(uid));
2349
+ if (!uid) return err3("uid is required for action='object'");
2350
+ return ok3(await client2.getDomainObject(uid));
744
2351
  case "object_context":
745
- if (!uid) return err("uid is required for action='object_context'");
746
- return ok(await client2.getDomainObjectContext(uid, depth));
2352
+ if (!uid) return err3("uid is required for action='object_context'");
2353
+ return ok3(await client2.getDomainObjectContext(uid, depth));
747
2354
  case "proposals":
748
- return ok(
2355
+ return ok3(
749
2356
  await client2.listOntologyProposals({
750
2357
  status: status ?? "pending",
751
2358
  schema_id,
@@ -754,21 +2361,21 @@ async function handleOntology(client2, args) {
754
2361
  })
755
2362
  );
756
2363
  case "proposal":
757
- if (!proposal_id) return err("proposal_id is required for action='proposal'");
758
- return ok(await client2.getOntologyProposal(proposal_id));
2364
+ if (!proposal_id) return err3("proposal_id is required for action='proposal'");
2365
+ return ok3(await client2.getOntologyProposal(proposal_id));
759
2366
  case "approve":
760
- if (!proposal_id) return err("proposal_id is required for action='approve'");
761
- return ok(await client2.approveOntologyProposal(proposal_id, { feedback }));
2367
+ if (!proposal_id) return err3("proposal_id is required for action='approve'");
2368
+ return ok3(await client2.approveOntologyProposal(proposal_id, { feedback }));
762
2369
  case "reject":
763
- if (!proposal_id) return err("proposal_id is required for action='reject'");
764
- return ok(await client2.rejectOntologyProposal(proposal_id, reason));
2370
+ if (!proposal_id) return err3("proposal_id is required for action='reject'");
2371
+ return ok3(await client2.rejectOntologyProposal(proposal_id, reason));
765
2372
  case "link": {
766
2373
  if (!from_uid || !to_uid || !relation_type) {
767
- return err(
2374
+ return err3(
768
2375
  "from_uid, to_uid, and relation_type are required for action='link'"
769
2376
  );
770
2377
  }
771
- return ok(
2378
+ return ok3(
772
2379
  await client2.linkDomainObjects({
773
2380
  from_uid,
774
2381
  to_uid,
@@ -779,11 +2386,11 @@ async function handleOntology(client2, args) {
779
2386
  );
780
2387
  }
781
2388
  case "extract": {
782
- if (!schema_id) return err("schema_id is required for action='extract'");
2389
+ if (!schema_id) return err3("schema_id is required for action='extract'");
783
2390
  if (!source_uids?.length) {
784
- return err("source_uids is required for action='extract'");
2391
+ return err3("source_uids is required for action='extract'");
785
2392
  }
786
- return ok(
2393
+ return ok3(
787
2394
  await client2.extractOntology({
788
2395
  ontology_schema_id: schema_id,
789
2396
  source_uids
@@ -791,23 +2398,34 @@ async function handleOntology(client2, args) {
791
2398
  );
792
2399
  }
793
2400
  default:
794
- return err(`Unknown ontology action: ${action}`);
2401
+ return err3(`Unknown ontology action: ${action}`);
795
2402
  }
796
2403
  }
797
- var TOOLS;
2404
+ var TOOLS, INVOCATION_CONTEXT_SCHEMA;
798
2405
  var init_tools = __esm({
799
2406
  "src/tools.ts"() {
800
2407
  "use strict";
2408
+ init_error_detail();
2409
+ init_code_tool();
2410
+ init_sync_tool();
801
2411
  TOOLS = [
802
2412
  {
803
2413
  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.",
2414
+ 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
2415
  inputSchema: {
806
2416
  type: "object",
807
2417
  properties: {
808
2418
  action: {
809
2419
  type: "string",
810
- enum: ["entity", "observation", "source", "snippet", "concept", "journal"],
2420
+ enum: [
2421
+ "entity",
2422
+ "observation",
2423
+ "source",
2424
+ "snippet",
2425
+ "concept",
2426
+ "journal",
2427
+ "lesson"
2428
+ ],
811
2429
  description: "Type of knowledge to capture"
812
2430
  },
813
2431
  label: {
@@ -822,6 +2440,31 @@ var init_tools = __esm({
822
2440
  type: "string",
823
2441
  description: "Journal content/body text (for action=journal)"
824
2442
  },
2443
+ session_uid: {
2444
+ type: "string",
2445
+ description: "Session UID that produced a lesson (for action=lesson)"
2446
+ },
2447
+ work_uid: {
2448
+ type: "string",
2449
+ description: "Durable Task/work UID this capture is relevant to"
2450
+ },
2451
+ execution_uid: {
2452
+ type: "string",
2453
+ description: "Material Execution that produced the lesson"
2454
+ },
2455
+ idempotency_key: {
2456
+ type: "string",
2457
+ description: "Stable retry key for an intentional capture"
2458
+ },
2459
+ supersedes_uid: {
2460
+ type: "string",
2461
+ description: "Earlier capture corrected or superseded by this one"
2462
+ },
2463
+ summarizes_uids: {
2464
+ type: "array",
2465
+ items: { type: "string" },
2466
+ description: "Source node UIDs the lesson was learned from (for action=lesson)"
2467
+ },
825
2468
  entity_type: {
826
2469
  type: "string",
827
2470
  enum: ["person", "organization", "place", "event", "nation", "concept"],
@@ -856,6 +2499,32 @@ var init_tools = __esm({
856
2499
  type: "object",
857
2500
  description: "Type-specific properties (e.g. birth_date for person, coordinates for place)"
858
2501
  },
2502
+ code_refs: {
2503
+ type: "array",
2504
+ items: {
2505
+ oneOf: [
2506
+ { type: "string" },
2507
+ {
2508
+ type: "object",
2509
+ properties: {
2510
+ repo: { type: "string" },
2511
+ path: { type: "string" },
2512
+ line: { type: "integer", minimum: 1 },
2513
+ symbol: { type: "string" },
2514
+ kind: { type: "string" },
2515
+ language: { type: "string" },
2516
+ signature: { type: "string" }
2517
+ }
2518
+ }
2519
+ ]
2520
+ },
2521
+ description: "Typed code targets to anchor and attach to the captured node"
2522
+ },
2523
+ repo: { type: "string", description: "Configured repository id or root" },
2524
+ repo_space_uid: {
2525
+ type: "string",
2526
+ description: "Writable shared repository/Project Space for new code anchors"
2527
+ },
859
2528
  agent_id: {
860
2529
  type: "string",
861
2530
  description: "Agent identity"
@@ -1029,6 +2698,16 @@ var init_tools = __esm({
1029
2698
  type: "object",
1030
2699
  description: "Type-specific properties (e.g. status, priority, deadline, horizon for goals)"
1031
2700
  },
2701
+ code_refs: {
2702
+ type: "array",
2703
+ items: { oneOf: [{ type: "string" }, { type: "object" }] },
2704
+ description: "Typed code targets to anchor and attach to the created commitment"
2705
+ },
2706
+ repo: { type: "string", description: "Configured repository id or root" },
2707
+ repo_space_uid: {
2708
+ type: "string",
2709
+ description: "Writable shared repository/Project Space for new code anchors"
2710
+ },
1032
2711
  agent_id: {
1033
2712
  type: "string",
1034
2713
  description: "Agent identity"
@@ -1039,7 +2718,7 @@ var init_tools = __esm({
1039
2718
  },
1040
2719
  {
1041
2720
  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.",
2721
+ 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.",
1043
2722
  inputSchema: {
1044
2723
  type: "object",
1045
2724
  properties: {
@@ -1051,9 +2730,19 @@ var init_tools = __esm({
1051
2730
  "add_step",
1052
2731
  "update_status",
1053
2732
  "get_plan",
2733
+ "resume_work",
2734
+ "claim_task",
2735
+ "heartbeat",
2736
+ "start_iteration",
2737
+ "checkpoint_iteration",
2738
+ "block_task",
2739
+ "complete_task",
2740
+ "abandon_iteration",
1054
2741
  "start_execution",
1055
2742
  "complete_execution",
1056
2743
  "fail_execution",
2744
+ "register_agent",
2745
+ "get_executions",
1057
2746
  // C33: governance WRITES are deliberately not exposed here.
1058
2747
  // `create_policy` let a model author the rules it is judged by;
1059
2748
  // `request_approval` + `resolve_approval` let it raise and then
@@ -1081,30 +2770,189 @@ var init_tools = __esm({
1081
2770
  },
1082
2771
  plan_uid: {
1083
2772
  type: "string",
1084
- description: "Plan UID (for add_step, get_plan)"
2773
+ description: "Plan UID (for create_plan execution link, add_step, get_plan)"
1085
2774
  },
1086
2775
  task_uid: {
1087
2776
  type: "string",
1088
- description: "Task/step UID (for update_status, execution actions)"
2777
+ description: "Task UID (for create_plan and graph-managed work actions)"
2778
+ },
2779
+ step_uid: {
2780
+ type: "string",
2781
+ description: "PlanStep UID for an iteration/checkpoint"
2782
+ },
2783
+ session_uid: {
2784
+ type: "string",
2785
+ description: "Durable Session UID that owns the task lease"
2786
+ },
2787
+ goal_uid: {
2788
+ type: "string",
2789
+ description: "Goal UID targeted by a task or plan"
1089
2790
  },
1090
2791
  target_uid: {
1091
2792
  type: "string",
1092
- description: "Parent flow/step UID or target node (for procedure/risk actions)"
2793
+ description: "Task/plan/step UID for update_status, or parent/target node for procedure and risk actions"
2794
+ },
2795
+ execution_uid: {
2796
+ type: "string",
2797
+ description: "Execution UID (required for complete_execution/fail_execution)"
2798
+ },
2799
+ executor_uid: {
2800
+ type: "string",
2801
+ description: "Agent node UID that executed the work"
2802
+ },
2803
+ affordance_uid: {
2804
+ type: "string",
2805
+ description: "Affordance invoked by start_execution"
2806
+ },
2807
+ produces_node_uid: {
2808
+ type: "string",
2809
+ description: "Node produced by a completed execution"
2810
+ },
2811
+ filter_plan_uid: {
2812
+ type: "string",
2813
+ description: "Restrict get_executions to one plan"
2814
+ },
2815
+ depends_on_uids: {
2816
+ type: "array",
2817
+ items: { type: "string" },
2818
+ description: "Plan-step UIDs that an added step depends on"
2819
+ },
2820
+ related_uids: {
2821
+ type: "array",
2822
+ items: { type: "string" },
2823
+ description: "Target node UIDs to link to created work or an execution"
1093
2824
  },
1094
2825
  status: {
1095
2826
  type: "string",
1096
2827
  description: "New status value (for update_status)"
1097
2828
  },
2829
+ task_status: {
2830
+ type: "string",
2831
+ description: "Task status requested by a checkpoint or abandon action"
2832
+ },
2833
+ step_status: {
2834
+ type: "string",
2835
+ description: "PlanStep status requested by a checkpoint"
2836
+ },
2837
+ execution_status: {
2838
+ type: "string",
2839
+ enum: ["completed", "failed", "abandoned"],
2840
+ description: "Terminal Execution status for checkpoint_iteration"
2841
+ },
2842
+ expected_version: {
2843
+ type: "number",
2844
+ description: "Current Task node version used as the optimistic fence"
2845
+ },
2846
+ lease_epoch: {
2847
+ type: "number",
2848
+ description: "Current monotonic task-lease epoch"
2849
+ },
2850
+ lease_ttl_secs: {
2851
+ type: "number",
2852
+ minimum: 1,
2853
+ description: "Requested server-time lease lifetime (default 300s)"
2854
+ },
2855
+ idempotency_key: {
2856
+ type: "string",
2857
+ description: "Caller-stable key required for every composite work mutation"
2858
+ },
2859
+ allow_takeover: {
2860
+ type: "boolean",
2861
+ description: "Governed claim override for a live lease owned by another agent"
2862
+ },
2863
+ release_lease: {
2864
+ type: "boolean",
2865
+ description: "Explicitly release or retain the lease after block/abandon/checkpoint"
2866
+ },
2867
+ override_reason: {
2868
+ type: "string",
2869
+ description: "Explicit completion reason when no terminal Execution evidence exists"
2870
+ },
2871
+ scope_uids: {
2872
+ type: "array",
2873
+ items: { type: "string" },
2874
+ description: "Ordinary target UIDs that bound deterministic resume selection"
2875
+ },
2876
+ limit: {
2877
+ type: "number",
2878
+ minimum: 1,
2879
+ maximum: 1,
2880
+ description: "Resume returns one authoritative Task"
2881
+ },
2882
+ input_snapshot: {
2883
+ type: "object",
2884
+ description: "Structured inputs captured when an execution starts"
2885
+ },
2886
+ output_snapshot: {
2887
+ type: "object",
2888
+ description: "Structured outputs captured when an execution completes or fails"
2889
+ },
2890
+ outcome: {
2891
+ type: "string",
2892
+ description: "Execution outcome"
2893
+ },
2894
+ error: {
2895
+ type: "string",
2896
+ description: "Execution error detail (for fail_execution)"
2897
+ },
2898
+ side_effects: {
2899
+ type: "array",
2900
+ items: { type: "string" },
2901
+ description: "Observable side effects of the execution"
2902
+ },
2903
+ next_action: {
2904
+ type: "string",
2905
+ description: "Recommended next action retained on terminal Execution evidence"
2906
+ },
2907
+ checkpoint_summary: {
2908
+ type: "string",
2909
+ description: "Bounded material-attempt checkpoint summary"
2910
+ },
2911
+ test_summary: {
2912
+ type: "string",
2913
+ description: "Test commands/results retained on the Execution"
2914
+ },
2915
+ produces_node_uids: {
2916
+ type: "array",
2917
+ items: { type: "string" },
2918
+ description: "Nodes produced by checkpoint_iteration"
2919
+ },
2920
+ confidence: {
2921
+ type: "number",
2922
+ minimum: 0,
2923
+ maximum: 1
2924
+ },
2925
+ salience: {
2926
+ type: "number",
2927
+ minimum: 0,
2928
+ maximum: 1
2929
+ },
1098
2930
  props: {
1099
2931
  type: "object",
1100
2932
  description: "Additional properties (e.g. likelihood, impact, mitigation for risks)"
1101
2933
  },
2934
+ code_refs: {
2935
+ type: "array",
2936
+ items: { oneOf: [{ type: "string" }, { type: "object" }] },
2937
+ description: "Typed code targets to anchor and attach to created work or execution"
2938
+ },
2939
+ repo: { type: "string", description: "Configured repository id or root" },
2940
+ repo_space_uid: {
2941
+ type: "string",
2942
+ description: "Writable shared repository/Project Space for new code anchors"
2943
+ },
1102
2944
  agent_id: {
1103
2945
  type: "string",
1104
2946
  description: "Agent identity"
1105
2947
  }
1106
2948
  },
1107
2949
  required: ["action"]
2950
+ },
2951
+ annotations: {
2952
+ readOnlyHint: false,
2953
+ destructiveHint: false,
2954
+ idempotentHint: false,
2955
+ openWorldHint: true
1108
2956
  }
1109
2957
  },
1110
2958
  {
@@ -1479,8 +3327,33 @@ var init_tools = __esm({
1479
3327
  },
1480
3328
  required: ["action"]
1481
3329
  }
1482
- }
3330
+ },
3331
+ SYNC_TOOL,
3332
+ CODE_TOOL
1483
3333
  ];
3334
+ INVOCATION_CONTEXT_SCHEMA = {
3335
+ type: "object",
3336
+ 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.",
3337
+ properties: {
3338
+ harness: { type: "string", enum: ["claude-code", "codex", "generic"] },
3339
+ harnessSessionId: { type: "string" },
3340
+ harnessTurnId: { type: "string" },
3341
+ cwd: { type: "string" },
3342
+ repoId: { type: "string" },
3343
+ branch: { type: "string" },
3344
+ commit: { type: "string" },
3345
+ worktreeState: { type: "string", enum: ["clean", "dirty"] },
3346
+ model: { type: "string" },
3347
+ injectedBy: { type: "string", enum: ["hook", "mcp-process", "caller"] }
3348
+ }
3349
+ };
3350
+ for (const tool of TOOLS) {
3351
+ if (tool.name === "mindgraph_retrieve") continue;
3352
+ const schema = tool.inputSchema;
3353
+ if (schema.properties && !schema.properties.invocation_context) {
3354
+ schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
3355
+ }
3356
+ }
1484
3357
  }
1485
3358
  });
1486
3359
 
@@ -1530,10 +3403,10 @@ async function handleGeneratedTool(client2, desc, args) {
1530
3403
  switch (desc.maps_to) {
1531
3404
  case "search": {
1532
3405
  const query = args.query;
1533
- if (!query) return err2(`query is required for ${desc.name}`);
3406
+ if (!query) return err4(`query is required for ${desc.name}`);
1534
3407
  const limit = typeof args.limit === "number" ? args.limit : void 0;
1535
3408
  const filters = Array.isArray(args.filters) ? args.filters : void 0;
1536
- return ok2(
3409
+ return ok4(
1537
3410
  await ontologyClient.searchDomainObjects(query, {
1538
3411
  schema_id: desc.schema_id,
1539
3412
  object_types: [desc.object_type],
@@ -1544,33 +3417,33 @@ async function handleGeneratedTool(client2, desc, args) {
1544
3417
  }
1545
3418
  case "object": {
1546
3419
  const uid = args.uid;
1547
- if (!uid) return err2(`uid is required for ${desc.name}`);
1548
- return ok2(await ontologyClient.getDomainObject(uid, {
3420
+ if (!uid) return err4(`uid is required for ${desc.name}`);
3421
+ return ok4(await ontologyClient.getDomainObject(uid, {
1549
3422
  schema_id: desc.schema_id,
1550
3423
  object_type: desc.object_type
1551
3424
  }));
1552
3425
  }
1553
3426
  case "object_context": {
1554
3427
  const uid = args.uid;
1555
- if (!uid) return err2(`uid is required for ${desc.name}`);
3428
+ if (!uid) return err4(`uid is required for ${desc.name}`);
1556
3429
  const depth = typeof args.depth === "number" ? args.depth : void 0;
1557
- return ok2(await ontologyClient.getDomainObjectContext(uid, depth, {
3430
+ return ok4(await ontologyClient.getDomainObjectContext(uid, depth, {
1558
3431
  schema_id: desc.schema_id,
1559
3432
  object_type: desc.object_type
1560
3433
  }));
1561
3434
  }
1562
3435
  case "related": {
1563
3436
  const uid = args.uid;
1564
- if (!uid) return err2(`uid is required for ${desc.name}`);
3437
+ if (!uid) return err4(`uid is required for ${desc.name}`);
1565
3438
  if (!desc.relation || !desc.entry_role || !desc.far_type) {
1566
- return err2(`invalid related descriptor: ${desc.name}`);
3439
+ return err4(`invalid related descriptor: ${desc.name}`);
1567
3440
  }
1568
3441
  if (!ontologyClient.queryRelatedDomainObjects) {
1569
- return err2(
3442
+ return err4(
1570
3443
  "related ontology tools require a graph-aware version of the mindgraph SDK"
1571
3444
  );
1572
3445
  }
1573
- return ok2(await ontologyClient.queryRelatedDomainObjects({
3446
+ return ok4(await ontologyClient.queryRelatedDomainObjects({
1574
3447
  schema_id: desc.schema_id,
1575
3448
  entry_type: desc.object_type,
1576
3449
  uid,
@@ -1585,9 +3458,9 @@ async function handleGeneratedTool(client2, desc, args) {
1585
3458
  }
1586
3459
  case "structured_query": {
1587
3460
  const select = args.select;
1588
- if (!select) return err2(`select is required for ${desc.name}`);
3461
+ if (!select) return err4(`select is required for ${desc.name}`);
1589
3462
  if (!ontologyClient.queryDomainStructured) {
1590
- return err2(
3463
+ return err4(
1591
3464
  "structured ontology tools require a graph-aware version of the mindgraph SDK"
1592
3465
  );
1593
3466
  }
@@ -1598,23 +3471,23 @@ async function handleGeneratedTool(client2, desc, args) {
1598
3471
  for (const field of STRUCTURED_QUERY_PASSTHROUGH) {
1599
3472
  if (args[field] !== void 0) request[field] = args[field];
1600
3473
  }
1601
- return ok2(await ontologyClient.queryDomainStructured(request));
3474
+ return ok4(await ontologyClient.queryDomainStructured(request));
1602
3475
  }
1603
3476
  default:
1604
- return err2(`unknown generated tool mapping: ${desc.maps_to}`);
3477
+ return err4(`unknown generated tool mapping: ${desc.maps_to}`);
1605
3478
  }
1606
3479
  } catch (e) {
1607
- return err2(e instanceof Error ? e.message : String(e));
3480
+ return err4(e instanceof Error ? e.message : String(e));
1608
3481
  }
1609
3482
  }
1610
- var ok2, err2, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
3483
+ var ok4, err4, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
1611
3484
  var init_generated_tools = __esm({
1612
3485
  "src/generated-tools.ts"() {
1613
3486
  "use strict";
1614
- ok2 = (data) => ({
3487
+ ok4 = (data) => ({
1615
3488
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1616
3489
  });
1617
- err2 = (message) => ({
3490
+ err4 = (message) => ({
1618
3491
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
1619
3492
  isError: true
1620
3493
  });
@@ -1644,7 +3517,7 @@ function isTransientStatus(status) {
1644
3517
  return status === 408 || status === 425 || status === 429 || status >= 500;
1645
3518
  }
1646
3519
  function sleep(ms) {
1647
- return new Promise((resolve) => setTimeout(resolve, ms));
3520
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1648
3521
  }
1649
3522
  function headers(apiKey, orgId) {
1650
3523
  const value = {
@@ -1660,6 +3533,52 @@ function unsupportedResponse(status, body) {
1660
3533
  const lower = body.toLowerCase();
1661
3534
  return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
1662
3535
  }
3536
+ function toolMutability(toolName, action) {
3537
+ if (toolName === "mindgraph_retrieve") return "read";
3538
+ if (toolName === "mindgraph_plan") {
3539
+ return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
3540
+ }
3541
+ if (toolName === "mindgraph_ingest") {
3542
+ return action === "job_status" ? "read" : "write";
3543
+ }
3544
+ if (toolName === "mindgraph_synthesize") {
3545
+ return action === "signals" ? "read" : "write";
3546
+ }
3547
+ if (toolName === "mindgraph_code") {
3548
+ return action === "anchor" ? "write" : "read";
3549
+ }
3550
+ if (toolName === "mindgraph_sync") {
3551
+ return action === "scan" || action === "status" ? "read" : "write";
3552
+ }
3553
+ if (toolName === "mindgraph_ontology") {
3554
+ return action && ONTOLOGY_READ_ACTIONS.has(action) ? "read" : "write";
3555
+ }
3556
+ if (/_(search|get|context|related|query)$/.test(toolName) && toolName !== "mindgraph_capture") {
3557
+ return "read";
3558
+ }
3559
+ return "write";
3560
+ }
3561
+ function governanceTarget(toolName, args) {
3562
+ const action = typeof args.action === "string" ? args.action : void 0;
3563
+ const targetUids = /* @__PURE__ */ new Set();
3564
+ for (const field of UID_FIELDS) {
3565
+ const value = args[field];
3566
+ if (typeof value === "string" && value.length > 0) targetUids.add(value);
3567
+ }
3568
+ for (const field of UID_ARRAY_FIELDS) {
3569
+ const value = args[field];
3570
+ if (!Array.isArray(value)) continue;
3571
+ for (const uid of value) {
3572
+ if (typeof uid === "string" && uid.length > 0) targetUids.add(uid);
3573
+ }
3574
+ }
3575
+ return {
3576
+ tool_name: toolName,
3577
+ ...action ? { action } : {},
3578
+ mutability: toolMutability(toolName, action),
3579
+ target_uids: [...targetUids]
3580
+ };
3581
+ }
1663
3582
  async function checkMcpGovernance(toolName, args, config) {
1664
3583
  if (config.disabled) return { allowed: true };
1665
3584
  if (capability === "unsupported") {
@@ -1682,7 +3601,7 @@ async function checkMcpGovernance(toolName, args, config) {
1682
3601
  action: "check",
1683
3602
  act: "tool_invoke",
1684
3603
  agent_id: String(args.agent_id || config.agentId),
1685
- target: { tool_name: toolName },
3604
+ target: governanceTarget(toolName, args),
1686
3605
  context: { adapter: "mindgraph-mcp", ...config.context },
1687
3606
  tier: "checkpoint"
1688
3607
  })
@@ -1769,7 +3688,7 @@ async function checkMcpGovernance(toolName, args, config) {
1769
3688
  message: verdict === "conditional" ? `Approval is required${policy ? ` by policy ${policy}` : ""}${approvalUid ? ` (approval ${approvalUid})` : ""}` : `Denied by MindGraph governance${policy ? ` policy ${policy}` : ""}`
1770
3689
  };
1771
3690
  }
1772
- var capability, UNSUPPORTED_LEASE_MS, unsupportedUntil, unsupportedLogged, DEFAULT_RETRY_DELAYS_MS, KNOWN_DUTIES;
3691
+ var capability, UNSUPPORTED_LEASE_MS, unsupportedUntil, unsupportedLogged, DEFAULT_RETRY_DELAYS_MS, KNOWN_DUTIES, PLAN_READ_ACTIONS, ONTOLOGY_READ_ACTIONS, UID_FIELDS, UID_ARRAY_FIELDS;
1773
3692
  var init_governance = __esm({
1774
3693
  "src/governance.ts"() {
1775
3694
  "use strict";
@@ -1779,6 +3698,59 @@ var init_governance = __esm({
1779
3698
  unsupportedLogged = false;
1780
3699
  DEFAULT_RETRY_DELAYS_MS = [150, 600];
1781
3700
  KNOWN_DUTIES = /* @__PURE__ */ new Set(["log", "require_approval"]);
3701
+ PLAN_READ_ACTIONS = /* @__PURE__ */ new Set([
3702
+ "get_plan",
3703
+ "get_executions",
3704
+ "get_pending",
3705
+ "get_assessments",
3706
+ "resume_work"
3707
+ ]);
3708
+ ONTOLOGY_READ_ACTIONS = /* @__PURE__ */ new Set([
3709
+ "list_schemas",
3710
+ "get_schema",
3711
+ "query",
3712
+ "search_objects",
3713
+ "list_objects",
3714
+ "get_object",
3715
+ "get_context",
3716
+ "list_proposals",
3717
+ "get_proposal"
3718
+ ]);
3719
+ UID_FIELDS = [
3720
+ "uid",
3721
+ "source_uid",
3722
+ "session_uid",
3723
+ "work_uid",
3724
+ "goal_uid",
3725
+ "project_uid",
3726
+ "task_uid",
3727
+ "plan_uid",
3728
+ "step_uid",
3729
+ "target_uid",
3730
+ "execution_uid",
3731
+ "executor_uid",
3732
+ "affordance_uid",
3733
+ "produces_node_uid",
3734
+ "filter_plan_uid",
3735
+ "parent_uid",
3736
+ "decision_uid",
3737
+ "chosen_option_uid",
3738
+ "governed_uid",
3739
+ "approval_uid",
3740
+ "requires_plan_uid",
3741
+ "start_uid",
3742
+ "end_uid"
3743
+ ];
3744
+ UID_ARRAY_FIELDS = [
3745
+ "related_uids",
3746
+ "depends_on_uids",
3747
+ "summarizes_uids",
3748
+ "informs_uid",
3749
+ "target_uids",
3750
+ "anchor_uids",
3751
+ "scope_uids",
3752
+ "produces_node_uids"
3753
+ ];
1782
3754
  }
1783
3755
  });
1784
3756
 
@@ -1902,14 +3874,14 @@ async function main() {
1902
3874
  `MindGraph MCP server v0.7.1 running on stdio (${BASE_URL})`
1903
3875
  );
1904
3876
  }
1905
- var import_server, import_stdio, import_types, import_mindgraph, API_KEY, BASE_URL, AGENT_ID, ORG_ID, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
3877
+ 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;
1906
3878
  var init_src = __esm({
1907
3879
  "src/index.ts"() {
1908
3880
  "use strict";
1909
3881
  import_server = require("@modelcontextprotocol/sdk/server/index.js");
1910
3882
  import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
1911
3883
  import_types = require("@modelcontextprotocol/sdk/types.js");
1912
- import_mindgraph = require("mindgraph");
3884
+ import_mindgraph2 = require("mindgraph");
1913
3885
  init_tools();
1914
3886
  init_generated_tools();
1915
3887
  init_governance();
@@ -1918,13 +3890,15 @@ var init_src = __esm({
1918
3890
  BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
1919
3891
  AGENT_ID = process.env.MINDGRAPH_AGENT_ID || "mcp";
1920
3892
  ORG_ID = process.env.MINDGRAPH_ORG_ID;
3893
+ PROFILE = process.env.MINDGRAPH_PROFILE || "general";
3894
+ HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
1921
3895
  if (!API_KEY) {
1922
3896
  console.error(
1923
3897
  "Error: MINDGRAPH_API_KEY environment variable is required.\nGet your API key at https://mindgraph.cloud/dashboard/keys\nOr run: npx mindgraph-mcp init"
1924
3898
  );
1925
3899
  process.exit(1);
1926
3900
  }
1927
- client = new import_mindgraph.MindGraph({
3901
+ client = new import_mindgraph2.MindGraph({
1928
3902
  baseUrl: BASE_URL,
1929
3903
  apiKey: API_KEY,
1930
3904
  orgId: ORG_ID,
@@ -1933,7 +3907,7 @@ var init_src = __esm({
1933
3907
  // window where npm may still resolve the preceding SDK declaration file.
1934
3908
  telemetrySurface: "mcp"
1935
3909
  });
1936
- INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph (7 tools).
3910
+ INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
1937
3911
 
1938
3912
  ## #1 RULE: Go straight to the user's topic
1939
3913
 
@@ -1996,22 +3970,42 @@ Capture knowledge when the user shares something worth remembering:
1996
3970
  - Questions to track \u2192 \`mindgraph_reason\` action "open_question"
1997
3971
  - Goals or projects \u2192 \`mindgraph_commit\` action "goal"/"project"
1998
3972
  - Decisions \u2192 \`mindgraph_commit\` action "open_decision"
3973
+ - Durable engineering work that must survive sessions/harnesses \u2192 \`mindgraph_plan\`
3974
+ - Typed code anchors, attached memory/work, callers/callees, or impact \u2192 \`mindgraph_code\`
3975
+ - Reusable learning from a material attempt \u2192 \`mindgraph_capture\` action "lesson"
1999
3976
  - Long-form content \u2192 \`mindgraph_ingest\`
2000
3977
  - 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)
2001
3978
 
2002
3979
  ## Tool tips
2003
3980
 
3981
+ - 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.
3982
+ - 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.
2004
3983
  - \`capture\`: "entity"/"observation" for facts, "journal" for personal/informal
2005
3984
  - \`reason\`: "claim" when evidence exists, "open_question"/"hypothesis" otherwise
2006
3985
  - \`retrieve\`: "context" returns graph nodes only by default \u2014 set include_chunks=true for source text
2007
3986
  - \`retrieve\`: "context" (FTS default) \u2192 "semantic" (conceptual fallback) \u2192 "hybrid" (both)
2008
3987
  - \`retrieve\`: "neighborhood"/"chain"/"path"/"subgraph" for graph exploration
3988
+ - \`code\`: pass path + symbol + signature when names are overloaded; ambiguity never writes
2009
3989
  - Confidence: 0.9+ for facts, 0.5-0.8 for uncertain, <0.5 for speculation
2010
3990
  - Don't narrate tool usage \u2014 capture naturally as part of conversation`;
3991
+ if (PROFILE === "coding") {
3992
+ INSTRUCTIONS += `
3993
+
3994
+ ## Coding profile
3995
+
3996
+ - 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.
3997
+ - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
3998
+ - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
3999
+ - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
4000
+ } else {
4001
+ INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
4002
+ (line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
4003
+ ).join("\n");
4004
+ }
2011
4005
  server = new import_server.Server(
2012
4006
  {
2013
4007
  name: "mindgraph",
2014
- version: "0.13.5"
4008
+ version: "0.14.1"
2015
4009
  },
2016
4010
  {
2017
4011
  capabilities: {
@@ -2024,7 +4018,7 @@ Capture knowledge when the user shares something worth remembering:
2024
4018
  );
2025
4019
  server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
2026
4020
  const generated = await getGeneratedTools(client);
2027
- return { tools: [...TOOLS, ...generated.tools] };
4021
+ return { tools: [...toolsForProfile(PROFILE), ...generated.tools] };
2028
4022
  });
2029
4023
  GOVERNANCE_CONFIG = {
2030
4024
  baseUrl: BASE_URL,
@@ -2229,30 +4223,683 @@ ${conversation}`
2229
4223
  });
2230
4224
 
2231
4225
  // src/cli.ts
2232
- var fs = __toESM(require("fs"));
2233
- var path = __toESM(require("path"));
2234
- var os = __toESM(require("os"));
2235
- var crypto = __toESM(require("crypto"));
4226
+ var fs5 = __toESM(require("fs"));
4227
+ var path5 = __toESM(require("path"));
4228
+ var os4 = __toESM(require("os"));
4229
+ var crypto4 = __toESM(require("crypto"));
2236
4230
  var import_child_process = require("child_process");
4231
+ var import_mindgraph3 = require("mindgraph");
4232
+
4233
+ // src/hook-env.ts
4234
+ var import_node_fs = __toESM(require("fs"));
4235
+ var import_node_os = __toESM(require("os"));
4236
+ var import_node_path = __toESM(require("path"));
4237
+ function hookEnvPath(dir) {
4238
+ return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
4239
+ }
4240
+ function loadHookEnv(dir) {
4241
+ try {
4242
+ const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
4243
+ const parsed = JSON.parse(raw);
4244
+ return {
4245
+ baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
4246
+ apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0
4247
+ };
4248
+ } catch {
4249
+ return {};
4250
+ }
4251
+ }
4252
+ function saveHookEnv(env, dir) {
4253
+ const file = hookEnvPath(dir);
4254
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
4255
+ const existing = loadHookEnv(dir);
4256
+ const merged = {
4257
+ baseUrl: env.baseUrl ?? existing.baseUrl,
4258
+ apiKey: env.apiKey ?? existing.apiKey
4259
+ };
4260
+ import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
4261
+ `, {
4262
+ mode: 384
4263
+ });
4264
+ import_node_fs.default.chmodSync(file, 384);
4265
+ return file;
4266
+ }
4267
+
4268
+ // src/cli-args.ts
4269
+ function parseArgs(argv) {
4270
+ const args = argv.slice(2);
4271
+ let command = "serve";
4272
+ let apiKey = process.env.MINDGRAPH_API_KEY;
4273
+ let baseUrl = process.env.MINDGRAPH_BASE_URL;
4274
+ let scope = "project";
4275
+ let projectDir;
4276
+ let harness;
4277
+ let hooks = false;
4278
+ for (let i = 0; i < args.length; i++) {
4279
+ const arg = args[i];
4280
+ switch (arg) {
4281
+ case "--api-key":
4282
+ apiKey = args[++i];
4283
+ break;
4284
+ case "--base-url":
4285
+ baseUrl = args[++i];
4286
+ break;
4287
+ case "--scope": {
4288
+ const value = args[++i];
4289
+ if (value !== "user" && value !== "project") {
4290
+ throw new Error("--scope must be user or project");
4291
+ }
4292
+ scope = value;
4293
+ break;
4294
+ }
4295
+ case "--project-dir":
4296
+ projectDir = args[++i];
4297
+ break;
4298
+ case "--harness":
4299
+ harness = args[++i];
4300
+ break;
4301
+ case "--hooks":
4302
+ hooks = true;
4303
+ break;
4304
+ case "--owner":
4305
+ ++i;
4306
+ break;
4307
+ case "--help":
4308
+ case "-h":
4309
+ command = "help";
4310
+ break;
4311
+ default:
4312
+ if (!arg.startsWith("-")) {
4313
+ command = arg;
4314
+ }
4315
+ break;
4316
+ }
4317
+ }
4318
+ return { command, apiKey, baseUrl, scope, projectDir, harness, hooks };
4319
+ }
4320
+
4321
+ // src/claude-hooks.ts
4322
+ var import_node_crypto2 = __toESM(require("crypto"));
4323
+ var import_node_fs3 = __toESM(require("fs"));
4324
+ var import_node_os3 = __toESM(require("os"));
4325
+ var import_node_path3 = __toESM(require("path"));
4326
+ var import_node_child_process2 = require("child_process");
4327
+ init_codegraph();
4328
+ function defaultLedger(sessionId) {
4329
+ return {
4330
+ harness: "claude-code",
4331
+ harnessSessionId: sessionId,
4332
+ materialToolCalls: 0,
4333
+ mutatingOperations: 0,
4334
+ memoryWritten: false,
4335
+ iterationCheckpointed: false,
4336
+ stopNudged: false,
4337
+ nativeTasksCreated: 0,
4338
+ nativeTasksTerminal: 0,
4339
+ baselineWorktree: "unknown",
4340
+ updatedAt: Date.now()
4341
+ };
4342
+ }
4343
+ function runtimeDir(options) {
4344
+ return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
4345
+ }
4346
+ function ledgerPath(sessionId, options) {
4347
+ const key = import_node_crypto2.default.createHash("sha256").update(`claude-code\0${sessionId}`).digest("hex");
4348
+ return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
4349
+ }
4350
+ function waitBriefly(ms) {
4351
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
4352
+ }
4353
+ function withLedger(sessionId, options, update) {
4354
+ const file = ledgerPath(sessionId, options);
4355
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true, mode: 448 });
4356
+ try {
4357
+ import_node_fs3.default.chmodSync(import_node_path3.default.dirname(file), 448);
4358
+ } catch {
4359
+ }
4360
+ const lock = `${file}.lock`;
4361
+ let lockFd;
4362
+ for (let attempt = 0; attempt < 50; attempt++) {
4363
+ try {
4364
+ lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
4365
+ break;
4366
+ } catch (error) {
4367
+ if (error.code !== "EEXIST") throw error;
4368
+ try {
4369
+ if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 1e4) import_node_fs3.default.unlinkSync(lock);
4370
+ } catch {
4371
+ }
4372
+ waitBriefly(10);
4373
+ }
4374
+ }
4375
+ if (lockFd === void 0) {
4376
+ return update(defaultLedger(sessionId));
4377
+ }
4378
+ try {
4379
+ let ledger = defaultLedger(sessionId);
4380
+ try {
4381
+ ledger = {
4382
+ ...ledger,
4383
+ ...JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"))
4384
+ };
4385
+ } catch {
4386
+ }
4387
+ const result = update(ledger);
4388
+ ledger.updatedAt = Date.now();
4389
+ const temporary = `${file}.${process.pid}.${import_node_crypto2.default.randomUUID()}.tmp`;
4390
+ import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
4391
+ `, { mode: 384 });
4392
+ import_node_fs3.default.renameSync(temporary, file);
4393
+ try {
4394
+ import_node_fs3.default.chmodSync(file, 384);
4395
+ } catch {
4396
+ }
4397
+ return result;
4398
+ } finally {
4399
+ import_node_fs3.default.closeSync(lockFd);
4400
+ try {
4401
+ import_node_fs3.default.unlinkSync(lock);
4402
+ } catch {
4403
+ }
4404
+ }
4405
+ }
4406
+ function readLedger(sessionId, options) {
4407
+ return withLedger(sessionId, options, (ledger) => ({ ...ledger }));
4408
+ }
4409
+ function gitValue(cwd, args) {
4410
+ try {
4411
+ return (0, import_node_child_process2.execFileSync)("git", ["-C", cwd, ...args], {
4412
+ encoding: "utf8",
4413
+ stdio: ["ignore", "pipe", "ignore"],
4414
+ timeout: 750
4415
+ }).trim();
4416
+ } catch {
4417
+ return void 0;
4418
+ }
4419
+ }
4420
+ function worktreeState(cwd) {
4421
+ const status = gitValue(cwd, ["status", "--porcelain"]);
4422
+ return status === void 0 ? "unknown" : status ? "dirty" : "clean";
4423
+ }
4424
+ async function invocationContext(input, options) {
4425
+ const repository = await new CodegraphAdapter({
4426
+ cwd: input.cwd,
4427
+ workspaceFile: options.workspaceFile
4428
+ }).resolveRepository();
4429
+ return {
4430
+ harness: "claude-code",
4431
+ harnessSessionId: input.session_id,
4432
+ cwd: input.cwd,
4433
+ repoId: repository.repoId,
4434
+ branch: gitValue(input.cwd, ["branch", "--show-current"]),
4435
+ commit: gitValue(input.cwd, ["rev-parse", "HEAD"]),
4436
+ worktreeState: worktreeState(input.cwd),
4437
+ model: input.model,
4438
+ injectedBy: "hook"
4439
+ };
4440
+ }
4441
+ function object(value) {
4442
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4443
+ }
4444
+ function responsePayload(value) {
4445
+ const direct = object(value);
4446
+ if (!direct) return void 0;
4447
+ if (Array.isArray(direct.content)) {
4448
+ for (const item of direct.content) {
4449
+ const block = object(item);
4450
+ if (block?.type === "text" && typeof block.text === "string") {
4451
+ try {
4452
+ const parsed = JSON.parse(block.text);
4453
+ const payload = object(parsed);
4454
+ if (payload) return payload;
4455
+ } catch {
4456
+ }
4457
+ }
4458
+ }
4459
+ }
4460
+ return direct;
4461
+ }
4462
+ function number(value) {
4463
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4464
+ }
4465
+ function string(value) {
4466
+ return typeof value === "string" && value ? value : void 0;
4467
+ }
4468
+ function boundedBrief(brief) {
4469
+ const encoded = JSON.stringify(brief, null, 2);
4470
+ const capped = encoded.length <= 9e3 ? encoded : `${encoded.slice(0, 8900)}
4471
+ \u2026[bounded work brief truncated]`;
4472
+ return [
4473
+ "MindGraph durable work brief (authoritative across sessions/harnesses):",
4474
+ capped,
4475
+ "Use the harness todo list only for local execution details; do not duplicate this durable Task there."
4476
+ ].join("\n");
4477
+ }
4478
+ async function repositoryScopeUids(input, client2, options, agentId) {
4479
+ if (!client2.entity) return [];
4480
+ const adapter = new CodegraphAdapter({
4481
+ cwd: input.cwd,
4482
+ workspaceFile: options.workspaceFile
4483
+ });
4484
+ const configured = await adapter.resolveRepositories();
4485
+ const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
4486
+ const resolved = await Promise.all(
4487
+ repositories.map(
4488
+ (repository) => client2.entity({
4489
+ action: "resolve_identity",
4490
+ identity: {
4491
+ namespace: "external.code",
4492
+ key_version: 1,
4493
+ key: repositoryIdentityKey(repository)
4494
+ },
4495
+ agent_id: agentId
4496
+ }).catch(() => void 0)
4497
+ )
4498
+ );
4499
+ return [
4500
+ ...new Set(
4501
+ resolved.flatMap((response) => {
4502
+ const payload = object(response);
4503
+ return payload?.status === "existing" && string(payload.uid) ? [string(payload.uid)] : [];
4504
+ })
4505
+ )
4506
+ ];
4507
+ }
4508
+ async function sessionStart(input, client2, options) {
4509
+ const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || "claude-code";
4510
+ const opened = object(
4511
+ await client2.session({
4512
+ action: "open",
4513
+ label: `Claude Code ${input.session_id}`,
4514
+ harness: "claude-code",
4515
+ harness_session_id: input.session_id,
4516
+ model: input.model,
4517
+ agent_id: agentId
4518
+ })
4519
+ );
4520
+ const sessionUid = string(opened?.uid);
4521
+ const scopeUids = await repositoryScopeUids(input, client2, options, agentId);
4522
+ let brief = object(
4523
+ await client2.plan({
4524
+ action: "resume_work",
4525
+ session_uid: sessionUid,
4526
+ ...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
4527
+ agent_id: agentId
4528
+ })
4529
+ );
4530
+ const task = object(brief?.task);
4531
+ const lease = object(brief?.lease);
4532
+ if (sessionUid && string(task?.uid) && number(task?.version)) {
4533
+ try {
4534
+ const claimed = object(
4535
+ await client2.plan({
4536
+ action: "claim_task",
4537
+ task_uid: task.uid,
4538
+ session_uid: sessionUid,
4539
+ expected_version: task.version,
4540
+ idempotency_key: `session-start:${input.session_id}:${task.uid}:${lease?.lease_epoch || 0}`,
4541
+ agent_id: agentId
4542
+ })
4543
+ );
4544
+ brief = object(
4545
+ await client2.plan({
4546
+ action: "resume_work",
4547
+ task_uid: task.uid,
4548
+ session_uid: sessionUid,
4549
+ ...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
4550
+ agent_id: agentId
4551
+ })
4552
+ );
4553
+ if (brief && claimed) brief.claim = claimed;
4554
+ } catch {
4555
+ }
4556
+ }
4557
+ withLedger(input.session_id, options, (ledger) => {
4558
+ ledger.sessionUid = sessionUid;
4559
+ ledger.baselineWorktree = worktreeState(input.cwd);
4560
+ const selectedTask = object(brief?.task);
4561
+ const selectedLease = object(brief?.lease) || object(brief?.claim);
4562
+ const executions = Array.isArray(brief?.recent_executions) ? brief?.recent_executions : [];
4563
+ const latest = object(executions[0]);
4564
+ ledger.taskUid = string(selectedTask?.uid);
4565
+ ledger.taskVersion = number(selectedTask?.version);
4566
+ ledger.leaseEpoch = number(selectedLease?.lease_epoch);
4567
+ ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
4568
+ ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
4569
+ return void 0;
4570
+ });
4571
+ return {
4572
+ hookSpecificOutput: {
4573
+ hookEventName: "SessionStart",
4574
+ additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
4575
+ }
4576
+ };
4577
+ }
4578
+ async function preTool(input, options) {
4579
+ const original = { ...input.tool_input || {} };
4580
+ const ledger = readLedger(input.session_id, options);
4581
+ const updatedInput = {
4582
+ ...original,
4583
+ invocation_context: await invocationContext(input, options)
4584
+ };
4585
+ if (ledger.sessionUid) updatedInput.session_uid = ledger.sessionUid;
4586
+ if (ledger.taskUid && updatedInput.task_uid === void 0) {
4587
+ updatedInput.task_uid = ledger.taskUid;
4588
+ }
4589
+ if (ledger.taskUid && input.tool_name?.includes("mindgraph_capture") && updatedInput.work_uid === void 0) {
4590
+ updatedInput.work_uid = ledger.taskUid;
4591
+ }
4592
+ if (ledger.executionUid && updatedInput.execution_uid === void 0) {
4593
+ updatedInput.execution_uid = ledger.executionUid;
4594
+ }
4595
+ if (ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
4596
+ updatedInput.lease_epoch = ledger.leaseEpoch;
4597
+ }
4598
+ if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
4599
+ updatedInput.expected_version = ledger.taskVersion;
4600
+ }
4601
+ return {
4602
+ hookSpecificOutput: {
4603
+ hookEventName: "PreToolUse",
4604
+ updatedInput
4605
+ }
4606
+ };
4607
+ }
4608
+ async function postTool(input, client2, options) {
4609
+ const name = input.tool_name || "";
4610
+ const args = input.tool_input || {};
4611
+ const action = string(args.action);
4612
+ const payload = responsePayload(input.tool_response);
4613
+ const nowSeconds = (options.now || (() => Date.now() / 1e3))();
4614
+ let heartbeat;
4615
+ withLedger(input.session_id, options, (ledger) => {
4616
+ ledger.materialToolCalls += 1;
4617
+ if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
4618
+ ledger.mutatingOperations += 1;
4619
+ }
4620
+ if (name.includes("mindgraph_capture") && !payload?.error) {
4621
+ ledger.memoryWritten = true;
4622
+ }
4623
+ if (name.includes("mindgraph_plan")) {
4624
+ if (action === "start_iteration" && !payload?.error) {
4625
+ ledger.executionUid = string(payload?.execution_uid) || ledger.executionUid;
4626
+ }
4627
+ if (["checkpoint_iteration", "block_task", "complete_task", "abandon_iteration"].includes(
4628
+ action || ""
4629
+ ) && !payload?.error) {
4630
+ ledger.iterationCheckpointed = true;
4631
+ if (action !== "block_task") ledger.executionUid = void 0;
4632
+ }
4633
+ ledger.taskVersion = number(payload?.task_version) || number(payload?.version) || ledger.taskVersion;
4634
+ ledger.leaseEpoch = number(payload?.lease_epoch) || ledger.leaseEpoch;
4635
+ ledger.leaseExpiresAt = number(payload?.lease_expires_at) || ledger.leaseExpiresAt;
4636
+ }
4637
+ if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 && ledger.leaseExpiresAt - nowSeconds <= 60) {
4638
+ heartbeat = {
4639
+ taskUid: ledger.taskUid,
4640
+ sessionUid: ledger.sessionUid,
4641
+ taskVersion: ledger.taskVersion,
4642
+ leaseEpoch: ledger.leaseEpoch
4643
+ };
4644
+ }
4645
+ return void 0;
4646
+ });
4647
+ if (heartbeat) {
4648
+ try {
4649
+ const response = object(
4650
+ await client2.plan({
4651
+ action: "heartbeat",
4652
+ task_uid: heartbeat.taskUid,
4653
+ session_uid: heartbeat.sessionUid,
4654
+ expected_version: heartbeat.taskVersion,
4655
+ lease_epoch: heartbeat.leaseEpoch,
4656
+ idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
4657
+ agent_id: options.agentId || "claude-code"
4658
+ })
4659
+ );
4660
+ withLedger(input.session_id, options, (ledger) => {
4661
+ ledger.leaseExpiresAt = number(response?.lease_expires_at) || ledger.leaseExpiresAt;
4662
+ return void 0;
4663
+ });
4664
+ } catch {
4665
+ }
4666
+ }
4667
+ return {};
4668
+ }
4669
+ function stop(input, options) {
4670
+ if (input.stop_hook_active) return {};
4671
+ return withLedger(input.session_id, options, (ledger) => {
4672
+ const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
4673
+ const substantial = Boolean(ledger.executionUid) || changed;
4674
+ if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
4675
+ return {};
4676
+ }
4677
+ ledger.stopNudged = true;
4678
+ return {
4679
+ decision: "block",
4680
+ 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."
4681
+ };
4682
+ });
4683
+ }
4684
+ async function sessionEnd(input, client2, options) {
4685
+ const ledger = readLedger(input.session_id, options);
4686
+ if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
4687
+ try {
4688
+ await client2.plan({
4689
+ action: "abandon_iteration",
4690
+ task_uid: ledger.taskUid,
4691
+ execution_uid: ledger.executionUid,
4692
+ session_uid: ledger.sessionUid,
4693
+ expected_version: ledger.taskVersion,
4694
+ lease_epoch: ledger.leaseEpoch,
4695
+ release_lease: true,
4696
+ summary: `Claude Code SessionEnd: ${input.reason || "interrupted"}`,
4697
+ idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
4698
+ agent_id: options.agentId || "claude-code"
4699
+ });
4700
+ } catch {
4701
+ }
4702
+ }
4703
+ if (ledger.sessionUid) {
4704
+ try {
4705
+ await client2.session({
4706
+ action: "close",
4707
+ session_uid: ledger.sessionUid,
4708
+ agent_id: options.agentId || "claude-code"
4709
+ });
4710
+ } catch {
4711
+ }
4712
+ }
4713
+ return {};
4714
+ }
4715
+ async function runClaudeHook(input, client2, options = {}) {
4716
+ switch (input.hook_event_name) {
4717
+ case "SessionStart":
4718
+ return sessionStart(input, client2, options);
4719
+ case "PreToolUse":
4720
+ if (!input.tool_name?.startsWith("mcp__mindgraph__")) return {};
4721
+ return preTool(input, options);
4722
+ case "PostToolUse":
4723
+ return postTool(input, client2, options);
4724
+ case "TaskCreated":
4725
+ withLedger(input.session_id, options, (ledger) => {
4726
+ ledger.nativeTasksCreated += 1;
4727
+ return void 0;
4728
+ });
4729
+ return {};
4730
+ case "TaskCompleted":
4731
+ withLedger(input.session_id, options, (ledger) => {
4732
+ ledger.nativeTasksTerminal += 1;
4733
+ return void 0;
4734
+ });
4735
+ return {};
4736
+ case "Stop":
4737
+ return stop(input, options);
4738
+ case "SessionEnd":
4739
+ return sessionEnd(input, client2, options);
4740
+ default:
4741
+ return {};
4742
+ }
4743
+ }
4744
+ async function readHookInput(stream = process.stdin) {
4745
+ const chunks = [];
4746
+ for await (const chunk of stream) {
4747
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4748
+ }
4749
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
4750
+ }
4751
+
4752
+ // src/hook-installer.ts
4753
+ var import_node_fs4 = __toESM(require("fs"));
4754
+ var import_node_os4 = __toESM(require("os"));
4755
+ var import_node_path4 = __toESM(require("path"));
4756
+ var import_node_crypto3 = __toESM(require("crypto"));
4757
+ var OWNER_MARKER = "--owner mindgraph";
4758
+ var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
4759
+ var COMMAND = `node "$HOME/${RUNNER_RELATIVE}" hook --harness claude-code --owner mindgraph`;
4760
+ function installHookRunner(sourceFile, homeDir) {
4761
+ const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
4762
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
4763
+ import_node_fs4.default.copyFileSync(sourceFile, target);
4764
+ return target;
4765
+ }
4766
+ function uninstallHookRunner(homeDir) {
4767
+ try {
4768
+ import_node_fs4.default.rmSync(import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE));
4769
+ } catch {
4770
+ }
4771
+ }
4772
+ function settingsPath(scope, projectDir = process.cwd()) {
4773
+ 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");
4774
+ }
4775
+ function readSettings(file) {
4776
+ if (!import_node_fs4.default.existsSync(file)) return {};
4777
+ return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
4778
+ }
4779
+ function ownedHook(timeout) {
4780
+ return {
4781
+ type: "command",
4782
+ command: COMMAND,
4783
+ timeout,
4784
+ statusMessage: "Syncing MindGraph session context"
4785
+ };
4786
+ }
4787
+ function desiredEntries() {
4788
+ return {
4789
+ SessionStart: [
4790
+ {
4791
+ matcher: "startup|resume|clear|compact|fork",
4792
+ hooks: [ownedHook(20)]
4793
+ }
4794
+ ],
4795
+ PreToolUse: [
4796
+ {
4797
+ matcher: "mcp__mindgraph__.*",
4798
+ hooks: [ownedHook(3)]
4799
+ }
4800
+ ],
4801
+ PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
4802
+ TaskCreated: [{ hooks: [ownedHook(3)] }],
4803
+ TaskCompleted: [{ hooks: [ownedHook(3)] }],
4804
+ Stop: [{ hooks: [ownedHook(5)] }],
4805
+ SessionEnd: [{ hooks: [ownedHook(8)] }]
4806
+ };
4807
+ }
4808
+ function hookCommand(value) {
4809
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
4810
+ const command = value.command;
4811
+ return typeof command === "string" ? command : void 0;
4812
+ }
4813
+ function entryHasOwnedHook(value) {
4814
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4815
+ const hooks = value.hooks;
4816
+ return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
4817
+ }
4818
+ function writeSettings(file, settings) {
4819
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
4820
+ const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
4821
+ import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
4822
+ `, {
4823
+ mode: 384
4824
+ });
4825
+ import_node_fs4.default.renameSync(temporary, file);
4826
+ }
4827
+ function installClaudeHooks(scope, projectDir = process.cwd()) {
4828
+ const file = settingsPath(scope, projectDir);
4829
+ const settings = readSettings(file);
4830
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
4831
+ let added = 0;
4832
+ for (const [event, wanted] of Object.entries(desiredEntries())) {
4833
+ const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
4834
+ for (const entry of wanted) {
4835
+ if (!current.some(entryHasOwnedHook)) {
4836
+ current.push(entry);
4837
+ added += 1;
4838
+ }
4839
+ }
4840
+ hooks[event] = current;
4841
+ }
4842
+ settings.hooks = hooks;
4843
+ writeSettings(file, settings);
4844
+ return { path: file, added };
4845
+ }
4846
+ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
4847
+ const file = settingsPath(scope, projectDir);
4848
+ if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
4849
+ const settings = readSettings(file);
4850
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
4851
+ let removed = 0;
4852
+ for (const [event, value] of Object.entries(hooks)) {
4853
+ if (!Array.isArray(value)) continue;
4854
+ const retainedEntries = [];
4855
+ for (const rawEntry of value) {
4856
+ if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
4857
+ retainedEntries.push(rawEntry);
4858
+ continue;
4859
+ }
4860
+ const entry = { ...rawEntry };
4861
+ if (!Array.isArray(entry.hooks)) {
4862
+ retainedEntries.push(entry);
4863
+ continue;
4864
+ }
4865
+ const retainedHooks = entry.hooks.filter((hook) => {
4866
+ const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
4867
+ if (owned) removed += 1;
4868
+ return !owned;
4869
+ });
4870
+ if (retainedHooks.length > 0) {
4871
+ entry.hooks = retainedHooks;
4872
+ retainedEntries.push(entry);
4873
+ }
4874
+ }
4875
+ if (retainedEntries.length > 0) hooks[event] = retainedEntries;
4876
+ else delete hooks[event];
4877
+ }
4878
+ settings.hooks = hooks;
4879
+ writeSettings(file, settings);
4880
+ return { path: file, removed };
4881
+ }
4882
+
4883
+ // src/cli.ts
2237
4884
  function getClaudeDesktopConfigPath() {
2238
4885
  switch (process.platform) {
2239
4886
  case "darwin":
2240
- return path.join(
2241
- os.homedir(),
4887
+ return path5.join(
4888
+ os4.homedir(),
2242
4889
  "Library",
2243
4890
  "Application Support",
2244
4891
  "Claude",
2245
4892
  "claude_desktop_config.json"
2246
4893
  );
2247
4894
  case "win32":
2248
- return path.join(
2249
- process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"),
4895
+ return path5.join(
4896
+ process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"),
2250
4897
  "Claude",
2251
4898
  "claude_desktop_config.json"
2252
4899
  );
2253
4900
  case "linux":
2254
- return path.join(
2255
- os.homedir(),
4901
+ return path5.join(
4902
+ os4.homedir(),
2256
4903
  ".config",
2257
4904
  "Claude",
2258
4905
  "claude_desktop_config.json"
@@ -2262,16 +4909,16 @@ function getClaudeDesktopConfigPath() {
2262
4909
  }
2263
4910
  }
2264
4911
  function readJsonFile(filePath) {
2265
- if (!fs.existsSync(filePath)) return {};
2266
- const content = fs.readFileSync(filePath, "utf-8");
4912
+ if (!fs5.existsSync(filePath)) return {};
4913
+ const content = fs5.readFileSync(filePath, "utf-8");
2267
4914
  return JSON.parse(content);
2268
4915
  }
2269
4916
  function writeJsonFile(filePath, data) {
2270
- const dir = path.dirname(filePath);
2271
- if (!fs.existsSync(dir)) {
2272
- fs.mkdirSync(dir, { recursive: true });
4917
+ const dir = path5.dirname(filePath);
4918
+ if (!fs5.existsSync(dir)) {
4919
+ fs5.mkdirSync(dir, { recursive: true });
2273
4920
  }
2274
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
4921
+ fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
2275
4922
  }
2276
4923
  function getMcpServerEntry(apiKey, baseUrl) {
2277
4924
  const env = { MINDGRAPH_API_KEY: apiKey };
@@ -2301,7 +4948,16 @@ function installClaudeDesktop(apiKey, baseUrl) {
2301
4948
  console.log("Restart Claude Desktop to activate.");
2302
4949
  }
2303
4950
  function installClaudeCode(apiKey, baseUrl) {
2304
- const envArgs = [`--env`, `MINDGRAPH_API_KEY=${apiKey}`];
4951
+ const envArgs = [
4952
+ `--env`,
4953
+ `MINDGRAPH_API_KEY=${apiKey}`,
4954
+ "--env",
4955
+ "MINDGRAPH_PROFILE=coding",
4956
+ "--env",
4957
+ "MINDGRAPH_HARNESS=claude-code",
4958
+ "--env",
4959
+ "MINDGRAPH_AGENT_ID=claude-code"
4960
+ ];
2305
4961
  if (baseUrl) {
2306
4962
  envArgs.push("--env", `MINDGRAPH_BASE_URL=${baseUrl}`);
2307
4963
  }
@@ -2331,7 +4987,7 @@ function installClaudeCode(apiKey, baseUrl) {
2331
4987
  }
2332
4988
  function uninstallClaudeDesktop() {
2333
4989
  const configPath = getClaudeDesktopConfigPath();
2334
- if (!fs.existsSync(configPath)) {
4990
+ if (!fs5.existsSync(configPath)) {
2335
4991
  console.log("Claude Desktop config not found. Nothing to remove.");
2336
4992
  return;
2337
4993
  }
@@ -2358,7 +5014,7 @@ function uninstallClaudeCode() {
2358
5014
  }
2359
5015
  function printStatus() {
2360
5016
  const desktopPath = getClaudeDesktopConfigPath();
2361
- if (fs.existsSync(desktopPath)) {
5017
+ if (fs5.existsSync(desktopPath)) {
2362
5018
  const config = readJsonFile(desktopPath);
2363
5019
  const servers = config.mcpServers;
2364
5020
  if (servers && "mindgraph" in servers) {
@@ -2376,6 +5032,36 @@ function printStatus() {
2376
5032
  `MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
2377
5033
  );
2378
5034
  }
5035
+ function withHooksFlag() {
5036
+ return process.argv.includes("--hooks");
5037
+ }
5038
+ function finishCodeInstall(apiKey, baseUrl, withHooks) {
5039
+ if (withHooks) {
5040
+ const runner = installHookRunner(__filename);
5041
+ console.log(`Installed pinned hook runner at ${runner}`);
5042
+ const result = installClaudeHooks("user");
5043
+ console.log(
5044
+ `Installed ${result.added} MindGraph Claude Code hook entries in ${result.path}`
5045
+ );
5046
+ const envPath = saveHookEnv({ apiKey, baseUrl });
5047
+ console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
5048
+ } else {
5049
+ console.log(
5050
+ "Tip: run with --hooks (or run install-hooks) to add the Claude Code session hooks \u2014 work-brief injection, provenance, reflection checkpoint."
5051
+ );
5052
+ }
5053
+ printCodegraphStatus();
5054
+ }
5055
+ function printCodegraphStatus() {
5056
+ try {
5057
+ (0, import_child_process.execFileSync)("codegraph", ["--version"], { stdio: "pipe" });
5058
+ console.log("Code intelligence: codegraph detected \u2014 anchors and structural recall are enabled.");
5059
+ } catch {
5060
+ console.log(
5061
+ "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."
5062
+ );
5063
+ }
5064
+ }
2379
5065
  function printUsage() {
2380
5066
  console.log(`
2381
5067
  mindgraph-mcp \u2014 MCP server for MindGraph knowledge graphs
@@ -2384,14 +5070,19 @@ USAGE:
2384
5070
  mindgraph-mcp Start the MCP server (stdio transport)
2385
5071
  mindgraph-mcp init Interactive setup for Claude Desktop
2386
5072
  mindgraph-mcp install Install into Claude Desktop config
2387
- mindgraph-mcp install-code Install into Claude Code
5073
+ mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
2388
5074
  mindgraph-mcp uninstall Remove from Claude Desktop config
2389
5075
  mindgraph-mcp uninstall-code Remove from Claude Code
5076
+ mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
5077
+ mindgraph-mcp uninstall-hooks --harness claude-code [--scope user|project]
2390
5078
  mindgraph-mcp status Show installation status
2391
5079
 
2392
5080
  OPTIONS:
2393
5081
  --api-key <key> MindGraph API key (or set MINDGRAPH_API_KEY env var)
2394
5082
  --base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
5083
+ --scope <scope> Hook settings scope: project (default) or user
5084
+ --project-dir <dir> Project root for project-scoped hooks
5085
+ --harness <name> Hook harness (currently claude-code)
2395
5086
  --help, -h Show this help message
2396
5087
 
2397
5088
  EXAMPLES:
@@ -2407,38 +5098,11 @@ EXAMPLES:
2407
5098
  Get your API key at: https://mindgraph.cloud/dashboard/keys
2408
5099
  `);
2409
5100
  }
2410
- function parseArgs(argv) {
2411
- const args = argv.slice(2);
2412
- let command = "serve";
2413
- let apiKey = process.env.MINDGRAPH_API_KEY;
2414
- let baseUrl = process.env.MINDGRAPH_BASE_URL;
2415
- for (let i = 0; i < args.length; i++) {
2416
- const arg = args[i];
2417
- switch (arg) {
2418
- case "--api-key":
2419
- apiKey = args[++i];
2420
- break;
2421
- case "--base-url":
2422
- baseUrl = args[++i];
2423
- break;
2424
- case "--help":
2425
- case "-h":
2426
- command = "help";
2427
- break;
2428
- default:
2429
- if (!arg.startsWith("-")) {
2430
- command = arg;
2431
- }
2432
- break;
2433
- }
2434
- }
2435
- return { command, apiKey, baseUrl };
2436
- }
2437
5101
  var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cloud";
2438
5102
  var POLL_INTERVAL = 2e3;
2439
5103
  var POLL_TIMEOUT = 10 * 60 * 1e3;
2440
5104
  function generateCode() {
2441
- return crypto.randomBytes(32).toString("hex");
5105
+ return crypto4.randomBytes(32).toString("hex");
2442
5106
  }
2443
5107
  function openBrowser(url) {
2444
5108
  try {
@@ -2475,7 +5139,7 @@ async function pollForKey(code) {
2475
5139
  } catch (e) {
2476
5140
  if (e instanceof Error && e.message.includes("Session")) throw e;
2477
5141
  }
2478
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
5142
+ await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
2479
5143
  }
2480
5144
  throw new Error("Timed out waiting for authorization.");
2481
5145
  }
@@ -2485,7 +5149,7 @@ async function interactiveInit(baseUrl) {
2485
5149
  input: process.stdin,
2486
5150
  output: process.stdout
2487
5151
  });
2488
- const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
5152
+ const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
2489
5153
  console.log("\nMindGraph MCP Server Setup\n");
2490
5154
  console.log(" 1. Sign in via browser (recommended)");
2491
5155
  console.log(" 2. Enter API key manually");
@@ -2532,10 +5196,12 @@ async function interactiveInit(baseUrl) {
2532
5196
  break;
2533
5197
  case "2":
2534
5198
  installClaudeCode(apiKey, baseUrl);
5199
+ finishCodeInstall(apiKey, baseUrl, withHooksFlag());
2535
5200
  break;
2536
5201
  case "3":
2537
5202
  installClaudeDesktop(apiKey, baseUrl);
2538
5203
  installClaudeCode(apiKey, baseUrl);
5204
+ finishCodeInstall(apiKey, baseUrl, withHooksFlag());
2539
5205
  break;
2540
5206
  default:
2541
5207
  console.error("Invalid choice.");
@@ -2544,7 +5210,7 @@ async function interactiveInit(baseUrl) {
2544
5210
  console.log("\nDone! Your knowledge graph is now connected.");
2545
5211
  }
2546
5212
  async function main2() {
2547
- const { command, apiKey, baseUrl } = parseArgs(process.argv);
5213
+ const { command, apiKey, baseUrl, scope, projectDir, harness, hooks } = parseArgs(process.argv);
2548
5214
  switch (command) {
2549
5215
  case "help":
2550
5216
  printUsage();
@@ -2569,6 +5235,7 @@ async function main2() {
2569
5235
  process.exit(1);
2570
5236
  }
2571
5237
  installClaudeCode(apiKey, baseUrl);
5238
+ finishCodeInstall(apiKey, baseUrl, withHooksFlag());
2572
5239
  break;
2573
5240
  case "uninstall":
2574
5241
  uninstallClaudeDesktop();
@@ -2576,6 +5243,64 @@ async function main2() {
2576
5243
  case "uninstall-code":
2577
5244
  uninstallClaudeCode();
2578
5245
  break;
5246
+ case "install-hooks": {
5247
+ if ((harness || "claude-code") !== "claude-code") {
5248
+ throw new Error("only --harness claude-code is available in this release");
5249
+ }
5250
+ const runner = installHookRunner(__filename);
5251
+ console.log(`Installed pinned hook runner at ${runner}`);
5252
+ const result = installClaudeHooks(scope, projectDir);
5253
+ console.log(
5254
+ `Installed ${result.added} MindGraph Claude Code hook entries in ${result.path}`
5255
+ );
5256
+ if (apiKey || baseUrl) {
5257
+ const envPath = saveHookEnv({ apiKey, baseUrl });
5258
+ console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
5259
+ } else {
5260
+ console.log(
5261
+ "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."
5262
+ );
5263
+ }
5264
+ break;
5265
+ }
5266
+ case "uninstall-hooks": {
5267
+ if ((harness || "claude-code") !== "claude-code") {
5268
+ throw new Error("only --harness claude-code is available in this release");
5269
+ }
5270
+ uninstallHookRunner();
5271
+ const result = uninstallClaudeHooks(scope, projectDir);
5272
+ console.log(
5273
+ `Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
5274
+ );
5275
+ break;
5276
+ }
5277
+ case "hook": {
5278
+ const stored = loadHookEnv();
5279
+ const hookApiKey = apiKey ?? stored.apiKey;
5280
+ const hookBaseUrl = baseUrl ?? stored.baseUrl;
5281
+ if (!hookApiKey) {
5282
+ process.stdout.write("{}\n");
5283
+ break;
5284
+ }
5285
+ try {
5286
+ const input = await readHookInput();
5287
+ const client2 = new import_mindgraph3.MindGraph({
5288
+ baseUrl: hookBaseUrl || "https://api.mindgraph.cloud",
5289
+ apiKey: hookApiKey,
5290
+ orgId: process.env.MINDGRAPH_ORG_ID,
5291
+ maxRetries: 0,
5292
+ telemetrySurface: "mcp"
5293
+ });
5294
+ const output = await runClaudeHook(input, client2, {
5295
+ agentId: process.env.MINDGRAPH_AGENT_ID || "claude-code"
5296
+ });
5297
+ process.stdout.write(`${JSON.stringify(output)}
5298
+ `);
5299
+ } catch {
5300
+ process.stdout.write("{}\n");
5301
+ }
5302
+ break;
5303
+ }
2579
5304
  case "status":
2580
5305
  printStatus();
2581
5306
  break;