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