mindgraph-mcp 0.13.5 → 0.14.0

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