mindgraph-mcp 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/cli.js +2011 -1791
- package/dist/index.js +1725 -1496
- package/dist/server.d.mts +15 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +1713 -1601
- package/dist/server.mjs +1702 -1589
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -35,14 +35,14 @@ function errorFields(body) {
|
|
|
35
35
|
...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
function canRetryRequest(method,
|
|
38
|
+
function canRetryRequest(method, path10, body) {
|
|
39
39
|
if (method === "GET" || method === "HEAD") return true;
|
|
40
40
|
if (method !== "POST") return false;
|
|
41
|
-
if (READ_POSTS.has(
|
|
41
|
+
if (READ_POSTS.has(path10)) return true;
|
|
42
42
|
const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
43
43
|
if (typeof fields.action !== "string") return false;
|
|
44
|
-
if (Object.prototype.hasOwnProperty.call(READ_ACTIONS,
|
|
45
|
-
return
|
|
44
|
+
if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path10) && READ_ACTIONS[path10].includes(fields.action)) return true;
|
|
45
|
+
return path10 === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
|
|
46
46
|
}
|
|
47
47
|
function permitsRetry(fields) {
|
|
48
48
|
return fields.retriable !== false && ![
|
|
@@ -179,8 +179,8 @@ var init_dist = __esm({
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
// ---- HTTP helpers ----
|
|
182
|
-
async request(method,
|
|
183
|
-
const url = `${this.baseUrl}${
|
|
182
|
+
async request(method, path10, body) {
|
|
183
|
+
const url = `${this.baseUrl}${path10}`;
|
|
184
184
|
const requestId = createRequestId();
|
|
185
185
|
const init = {
|
|
186
186
|
method,
|
|
@@ -195,12 +195,12 @@ var init_dist = __esm({
|
|
|
195
195
|
}
|
|
196
196
|
const retrySafe = canRetryRequest(
|
|
197
197
|
method,
|
|
198
|
-
|
|
198
|
+
path10,
|
|
199
199
|
typeof init.body === "string" ? JSON.parse(init.body) : void 0
|
|
200
200
|
);
|
|
201
201
|
let lastError;
|
|
202
202
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
203
|
-
const res = await this.fetchWithDeadline(url, init, method,
|
|
203
|
+
const res = await this.fetchWithDeadline(url, init, method, path10);
|
|
204
204
|
if (!res.ok) {
|
|
205
205
|
const text22 = await res.text().catch(() => "");
|
|
206
206
|
let parsed;
|
|
@@ -210,7 +210,7 @@ var init_dist = __esm({
|
|
|
210
210
|
parsed = text22;
|
|
211
211
|
}
|
|
212
212
|
const err6 = new MindGraphError(
|
|
213
|
-
`${method} ${
|
|
213
|
+
`${method} ${path10} failed: ${res.status}`,
|
|
214
214
|
res.status,
|
|
215
215
|
parsed
|
|
216
216
|
);
|
|
@@ -234,7 +234,7 @@ var init_dist = __esm({
|
|
|
234
234
|
* "timeout"`, `status: 0`). Without it the request is sent unchanged — no
|
|
235
235
|
* AbortSignal is attached, preserving the pre-0.16 behaviour.
|
|
236
236
|
*/
|
|
237
|
-
async fetchWithDeadline(url, init, method,
|
|
237
|
+
async fetchWithDeadline(url, init, method, path10) {
|
|
238
238
|
if (this.timeoutMs === void 0) {
|
|
239
239
|
return fetch(url, init);
|
|
240
240
|
}
|
|
@@ -245,7 +245,7 @@ var init_dist = __esm({
|
|
|
245
245
|
} catch (error2) {
|
|
246
246
|
if (controller.signal.aborted) {
|
|
247
247
|
throw new MindGraphError(
|
|
248
|
-
`${method} ${
|
|
248
|
+
`${method} ${path10} timed out after ${this.timeoutMs}ms`,
|
|
249
249
|
0,
|
|
250
250
|
{ error: "timeout", code: "timeout", retriable: false }
|
|
251
251
|
);
|
|
@@ -255,17 +255,17 @@ var init_dist = __esm({
|
|
|
255
255
|
clearTimeout(timer);
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
-
get(
|
|
259
|
-
return this.request("GET",
|
|
258
|
+
get(path10) {
|
|
259
|
+
return this.request("GET", path10);
|
|
260
260
|
}
|
|
261
|
-
post(
|
|
262
|
-
return this.request("POST",
|
|
261
|
+
post(path10, body) {
|
|
262
|
+
return this.request("POST", path10, body);
|
|
263
263
|
}
|
|
264
|
-
patch(
|
|
265
|
-
return this.request("PATCH",
|
|
264
|
+
patch(path10, body) {
|
|
265
|
+
return this.request("PATCH", path10, body);
|
|
266
266
|
}
|
|
267
|
-
del(
|
|
268
|
-
return this.request("DELETE",
|
|
267
|
+
del(path10) {
|
|
268
|
+
return this.request("DELETE", path10);
|
|
269
269
|
}
|
|
270
270
|
// ---- Health ----
|
|
271
271
|
async health() {
|
|
@@ -1240,14 +1240,14 @@ function unavailabilityCaveats(reason, message) {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
return caveats;
|
|
1242
1242
|
}
|
|
1243
|
-
function normalizePath(
|
|
1244
|
-
return
|
|
1243
|
+
function normalizePath(path10) {
|
|
1244
|
+
return path10.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1245
1245
|
}
|
|
1246
1246
|
function normalizeSignature(signature) {
|
|
1247
1247
|
return signature.replace(/\s+/g, " ").trim();
|
|
1248
1248
|
}
|
|
1249
|
-
function languageFromPath(
|
|
1250
|
-
const extension = (0, import_node_path2.extname)(
|
|
1249
|
+
function languageFromPath(path10) {
|
|
1250
|
+
const extension = (0, import_node_path2.extname)(path10).toLowerCase();
|
|
1251
1251
|
return {
|
|
1252
1252
|
".c": "c",
|
|
1253
1253
|
".cc": "cpp",
|
|
@@ -1313,6 +1313,15 @@ function nearestExistingDirectory(candidate) {
|
|
|
1313
1313
|
current = parent;
|
|
1314
1314
|
}
|
|
1315
1315
|
}
|
|
1316
|
+
function findChildWorkspaceFiles(start) {
|
|
1317
|
+
let entries;
|
|
1318
|
+
try {
|
|
1319
|
+
entries = (0, import_node_fs2.readdirSync)(start, { withFileTypes: true });
|
|
1320
|
+
} catch {
|
|
1321
|
+
return [];
|
|
1322
|
+
}
|
|
1323
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).slice(0, MAX_CHILD_DIRECTORIES).map((entry) => (0, import_node_path2.join)(start, entry.name, ".mindgraph", "workspace.json")).filter((candidate) => (0, import_node_fs2.existsSync)(candidate)).sort();
|
|
1324
|
+
}
|
|
1316
1325
|
function findWorkspaceFile(start, explicit) {
|
|
1317
1326
|
if (explicit) return (0, import_node_path2.resolve)(start, explicit);
|
|
1318
1327
|
let current = (0, import_node_path2.resolve)(start);
|
|
@@ -1408,7 +1417,7 @@ function codeRefIdentityKey(ref) {
|
|
|
1408
1417
|
}
|
|
1409
1418
|
};
|
|
1410
1419
|
}
|
|
1411
|
-
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto3, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
|
|
1420
|
+
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto3, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, MAX_CHILD_DIRECTORIES, CodegraphAdapter;
|
|
1412
1421
|
var init_codegraph = __esm({
|
|
1413
1422
|
"src/codegraph.ts"() {
|
|
1414
1423
|
"use strict";
|
|
@@ -1431,12 +1440,14 @@ var init_codegraph = __esm({
|
|
|
1431
1440
|
DEFAULT_TIMEOUT_MS = 5e3;
|
|
1432
1441
|
DEFAULT_STATUS_TTL_MS = 6e4;
|
|
1433
1442
|
MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
1443
|
+
MAX_CHILD_DIRECTORIES = 200;
|
|
1434
1444
|
CodegraphAdapter = class {
|
|
1435
1445
|
timeoutMs;
|
|
1436
1446
|
statusTtlMs;
|
|
1437
1447
|
env;
|
|
1438
1448
|
cwd;
|
|
1439
1449
|
workspaceFile;
|
|
1450
|
+
childWorkspaceFiles;
|
|
1440
1451
|
now;
|
|
1441
1452
|
identityCache = /* @__PURE__ */ new Map();
|
|
1442
1453
|
statusCache = /* @__PURE__ */ new Map();
|
|
@@ -1449,6 +1460,7 @@ var init_codegraph = __esm({
|
|
|
1449
1460
|
this.cwd,
|
|
1450
1461
|
options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
|
|
1451
1462
|
);
|
|
1463
|
+
this.childWorkspaceFiles = !this.workspaceFile && options.childWorkspaces ? findChildWorkspaceFiles(this.cwd) : [];
|
|
1452
1464
|
this.now = options.now ?? Date.now;
|
|
1453
1465
|
}
|
|
1454
1466
|
async resolveRepository(repo, fallbackSpaceUid) {
|
|
@@ -1772,13 +1784,15 @@ var init_codegraph = __esm({
|
|
|
1772
1784
|
}
|
|
1773
1785
|
configuredRepositories() {
|
|
1774
1786
|
const configured = [];
|
|
1775
|
-
|
|
1787
|
+
const workspaceFiles = this.workspaceFile ? [this.workspaceFile] : this.childWorkspaceFiles;
|
|
1788
|
+
for (const workspaceFile of workspaceFiles) {
|
|
1789
|
+
if (!(0, import_node_fs2.existsSync)(workspaceFile)) continue;
|
|
1776
1790
|
try {
|
|
1777
1791
|
const parsed = JSON.parse(
|
|
1778
|
-
(0, import_node_fs2.readFileSync)(
|
|
1792
|
+
(0, import_node_fs2.readFileSync)(workspaceFile, "utf8")
|
|
1779
1793
|
);
|
|
1780
1794
|
if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
|
|
1781
|
-
const configDirectory = (0, import_node_path2.dirname)(
|
|
1795
|
+
const configDirectory = (0, import_node_path2.dirname)(workspaceFile);
|
|
1782
1796
|
const base = (0, import_node_path2.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path2.dirname)(configDirectory) : configDirectory;
|
|
1783
1797
|
for (const repository of parsed.repositories) {
|
|
1784
1798
|
if (!repository || typeof repository.root !== "string") continue;
|
|
@@ -1959,1735 +1973,1926 @@ var init_codegraph = __esm({
|
|
|
1959
1973
|
}
|
|
1960
1974
|
});
|
|
1961
1975
|
|
|
1962
|
-
// src/
|
|
1963
|
-
function
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1976
|
+
// src/render.ts
|
|
1977
|
+
function obj(value) {
|
|
1978
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1979
|
+
}
|
|
1980
|
+
function str(value) {
|
|
1981
|
+
return typeof value === "string" && value ? value : void 0;
|
|
1982
|
+
}
|
|
1983
|
+
function num(value) {
|
|
1984
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1985
|
+
}
|
|
1986
|
+
function edgeTypeName(value) {
|
|
1987
|
+
return str(value) ?? str(obj(value)?.Custom);
|
|
1988
|
+
}
|
|
1989
|
+
function renderBudget(requested) {
|
|
1990
|
+
if (requested === void 0 || !Number.isFinite(requested)) return RENDER_CHAR_BUDGET;
|
|
1991
|
+
return Math.min(
|
|
1992
|
+
RENDER_CHAR_BUDGET,
|
|
1993
|
+
Math.max(MIN_RENDER_CHAR_BUDGET, Math.floor(requested))
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
function clip(value, max, state) {
|
|
1997
|
+
const text2 = str(value);
|
|
1998
|
+
if (!text2) return void 0;
|
|
1999
|
+
if (text2.length <= max) return text2;
|
|
2000
|
+
state.bounded = true;
|
|
2001
|
+
return `${text2.slice(0, max - 1)}\u2026`;
|
|
2002
|
+
}
|
|
2003
|
+
function isNodeShaped(value) {
|
|
2004
|
+
const node = obj(value);
|
|
2005
|
+
return Boolean(
|
|
2006
|
+
node && str(node.uid) && (str(node.label) || str(node.node_type) || obj(node.node_type))
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
function nodeDisplayType(node) {
|
|
2010
|
+
const props = obj(node.props);
|
|
2011
|
+
const plain = str(node.node_type);
|
|
2012
|
+
return (plain && plain !== "Custom" ? plain : void 0) ?? str(obj(node.node_type)?.Custom) ?? str(props?.object_type) ?? str(props?.type_name) ?? str(obj(props?.data)?.domain_type) ?? plain ?? "Node";
|
|
2013
|
+
}
|
|
2014
|
+
function nodeFields(node) {
|
|
2015
|
+
const props = obj(node.props);
|
|
2016
|
+
return obj(props?.fields) ?? obj(obj(props?.data)?.fields);
|
|
2017
|
+
}
|
|
2018
|
+
function unwrapSearchResult(value) {
|
|
2019
|
+
if (isNodeShaped(value)) return value;
|
|
2020
|
+
const wrapper = obj(value);
|
|
2021
|
+
const inner = obj(wrapper?.node) ?? obj(wrapper?.object);
|
|
2022
|
+
if (inner && isNodeShaped(inner)) {
|
|
2023
|
+
const score = num(wrapper.score);
|
|
2024
|
+
return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
|
|
1967
2025
|
}
|
|
1968
|
-
return
|
|
2026
|
+
return void 0;
|
|
1969
2027
|
}
|
|
1970
|
-
function
|
|
1971
|
-
|
|
1972
|
-
|
|
2028
|
+
function supersededBy(node) {
|
|
2029
|
+
const marker = node.superseded;
|
|
2030
|
+
if (!marker) return void 0;
|
|
2031
|
+
const head = obj(marker);
|
|
2032
|
+
if (!head) return str(node.superseded_by) ?? "";
|
|
2033
|
+
const label = (str(head.head_label) ?? str(head.head_uid) ?? "").replace(/\s+/g, " ").trim();
|
|
2034
|
+
const short = label.length <= 80 ? label : `${label.slice(0, 79)}\u2026`;
|
|
2035
|
+
const date = str(head.head_stated_at);
|
|
2036
|
+
return short && date ? `${short} (${date.slice(0, 10)})` : short;
|
|
2037
|
+
}
|
|
2038
|
+
function statusTag(node) {
|
|
2039
|
+
const tags = [];
|
|
2040
|
+
const replacement = supersededBy(node);
|
|
2041
|
+
if (replacement !== void 0) {
|
|
2042
|
+
tags.push(replacement ? `SUPERSEDED by ${replacement}` : "SUPERSEDED");
|
|
1973
2043
|
}
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2044
|
+
const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
|
|
2045
|
+
if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
|
|
2046
|
+
if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
|
|
2047
|
+
if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
|
|
2048
|
+
return tags.length > 0 ? ` [${tags.join("; ")}]` : "";
|
|
2049
|
+
}
|
|
2050
|
+
function firstQuote(node, state) {
|
|
2051
|
+
const chunks = Array.isArray(node.source_chunks) ? node.source_chunks : [];
|
|
2052
|
+
for (const raw of chunks) {
|
|
2053
|
+
const chunk = obj(raw);
|
|
2054
|
+
const quote = str(chunk?.quote) ?? str(obj(chunk?.anchor)?.exact);
|
|
2055
|
+
if (quote) return clip(quote, QUOTE_MAX, state);
|
|
1978
2056
|
}
|
|
1979
|
-
return
|
|
2057
|
+
return void 0;
|
|
1980
2058
|
}
|
|
1981
|
-
function
|
|
1982
|
-
const
|
|
1983
|
-
const
|
|
1984
|
-
const
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
...code !== void 0 ? { code } : {},
|
|
1989
|
-
...typeof body.retriable === "boolean" ? { retriable: body.retriable } : {}
|
|
1990
|
-
};
|
|
1991
|
-
return {
|
|
1992
|
-
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
1993
|
-
structuredContent: payload,
|
|
1994
|
-
isError: true
|
|
1995
|
-
};
|
|
2059
|
+
function sourceTag(node) {
|
|
2060
|
+
const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
|
|
2061
|
+
const first = obj(docs[0]);
|
|
2062
|
+
const title = str(first?.title);
|
|
2063
|
+
if (!title) return void 0;
|
|
2064
|
+
const extra = docs.length > 1 ? ` (+${docs.length - 1} more)` : "";
|
|
2065
|
+
return `from: ${title}${extra}`;
|
|
1996
2066
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
const
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
};
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
}
|
|
2036
|
-
const missingSpace = resolved.find(
|
|
2037
|
-
({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
|
|
2038
|
-
);
|
|
2039
|
-
if (missingSpace) {
|
|
2040
|
-
return err(
|
|
2041
|
-
"missing_repository_space",
|
|
2042
|
-
`repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
|
|
2043
|
-
);
|
|
2044
|
-
}
|
|
2045
|
-
const repositoryAnchors = /* @__PURE__ */ new Map();
|
|
2046
|
-
for (const { repository } of resolved) {
|
|
2047
|
-
if (repositoryAnchors.has(repository.repoRoot)) continue;
|
|
2048
|
-
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
2049
|
-
const anchored = await anchorRepository(
|
|
2050
|
-
codeClient,
|
|
2051
|
-
repository,
|
|
2052
|
-
spaceUid,
|
|
2053
|
-
agentId
|
|
2054
|
-
);
|
|
2055
|
-
repositoryAnchors.set(repository.repoRoot, {
|
|
2056
|
-
...anchored,
|
|
2057
|
-
repository
|
|
2058
|
-
});
|
|
2059
|
-
}
|
|
2060
|
-
const anchors = [];
|
|
2061
|
-
for (const { ref, repository } of resolved) {
|
|
2062
|
-
const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
|
|
2063
|
-
if (!repositoryAnchor.uid) continue;
|
|
2064
|
-
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
2065
|
-
const anchor = await anchorCodeRef(
|
|
2066
|
-
codeClient,
|
|
2067
|
-
ref,
|
|
2068
|
-
repositoryAnchor.uid,
|
|
2069
|
-
spaceUid,
|
|
2070
|
-
agentId
|
|
2071
|
-
);
|
|
2072
|
-
anchors.push(anchor);
|
|
2073
|
-
}
|
|
2074
|
-
return ok({
|
|
2075
|
-
availability: resolution.availability,
|
|
2076
|
-
repository: repositoryAnchors.values().next().value,
|
|
2077
|
-
repositories: [...repositoryAnchors.values()],
|
|
2078
|
-
anchors,
|
|
2079
|
-
unresolved,
|
|
2080
|
-
wrote: anchors.some((anchor) => anchor.created)
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
case "recall": {
|
|
2084
|
-
const materialized = new Set(anchorUids);
|
|
2085
|
-
let resolution;
|
|
2086
|
-
if (refs.length > 0) {
|
|
2087
|
-
resolution = await adapter.resolveRefs(refs, options);
|
|
2088
|
-
for (const result of resolution.results) {
|
|
2089
|
-
if (!result.resolved) continue;
|
|
2090
|
-
const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
|
|
2091
|
-
if (uid) materialized.add(uid);
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
if (materialized.size === 0) {
|
|
2095
|
-
return ok({
|
|
2096
|
-
availability: resolution?.availability,
|
|
2097
|
-
anchors: [],
|
|
2098
|
-
context: [],
|
|
2099
|
-
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
2100
|
-
});
|
|
2101
|
-
}
|
|
2102
|
-
const context = await Promise.all(
|
|
2103
|
-
[...materialized].map(async (uid) => ({
|
|
2104
|
-
anchor_uid: uid,
|
|
2105
|
-
graph: await codeClient.traverse({
|
|
2106
|
-
action: "neighborhood",
|
|
2107
|
-
start_uid: uid,
|
|
2108
|
-
depth: 2,
|
|
2109
|
-
max_nodes: Math.min(limit ?? 20, 20)
|
|
2110
|
-
})
|
|
2111
|
-
}))
|
|
2112
|
-
);
|
|
2113
|
-
return ok({
|
|
2114
|
-
availability: resolution?.availability,
|
|
2115
|
-
anchors: [...materialized],
|
|
2116
|
-
context,
|
|
2117
|
-
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
2118
|
-
});
|
|
2119
|
-
}
|
|
2120
|
-
case "expand": {
|
|
2121
|
-
const target = await resolveSingleTarget(
|
|
2122
|
-
codeClient,
|
|
2123
|
-
adapter,
|
|
2124
|
-
refs,
|
|
2125
|
-
anchorUids,
|
|
2126
|
-
options
|
|
2127
|
-
);
|
|
2128
|
-
if ("error" in target) return target.error;
|
|
2129
|
-
const expanded = await adapter.expand(target.ref, limit);
|
|
2130
|
-
const joined = await joinMaterialized(
|
|
2131
|
-
codeClient,
|
|
2132
|
-
[...expanded.callers, ...expanded.callees],
|
|
2133
|
-
agentId,
|
|
2134
|
-
Math.min(limit ?? 20, 20)
|
|
2135
|
-
);
|
|
2136
|
-
return ok({ anchor: target.ref, ...expanded, materialized: joined });
|
|
2137
|
-
}
|
|
2138
|
-
case "affected": {
|
|
2139
|
-
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
2140
|
-
let graphContext;
|
|
2141
|
-
const graphRefs = [];
|
|
2142
|
-
let unavailableRepositories = [];
|
|
2143
|
-
if (targetUid) {
|
|
2144
|
-
graphContext = await codeClient.traverse({
|
|
2145
|
-
action: "neighborhood",
|
|
2146
|
-
start_uid: targetUid,
|
|
2147
|
-
depth: 2,
|
|
2148
|
-
max_nodes: 50
|
|
2149
|
-
});
|
|
2150
|
-
const fromGraph = await codeRefsFromGraphContext(
|
|
2151
|
-
codeClient,
|
|
2152
|
-
adapter,
|
|
2153
|
-
graphContext
|
|
2154
|
-
);
|
|
2155
|
-
graphRefs.push(...fromGraph.refs);
|
|
2156
|
-
unavailableRepositories = fromGraph.unavailable;
|
|
2157
|
-
}
|
|
2158
|
-
if (refs.length > 0 || anchorUids.length > 0) {
|
|
2159
|
-
const target = await resolveSingleTarget(
|
|
2160
|
-
codeClient,
|
|
2161
|
-
adapter,
|
|
2162
|
-
refs,
|
|
2163
|
-
anchorUids,
|
|
2164
|
-
options
|
|
2165
|
-
);
|
|
2166
|
-
if ("error" in target) return target.error;
|
|
2167
|
-
graphRefs.unshift(target.ref);
|
|
2168
|
-
}
|
|
2169
|
-
const unique = uniqueRefs(graphRefs);
|
|
2170
|
-
if (unique.length === 0) {
|
|
2171
|
-
return ok({
|
|
2172
|
-
target_uid: targetUid,
|
|
2173
|
-
graph_context: graphContext,
|
|
2174
|
-
affected: [],
|
|
2175
|
-
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
2176
|
-
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
2177
|
-
});
|
|
2178
|
-
}
|
|
2179
|
-
const impacts = await Promise.all(
|
|
2180
|
-
unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
|
|
2181
|
-
);
|
|
2182
|
-
const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
|
|
2183
|
-
const joined = await joinMaterialized(
|
|
2184
|
-
codeClient,
|
|
2185
|
-
structural,
|
|
2186
|
-
agentId,
|
|
2187
|
-
Math.min(limit ?? 50, 50)
|
|
2188
|
-
);
|
|
2189
|
-
return ok({
|
|
2190
|
-
target_uid: targetUid,
|
|
2191
|
-
graph_context: graphContext,
|
|
2192
|
-
impacts,
|
|
2193
|
-
materialized: joined,
|
|
2194
|
-
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
|
-
default:
|
|
2198
|
-
return err("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
2199
|
-
}
|
|
2200
|
-
} catch (cause) {
|
|
2201
|
-
if (cause instanceof UnknownRepositoryError) {
|
|
2202
|
-
return err("unknown_repository", cause.message);
|
|
2203
|
-
}
|
|
2204
|
-
return toolError(cause, "code_tool_failed");
|
|
2067
|
+
function layer7Fields(node, state) {
|
|
2068
|
+
const fields = nodeFields(node);
|
|
2069
|
+
if (!fields) return void 0;
|
|
2070
|
+
const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
|
|
2071
|
+
if (pairs.length === 0) return void 0;
|
|
2072
|
+
return clip(pairs.join("; "), SUMMARY_MAX, state);
|
|
2073
|
+
}
|
|
2074
|
+
function nodeSummaryText(node, state) {
|
|
2075
|
+
const props = obj(node.props);
|
|
2076
|
+
return clip(node.summary, SUMMARY_MAX, state) ?? clip(props?.content, SUMMARY_MAX, state) ?? clip(props?.description, SUMMARY_MAX, state) ?? clip(props?.definition, SUMMARY_MAX, state);
|
|
2077
|
+
}
|
|
2078
|
+
function nodeLines(node, state) {
|
|
2079
|
+
const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
|
|
2080
|
+
const type = nodeDisplayType(node);
|
|
2081
|
+
const confidence = num(node.confidence);
|
|
2082
|
+
const score = num(node.retrieval_score);
|
|
2083
|
+
const parenthetical = [
|
|
2084
|
+
type,
|
|
2085
|
+
confidence !== void 0 ? `confidence ${confidence}` : void 0,
|
|
2086
|
+
score !== void 0 ? `score ${Math.round(score * 100) / 100}` : void 0
|
|
2087
|
+
].filter(Boolean).join(", ");
|
|
2088
|
+
const head = `- **${label}** [${str(node.uid) || "?"}] (${parenthetical})${statusTag(node)}`;
|
|
2089
|
+
const summary = nodeSummaryText(node, state);
|
|
2090
|
+
const lines = [summary && summary !== label ? `${head}: ${summary}` : head];
|
|
2091
|
+
const fields = layer7Fields(node, state);
|
|
2092
|
+
if (fields) lines.push(` - fields: ${fields}`);
|
|
2093
|
+
const quote = firstQuote(node, state);
|
|
2094
|
+
if (quote) lines.push(` - source quote: "${quote}"`);
|
|
2095
|
+
const source = sourceTag(node);
|
|
2096
|
+
if (source) lines.push(` - ${source}`);
|
|
2097
|
+
const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
|
|
2098
|
+
if (believers.length > 0) {
|
|
2099
|
+
const rendered2 = believers.slice(0, 3).map((raw) => {
|
|
2100
|
+
const believer = obj(raw);
|
|
2101
|
+
const conf = num(believer?.confidence);
|
|
2102
|
+
return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
|
|
2103
|
+
}).join(", ");
|
|
2104
|
+
lines.push(` - believed by: ${rendered2}`);
|
|
2205
2105
|
}
|
|
2106
|
+
return lines.join("\n");
|
|
2206
2107
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2108
|
+
function groupedNodeSections(nodes, state, headingPrefix = "") {
|
|
2109
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2110
|
+
for (const node of nodes) {
|
|
2111
|
+
const type = nodeDisplayType(node);
|
|
2112
|
+
const group = groups.get(type) ?? [];
|
|
2113
|
+
group.push(node);
|
|
2114
|
+
groups.set(type, group);
|
|
2210
2115
|
}
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
wrote: false,
|
|
2218
|
-
caveat: "the write response exposed no primary UID to attach"
|
|
2219
|
-
}
|
|
2116
|
+
const sections = [];
|
|
2117
|
+
let first = true;
|
|
2118
|
+
for (const [type, group] of groups) {
|
|
2119
|
+
sections.push({
|
|
2120
|
+
heading: `${first ? headingPrefix : ""}### ${type}`,
|
|
2121
|
+
items: group.map((node) => nodeLines(node, state))
|
|
2220
2122
|
});
|
|
2123
|
+
first = false;
|
|
2221
2124
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2125
|
+
return sections;
|
|
2126
|
+
}
|
|
2127
|
+
function relationshipItems(edges, labelByUid, state, options) {
|
|
2128
|
+
const lines = [];
|
|
2129
|
+
for (const raw of edges) {
|
|
2130
|
+
const edge = obj(raw);
|
|
2131
|
+
if (!edge) continue;
|
|
2132
|
+
const type = edgeTypeName(edge.edge_type) || "?";
|
|
2133
|
+
const from = str(edge.from_uid);
|
|
2134
|
+
const to = str(edge.to_uid);
|
|
2135
|
+
if (CURATION_EDGE_TYPES.has(type)) continue;
|
|
2136
|
+
if (!from || !to || from === to) continue;
|
|
2137
|
+
const fromLabel = labelByUid.get(from) ?? (options?.fallbackToUid ? `[${from}]` : void 0);
|
|
2138
|
+
const toLabel = labelByUid.get(to) ?? (options?.fallbackToUid ? `[${to}]` : void 0);
|
|
2139
|
+
if (!fromLabel || !toLabel) continue;
|
|
2140
|
+
lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
|
|
2141
|
+
}
|
|
2142
|
+
if (lines.length > RELATIONSHIP_MAX) {
|
|
2143
|
+
state.bounded = true;
|
|
2144
|
+
const extra = lines.length - RELATIONSHIP_MAX;
|
|
2145
|
+
state.omitted += extra;
|
|
2146
|
+
return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
|
|
2147
|
+
}
|
|
2148
|
+
return lines;
|
|
2149
|
+
}
|
|
2150
|
+
function assemble(header, sections, state, maxChars) {
|
|
2151
|
+
const budget = renderBudget(maxChars);
|
|
2152
|
+
const packingBudget = Math.max(header.length, budget - BOUNDED_MARKER_RESERVE);
|
|
2153
|
+
const lines = [header];
|
|
2154
|
+
let used = header.length;
|
|
2155
|
+
for (const section of sections) {
|
|
2156
|
+
if (section.items.length === 0 && !section.heading) continue;
|
|
2157
|
+
const headingCost = section.heading.length + 2;
|
|
2158
|
+
if (used + headingCost > packingBudget) {
|
|
2159
|
+
state.bounded = true;
|
|
2160
|
+
state.omitted += Math.max(1, section.items.length);
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
let sectionUsed = 0;
|
|
2164
|
+
const kept = [];
|
|
2165
|
+
let dropped = 0;
|
|
2166
|
+
for (const item of section.items) {
|
|
2167
|
+
if (used + headingCost + sectionUsed + item.length + 1 > packingBudget) {
|
|
2168
|
+
dropped += 1;
|
|
2169
|
+
continue;
|
|
2258
2170
|
}
|
|
2171
|
+
kept.push(item);
|
|
2172
|
+
sectionUsed += item.length + 1;
|
|
2259
2173
|
}
|
|
2260
|
-
if (
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2174
|
+
if (kept.length === 0 && section.items.length > 0) {
|
|
2175
|
+
state.bounded = true;
|
|
2176
|
+
state.omitted += dropped;
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
if (dropped > 0) {
|
|
2180
|
+
state.bounded = true;
|
|
2181
|
+
state.omitted += dropped;
|
|
2182
|
+
const marker = `(+${dropped} more not shown)`;
|
|
2183
|
+
if (used + headingCost + sectionUsed + marker.length + 1 <= packingBudget) {
|
|
2184
|
+
kept.push(marker);
|
|
2185
|
+
sectionUsed += marker.length + 1;
|
|
2186
|
+
}
|
|
2268
2187
|
}
|
|
2188
|
+
lines.push(
|
|
2189
|
+
section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
|
|
2190
|
+
${kept.join("\n")}` : kept.join("\n")
|
|
2191
|
+
);
|
|
2192
|
+
used += headingCost + sectionUsed;
|
|
2269
2193
|
}
|
|
2270
|
-
|
|
2194
|
+
if (state.bounded) {
|
|
2195
|
+
lines.push(
|
|
2196
|
+
`\u2026[rendered context bounded to ${budget} chars; ${state.omitted} item(s) omitted \u2014 fetch full node content by uid, narrow the query, raise max_output_chars, or pass format: "json" for the raw response]`
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
return lines.join("\n\n").slice(0, budget);
|
|
2271
2200
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
canonical_name: repository.repoId,
|
|
2279
|
-
identifiers: { repo_id: repository.repoId }
|
|
2280
|
-
},
|
|
2281
|
-
identity: {
|
|
2282
|
-
namespace: "external.code",
|
|
2283
|
-
key_version: 1,
|
|
2284
|
-
key: repositoryIdentityKey(repository)
|
|
2285
|
-
},
|
|
2286
|
-
identity_space_uid: spaceUid,
|
|
2287
|
-
agent_id: agentId
|
|
2201
|
+
function policySections(payload, state) {
|
|
2202
|
+
const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
|
|
2203
|
+
if (policies.length === 0) return [];
|
|
2204
|
+
const items = policies.map((raw) => obj(raw)).filter((policy) => Boolean(policy)).map((policy) => {
|
|
2205
|
+
const rules = Array.isArray(policy.prose_rules) ? policy.prose_rules.map(String).join("; ") : policy.prose_rules;
|
|
2206
|
+
return `- ${str(policy.name) || "(unnamed policy)"}: ${clip(rules, SUMMARY_MAX, state) || "(structured rules apply)"}`;
|
|
2288
2207
|
});
|
|
2208
|
+
return [{ heading: "## Applicable policies", items }];
|
|
2289
2209
|
}
|
|
2290
|
-
|
|
2291
|
-
const
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
identifiers: {
|
|
2298
|
-
repo_id: ref.repoId,
|
|
2299
|
-
path: ref.path,
|
|
2300
|
-
qualified_name: ref.qualifiedName
|
|
2301
|
-
},
|
|
2302
|
-
attributes: { code_ref: storedCodeRef(ref) }
|
|
2303
|
-
},
|
|
2304
|
-
identity: {
|
|
2305
|
-
namespace: "external.code",
|
|
2306
|
-
key_version: 1,
|
|
2307
|
-
key: codeRefIdentityKey(ref)
|
|
2308
|
-
},
|
|
2309
|
-
identity_space_uid: spaceUid,
|
|
2310
|
-
agent_id: agentId
|
|
2311
|
-
});
|
|
2312
|
-
if (response.uid && response.created !== false && response.status !== "existing") {
|
|
2313
|
-
await client2.entity({
|
|
2314
|
-
action: "relate",
|
|
2315
|
-
source_uid: response.uid,
|
|
2316
|
-
target_uid: repositoryUid,
|
|
2317
|
-
edge_type: "PART_OF",
|
|
2318
|
-
agent_id: agentId
|
|
2319
|
-
});
|
|
2320
|
-
}
|
|
2321
|
-
return { ...response, ref };
|
|
2322
|
-
}
|
|
2323
|
-
async function resolveMaterializedUid(client2, ref, agentId) {
|
|
2324
|
-
const response = await client2.entity({
|
|
2325
|
-
action: "resolve_identity",
|
|
2326
|
-
identity: {
|
|
2327
|
-
namespace: "external.code",
|
|
2328
|
-
key_version: 1,
|
|
2329
|
-
key: codeRefIdentityKey(ref)
|
|
2330
|
-
},
|
|
2331
|
-
agent_id: agentId
|
|
2332
|
-
});
|
|
2333
|
-
return response.status === "existing" ? response.uid : void 0;
|
|
2334
|
-
}
|
|
2335
|
-
async function codeRefFromAnchor(client2, adapter, uid) {
|
|
2336
|
-
const node = await client2.getNode(uid);
|
|
2337
|
-
const attributes = node.props?.attributes;
|
|
2338
|
-
if (!attributes || typeof attributes !== "object") return void 0;
|
|
2339
|
-
const codeRef = attributes.code_ref;
|
|
2340
|
-
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
2341
|
-
try {
|
|
2342
|
-
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
2343
|
-
return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
|
|
2344
|
-
} catch (cause) {
|
|
2345
|
-
if (cause instanceof UnknownRepositoryError) {
|
|
2346
|
-
return { missingRepo: codeRef.repoId };
|
|
2210
|
+
function hiddenSections(payload) {
|
|
2211
|
+
const hidden = payload.hidden_item_count;
|
|
2212
|
+
if (hidden === void 0 || hidden === null) return [];
|
|
2213
|
+
return [
|
|
2214
|
+
{
|
|
2215
|
+
heading: `Note: ${hidden} item(s) were hidden by access scoping.`,
|
|
2216
|
+
items: []
|
|
2347
2217
|
}
|
|
2348
|
-
|
|
2349
|
-
}
|
|
2218
|
+
];
|
|
2350
2219
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
}
|
|
2360
|
-
if ("missingRepo" in outcome) {
|
|
2361
|
-
return {
|
|
2362
|
-
error: err(
|
|
2363
|
-
"repository_unavailable",
|
|
2364
|
-
`repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
|
|
2365
|
-
)
|
|
2366
|
-
};
|
|
2220
|
+
function renderContext(response, maxChars) {
|
|
2221
|
+
const payload = obj(response);
|
|
2222
|
+
if (!payload) return void 0;
|
|
2223
|
+
if (typeof payload.context_text === "string" && payload.context_text.length > 0) {
|
|
2224
|
+
const text2 = payload.context_text;
|
|
2225
|
+
if (maxChars !== void 0 && text2.length > maxChars) {
|
|
2226
|
+
return `${text2.slice(0, maxChars)}
|
|
2227
|
+
[truncated to ${maxChars} characters]`;
|
|
2367
2228
|
}
|
|
2368
|
-
return
|
|
2369
|
-
}
|
|
2370
|
-
if (refs.length !== 1) {
|
|
2371
|
-
return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
2372
|
-
}
|
|
2373
|
-
const resolution = await adapter.resolveRefs(refs, options);
|
|
2374
|
-
const result = resolution.results[0];
|
|
2375
|
-
if (!result.resolved) {
|
|
2376
|
-
return {
|
|
2377
|
-
error: ok({
|
|
2378
|
-
availability: resolution.availability,
|
|
2379
|
-
result,
|
|
2380
|
-
wrote: false
|
|
2381
|
-
})
|
|
2382
|
-
};
|
|
2229
|
+
return text2;
|
|
2383
2230
|
}
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
const
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2231
|
+
const graph = obj(payload.graph);
|
|
2232
|
+
const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
2233
|
+
const shaped = rawNodes.filter(isNodeShaped);
|
|
2234
|
+
const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(str(node.node_type) || ""));
|
|
2235
|
+
const infraOnly = shaped.length - nodes.length;
|
|
2236
|
+
const articles = Array.isArray(payload.articles) ? payload.articles : [];
|
|
2237
|
+
const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
|
|
2238
|
+
const state = { bounded: false, omitted: 0 };
|
|
2239
|
+
const sections = [];
|
|
2240
|
+
if (articles.length > 0) {
|
|
2241
|
+
sections.push({
|
|
2242
|
+
heading: "## Knowledge articles",
|
|
2243
|
+
items: articles.map((raw) => obj(raw)).filter((article) => Boolean(article)).map(
|
|
2244
|
+
(article) => `### ${str(article.label) || "(untitled)"} [${str(article.uid) || "?"}]
|
|
2245
|
+
${clip(article.content, ARTICLE_MAX, state) || ""}`
|
|
2246
|
+
)
|
|
2400
2247
|
});
|
|
2401
2248
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
const codeRef = attributes.code_ref;
|
|
2414
|
-
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
2415
|
-
});
|
|
2416
|
-
const refs = [];
|
|
2417
|
-
const unavailable = /* @__PURE__ */ new Set();
|
|
2418
|
-
for (const ref of stored) {
|
|
2419
|
-
try {
|
|
2420
|
-
const repository = await adapter.resolveRepository(ref.repoId);
|
|
2421
|
-
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
2422
|
-
} catch (cause) {
|
|
2423
|
-
if (cause instanceof UnknownRepositoryError) {
|
|
2424
|
-
unavailable.add(ref.repoId);
|
|
2425
|
-
} else {
|
|
2426
|
-
throw cause;
|
|
2427
|
-
}
|
|
2249
|
+
if (nodes.length > 0) {
|
|
2250
|
+
sections.push(...groupedNodeSections(nodes, state, "## Knowledge graph\n\n"));
|
|
2251
|
+
const labelByUid = /* @__PURE__ */ new Map();
|
|
2252
|
+
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
2253
|
+
const relationships = relationshipItems(
|
|
2254
|
+
Array.isArray(graph?.edges) ? graph.edges : [],
|
|
2255
|
+
labelByUid,
|
|
2256
|
+
state
|
|
2257
|
+
);
|
|
2258
|
+
if (relationships.length > 0) {
|
|
2259
|
+
sections.push({ heading: "## Relationships", items: relationships });
|
|
2428
2260
|
}
|
|
2261
|
+
} else if (infraOnly > 0) {
|
|
2262
|
+
sections.push({
|
|
2263
|
+
heading: `Note: ${infraOnly} document/chunk node(s) matched \u2014 use action "document_index" or include_chunks for their content.`,
|
|
2264
|
+
items: []
|
|
2265
|
+
});
|
|
2429
2266
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2267
|
+
if (chunks.length > 0) {
|
|
2268
|
+
sections.push({
|
|
2269
|
+
heading: "## Source excerpts",
|
|
2270
|
+
items: chunks.map((raw) => obj(raw)).filter((chunk) => Boolean(chunk)).map(
|
|
2271
|
+
(chunk) => `- [${str(chunk.chunk_uid) || "?"}] from "${str(chunk.document_title) || "unknown document"}": "${clip(chunk.content, CHUNK_MAX, state) || ""}"`
|
|
2272
|
+
)
|
|
2273
|
+
});
|
|
2436
2274
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
} else {
|
|
2442
|
-
collectUids(child, uids);
|
|
2443
|
-
}
|
|
2275
|
+
sections.push(...policySections(payload, state));
|
|
2276
|
+
sections.push(...hiddenSections(payload));
|
|
2277
|
+
if (sections.length === 0) {
|
|
2278
|
+
return "No results found for this search.";
|
|
2444
2279
|
}
|
|
2280
|
+
if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
|
|
2281
|
+
return assemble(
|
|
2282
|
+
"# Retrieved context\n\nNo graph results matched this search.",
|
|
2283
|
+
sections,
|
|
2284
|
+
state,
|
|
2285
|
+
maxChars
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
return assemble("# Retrieved context", sections, state, maxChars);
|
|
2445
2289
|
}
|
|
2446
|
-
function
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
const
|
|
2453
|
-
|
|
2290
|
+
function resumeEntry(raw, state, fallbackType) {
|
|
2291
|
+
const wrapper = obj(raw);
|
|
2292
|
+
if (!wrapper) return void 0;
|
|
2293
|
+
const value = obj(wrapper.node) ?? wrapper;
|
|
2294
|
+
if (isNodeShaped(value)) return nodeLines(value, state);
|
|
2295
|
+
const uid = str(value.uid);
|
|
2296
|
+
const props = obj(value.props);
|
|
2297
|
+
const label = clip(value.label, LABEL_MAX, state) ?? clip(value.name, LABEL_MAX, state) ?? clip(value.title, LABEL_MAX, state) ?? clip(value.action, LABEL_MAX, state) ?? fallbackType;
|
|
2298
|
+
const status = str(value.status) ?? str(props?.status);
|
|
2299
|
+
const summary = clip(value.summary, SUMMARY_MAX, state) ?? clip(value.description, SUMMARY_MAX, state) ?? clip(value.content, SUMMARY_MAX, state) ?? clip(value.outcome_summary, SUMMARY_MAX, state) ?? clip(props?.summary, SUMMARY_MAX, state);
|
|
2300
|
+
const details = [
|
|
2301
|
+
status ? `status ${status}` : void 0,
|
|
2302
|
+
str(value.path) ? `path ${str(value.path)}` : void 0,
|
|
2303
|
+
str(value.symbol) ? `symbol ${str(value.symbol)}` : void 0
|
|
2304
|
+
].filter(Boolean);
|
|
2305
|
+
const head = `- **${label}**${uid ? ` [${uid}]` : ""}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
|
|
2306
|
+
return summary && summary !== label ? `${head}: ${summary}` : head;
|
|
2454
2307
|
}
|
|
2455
|
-
function
|
|
2456
|
-
const
|
|
2457
|
-
return
|
|
2458
|
-
const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
|
|
2459
|
-
if (seen.has(key)) return false;
|
|
2460
|
-
seen.add(key);
|
|
2461
|
-
return true;
|
|
2462
|
-
});
|
|
2308
|
+
function resumeSection(heading, values, state, fallbackType) {
|
|
2309
|
+
const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
|
|
2310
|
+
return items.length > 0 ? { heading, items } : void 0;
|
|
2463
2311
|
}
|
|
2464
|
-
function
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
"
|
|
2471
|
-
"
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
];
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
2494
|
-
});
|
|
2495
|
-
err = (code, message, details) => ({
|
|
2496
|
-
content: [
|
|
2497
|
-
{
|
|
2498
|
-
type: "text",
|
|
2499
|
-
text: JSON.stringify({ error: message, code, details })
|
|
2500
|
-
}
|
|
2501
|
-
],
|
|
2502
|
-
isError: true
|
|
2312
|
+
function renderResumeContext(response, maxChars, repositoryNames = {}) {
|
|
2313
|
+
const payload = obj(response);
|
|
2314
|
+
if (!payload) return void 0;
|
|
2315
|
+
const state = { bounded: false, omitted: 0 };
|
|
2316
|
+
const status = str(payload.status);
|
|
2317
|
+
if (!payload.task && status === "no_eligible_work") {
|
|
2318
|
+
const scopedOut = typeof payload.scoped_out_tasks === "number" ? payload.scoped_out_tasks : 0;
|
|
2319
|
+
const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
|
|
2320
|
+
return assemble(
|
|
2321
|
+
"# Resume context",
|
|
2322
|
+
[{ heading: `No eligible work was found in the requested scope.${note}`, items: [] }],
|
|
2323
|
+
state,
|
|
2324
|
+
maxChars
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
const sections = [];
|
|
2328
|
+
const push = (section) => {
|
|
2329
|
+
if (section) sections.push(section);
|
|
2330
|
+
};
|
|
2331
|
+
push(resumeSection("## Current task", [payload.task], state, "Task"));
|
|
2332
|
+
const other = otherEligibleWork(payload, repositoryNames);
|
|
2333
|
+
if (other.shown > 0) {
|
|
2334
|
+
sections.push({
|
|
2335
|
+
heading: `## Other eligible work (${other.shown} shown${other.more > 0 ? `, ${other.more} more not listed` : ""})`,
|
|
2336
|
+
items: other.groups.flatMap(
|
|
2337
|
+
(group) => group.tasks.map(
|
|
2338
|
+
(task) => `- ${group.repository}: ${task.label} [${task.uid}]${task.detail ? ` \u2014 ${task.detail}` : ""}`
|
|
2339
|
+
)
|
|
2340
|
+
)
|
|
2503
2341
|
});
|
|
2504
|
-
CODE_TOOL = {
|
|
2505
|
-
name: "mindgraph_code",
|
|
2506
|
-
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. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
|
|
2507
|
-
inputSchema: {
|
|
2508
|
-
type: "object",
|
|
2509
|
-
properties: {
|
|
2510
|
-
action: {
|
|
2511
|
-
type: "string",
|
|
2512
|
-
enum: ["anchor", "recall", "expand", "affected"]
|
|
2513
|
-
},
|
|
2514
|
-
code_refs: {
|
|
2515
|
-
type: "array",
|
|
2516
|
-
maxItems: 50,
|
|
2517
|
-
items: {
|
|
2518
|
-
oneOf: [
|
|
2519
|
-
{ type: "string" },
|
|
2520
|
-
{
|
|
2521
|
-
type: "object",
|
|
2522
|
-
properties: {
|
|
2523
|
-
repo: { type: "string" },
|
|
2524
|
-
path: { type: "string" },
|
|
2525
|
-
line: { type: "integer", minimum: 1 },
|
|
2526
|
-
symbol: { type: "string" },
|
|
2527
|
-
kind: { type: "string" },
|
|
2528
|
-
language: { type: "string" },
|
|
2529
|
-
signature: { type: "string" }
|
|
2530
|
-
},
|
|
2531
|
-
additionalProperties: false
|
|
2532
|
-
}
|
|
2533
|
-
]
|
|
2534
|
-
}
|
|
2535
|
-
},
|
|
2536
|
-
anchor_uids: {
|
|
2537
|
-
type: "array",
|
|
2538
|
-
maxItems: 50,
|
|
2539
|
-
items: { type: "string" },
|
|
2540
|
-
description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
|
|
2541
|
-
},
|
|
2542
|
-
repo: {
|
|
2543
|
-
type: "string",
|
|
2544
|
-
description: "Configured repository id or repository root"
|
|
2545
|
-
},
|
|
2546
|
-
repo_space_uid: {
|
|
2547
|
-
type: "string",
|
|
2548
|
-
description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
|
|
2549
|
-
},
|
|
2550
|
-
task_uid: { type: "string" },
|
|
2551
|
-
execution_uid: { type: "string" },
|
|
2552
|
-
limit: { type: "integer", minimum: 1, maximum: 50 },
|
|
2553
|
-
agent_id: { type: "string" }
|
|
2554
|
-
},
|
|
2555
|
-
required: ["action"]
|
|
2556
|
-
},
|
|
2557
|
-
annotations: {
|
|
2558
|
-
readOnlyHint: false,
|
|
2559
|
-
destructiveHint: false,
|
|
2560
|
-
idempotentHint: true,
|
|
2561
|
-
openWorldHint: true
|
|
2562
|
-
}
|
|
2563
|
-
};
|
|
2564
2342
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2343
|
+
push(
|
|
2344
|
+
resumeSection(
|
|
2345
|
+
"## Goal and project",
|
|
2346
|
+
[payload.goal, payload.project].filter(Boolean),
|
|
2347
|
+
state,
|
|
2348
|
+
"Scope"
|
|
2349
|
+
)
|
|
2572
2350
|
);
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2351
|
+
push(resumeSection("## Plan", [payload.plan], state, "Plan"));
|
|
2352
|
+
push(
|
|
2353
|
+
resumeSection(
|
|
2354
|
+
"## Next steps",
|
|
2355
|
+
Array.isArray(payload.next_steps) ? payload.next_steps : [],
|
|
2356
|
+
state,
|
|
2357
|
+
"Next step"
|
|
2358
|
+
)
|
|
2359
|
+
);
|
|
2360
|
+
push(
|
|
2361
|
+
resumeSection(
|
|
2362
|
+
"## Blockers",
|
|
2363
|
+
Array.isArray(payload.blockers) ? payload.blockers : [],
|
|
2364
|
+
state,
|
|
2365
|
+
"Blocker"
|
|
2366
|
+
)
|
|
2367
|
+
);
|
|
2368
|
+
const knowledge = obj(payload.knowledge);
|
|
2369
|
+
push(
|
|
2370
|
+
resumeSection(
|
|
2371
|
+
"## Relevant lessons and risks",
|
|
2372
|
+
[
|
|
2373
|
+
...Array.isArray(knowledge?.lessons) ? knowledge.lessons : [],
|
|
2374
|
+
...Array.isArray(knowledge?.risks) ? knowledge.risks : []
|
|
2375
|
+
],
|
|
2376
|
+
state,
|
|
2377
|
+
"Knowledge"
|
|
2378
|
+
)
|
|
2379
|
+
);
|
|
2380
|
+
push(
|
|
2381
|
+
resumeSection(
|
|
2382
|
+
"## Active execution",
|
|
2383
|
+
[payload.active_execution].filter(Boolean),
|
|
2384
|
+
state,
|
|
2385
|
+
"Execution"
|
|
2386
|
+
)
|
|
2387
|
+
);
|
|
2388
|
+
push(
|
|
2389
|
+
resumeSection(
|
|
2390
|
+
"## Recent executions",
|
|
2391
|
+
Array.isArray(payload.recent_executions) ? payload.recent_executions : [],
|
|
2392
|
+
state,
|
|
2393
|
+
"Execution"
|
|
2394
|
+
)
|
|
2395
|
+
);
|
|
2396
|
+
push(
|
|
2397
|
+
resumeSection(
|
|
2398
|
+
"## Code targets",
|
|
2399
|
+
Array.isArray(payload.code_targets) ? payload.code_targets : [],
|
|
2400
|
+
state,
|
|
2401
|
+
"Code target"
|
|
2402
|
+
)
|
|
2403
|
+
);
|
|
2404
|
+
push(resumeSection("## Lease", [payload.lease].filter(Boolean), state, "Lease"));
|
|
2405
|
+
const warning = clip(payload.warning, SUMMARY_MAX, state);
|
|
2406
|
+
if (warning) sections.push({ heading: "## Warning", items: [`- ${warning}`] });
|
|
2407
|
+
const selectionReason = clip(payload.selection_reason, SUMMARY_MAX, state);
|
|
2408
|
+
if (selectionReason) {
|
|
2409
|
+
sections.push({ heading: "## Selection", items: [`- ${selectionReason}`] });
|
|
2578
2410
|
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
function bareToolName(name) {
|
|
2582
|
-
const cut = name.lastIndexOf("__");
|
|
2583
|
-
return cut === -1 ? name : name.slice(cut + 2);
|
|
2584
|
-
}
|
|
2585
|
-
function callKey(toolName, toolInput) {
|
|
2586
|
-
return import_node_crypto4.default.createHash("sha256").update(`${bareToolName(toolName)}\0${stableJson(toolInput)}`).digest("hex");
|
|
2587
|
-
}
|
|
2588
|
-
function sessionKey(sessionId) {
|
|
2589
|
-
return import_node_crypto4.default.createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
2411
|
+
if (sections.length === 0) return void 0;
|
|
2412
|
+
return assemble("# Resume context", sections, state, maxChars);
|
|
2590
2413
|
}
|
|
2591
|
-
function
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
const
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2414
|
+
function otherEligibleWork(brief, names = {}) {
|
|
2415
|
+
const listed = Array.isArray(brief?.other_eligible) ? brief.other_eligible : [];
|
|
2416
|
+
const groups = [];
|
|
2417
|
+
let shown = 0;
|
|
2418
|
+
for (const entry of listed) {
|
|
2419
|
+
const task = obj(entry);
|
|
2420
|
+
const uid = typeof task?.uid === "string" ? task.uid : void 0;
|
|
2421
|
+
if (!task || !uid) continue;
|
|
2422
|
+
const scopes = Array.isArray(task.scope_uids) ? task.scope_uids.map(String) : [];
|
|
2423
|
+
const named = scopes.find((scope) => names[scope]);
|
|
2424
|
+
const repository = named ? names[named] : "Other scopes";
|
|
2425
|
+
let group = groups.find((candidate) => candidate.repository === repository);
|
|
2426
|
+
if (!group) {
|
|
2427
|
+
group = { repository, tasks: [] };
|
|
2428
|
+
groups.push(group);
|
|
2429
|
+
}
|
|
2430
|
+
const label = typeof task.label === "string" && task.label ? task.label : "(unlabeled)";
|
|
2431
|
+
const detail = [
|
|
2432
|
+
typeof task.status === "string" ? task.status : void 0,
|
|
2433
|
+
typeof task.priority === "string" && task.priority ? `priority ${task.priority}` : void 0
|
|
2434
|
+
].filter(Boolean).join(", ");
|
|
2435
|
+
group.tasks.push({
|
|
2436
|
+
uid,
|
|
2437
|
+
label: label.length > 160 ? `${label.slice(0, 159)}\u2026` : label,
|
|
2438
|
+
detail
|
|
2439
|
+
});
|
|
2440
|
+
shown += 1;
|
|
2604
2441
|
}
|
|
2442
|
+
const total = typeof brief?.eligible_total === "number" ? brief.eligible_total : 0;
|
|
2443
|
+
return { groups, shown, more: Math.max(0, total - 1 - shown) };
|
|
2605
2444
|
}
|
|
2606
|
-
function
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
};
|
|
2618
|
-
const file = import_node_path3.default.join(
|
|
2619
|
-
dir,
|
|
2620
|
-
`${callKey(toolName, toolInput)}.${sessionKey(harnessSessionId)}.json`
|
|
2621
|
-
);
|
|
2622
|
-
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
2623
|
-
import_node_fs3.default.writeFileSync(temporary, JSON.stringify(record), { mode: 384 });
|
|
2624
|
-
import_node_fs3.default.renameSync(temporary, file);
|
|
2625
|
-
} catch {
|
|
2445
|
+
function renderOtherEligibleWork(brief, names = {}) {
|
|
2446
|
+
const { groups, shown, more } = otherEligibleWork(brief, names);
|
|
2447
|
+
if (shown === 0) return void 0;
|
|
2448
|
+
const lines = [
|
|
2449
|
+
`Other eligible work in this scope (${shown} shown${more > 0 ? `, ${more} more not listed` : ""}; resume one by task_uid):`
|
|
2450
|
+
];
|
|
2451
|
+
for (const group of groups) {
|
|
2452
|
+
lines.push(`${group.repository}:`);
|
|
2453
|
+
for (const task of group.tasks) {
|
|
2454
|
+
lines.push(`- ${task.label} [${task.uid}]${task.detail ? ` \u2014 ${task.detail}` : ""}`);
|
|
2455
|
+
}
|
|
2626
2456
|
}
|
|
2457
|
+
return lines.join("\n");
|
|
2627
2458
|
}
|
|
2628
|
-
function
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
try {
|
|
2641
|
-
const record = JSON.parse(
|
|
2642
|
-
import_node_fs3.default.readFileSync(import_node_path3.default.join(dir, name), "utf8")
|
|
2643
|
-
);
|
|
2644
|
-
if (typeof record.createdAt === "number" && now - record.createdAt <= TTL_MS && record.fills && typeof record.fills === "object") {
|
|
2645
|
-
matches.push(record);
|
|
2459
|
+
function renderNodeList(response, title, limit, maxChars) {
|
|
2460
|
+
let candidates = [];
|
|
2461
|
+
if (Array.isArray(response)) {
|
|
2462
|
+
candidates = response;
|
|
2463
|
+
} else {
|
|
2464
|
+
const payload = obj(response);
|
|
2465
|
+
if (!payload) return void 0;
|
|
2466
|
+
for (const key of ["nodes", "results", "items", "goals", "questions", "claims", "preferences", "documents"]) {
|
|
2467
|
+
const candidate = payload[key];
|
|
2468
|
+
if (Array.isArray(candidate) && candidate.some((entry) => unwrapSearchResult(entry))) {
|
|
2469
|
+
candidates = candidate;
|
|
2470
|
+
break;
|
|
2646
2471
|
}
|
|
2647
|
-
} catch {
|
|
2648
2472
|
}
|
|
2649
2473
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2474
|
+
let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
|
|
2475
|
+
if (nodes.length === 0) return void 0;
|
|
2476
|
+
const total = nodes.length;
|
|
2477
|
+
const state = { bounded: false, omitted: 0 };
|
|
2478
|
+
if (limit !== void 0 && limit > 0 && nodes.length > limit) {
|
|
2479
|
+
state.omitted += nodes.length - limit;
|
|
2480
|
+
nodes = nodes.slice(0, limit);
|
|
2481
|
+
state.bounded = true;
|
|
2653
2482
|
}
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
import_node_fs3 = __toESM(require("fs"));
|
|
2662
|
-
import_node_os3 = __toESM(require("os"));
|
|
2663
|
-
import_node_path3 = __toESM(require("path"));
|
|
2664
|
-
TTL_MS = 10 * 6e4;
|
|
2665
|
-
}
|
|
2666
|
-
});
|
|
2667
|
-
|
|
2668
|
-
// src/hook-installer.ts
|
|
2669
|
-
function storedFeatures(homeDir) {
|
|
2670
|
-
const { promptRecall: promptRecall2, capture } = loadHookEnv(
|
|
2671
|
-
import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), ".mindgraph")
|
|
2483
|
+
const sections = groupedNodeSections(nodes, state);
|
|
2484
|
+
const shown = nodes.length;
|
|
2485
|
+
return assemble(
|
|
2486
|
+
`# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
|
|
2487
|
+
sections,
|
|
2488
|
+
state,
|
|
2489
|
+
maxChars
|
|
2672
2490
|
);
|
|
2673
|
-
return { promptRecall: promptRecall2, capture };
|
|
2674
|
-
}
|
|
2675
|
-
function command(harness) {
|
|
2676
|
-
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
2677
|
-
}
|
|
2678
|
-
function windowsCommand(harness) {
|
|
2679
|
-
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
2680
2491
|
}
|
|
2681
|
-
function
|
|
2682
|
-
const
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2492
|
+
function renderTraversal(response, maxChars) {
|
|
2493
|
+
const directSteps = Array.isArray(response) ? response : void 0;
|
|
2494
|
+
const payload = obj(response);
|
|
2495
|
+
if (!payload && !directSteps) return void 0;
|
|
2496
|
+
const body = payload ?? {};
|
|
2497
|
+
const container = obj(body.graph) ?? body;
|
|
2498
|
+
const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
|
|
2499
|
+
const nodes = rawNodes.filter(isNodeShaped);
|
|
2500
|
+
const paths = Array.isArray(body.paths) ? body.paths : [];
|
|
2501
|
+
const hasSteps = directSteps !== void 0 || Object.hasOwn(body, "steps");
|
|
2502
|
+
const rawSteps = directSteps ?? (Array.isArray(body.steps) ? body.steps : []);
|
|
2503
|
+
if (nodes.length === 0 && paths.length === 0 && !hasSteps) return void 0;
|
|
2504
|
+
const state = { bounded: false, omitted: 0 };
|
|
2505
|
+
const sections = [];
|
|
2506
|
+
const labelByUid = /* @__PURE__ */ new Map();
|
|
2507
|
+
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
2508
|
+
if (paths.length > 0) {
|
|
2509
|
+
const items = paths.map((raw) => obj(raw)).filter((path10) => Boolean(path10)).map((path10, index) => {
|
|
2510
|
+
const uids = Array.isArray(path10.node_uids) ? path10.node_uids.map(String) : [];
|
|
2511
|
+
const labels = Array.isArray(path10.labels) ? path10.labels.map(String) : Array.isArray(path10.nodes) ? path10.nodes.map((hop) => {
|
|
2512
|
+
const hopNode = obj(hop);
|
|
2513
|
+
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
2514
|
+
}) : [];
|
|
2515
|
+
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
2516
|
+
const cost = num(path10.cost) ?? num(path10.path_cost);
|
|
2517
|
+
const confidence = num(path10.path_confidence);
|
|
2518
|
+
const meta = [
|
|
2519
|
+
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
2520
|
+
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
2521
|
+
].filter(Boolean);
|
|
2522
|
+
const head = `${index + 1}. ${route}${meta.length > 0 ? ` (${meta.join(", ")})` : ""}`;
|
|
2523
|
+
return labels.length > 0 && uids.length > 0 ? `${head}
|
|
2524
|
+
uids: ${uids.join(" \u2192 ")}` : head;
|
|
2525
|
+
});
|
|
2526
|
+
sections.push({ heading: "## Paths", items });
|
|
2693
2527
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2528
|
+
if (hasSteps) {
|
|
2529
|
+
const stepItems = rawSteps.map((raw) => obj(raw)).filter((step) => Boolean(step)).map((step) => {
|
|
2530
|
+
const depth = num(step.depth);
|
|
2531
|
+
const uid = str(step.node_uid) ?? str(step.uid) ?? str(step.edge_to_uid) ?? "?";
|
|
2532
|
+
const parent = str(step.parent_uid) ?? str(step.edge_from_uid);
|
|
2533
|
+
const label = str(step.label) || "(unlabeled)";
|
|
2534
|
+
const relation = edgeTypeName(step.edge_type);
|
|
2535
|
+
const role = str(step.traversal_role);
|
|
2536
|
+
const type = str(step.node_type) ?? str(obj(step.node_type)?.Custom);
|
|
2537
|
+
const cost = num(step.path_cost);
|
|
2538
|
+
const confidence = num(step.path_confidence);
|
|
2539
|
+
const prefix = [
|
|
2540
|
+
depth !== void 0 ? `depth ${depth}` : void 0,
|
|
2541
|
+
role,
|
|
2542
|
+
relation ? `\u2014${relation}\u2192` : void 0
|
|
2543
|
+
].filter(Boolean).join(" ");
|
|
2544
|
+
const meta = [
|
|
2545
|
+
type,
|
|
2546
|
+
parent ? `parent ${parent}` : void 0,
|
|
2547
|
+
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
2548
|
+
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
2549
|
+
].filter(Boolean).join(", ");
|
|
2550
|
+
return `- ${prefix ? `${prefix}: ` : ""}**${label}** [${uid}]${meta ? ` (${meta})` : ""}`;
|
|
2551
|
+
});
|
|
2552
|
+
if (stepItems.length > 0) {
|
|
2553
|
+
sections.push({ heading: "## Traversal steps", items: stepItems });
|
|
2554
|
+
} else {
|
|
2555
|
+
const start = str(body.start_uid) || "?";
|
|
2556
|
+
const end = str(body.end_uid);
|
|
2557
|
+
const mode2 = str(body.mode);
|
|
2558
|
+
sections.push({
|
|
2559
|
+
heading: mode2 === "path" && end ? `No path found from [${start}] to [${end}].` : `No traversal steps returned from [${start}].`,
|
|
2560
|
+
items: []
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
if (nodes.length > 0) {
|
|
2565
|
+
sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
|
|
2566
|
+
const relationships = relationshipItems(
|
|
2567
|
+
Array.isArray(container.edges) ? container.edges : [],
|
|
2568
|
+
labelByUid,
|
|
2569
|
+
state
|
|
2570
|
+
);
|
|
2571
|
+
if (relationships.length > 0) {
|
|
2572
|
+
sections.push({ heading: "## Relationships", items: relationships });
|
|
2573
|
+
}
|
|
2705
2574
|
}
|
|
2575
|
+
const mode = str(body.mode);
|
|
2576
|
+
return assemble(
|
|
2577
|
+
`# Graph traversal${mode ? ` (${mode})` : ""}`,
|
|
2578
|
+
sections,
|
|
2579
|
+
state,
|
|
2580
|
+
maxChars
|
|
2581
|
+
);
|
|
2706
2582
|
}
|
|
2707
|
-
function
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2583
|
+
function neighborLine(value) {
|
|
2584
|
+
const node = obj(value);
|
|
2585
|
+
if (!node) return void 0;
|
|
2586
|
+
const label = str(node.label) || "(unlabeled)";
|
|
2587
|
+
const uid = str(node.uid);
|
|
2588
|
+
const type = str(node.node_type);
|
|
2589
|
+
return `- ${label}${uid ? ` [${uid}]` : ""}${type ? ` (${type})` : ""}`;
|
|
2712
2590
|
}
|
|
2713
|
-
function
|
|
2714
|
-
|
|
2715
|
-
|
|
2591
|
+
function renderOntologyAnswer(response) {
|
|
2592
|
+
const payload = obj(response);
|
|
2593
|
+
if (!payload) return void 0;
|
|
2594
|
+
const objects = Array.isArray(payload.objects) ? payload.objects.filter(isNodeShaped) : isNodeShaped(payload.object) ? [payload.object] : [];
|
|
2595
|
+
const cognitive = obj(payload.cognitive_context);
|
|
2596
|
+
const provenance = Array.isArray(payload.provenance) ? payload.provenance : [];
|
|
2597
|
+
const hasQuerySignature = Array.isArray(payload.objects) || obj(payload.confidence) !== void 0;
|
|
2598
|
+
const cognitiveEmpty = !cognitive || Object.values(cognitive).every((v) => !Array.isArray(v) || v.length === 0);
|
|
2599
|
+
if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
|
|
2600
|
+
return hasQuerySignature ? "No matching domain objects." : void 0;
|
|
2716
2601
|
}
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
if (!
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
return { refreshed: false, reason: "unparseable", runnerVersion };
|
|
2738
|
-
}
|
|
2739
|
-
if (!semverNewer(server, pinned)) {
|
|
2740
|
-
return {
|
|
2741
|
-
refreshed: false,
|
|
2742
|
-
reason: runnerVersion === serverVersion ? "current" : "downgrade",
|
|
2743
|
-
runnerVersion
|
|
2744
|
-
};
|
|
2602
|
+
const state = { bounded: false, omitted: 0 };
|
|
2603
|
+
const sections = [];
|
|
2604
|
+
if (objects.length > 0) {
|
|
2605
|
+
sections.push(...groupedNodeSections(objects, state));
|
|
2606
|
+
}
|
|
2607
|
+
const labelByUid = /* @__PURE__ */ new Map();
|
|
2608
|
+
for (const node of objects) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
2609
|
+
const graph = obj(payload.graph);
|
|
2610
|
+
for (const raw of Array.isArray(graph?.nodes) ? graph.nodes : []) {
|
|
2611
|
+
const node = obj(raw);
|
|
2612
|
+
const uid = str(node?.uid);
|
|
2613
|
+
if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
|
|
2614
|
+
}
|
|
2615
|
+
if (cognitive) {
|
|
2616
|
+
for (const rawNodes of Object.values(cognitive)) {
|
|
2617
|
+
if (!Array.isArray(rawNodes)) continue;
|
|
2618
|
+
for (const raw of rawNodes) {
|
|
2619
|
+
const node = obj(raw);
|
|
2620
|
+
const uid = str(node?.uid);
|
|
2621
|
+
if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
|
|
2745
2622
|
}
|
|
2746
2623
|
}
|
|
2747
|
-
installHookRunner(sourceBundle, home, serverVersion);
|
|
2748
|
-
return { refreshed: true, reason: "updated", runnerVersion };
|
|
2749
|
-
} catch {
|
|
2750
|
-
return { refreshed: false, reason: "error" };
|
|
2751
2624
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
if (!
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
projectDir,
|
|
2765
|
-
void 0,
|
|
2766
|
-
homeDir,
|
|
2767
|
-
storedFeatures(homeDir)
|
|
2768
|
-
);
|
|
2769
|
-
refreshed.push(file);
|
|
2770
|
-
} catch {
|
|
2625
|
+
const relations = Array.isArray(payload.relations) ? payload.relations : [];
|
|
2626
|
+
const relationLines = relationshipItems(relations, labelByUid, state, { fallbackToUid: true });
|
|
2627
|
+
if (relationLines.length > 0) {
|
|
2628
|
+
sections.push({ heading: "## Relations", items: relationLines });
|
|
2629
|
+
}
|
|
2630
|
+
if (cognitive) {
|
|
2631
|
+
for (const [category, rawNodes] of Object.entries(cognitive)) {
|
|
2632
|
+
if (!Array.isArray(rawNodes) || rawNodes.length === 0) continue;
|
|
2633
|
+
const items = rawNodes.map(neighborLine).filter((line) => Boolean(line));
|
|
2634
|
+
if (items.length > 0) {
|
|
2635
|
+
sections.push({ heading: `### Cognitive context \u2014 ${category}`, items });
|
|
2636
|
+
}
|
|
2771
2637
|
}
|
|
2772
2638
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
if (harness === "codex") {
|
|
2782
|
-
return scope === "user" ? import_node_path5.default.join(codexHomeDir, "hooks.json") : import_node_path5.default.join(projectDir, ".codex", "hooks.json");
|
|
2639
|
+
if (provenance.length > 0) {
|
|
2640
|
+
const items = provenance.map((raw) => obj(raw)).filter((entry) => Boolean(entry)).map((entry) => {
|
|
2641
|
+
const span = clip(entry.text_span, QUOTE_MAX, state) || "";
|
|
2642
|
+
const by = str(entry.ingested_by_name);
|
|
2643
|
+
const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${str(entry.source_uid) || "?"}]`;
|
|
2644
|
+
return str(entry.span_kind) === "anchor" ? `- "${span}"${suffix}` : `- context: ${span}${suffix}`;
|
|
2645
|
+
});
|
|
2646
|
+
sections.push({ heading: "## Sources", items });
|
|
2783
2647
|
}
|
|
2784
|
-
|
|
2648
|
+
const confidence = num(obj(payload.confidence)?.overall);
|
|
2649
|
+
const notes = [];
|
|
2650
|
+
if (confidence !== void 0) notes.push(`overall confidence ${confidence}`);
|
|
2651
|
+
if (payload.has_more === true) notes.push("more results exist (has_more)");
|
|
2652
|
+
if (payload.seed_cap_hit === true) notes.push("seed cap hit \u2014 narrow the query for a complete set");
|
|
2653
|
+
if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
|
|
2654
|
+
return assemble(
|
|
2655
|
+
`# Ontology results (${objects.length} object${objects.length === 1 ? "" : "s"})`,
|
|
2656
|
+
sections,
|
|
2657
|
+
state
|
|
2658
|
+
);
|
|
2785
2659
|
}
|
|
2786
|
-
function
|
|
2787
|
-
|
|
2788
|
-
|
|
2660
|
+
function renderObjectList(response, title) {
|
|
2661
|
+
const payload = obj(response);
|
|
2662
|
+
if (!payload) return void 0;
|
|
2663
|
+
const rawItems = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.rows) && payload.rows.some((row) => unwrapSearchResult(row)) ? payload.rows : void 0;
|
|
2664
|
+
const items = (rawItems ?? []).map(unwrapSearchResult).filter((node) => Boolean(node));
|
|
2665
|
+
if (items.length === 0) {
|
|
2666
|
+
return rawItems ? `# ${title} (0 results)` : void 0;
|
|
2667
|
+
}
|
|
2668
|
+
const state = { bounded: false, omitted: 0 };
|
|
2669
|
+
const sections = groupedNodeSections(items, state);
|
|
2670
|
+
const total = num(payload.total_count);
|
|
2671
|
+
const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
|
|
2672
|
+
const notes = [];
|
|
2673
|
+
if (payload.has_more === true) notes.push("more results exist");
|
|
2674
|
+
if (truncations.length > 0) notes.push(`truncated: ${truncations.join(", ")}`);
|
|
2675
|
+
if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
|
|
2676
|
+
const count = total !== void 0 && total !== items.length ? `${items.length} of ${total}` : `${items.length}`;
|
|
2677
|
+
return assemble(`# ${title} (${count})`, sections, state);
|
|
2789
2678
|
}
|
|
2790
|
-
function
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
30,
|
|
2816
|
-
harness === "codex" ? 1e4 : void 0
|
|
2817
|
-
)
|
|
2818
|
-
]
|
|
2819
|
-
}
|
|
2820
|
-
],
|
|
2821
|
-
PreToolUse: [
|
|
2822
|
-
{
|
|
2823
|
-
matcher: "mcp__mindgraph__.*",
|
|
2824
|
-
// Repository resolution over a large multi-repo workspace plus three
|
|
2825
|
-
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
2826
|
-
// PreToolUse means the call goes through untagged.
|
|
2827
|
-
hooks: [ownedHook(harness, 5)]
|
|
2828
|
-
}
|
|
2829
|
-
],
|
|
2830
|
-
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
2831
|
-
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
2832
|
-
// Claude Code: only the tools the hook acts on (MindGraph calls, edits,
|
|
2833
|
-
// and the shell/agent calls that keep a lease alive). Read, Grep and Glob
|
|
2834
|
-
// are most of a session's tool calls and the hook did nothing useful for
|
|
2835
|
-
// them but start a process (roadmap H8). Codex names its tools
|
|
2836
|
-
// differently, so it keeps the catch-all.
|
|
2837
|
-
PostToolUse: [
|
|
2838
|
-
{
|
|
2839
|
-
matcher: harness === "claude-code" ? CLAUDE_POST_TOOL_MATCHER : ".*",
|
|
2840
|
-
hooks: [ownedHook(harness, 10)]
|
|
2841
|
-
}
|
|
2842
|
-
],
|
|
2843
|
-
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
2844
|
-
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
2845
|
-
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
2846
|
-
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
2847
|
-
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
2848
|
-
// map), so the codex adapter instead makes at most ONE call there.
|
|
2849
|
-
SessionEnd: [
|
|
2850
|
-
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
2851
|
-
]
|
|
2852
|
-
};
|
|
2853
|
-
if (harness !== "claude-code") return common;
|
|
2854
|
-
return {
|
|
2855
|
-
...common,
|
|
2856
|
-
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
2857
|
-
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }],
|
|
2858
|
-
// Bounded recall per prompt: the core gives up at 4s and fails open, so
|
|
2859
|
-
// a 5s budget never holds the prompt longer than that.
|
|
2860
|
-
...features.promptRecall !== false ? { UserPromptSubmit: [{ hooks: [ownedHook(harness, 5)] }] } : {},
|
|
2861
|
-
// Opt-in transcript capture before compaction (cold-tenant ingest).
|
|
2862
|
-
...features.capture === true ? { PreCompact: [{ hooks: [ownedHook(harness, 30)] }] } : {}
|
|
2863
|
-
};
|
|
2864
|
-
}
|
|
2865
|
-
function hookCommand(value) {
|
|
2866
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2867
|
-
const command2 = value.command;
|
|
2868
|
-
return typeof command2 === "string" ? command2 : void 0;
|
|
2869
|
-
}
|
|
2870
|
-
function entryHasOwnedHook(value) {
|
|
2871
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2872
|
-
const hooks = value.hooks;
|
|
2873
|
-
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
2874
|
-
}
|
|
2875
|
-
function writeSettings(file, settings) {
|
|
2876
|
-
import_node_fs6.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
|
|
2877
|
-
const temporary = `${file}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
|
|
2878
|
-
import_node_fs6.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
2879
|
-
`, {
|
|
2880
|
-
mode: 384
|
|
2679
|
+
function renderJobs(response) {
|
|
2680
|
+
const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
|
|
2681
|
+
if (!Array.isArray(jobs)) return void 0;
|
|
2682
|
+
if (jobs.length === 0) return "No ingestion jobs.";
|
|
2683
|
+
const state = { bounded: false, omitted: 0 };
|
|
2684
|
+
const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
|
|
2685
|
+
const shown = sorted.slice(0, JOBS_RENDER_MAX);
|
|
2686
|
+
if (shown.length < sorted.length) state.bounded = true;
|
|
2687
|
+
const items = shown.map((job) => {
|
|
2688
|
+
const progress = obj(job.progress);
|
|
2689
|
+
const failure = obj(job.error_details);
|
|
2690
|
+
const message = str(failure?.message) ?? str(job.error);
|
|
2691
|
+
const done = num(progress?.processed_chunks);
|
|
2692
|
+
const totalChunks = num(progress?.total_chunks);
|
|
2693
|
+
const bits = [
|
|
2694
|
+
str(job.status) || "?",
|
|
2695
|
+
done !== void 0 && totalChunks !== void 0 ? `${done}/${totalChunks} chunks` : void 0,
|
|
2696
|
+
num(progress?.nodes_created) !== void 0 ? `${num(progress?.nodes_created)} nodes` : void 0,
|
|
2697
|
+
num(job.queue_position) !== void 0 ? `queue #${num(job.queue_position)}` : void 0,
|
|
2698
|
+
message ? `error: ${clip(message, 160, state)}` : void 0,
|
|
2699
|
+
str(failure?.code) ? `code: ${clip(failure?.code, 80, state)}` : void 0,
|
|
2700
|
+
num(failure?.status) !== void 0 ? `status: ${num(failure?.status)}` : void 0,
|
|
2701
|
+
typeof failure?.retriable === "boolean" ? `retriable: ${failure.retriable}` : void 0
|
|
2702
|
+
].filter(Boolean);
|
|
2703
|
+
return `- [${str(job.id) || "?"}] ${clip(job.title, 120, state) || "(untitled)"} \u2014 ${bits.join(", ")}`;
|
|
2881
2704
|
});
|
|
2882
|
-
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2888
|
-
let added = 0;
|
|
2889
|
-
let updated = 0;
|
|
2890
|
-
const desired = desiredEntries(harness, features);
|
|
2891
|
-
for (const optional of ["UserPromptSubmit", "PreCompact"]) {
|
|
2892
|
-
if (optional in desired || !Array.isArray(hooks[optional])) continue;
|
|
2893
|
-
const kept = hooks[optional].filter(
|
|
2894
|
-
(entry) => !entryHasOwnedHook(entry)
|
|
2895
|
-
);
|
|
2896
|
-
if (kept.length > 0) hooks[optional] = kept;
|
|
2897
|
-
else delete hooks[optional];
|
|
2898
|
-
}
|
|
2899
|
-
for (const [event, wanted] of Object.entries(desired)) {
|
|
2900
|
-
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
2901
|
-
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
2902
|
-
const hadOwned = kept.length !== current.length;
|
|
2903
|
-
for (const entry of wanted) {
|
|
2904
|
-
kept.push(entry);
|
|
2905
|
-
if (hadOwned) updated += 1;
|
|
2906
|
-
else added += 1;
|
|
2907
|
-
}
|
|
2908
|
-
hooks[event] = kept;
|
|
2909
|
-
}
|
|
2910
|
-
settings.hooks = hooks;
|
|
2911
|
-
writeSettings(file, settings);
|
|
2912
|
-
return { path: file, added, updated };
|
|
2705
|
+
return assemble(
|
|
2706
|
+
`# Ingestion jobs (${shown.length}${shown.length < sorted.length ? ` of ${sorted.length}` : ""})`,
|
|
2707
|
+
[{ heading: "", items }],
|
|
2708
|
+
state
|
|
2709
|
+
);
|
|
2913
2710
|
}
|
|
2914
|
-
function
|
|
2915
|
-
const
|
|
2916
|
-
if (!
|
|
2917
|
-
const
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
}
|
|
2928
|
-
const
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
return !owned;
|
|
2937
|
-
});
|
|
2938
|
-
if (retainedHooks.length > 0) {
|
|
2939
|
-
entry.hooks = retainedHooks;
|
|
2940
|
-
retainedEntries.push(entry);
|
|
2941
|
-
}
|
|
2711
|
+
function renderSignals(response) {
|
|
2712
|
+
const payload = obj(response);
|
|
2713
|
+
if (!payload) return void 0;
|
|
2714
|
+
const state = { bounded: false, omitted: 0 };
|
|
2715
|
+
const sections = [];
|
|
2716
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
2717
|
+
if (!Array.isArray(value) || value.length === 0) continue;
|
|
2718
|
+
const items = value.map((raw) => obj(raw)).filter((item) => Boolean(item)).map((item) => {
|
|
2719
|
+
const fromLabel = str(item.from_label);
|
|
2720
|
+
const toLabel = str(item.to_label);
|
|
2721
|
+
const anyLabelKey = Object.keys(item).find(
|
|
2722
|
+
(k) => k.endsWith("_label") && str(item[k])
|
|
2723
|
+
);
|
|
2724
|
+
const label = str(item.label) ?? str(item.title) ?? str(item.name) ?? (fromLabel && toLabel ? `${fromLabel} \u2194 ${toLabel}` : void 0) ?? (anyLabelKey ? str(item[anyLabelKey]) : void 0) ?? str(item.uid) ?? str(item.target_uid) ?? "(item)";
|
|
2725
|
+
const uid = str(item.uid) ?? str(item.target_uid);
|
|
2726
|
+
const numbers = Object.entries(item).filter(([, v]) => typeof v === "number").slice(0, 4).map(([k, v]) => `${k} ${Math.round(v * 100) / 100}`);
|
|
2727
|
+
const lists = Object.entries(item).filter(([, v]) => Array.isArray(v) && v.length > 0).slice(0, 2).map(([k, v]) => `${k}: ${v.length}`);
|
|
2728
|
+
const meta = [...numbers, ...lists].join(", ");
|
|
2729
|
+
return `- ${clip(label, 160, state)}${uid && uid !== label ? ` [${uid}]` : ""}${meta ? ` (${meta})` : ""}`;
|
|
2730
|
+
});
|
|
2731
|
+
if (items.length > 0) {
|
|
2732
|
+
sections.push({ heading: `## ${key.replace(/_/g, " ")}`, items });
|
|
2942
2733
|
}
|
|
2943
|
-
if (retainedEntries.length > 0) hooks[event] = retainedEntries;
|
|
2944
|
-
else delete hooks[event];
|
|
2945
2734
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
return { path: file, removed };
|
|
2949
|
-
}
|
|
2950
|
-
function installClaudeHooks(scope, projectDir = process.cwd(), features = {}) {
|
|
2951
|
-
return installHooks("claude-code", scope, projectDir, void 0, void 0, features);
|
|
2952
|
-
}
|
|
2953
|
-
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2954
|
-
return uninstallHooks("claude-code", scope, projectDir);
|
|
2955
|
-
}
|
|
2956
|
-
function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2957
|
-
return installHooks("codex", scope, projectDir, codexHomeDir);
|
|
2958
|
-
}
|
|
2959
|
-
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2960
|
-
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
2735
|
+
if (sections.length === 0) return void 0;
|
|
2736
|
+
return assemble("# Synthesis signals", sections, state);
|
|
2961
2737
|
}
|
|
2962
|
-
var
|
|
2963
|
-
var
|
|
2964
|
-
"src/
|
|
2738
|
+
var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, MIN_RENDER_CHAR_BUDGET, BOUNDED_MARKER_RESERVE, CURATION_EDGE_TYPES, INFRA_NODE_TYPES, JOBS_RENDER_MAX;
|
|
2739
|
+
var init_render = __esm({
|
|
2740
|
+
"src/render.ts"() {
|
|
2965
2741
|
"use strict";
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2742
|
+
LABEL_MAX = 200;
|
|
2743
|
+
SUMMARY_MAX = 400;
|
|
2744
|
+
QUOTE_MAX = 180;
|
|
2745
|
+
ARTICLE_MAX = 6e3;
|
|
2746
|
+
CHUNK_MAX = 6e3;
|
|
2747
|
+
RELATIONSHIP_MAX = 40;
|
|
2748
|
+
RENDER_CHAR_BUDGET = 24e3;
|
|
2749
|
+
MIN_RENDER_CHAR_BUDGET = 512;
|
|
2750
|
+
BOUNDED_MARKER_RESERVE = 220;
|
|
2751
|
+
CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
|
|
2752
|
+
INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
|
|
2753
|
+
JOBS_RENDER_MAX = 20;
|
|
2975
2754
|
}
|
|
2976
2755
|
});
|
|
2977
2756
|
|
|
2978
|
-
// src/
|
|
2979
|
-
function
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
function num(value) {
|
|
2986
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2757
|
+
// src/error-detail.ts
|
|
2758
|
+
function errorDetail(e) {
|
|
2759
|
+
if (e instanceof MindGraphError && e.body !== void 0) {
|
|
2760
|
+
const detail = typeof e.body === "string" ? e.body : JSON.stringify(e.body);
|
|
2761
|
+
return `${e.message} \u2014 ${detail}`;
|
|
2762
|
+
}
|
|
2763
|
+
return e instanceof Error ? e.message : String(e);
|
|
2987
2764
|
}
|
|
2988
|
-
function
|
|
2989
|
-
|
|
2765
|
+
function conflictState(e) {
|
|
2766
|
+
if (!(e instanceof MindGraphError) || !e.body || typeof e.body !== "object") {
|
|
2767
|
+
return {};
|
|
2768
|
+
}
|
|
2769
|
+
const body = e.body;
|
|
2770
|
+
const lifted = {};
|
|
2771
|
+
for (const key of CONFLICT_STATE_KEYS) {
|
|
2772
|
+
if (body[key] !== void 0) lifted[key] = body[key];
|
|
2773
|
+
}
|
|
2774
|
+
return lifted;
|
|
2990
2775
|
}
|
|
2991
|
-
function
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
const node = obj(value);
|
|
3007
|
-
return Boolean(
|
|
3008
|
-
node && str2(node.uid) && (str2(node.label) || str2(node.node_type) || obj(node.node_type))
|
|
3009
|
-
);
|
|
3010
|
-
}
|
|
3011
|
-
function nodeDisplayType(node) {
|
|
3012
|
-
const props = obj(node.props);
|
|
3013
|
-
const plain = str2(node.node_type);
|
|
3014
|
-
return (plain && plain !== "Custom" ? plain : void 0) ?? str2(obj(node.node_type)?.Custom) ?? str2(props?.object_type) ?? str2(props?.type_name) ?? str2(obj(props?.data)?.domain_type) ?? plain ?? "Node";
|
|
3015
|
-
}
|
|
3016
|
-
function nodeFields(node) {
|
|
3017
|
-
const props = obj(node.props);
|
|
3018
|
-
return obj(props?.fields) ?? obj(obj(props?.data)?.fields);
|
|
2776
|
+
function toolError(e, fallbackCode) {
|
|
2777
|
+
const body = e instanceof MindGraphError && e.body && typeof e.body === "object" && !Array.isArray(e.body) ? e.body : {};
|
|
2778
|
+
const code = typeof body.code === "string" ? body.code : fallbackCode;
|
|
2779
|
+
const payload = {
|
|
2780
|
+
error: errorDetail(e),
|
|
2781
|
+
...conflictState(e),
|
|
2782
|
+
...e instanceof MindGraphError ? { status: e.status } : {},
|
|
2783
|
+
...code !== void 0 ? { code } : {},
|
|
2784
|
+
...typeof body.retriable === "boolean" ? { retriable: body.retriable } : {}
|
|
2785
|
+
};
|
|
2786
|
+
return {
|
|
2787
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
2788
|
+
structuredContent: payload,
|
|
2789
|
+
isError: true
|
|
2790
|
+
};
|
|
3019
2791
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
2792
|
+
var CONFLICT_STATE_KEYS;
|
|
2793
|
+
var init_error_detail = __esm({
|
|
2794
|
+
"src/error-detail.ts"() {
|
|
2795
|
+
"use strict";
|
|
2796
|
+
init_dist();
|
|
2797
|
+
CONFLICT_STATE_KEYS = [
|
|
2798
|
+
"current_version",
|
|
2799
|
+
"current_epoch",
|
|
2800
|
+
"lease_expires_at",
|
|
2801
|
+
"lease_owner_agent_id"
|
|
2802
|
+
];
|
|
3027
2803
|
}
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
2804
|
+
});
|
|
2805
|
+
|
|
2806
|
+
// src/code-tool.ts
|
|
2807
|
+
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
|
|
2808
|
+
const codeClient = client2;
|
|
2809
|
+
const action = args.action;
|
|
2810
|
+
const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
|
|
2811
|
+
const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
|
|
2812
|
+
const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
|
|
2813
|
+
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
|
|
2814
|
+
const options = {
|
|
2815
|
+
repo: typeof args.repo === "string" ? args.repo : void 0,
|
|
2816
|
+
fallbackSpaceUid
|
|
2817
|
+
};
|
|
2818
|
+
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
2819
|
+
try {
|
|
2820
|
+
switch (action) {
|
|
2821
|
+
case "anchor": {
|
|
2822
|
+
if (refs.length === 0) return err("missing_code_refs", "code_refs is required for anchor");
|
|
2823
|
+
const resolution = await adapter.resolveRefs(refs, options);
|
|
2824
|
+
const resolved = resolution.results.flatMap(
|
|
2825
|
+
(result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
|
|
2826
|
+
);
|
|
2827
|
+
const unresolved = resolution.results.filter((result) => !result.resolved);
|
|
2828
|
+
if (resolved.length === 0) {
|
|
2829
|
+
return ok({ ...resolution, anchors: [], wrote: false });
|
|
2830
|
+
}
|
|
2831
|
+
const missingSpace = resolved.find(
|
|
2832
|
+
({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
|
|
2833
|
+
);
|
|
2834
|
+
if (missingSpace) {
|
|
2835
|
+
return err(
|
|
2836
|
+
"missing_repository_space",
|
|
2837
|
+
`repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
const repositoryAnchors = /* @__PURE__ */ new Map();
|
|
2841
|
+
for (const { repository } of resolved) {
|
|
2842
|
+
if (repositoryAnchors.has(repository.repoRoot)) continue;
|
|
2843
|
+
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
2844
|
+
const anchored = await anchorRepository(
|
|
2845
|
+
codeClient,
|
|
2846
|
+
repository,
|
|
2847
|
+
spaceUid,
|
|
2848
|
+
agentId
|
|
2849
|
+
);
|
|
2850
|
+
repositoryAnchors.set(repository.repoRoot, {
|
|
2851
|
+
...anchored,
|
|
2852
|
+
repository
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
const anchors = [];
|
|
2856
|
+
for (const { ref, repository } of resolved) {
|
|
2857
|
+
const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
|
|
2858
|
+
if (!repositoryAnchor.uid) continue;
|
|
2859
|
+
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
2860
|
+
const anchor = await anchorCodeRef(
|
|
2861
|
+
codeClient,
|
|
2862
|
+
ref,
|
|
2863
|
+
repositoryAnchor.uid,
|
|
2864
|
+
spaceUid,
|
|
2865
|
+
agentId
|
|
2866
|
+
);
|
|
2867
|
+
anchors.push(anchor);
|
|
2868
|
+
}
|
|
2869
|
+
return ok({
|
|
2870
|
+
availability: resolution.availability,
|
|
2871
|
+
repository: repositoryAnchors.values().next().value,
|
|
2872
|
+
repositories: [...repositoryAnchors.values()],
|
|
2873
|
+
anchors,
|
|
2874
|
+
unresolved,
|
|
2875
|
+
wrote: anchors.some((anchor) => anchor.created)
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
case "recall": {
|
|
2879
|
+
const materialized = new Set(anchorUids);
|
|
2880
|
+
let resolution;
|
|
2881
|
+
if (refs.length > 0) {
|
|
2882
|
+
resolution = await adapter.resolveRefs(refs, options);
|
|
2883
|
+
for (const result of resolution.results) {
|
|
2884
|
+
if (!result.resolved) continue;
|
|
2885
|
+
const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
|
|
2886
|
+
if (uid) materialized.add(uid);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (materialized.size === 0) {
|
|
2890
|
+
return ok({
|
|
2891
|
+
availability: resolution?.availability,
|
|
2892
|
+
anchors: [],
|
|
2893
|
+
context: [],
|
|
2894
|
+
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
const context = await Promise.all(
|
|
2898
|
+
[...materialized].map(async (uid) => ({
|
|
2899
|
+
anchor_uid: uid,
|
|
2900
|
+
graph: await codeClient.traverse({
|
|
2901
|
+
action: "neighborhood",
|
|
2902
|
+
start_uid: uid,
|
|
2903
|
+
depth: 2,
|
|
2904
|
+
max_nodes: Math.min(limit ?? 20, 20)
|
|
2905
|
+
})
|
|
2906
|
+
}))
|
|
2907
|
+
);
|
|
2908
|
+
return ok({
|
|
2909
|
+
availability: resolution?.availability,
|
|
2910
|
+
anchors: [...materialized],
|
|
2911
|
+
context,
|
|
2912
|
+
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
case "expand": {
|
|
2916
|
+
const target = await resolveSingleTarget(
|
|
2917
|
+
codeClient,
|
|
2918
|
+
adapter,
|
|
2919
|
+
refs,
|
|
2920
|
+
anchorUids,
|
|
2921
|
+
options
|
|
2922
|
+
);
|
|
2923
|
+
if ("error" in target) return target.error;
|
|
2924
|
+
const expanded = await adapter.expand(target.ref, limit);
|
|
2925
|
+
const joined = await joinMaterialized(
|
|
2926
|
+
codeClient,
|
|
2927
|
+
[...expanded.callers, ...expanded.callees],
|
|
2928
|
+
agentId,
|
|
2929
|
+
Math.min(limit ?? 20, 20)
|
|
2930
|
+
);
|
|
2931
|
+
return ok({ anchor: target.ref, ...expanded, materialized: joined });
|
|
2932
|
+
}
|
|
2933
|
+
case "affected": {
|
|
2934
|
+
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
2935
|
+
let graphContext;
|
|
2936
|
+
const graphRefs = [];
|
|
2937
|
+
let unavailableRepositories = [];
|
|
2938
|
+
if (targetUid) {
|
|
2939
|
+
graphContext = await codeClient.traverse({
|
|
2940
|
+
action: "neighborhood",
|
|
2941
|
+
start_uid: targetUid,
|
|
2942
|
+
depth: 2,
|
|
2943
|
+
max_nodes: 50
|
|
2944
|
+
});
|
|
2945
|
+
const fromGraph = await codeRefsFromGraphContext(
|
|
2946
|
+
codeClient,
|
|
2947
|
+
adapter,
|
|
2948
|
+
graphContext
|
|
2949
|
+
);
|
|
2950
|
+
graphRefs.push(...fromGraph.refs);
|
|
2951
|
+
unavailableRepositories = fromGraph.unavailable;
|
|
2952
|
+
}
|
|
2953
|
+
if (refs.length > 0 || anchorUids.length > 0) {
|
|
2954
|
+
const target = await resolveSingleTarget(
|
|
2955
|
+
codeClient,
|
|
2956
|
+
adapter,
|
|
2957
|
+
refs,
|
|
2958
|
+
anchorUids,
|
|
2959
|
+
options
|
|
2960
|
+
);
|
|
2961
|
+
if ("error" in target) return target.error;
|
|
2962
|
+
graphRefs.unshift(target.ref);
|
|
2963
|
+
}
|
|
2964
|
+
const unique = uniqueRefs(graphRefs);
|
|
2965
|
+
if (unique.length === 0) {
|
|
2966
|
+
return ok({
|
|
2967
|
+
target_uid: targetUid,
|
|
2968
|
+
graph_context: graphContext,
|
|
2969
|
+
affected: [],
|
|
2970
|
+
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
2971
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
const impacts = await Promise.all(
|
|
2975
|
+
unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
|
|
2976
|
+
);
|
|
2977
|
+
const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
|
|
2978
|
+
const joined = await joinMaterialized(
|
|
2979
|
+
codeClient,
|
|
2980
|
+
structural,
|
|
2981
|
+
agentId,
|
|
2982
|
+
Math.min(limit ?? 50, 50)
|
|
2983
|
+
);
|
|
2984
|
+
return ok({
|
|
2985
|
+
target_uid: targetUid,
|
|
2986
|
+
graph_context: graphContext,
|
|
2987
|
+
impacts,
|
|
2988
|
+
materialized: joined,
|
|
2989
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
default:
|
|
2993
|
+
return err("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
2994
|
+
}
|
|
2995
|
+
} catch (cause) {
|
|
2996
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2997
|
+
return err("unknown_repository", cause.message);
|
|
2998
|
+
}
|
|
2999
|
+
return toolError(cause, "code_tool_failed");
|
|
3036
3000
|
}
|
|
3037
|
-
const truth = str2(node.truth_status) ?? str2(obj(node.props)?.truth_status);
|
|
3038
|
-
if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
|
|
3039
|
-
if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
|
|
3040
|
-
if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
|
|
3041
|
-
return tags.length > 0 ? ` [${tags.join("; ")}]` : "";
|
|
3042
3001
|
}
|
|
3043
|
-
function
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3002
|
+
async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
3003
|
+
if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
|
|
3004
|
+
return result;
|
|
3005
|
+
}
|
|
3006
|
+
const original = JSON.parse(result.content[0]?.text ?? "null");
|
|
3007
|
+
const targetUids = primaryUids(original);
|
|
3008
|
+
if (targetUids.length === 0) {
|
|
3009
|
+
return ok({
|
|
3010
|
+
result: original,
|
|
3011
|
+
code_anchors: {
|
|
3012
|
+
wrote: false,
|
|
3013
|
+
caveat: "the write response exposed no primary UID to attach"
|
|
3014
|
+
}
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
3017
|
+
const anchorResult = await handleCodeTool(
|
|
3018
|
+
client2,
|
|
3019
|
+
{
|
|
3020
|
+
action: "anchor",
|
|
3021
|
+
code_refs: args.code_refs,
|
|
3022
|
+
repo: args.repo,
|
|
3023
|
+
repo_space_uid: args.repo_space_uid,
|
|
3024
|
+
agent_id: args.agent_id,
|
|
3025
|
+
// Keeps the default adapter anchored to the live session directory
|
|
3026
|
+
// rather than wherever the MCP server process was launched.
|
|
3027
|
+
invocation_context: args.invocation_context
|
|
3028
|
+
},
|
|
3029
|
+
adapter
|
|
3030
|
+
);
|
|
3031
|
+
const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
|
|
3032
|
+
if (!anchorResult.isError) {
|
|
3033
|
+
const codeClient = client2;
|
|
3034
|
+
const anchorUids = (anchors.anchors ?? []).flatMap(
|
|
3035
|
+
(anchor) => anchor.uid ? [anchor.uid] : []
|
|
3036
|
+
);
|
|
3037
|
+
const relateFailures = [];
|
|
3038
|
+
for (const targetUid of targetUids) {
|
|
3039
|
+
for (const anchorUid of anchorUids) {
|
|
3040
|
+
try {
|
|
3041
|
+
await codeClient.entity({
|
|
3042
|
+
action: "relate",
|
|
3043
|
+
source_uid: targetUid,
|
|
3044
|
+
target_uid: anchorUid,
|
|
3045
|
+
edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
|
|
3046
|
+
agent_id: args.agent_id
|
|
3047
|
+
});
|
|
3048
|
+
} catch (cause) {
|
|
3049
|
+
relateFailures.push(
|
|
3050
|
+
`${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
if (relateFailures.length > 0) {
|
|
3056
|
+
return ok({
|
|
3057
|
+
result: original,
|
|
3058
|
+
code_anchors: {
|
|
3059
|
+
...anchors,
|
|
3060
|
+
caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
|
|
3061
|
+
}
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3049
3064
|
}
|
|
3050
|
-
return
|
|
3065
|
+
return ok({ result: original, code_anchors: anchors });
|
|
3051
3066
|
}
|
|
3052
|
-
function
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3067
|
+
async function anchorRepository(client2, repository, spaceUid, agentId) {
|
|
3068
|
+
return client2.entity({
|
|
3069
|
+
action: "create",
|
|
3070
|
+
label: repository.repoId,
|
|
3071
|
+
props: {
|
|
3072
|
+
entity_type: "repository",
|
|
3073
|
+
canonical_name: repository.repoId,
|
|
3074
|
+
identifiers: { repo_id: repository.repoId }
|
|
3075
|
+
},
|
|
3076
|
+
identity: {
|
|
3077
|
+
namespace: "external.code",
|
|
3078
|
+
key_version: 1,
|
|
3079
|
+
key: repositoryIdentityKey(repository)
|
|
3080
|
+
},
|
|
3081
|
+
identity_space_uid: spaceUid,
|
|
3082
|
+
agent_id: agentId
|
|
3083
|
+
});
|
|
3059
3084
|
}
|
|
3060
|
-
function
|
|
3061
|
-
const
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3085
|
+
async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
|
|
3086
|
+
const response = await client2.entity({
|
|
3087
|
+
action: "create",
|
|
3088
|
+
label: ref.qualifiedName,
|
|
3089
|
+
props: {
|
|
3090
|
+
entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
|
|
3091
|
+
canonical_name: ref.qualifiedName,
|
|
3092
|
+
identifiers: {
|
|
3093
|
+
repo_id: ref.repoId,
|
|
3094
|
+
path: ref.path,
|
|
3095
|
+
qualified_name: ref.qualifiedName
|
|
3096
|
+
},
|
|
3097
|
+
attributes: { code_ref: storedCodeRef(ref) }
|
|
3098
|
+
},
|
|
3099
|
+
identity: {
|
|
3100
|
+
namespace: "external.code",
|
|
3101
|
+
key_version: 1,
|
|
3102
|
+
key: codeRefIdentityKey(ref)
|
|
3103
|
+
},
|
|
3104
|
+
identity_space_uid: spaceUid,
|
|
3105
|
+
agent_id: agentId
|
|
3106
|
+
});
|
|
3107
|
+
if (response.uid && response.created !== false && response.status !== "existing") {
|
|
3108
|
+
await client2.entity({
|
|
3109
|
+
action: "relate",
|
|
3110
|
+
source_uid: response.uid,
|
|
3111
|
+
target_uid: repositoryUid,
|
|
3112
|
+
edge_type: "PART_OF",
|
|
3113
|
+
agent_id: agentId
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
return { ...response, ref };
|
|
3066
3117
|
}
|
|
3067
|
-
function
|
|
3068
|
-
const
|
|
3069
|
-
|
|
3118
|
+
async function resolveMaterializedUid(client2, ref, agentId) {
|
|
3119
|
+
const response = await client2.entity({
|
|
3120
|
+
action: "resolve_identity",
|
|
3121
|
+
identity: {
|
|
3122
|
+
namespace: "external.code",
|
|
3123
|
+
key_version: 1,
|
|
3124
|
+
key: codeRefIdentityKey(ref)
|
|
3125
|
+
},
|
|
3126
|
+
agent_id: agentId
|
|
3127
|
+
});
|
|
3128
|
+
return response.status === "existing" ? response.uid : void 0;
|
|
3070
3129
|
}
|
|
3071
|
-
function
|
|
3072
|
-
const
|
|
3073
|
-
const
|
|
3074
|
-
|
|
3075
|
-
const
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
if (fields) lines.push(` - fields: ${fields}`);
|
|
3086
|
-
const quote = firstQuote(node, state);
|
|
3087
|
-
if (quote) lines.push(` - source quote: "${quote}"`);
|
|
3088
|
-
const source = sourceTag(node);
|
|
3089
|
-
if (source) lines.push(` - ${source}`);
|
|
3090
|
-
const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
|
|
3091
|
-
if (believers.length > 0) {
|
|
3092
|
-
const rendered2 = believers.slice(0, 3).map((raw) => {
|
|
3093
|
-
const believer = obj(raw);
|
|
3094
|
-
const conf = num(believer?.confidence);
|
|
3095
|
-
return `${str2(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
|
|
3096
|
-
}).join(", ");
|
|
3097
|
-
lines.push(` - believed by: ${rendered2}`);
|
|
3130
|
+
async function codeRefFromAnchor(client2, adapter, uid) {
|
|
3131
|
+
const node = await client2.getNode(uid);
|
|
3132
|
+
const attributes = node.props?.attributes;
|
|
3133
|
+
if (!attributes || typeof attributes !== "object") return void 0;
|
|
3134
|
+
const codeRef = attributes.code_ref;
|
|
3135
|
+
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
3136
|
+
try {
|
|
3137
|
+
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
3138
|
+
return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
|
|
3139
|
+
} catch (cause) {
|
|
3140
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
3141
|
+
return { missingRepo: codeRef.repoId };
|
|
3142
|
+
}
|
|
3143
|
+
throw cause;
|
|
3098
3144
|
}
|
|
3099
|
-
return lines.join("\n");
|
|
3100
3145
|
}
|
|
3101
|
-
function
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
const type = nodeDisplayType(node);
|
|
3105
|
-
const group = groups.get(type) ?? [];
|
|
3106
|
-
group.push(node);
|
|
3107
|
-
groups.set(type, group);
|
|
3146
|
+
async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
|
|
3147
|
+
if (refs.length > 1 || anchorUids.length > 1) {
|
|
3148
|
+
return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
|
|
3108
3149
|
}
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3150
|
+
if (anchorUids.length === 1) {
|
|
3151
|
+
const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
|
|
3152
|
+
if (!outcome) {
|
|
3153
|
+
return { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
|
|
3154
|
+
}
|
|
3155
|
+
if ("missingRepo" in outcome) {
|
|
3156
|
+
return {
|
|
3157
|
+
error: err(
|
|
3158
|
+
"repository_unavailable",
|
|
3159
|
+
`repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
|
|
3160
|
+
)
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
return { ref: outcome.ref };
|
|
3117
3164
|
}
|
|
3118
|
-
|
|
3119
|
-
}
|
|
3120
|
-
function relationshipItems(edges, labelByUid, state, options) {
|
|
3121
|
-
const lines = [];
|
|
3122
|
-
for (const raw of edges) {
|
|
3123
|
-
const edge = obj(raw);
|
|
3124
|
-
if (!edge) continue;
|
|
3125
|
-
const type = edgeTypeName(edge.edge_type) || "?";
|
|
3126
|
-
const from = str2(edge.from_uid);
|
|
3127
|
-
const to = str2(edge.to_uid);
|
|
3128
|
-
if (CURATION_EDGE_TYPES.has(type)) continue;
|
|
3129
|
-
if (!from || !to || from === to) continue;
|
|
3130
|
-
const fromLabel = labelByUid.get(from) ?? (options?.fallbackToUid ? `[${from}]` : void 0);
|
|
3131
|
-
const toLabel = labelByUid.get(to) ?? (options?.fallbackToUid ? `[${to}]` : void 0);
|
|
3132
|
-
if (!fromLabel || !toLabel) continue;
|
|
3133
|
-
lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
|
|
3165
|
+
if (refs.length !== 1) {
|
|
3166
|
+
return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
3134
3167
|
}
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3168
|
+
const resolution = await adapter.resolveRefs(refs, options);
|
|
3169
|
+
const result = resolution.results[0];
|
|
3170
|
+
if (!result.resolved) {
|
|
3171
|
+
return {
|
|
3172
|
+
error: ok({
|
|
3173
|
+
availability: resolution.availability,
|
|
3174
|
+
result,
|
|
3175
|
+
wrote: false
|
|
3176
|
+
})
|
|
3177
|
+
};
|
|
3140
3178
|
}
|
|
3141
|
-
return
|
|
3179
|
+
return { ref: result.resolved };
|
|
3142
3180
|
}
|
|
3143
|
-
function
|
|
3144
|
-
const
|
|
3145
|
-
const
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
if (
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3181
|
+
async function joinMaterialized(client2, refs, agentId, limit) {
|
|
3182
|
+
const joined = [];
|
|
3183
|
+
for (const ref of uniqueRefs(refs).slice(0, limit)) {
|
|
3184
|
+
const uid = await resolveMaterializedUid(client2, ref, agentId);
|
|
3185
|
+
if (!uid) continue;
|
|
3186
|
+
joined.push({
|
|
3187
|
+
ref,
|
|
3188
|
+
anchor_uid: uid,
|
|
3189
|
+
graph: await client2.traverse({
|
|
3190
|
+
action: "neighborhood",
|
|
3191
|
+
start_uid: uid,
|
|
3192
|
+
depth: 2,
|
|
3193
|
+
max_nodes: 20
|
|
3194
|
+
})
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
return joined;
|
|
3198
|
+
}
|
|
3199
|
+
async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
3200
|
+
const uids = /* @__PURE__ */ new Set();
|
|
3201
|
+
collectUids(context, uids);
|
|
3202
|
+
if (uids.size === 0) return { refs: [], unavailable: [] };
|
|
3203
|
+
const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
|
|
3204
|
+
const stored = nodes.flatMap((node) => {
|
|
3205
|
+
if (!node) return [];
|
|
3206
|
+
const attributes = node.props?.attributes;
|
|
3207
|
+
if (!attributes || typeof attributes !== "object") return [];
|
|
3208
|
+
const codeRef = attributes.code_ref;
|
|
3209
|
+
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
3210
|
+
});
|
|
3211
|
+
const refs = [];
|
|
3212
|
+
const unavailable = /* @__PURE__ */ new Set();
|
|
3213
|
+
for (const ref of stored) {
|
|
3214
|
+
try {
|
|
3215
|
+
const repository = await adapter.resolveRepository(ref.repoId);
|
|
3216
|
+
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
3217
|
+
} catch (cause) {
|
|
3218
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
3219
|
+
unavailable.add(ref.repoId);
|
|
3220
|
+
} else {
|
|
3221
|
+
throw cause;
|
|
3179
3222
|
}
|
|
3180
3223
|
}
|
|
3181
|
-
lines.push(
|
|
3182
|
-
section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
|
|
3183
|
-
${kept.join("\n")}` : kept.join("\n")
|
|
3184
|
-
);
|
|
3185
|
-
used += headingCost + sectionUsed;
|
|
3186
3224
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3225
|
+
return { refs, unavailable: [...unavailable].sort() };
|
|
3226
|
+
}
|
|
3227
|
+
function collectUids(value, uids) {
|
|
3228
|
+
if (Array.isArray(value)) {
|
|
3229
|
+
value.forEach((item) => collectUids(item, uids));
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
3232
|
+
if (!value || typeof value !== "object") return;
|
|
3233
|
+
for (const [key, child] of Object.entries(value)) {
|
|
3234
|
+
if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
|
|
3235
|
+
uids.add(child);
|
|
3236
|
+
} else {
|
|
3237
|
+
collectUids(child, uids);
|
|
3238
|
+
}
|
|
3191
3239
|
}
|
|
3192
|
-
return lines.join("\n\n").slice(0, budget);
|
|
3193
3240
|
}
|
|
3194
|
-
function
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3241
|
+
function isStoredCodeRef(value) {
|
|
3242
|
+
if (!value || typeof value !== "object") return false;
|
|
3243
|
+
const ref = value;
|
|
3244
|
+
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";
|
|
3245
|
+
}
|
|
3246
|
+
function storedCodeRef(ref) {
|
|
3247
|
+
const { repoRoot: _localOnly, ...stored } = ref;
|
|
3248
|
+
return stored;
|
|
3249
|
+
}
|
|
3250
|
+
function uniqueRefs(refs) {
|
|
3251
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3252
|
+
return refs.filter((ref) => {
|
|
3253
|
+
const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
|
|
3254
|
+
if (seen.has(key)) return false;
|
|
3255
|
+
seen.add(key);
|
|
3256
|
+
return true;
|
|
3200
3257
|
});
|
|
3201
|
-
return [{ heading: "## Applicable policies", items }];
|
|
3202
3258
|
}
|
|
3203
|
-
function
|
|
3204
|
-
|
|
3205
|
-
|
|
3259
|
+
function primaryUids(value) {
|
|
3260
|
+
if (!value || typeof value !== "object") return [];
|
|
3261
|
+
const object2 = value;
|
|
3262
|
+
const keys = [
|
|
3263
|
+
"uid",
|
|
3264
|
+
"claim_uid",
|
|
3265
|
+
"goal_uid",
|
|
3266
|
+
"project_uid",
|
|
3267
|
+
"milestone_uid",
|
|
3268
|
+
"decision_uid",
|
|
3269
|
+
"task_uid",
|
|
3270
|
+
"plan_uid",
|
|
3271
|
+
"step_uid",
|
|
3272
|
+
"execution_uid",
|
|
3273
|
+
"lesson_uid"
|
|
3274
|
+
];
|
|
3206
3275
|
return [
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
}
|
|
3276
|
+
...new Set(
|
|
3277
|
+
keys.flatMap((key) => typeof object2[key] === "string" ? [object2[key]] : [])
|
|
3278
|
+
)
|
|
3211
3279
|
];
|
|
3212
3280
|
}
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
[
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3281
|
+
var ok, err, CODE_TOOL;
|
|
3282
|
+
var init_code_tool = __esm({
|
|
3283
|
+
"src/code-tool.ts"() {
|
|
3284
|
+
"use strict";
|
|
3285
|
+
init_error_detail();
|
|
3286
|
+
init_codegraph();
|
|
3287
|
+
ok = (data) => ({
|
|
3288
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
3289
|
+
});
|
|
3290
|
+
err = (code, message, details) => ({
|
|
3291
|
+
content: [
|
|
3292
|
+
{
|
|
3293
|
+
type: "text",
|
|
3294
|
+
text: JSON.stringify({ error: message, code, details })
|
|
3295
|
+
}
|
|
3296
|
+
],
|
|
3297
|
+
isError: true
|
|
3298
|
+
});
|
|
3299
|
+
CODE_TOOL = {
|
|
3300
|
+
name: "mindgraph_code",
|
|
3301
|
+
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. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
|
|
3302
|
+
inputSchema: {
|
|
3303
|
+
type: "object",
|
|
3304
|
+
properties: {
|
|
3305
|
+
action: {
|
|
3306
|
+
type: "string",
|
|
3307
|
+
enum: ["anchor", "recall", "expand", "affected"]
|
|
3308
|
+
},
|
|
3309
|
+
code_refs: {
|
|
3310
|
+
type: "array",
|
|
3311
|
+
maxItems: 50,
|
|
3312
|
+
items: {
|
|
3313
|
+
oneOf: [
|
|
3314
|
+
{ type: "string" },
|
|
3315
|
+
{
|
|
3316
|
+
type: "object",
|
|
3317
|
+
properties: {
|
|
3318
|
+
repo: { type: "string" },
|
|
3319
|
+
path: { type: "string" },
|
|
3320
|
+
line: { type: "integer", minimum: 1 },
|
|
3321
|
+
symbol: { type: "string" },
|
|
3322
|
+
kind: { type: "string" },
|
|
3323
|
+
language: { type: "string" },
|
|
3324
|
+
signature: { type: "string" }
|
|
3325
|
+
},
|
|
3326
|
+
additionalProperties: false
|
|
3327
|
+
}
|
|
3328
|
+
]
|
|
3329
|
+
}
|
|
3330
|
+
},
|
|
3331
|
+
anchor_uids: {
|
|
3332
|
+
type: "array",
|
|
3333
|
+
maxItems: 50,
|
|
3334
|
+
items: { type: "string" },
|
|
3335
|
+
description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
|
|
3336
|
+
},
|
|
3337
|
+
repo: {
|
|
3338
|
+
type: "string",
|
|
3339
|
+
description: "Configured repository id or repository root"
|
|
3340
|
+
},
|
|
3341
|
+
repo_space_uid: {
|
|
3342
|
+
type: "string",
|
|
3343
|
+
description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
|
|
3344
|
+
},
|
|
3345
|
+
task_uid: { type: "string" },
|
|
3346
|
+
execution_uid: { type: "string" },
|
|
3347
|
+
limit: { type: "integer", minimum: 1, maximum: 50 },
|
|
3348
|
+
agent_id: { type: "string" }
|
|
3349
|
+
},
|
|
3350
|
+
required: ["action"]
|
|
3351
|
+
},
|
|
3352
|
+
annotations: {
|
|
3353
|
+
readOnlyHint: false,
|
|
3354
|
+
destructiveHint: false,
|
|
3355
|
+
idempotentHint: true,
|
|
3356
|
+
openWorldHint: true
|
|
3357
|
+
}
|
|
3358
|
+
};
|
|
3223
3359
|
}
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
(
|
|
3238
|
-
${clip(article.content, ARTICLE_MAX, state) || ""}`
|
|
3360
|
+
});
|
|
3361
|
+
|
|
3362
|
+
// src/workspace-scope.ts
|
|
3363
|
+
function readScopeCache(cache) {
|
|
3364
|
+
if (!cache) return {};
|
|
3365
|
+
try {
|
|
3366
|
+
const parsed = JSON.parse(
|
|
3367
|
+
import_node_fs3.default.readFileSync(import_node_path3.default.join(cache.dir, SCOPE_CACHE_FILE), "utf8")
|
|
3368
|
+
);
|
|
3369
|
+
if (parsed.v !== 1 || parsed.tenant !== cache.tenant || !parsed.entries) return {};
|
|
3370
|
+
const now = (cache.now ?? Date.now)();
|
|
3371
|
+
return Object.fromEntries(
|
|
3372
|
+
Object.entries(parsed.entries).filter(
|
|
3373
|
+
([, entry]) => typeof entry?.uid === "string" && typeof entry.at === "number" && now - entry.at >= 0 && now - entry.at < SCOPE_CACHE_TTL_MS
|
|
3239
3374
|
)
|
|
3240
|
-
|
|
3375
|
+
);
|
|
3376
|
+
} catch {
|
|
3377
|
+
return {};
|
|
3241
3378
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3379
|
+
}
|
|
3380
|
+
function writeScopeCache(cache, entries) {
|
|
3381
|
+
if (!cache) return;
|
|
3382
|
+
try {
|
|
3383
|
+
import_node_fs3.default.mkdirSync(cache.dir, { recursive: true, mode: 448 });
|
|
3384
|
+
const file = import_node_path3.default.join(cache.dir, SCOPE_CACHE_FILE);
|
|
3385
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
3386
|
+
import_node_fs3.default.writeFileSync(
|
|
3387
|
+
temporary,
|
|
3388
|
+
JSON.stringify({ v: 1, tenant: cache.tenant, entries }),
|
|
3389
|
+
{ mode: 384 }
|
|
3250
3390
|
);
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
}
|
|
3254
|
-
} else if (infraOnly > 0) {
|
|
3255
|
-
sections.push({
|
|
3256
|
-
heading: `Note: ${infraOnly} document/chunk node(s) matched \u2014 use action "document_index" or include_chunks for their content.`,
|
|
3257
|
-
items: []
|
|
3258
|
-
});
|
|
3391
|
+
import_node_fs3.default.renameSync(temporary, file);
|
|
3392
|
+
} catch {
|
|
3259
3393
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3394
|
+
}
|
|
3395
|
+
function record(value) {
|
|
3396
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3397
|
+
}
|
|
3398
|
+
async function resolveRepositoryScope(cwd, client2, agentId, options = {}) {
|
|
3399
|
+
if (!client2.entity) return { uids: [], names: {}, failures: [] };
|
|
3400
|
+
const adapter = new CodegraphAdapter({
|
|
3401
|
+
cwd,
|
|
3402
|
+
workspaceFile: options.workspaceFile,
|
|
3403
|
+
childWorkspaces: true
|
|
3404
|
+
});
|
|
3405
|
+
const configured = await adapter.resolveRepositories();
|
|
3406
|
+
const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
|
|
3407
|
+
const failures = [];
|
|
3408
|
+
const names = {};
|
|
3409
|
+
const cached = readScopeCache(options.cache);
|
|
3410
|
+
const fresh = {};
|
|
3411
|
+
const stamp = (options.cache?.now ?? Date.now)();
|
|
3412
|
+
const resolved = await Promise.all(
|
|
3413
|
+
repositories.map(async (repository) => {
|
|
3414
|
+
const remembered = cached[repository.repoId];
|
|
3415
|
+
if (remembered) {
|
|
3416
|
+
names[remembered.uid] = repository.repoId;
|
|
3417
|
+
return remembered.uid;
|
|
3418
|
+
}
|
|
3419
|
+
try {
|
|
3420
|
+
const payload = record(
|
|
3421
|
+
await client2.entity({
|
|
3422
|
+
action: "resolve_identity",
|
|
3423
|
+
identity: {
|
|
3424
|
+
namespace: "external.code",
|
|
3425
|
+
key_version: 1,
|
|
3426
|
+
key: repositoryIdentityKey(repository)
|
|
3427
|
+
},
|
|
3428
|
+
agent_id: agentId
|
|
3429
|
+
})
|
|
3430
|
+
);
|
|
3431
|
+
if (payload?.status === "existing" && typeof payload.uid === "string" && payload.uid) {
|
|
3432
|
+
names[payload.uid] = repository.repoId;
|
|
3433
|
+
fresh[repository.repoId] = { uid: payload.uid, at: stamp };
|
|
3434
|
+
return payload.uid;
|
|
3435
|
+
}
|
|
3436
|
+
if (payload?.status === "absent") {
|
|
3437
|
+
const anchored = await anchorRepository(
|
|
3438
|
+
client2,
|
|
3439
|
+
repository,
|
|
3440
|
+
repository.spaceUid ?? `space:agent:${agentId}`,
|
|
3441
|
+
agentId
|
|
3442
|
+
);
|
|
3443
|
+
if (anchored?.uid) {
|
|
3444
|
+
names[anchored.uid] = repository.repoId;
|
|
3445
|
+
fresh[repository.repoId] = { uid: anchored.uid, at: stamp };
|
|
3446
|
+
return anchored.uid;
|
|
3447
|
+
}
|
|
3448
|
+
failures.push(
|
|
3449
|
+
`${repository.repoId}: anchor ${anchored?.status ?? "failed"}`
|
|
3450
|
+
);
|
|
3451
|
+
return void 0;
|
|
3452
|
+
}
|
|
3453
|
+
failures.push(
|
|
3454
|
+
`${repository.repoId}: ${typeof payload?.status === "string" && payload.status ? payload.status : "unresolved"}`
|
|
3455
|
+
);
|
|
3456
|
+
return void 0;
|
|
3457
|
+
} catch (error2) {
|
|
3458
|
+
failures.push(
|
|
3459
|
+
`${repository.repoId}: ${error2?.message || "resolve failed"}`
|
|
3460
|
+
);
|
|
3461
|
+
return void 0;
|
|
3462
|
+
}
|
|
3463
|
+
})
|
|
3464
|
+
);
|
|
3465
|
+
if (Object.keys(fresh).length > 0) {
|
|
3466
|
+
writeScopeCache(options.cache, { ...cached, ...fresh });
|
|
3267
3467
|
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3468
|
+
return {
|
|
3469
|
+
uids: [...new Set(resolved.filter((uid) => Boolean(uid)))],
|
|
3470
|
+
names,
|
|
3471
|
+
failures
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
var import_node_fs3, import_node_path3, WORKSPACE_RESUME_LIMIT, SCOPE_CACHE_TTL_MS, SCOPE_CACHE_FILE;
|
|
3475
|
+
var init_workspace_scope = __esm({
|
|
3476
|
+
"src/workspace-scope.ts"() {
|
|
3477
|
+
"use strict";
|
|
3478
|
+
import_node_fs3 = __toESM(require("fs"));
|
|
3479
|
+
import_node_path3 = __toESM(require("path"));
|
|
3480
|
+
init_codegraph();
|
|
3481
|
+
init_code_tool();
|
|
3482
|
+
WORKSPACE_RESUME_LIMIT = 9;
|
|
3483
|
+
SCOPE_CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
3484
|
+
SCOPE_CACHE_FILE = "repository-scopes.json";
|
|
3272
3485
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3486
|
+
});
|
|
3487
|
+
|
|
3488
|
+
// src/call-ledger.ts
|
|
3489
|
+
function callsDir(options) {
|
|
3490
|
+
return import_node_path4.default.join(
|
|
3491
|
+
options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path4.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime"),
|
|
3492
|
+
"calls"
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
function stableJson(value) {
|
|
3496
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
3497
|
+
if (value && typeof value === "object") {
|
|
3498
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
|
|
3280
3499
|
}
|
|
3281
|
-
return
|
|
3500
|
+
return JSON.stringify(value) ?? "null";
|
|
3282
3501
|
}
|
|
3283
|
-
function
|
|
3284
|
-
const
|
|
3285
|
-
|
|
3286
|
-
const value = obj(wrapper.node) ?? wrapper;
|
|
3287
|
-
if (isNodeShaped(value)) return nodeLines(value, state);
|
|
3288
|
-
const uid = str2(value.uid);
|
|
3289
|
-
const props = obj(value.props);
|
|
3290
|
-
const label = clip(value.label, LABEL_MAX, state) ?? clip(value.name, LABEL_MAX, state) ?? clip(value.title, LABEL_MAX, state) ?? clip(value.action, LABEL_MAX, state) ?? fallbackType;
|
|
3291
|
-
const status = str2(value.status) ?? str2(props?.status);
|
|
3292
|
-
const summary = clip(value.summary, SUMMARY_MAX, state) ?? clip(value.description, SUMMARY_MAX, state) ?? clip(value.content, SUMMARY_MAX, state) ?? clip(value.outcome_summary, SUMMARY_MAX, state) ?? clip(props?.summary, SUMMARY_MAX, state);
|
|
3293
|
-
const details = [
|
|
3294
|
-
status ? `status ${status}` : void 0,
|
|
3295
|
-
str2(value.path) ? `path ${str2(value.path)}` : void 0,
|
|
3296
|
-
str2(value.symbol) ? `symbol ${str2(value.symbol)}` : void 0
|
|
3297
|
-
].filter(Boolean);
|
|
3298
|
-
const head = `- **${label}**${uid ? ` [${uid}]` : ""}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
|
|
3299
|
-
return summary && summary !== label ? `${head}: ${summary}` : head;
|
|
3502
|
+
function bareToolName(name) {
|
|
3503
|
+
const cut = name.lastIndexOf("__");
|
|
3504
|
+
return cut === -1 ? name : name.slice(cut + 2);
|
|
3300
3505
|
}
|
|
3301
|
-
function
|
|
3302
|
-
|
|
3303
|
-
return items.length > 0 ? { heading, items } : void 0;
|
|
3506
|
+
function callKey(toolName, toolInput) {
|
|
3507
|
+
return import_node_crypto4.default.createHash("sha256").update(`${bareToolName(toolName)}\0${stableJson(toolInput)}`).digest("hex");
|
|
3304
3508
|
}
|
|
3305
|
-
function
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
return
|
|
3314
|
-
"# Resume context",
|
|
3315
|
-
[{ heading: `No eligible work was found in the requested scope.${note}`, items: [] }],
|
|
3316
|
-
state,
|
|
3317
|
-
maxChars
|
|
3318
|
-
);
|
|
3509
|
+
function sessionKey(sessionId) {
|
|
3510
|
+
return import_node_crypto4.default.createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
3511
|
+
}
|
|
3512
|
+
function prune(dir, now) {
|
|
3513
|
+
let names;
|
|
3514
|
+
try {
|
|
3515
|
+
names = import_node_fs4.default.readdirSync(dir);
|
|
3516
|
+
} catch {
|
|
3517
|
+
return;
|
|
3319
3518
|
}
|
|
3320
|
-
const
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
resumeSection(
|
|
3327
|
-
"## Goal and project",
|
|
3328
|
-
[payload.goal, payload.project].filter(Boolean),
|
|
3329
|
-
state,
|
|
3330
|
-
"Scope"
|
|
3331
|
-
)
|
|
3332
|
-
);
|
|
3333
|
-
push(resumeSection("## Plan", [payload.plan], state, "Plan"));
|
|
3334
|
-
push(
|
|
3335
|
-
resumeSection(
|
|
3336
|
-
"## Next steps",
|
|
3337
|
-
Array.isArray(payload.next_steps) ? payload.next_steps : [],
|
|
3338
|
-
state,
|
|
3339
|
-
"Next step"
|
|
3340
|
-
)
|
|
3341
|
-
);
|
|
3342
|
-
push(
|
|
3343
|
-
resumeSection(
|
|
3344
|
-
"## Blockers",
|
|
3345
|
-
Array.isArray(payload.blockers) ? payload.blockers : [],
|
|
3346
|
-
state,
|
|
3347
|
-
"Blocker"
|
|
3348
|
-
)
|
|
3349
|
-
);
|
|
3350
|
-
const knowledge = obj(payload.knowledge);
|
|
3351
|
-
push(
|
|
3352
|
-
resumeSection(
|
|
3353
|
-
"## Relevant lessons and risks",
|
|
3354
|
-
[
|
|
3355
|
-
...Array.isArray(knowledge?.lessons) ? knowledge.lessons : [],
|
|
3356
|
-
...Array.isArray(knowledge?.risks) ? knowledge.risks : []
|
|
3357
|
-
],
|
|
3358
|
-
state,
|
|
3359
|
-
"Knowledge"
|
|
3360
|
-
)
|
|
3361
|
-
);
|
|
3362
|
-
push(
|
|
3363
|
-
resumeSection(
|
|
3364
|
-
"## Active execution",
|
|
3365
|
-
[payload.active_execution].filter(Boolean),
|
|
3366
|
-
state,
|
|
3367
|
-
"Execution"
|
|
3368
|
-
)
|
|
3369
|
-
);
|
|
3370
|
-
push(
|
|
3371
|
-
resumeSection(
|
|
3372
|
-
"## Recent executions",
|
|
3373
|
-
Array.isArray(payload.recent_executions) ? payload.recent_executions : [],
|
|
3374
|
-
state,
|
|
3375
|
-
"Execution"
|
|
3376
|
-
)
|
|
3377
|
-
);
|
|
3378
|
-
push(
|
|
3379
|
-
resumeSection(
|
|
3380
|
-
"## Code targets",
|
|
3381
|
-
Array.isArray(payload.code_targets) ? payload.code_targets : [],
|
|
3382
|
-
state,
|
|
3383
|
-
"Code target"
|
|
3384
|
-
)
|
|
3385
|
-
);
|
|
3386
|
-
push(resumeSection("## Lease", [payload.lease].filter(Boolean), state, "Lease"));
|
|
3387
|
-
const warning = clip(payload.warning, SUMMARY_MAX, state);
|
|
3388
|
-
if (warning) sections.push({ heading: "## Warning", items: [`- ${warning}`] });
|
|
3389
|
-
const selectionReason = clip(payload.selection_reason, SUMMARY_MAX, state);
|
|
3390
|
-
if (selectionReason) {
|
|
3391
|
-
sections.push({ heading: "## Selection", items: [`- ${selectionReason}`] });
|
|
3519
|
+
for (const name of names) {
|
|
3520
|
+
const file = import_node_path4.default.join(dir, name);
|
|
3521
|
+
try {
|
|
3522
|
+
if (now - import_node_fs4.default.statSync(file).mtimeMs > TTL_MS) import_node_fs4.default.unlinkSync(file);
|
|
3523
|
+
} catch {
|
|
3524
|
+
}
|
|
3392
3525
|
}
|
|
3393
|
-
if (sections.length === 0) return void 0;
|
|
3394
|
-
return assemble("# Resume context", sections, state, maxChars);
|
|
3395
3526
|
}
|
|
3396
|
-
function
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3527
|
+
function recordPendingCall(toolName, toolInput, harnessSessionId, fills, options = {}) {
|
|
3528
|
+
const now = (options.now ?? Date.now)();
|
|
3529
|
+
const dir = callsDir(options);
|
|
3530
|
+
try {
|
|
3531
|
+
import_node_fs4.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3532
|
+
prune(dir, now);
|
|
3533
|
+
const record2 = {
|
|
3534
|
+
harnessSessionId,
|
|
3535
|
+
projectDir: options.projectDir ?? process.env.CLAUDE_PROJECT_DIR,
|
|
3536
|
+
fills,
|
|
3537
|
+
createdAt: now
|
|
3538
|
+
};
|
|
3539
|
+
const file = import_node_path4.default.join(
|
|
3540
|
+
dir,
|
|
3541
|
+
`${callKey(toolName, toolInput)}.${sessionKey(harnessSessionId)}.json`
|
|
3542
|
+
);
|
|
3543
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
3544
|
+
import_node_fs4.default.writeFileSync(temporary, JSON.stringify(record2), { mode: 384 });
|
|
3545
|
+
import_node_fs4.default.renameSync(temporary, file);
|
|
3546
|
+
} catch {
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
function resolvePendingCall(toolName, toolInput, options = {}) {
|
|
3550
|
+
const now = (options.now ?? Date.now)();
|
|
3551
|
+
const dir = callsDir(options);
|
|
3552
|
+
const prefix = `${callKey(toolName, toolInput)}.`;
|
|
3553
|
+
let names;
|
|
3554
|
+
try {
|
|
3555
|
+
names = import_node_fs4.default.readdirSync(dir).filter((name) => name.startsWith(prefix) && name.endsWith(".json"));
|
|
3556
|
+
} catch {
|
|
3557
|
+
return void 0;
|
|
3558
|
+
}
|
|
3559
|
+
let matches = [];
|
|
3560
|
+
for (const name of names) {
|
|
3561
|
+
try {
|
|
3562
|
+
const record2 = JSON.parse(
|
|
3563
|
+
import_node_fs4.default.readFileSync(import_node_path4.default.join(dir, name), "utf8")
|
|
3564
|
+
);
|
|
3565
|
+
if (typeof record2.createdAt === "number" && now - record2.createdAt <= TTL_MS && record2.fills && typeof record2.fills === "object") {
|
|
3566
|
+
matches.push(record2);
|
|
3408
3567
|
}
|
|
3568
|
+
} catch {
|
|
3409
3569
|
}
|
|
3410
3570
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
const state = { bounded: false, omitted: 0 };
|
|
3415
|
-
if (limit !== void 0 && limit > 0 && nodes.length > limit) {
|
|
3416
|
-
state.omitted += nodes.length - limit;
|
|
3417
|
-
nodes = nodes.slice(0, limit);
|
|
3418
|
-
state.bounded = true;
|
|
3571
|
+
if (matches.length > 1) {
|
|
3572
|
+
const projectDir = options.projectDir ?? process.env.CLAUDE_PROJECT_DIR;
|
|
3573
|
+
matches = projectDir ? matches.filter((record2) => record2.projectDir === projectDir) : [];
|
|
3419
3574
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3575
|
+
return matches.length === 1 ? matches[0].fills : void 0;
|
|
3576
|
+
}
|
|
3577
|
+
var import_node_crypto4, import_node_fs4, import_node_os3, import_node_path4, TTL_MS;
|
|
3578
|
+
var init_call_ledger = __esm({
|
|
3579
|
+
"src/call-ledger.ts"() {
|
|
3580
|
+
"use strict";
|
|
3581
|
+
import_node_crypto4 = __toESM(require("crypto"));
|
|
3582
|
+
import_node_fs4 = __toESM(require("fs"));
|
|
3583
|
+
import_node_os3 = __toESM(require("os"));
|
|
3584
|
+
import_node_path4 = __toESM(require("path"));
|
|
3585
|
+
TTL_MS = 10 * 6e4;
|
|
3586
|
+
}
|
|
3587
|
+
});
|
|
3588
|
+
|
|
3589
|
+
// src/hook-installer.ts
|
|
3590
|
+
function storedFeatures(homeDir) {
|
|
3591
|
+
const { promptRecall: promptRecall2, capture } = loadHookEnv(
|
|
3592
|
+
import_node_path6.default.join(homeDir || import_node_os5.default.homedir(), ".mindgraph")
|
|
3427
3593
|
);
|
|
3594
|
+
return { promptRecall: promptRecall2, capture };
|
|
3595
|
+
}
|
|
3596
|
+
function command(harness) {
|
|
3597
|
+
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
3598
|
+
}
|
|
3599
|
+
function windowsCommand(harness) {
|
|
3600
|
+
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
3601
|
+
}
|
|
3602
|
+
function installHookRunner(sourceFile, homeDir, version) {
|
|
3603
|
+
const target = import_node_path6.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_RELATIVE);
|
|
3604
|
+
import_node_fs7.default.mkdirSync(import_node_path6.default.dirname(target), { recursive: true });
|
|
3605
|
+
const temporary = `${target}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
|
|
3606
|
+
import_node_fs7.default.copyFileSync(sourceFile, temporary);
|
|
3607
|
+
import_node_fs7.default.renameSync(temporary, target);
|
|
3608
|
+
const versionPath = import_node_path6.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_VERSION_RELATIVE);
|
|
3609
|
+
if (version) {
|
|
3610
|
+
import_node_fs7.default.writeFileSync(versionPath, `${version}
|
|
3611
|
+
`);
|
|
3612
|
+
} else {
|
|
3613
|
+
import_node_fs7.default.rmSync(versionPath, { force: true });
|
|
3614
|
+
}
|
|
3615
|
+
return target;
|
|
3616
|
+
}
|
|
3617
|
+
function installedHookRunnerVersion(homeDir) {
|
|
3618
|
+
try {
|
|
3619
|
+
const raw = import_node_fs7.default.readFileSync(
|
|
3620
|
+
import_node_path6.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_VERSION_RELATIVE),
|
|
3621
|
+
"utf8"
|
|
3622
|
+
).trim();
|
|
3623
|
+
return raw || void 0;
|
|
3624
|
+
} catch {
|
|
3625
|
+
return void 0;
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
function parseSemver(value) {
|
|
3629
|
+
if (!value) return void 0;
|
|
3630
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
|
|
3631
|
+
if (!match) return void 0;
|
|
3632
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
3428
3633
|
}
|
|
3429
|
-
function
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
if (!payload && !directSteps) return void 0;
|
|
3433
|
-
const body = payload ?? {};
|
|
3434
|
-
const container = obj(body.graph) ?? body;
|
|
3435
|
-
const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
|
|
3436
|
-
const nodes = rawNodes.filter(isNodeShaped);
|
|
3437
|
-
const paths = Array.isArray(body.paths) ? body.paths : [];
|
|
3438
|
-
const hasSteps = directSteps !== void 0 || Object.hasOwn(body, "steps");
|
|
3439
|
-
const rawSteps = directSteps ?? (Array.isArray(body.steps) ? body.steps : []);
|
|
3440
|
-
if (nodes.length === 0 && paths.length === 0 && !hasSteps) return void 0;
|
|
3441
|
-
const state = { bounded: false, omitted: 0 };
|
|
3442
|
-
const sections = [];
|
|
3443
|
-
const labelByUid = /* @__PURE__ */ new Map();
|
|
3444
|
-
for (const node of nodes) labelByUid.set(str2(node.uid), str2(node.label) || "?");
|
|
3445
|
-
if (paths.length > 0) {
|
|
3446
|
-
const items = paths.map((raw) => obj(raw)).filter((path9) => Boolean(path9)).map((path9, index) => {
|
|
3447
|
-
const uids = Array.isArray(path9.node_uids) ? path9.node_uids.map(String) : [];
|
|
3448
|
-
const labels = Array.isArray(path9.labels) ? path9.labels.map(String) : Array.isArray(path9.nodes) ? path9.nodes.map((hop) => {
|
|
3449
|
-
const hopNode = obj(hop);
|
|
3450
|
-
return str2(hopNode?.label) || labelByUid.get(str2(hopNode?.uid) || "") || str2(hop) || "?";
|
|
3451
|
-
}) : [];
|
|
3452
|
-
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
3453
|
-
const cost = num(path9.cost) ?? num(path9.path_cost);
|
|
3454
|
-
const confidence = num(path9.path_confidence);
|
|
3455
|
-
const meta = [
|
|
3456
|
-
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
3457
|
-
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
3458
|
-
].filter(Boolean);
|
|
3459
|
-
const head = `${index + 1}. ${route}${meta.length > 0 ? ` (${meta.join(", ")})` : ""}`;
|
|
3460
|
-
return labels.length > 0 && uids.length > 0 ? `${head}
|
|
3461
|
-
uids: ${uids.join(" \u2192 ")}` : head;
|
|
3462
|
-
});
|
|
3463
|
-
sections.push({ heading: "## Paths", items });
|
|
3634
|
+
function semverNewer(a, b) {
|
|
3635
|
+
for (let index = 0; index < 3; index += 1) {
|
|
3636
|
+
if (a[index] !== b[index]) return a[index] > b[index];
|
|
3464
3637
|
}
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
const relation = edgeTypeName(step.edge_type);
|
|
3472
|
-
const role = str2(step.traversal_role);
|
|
3473
|
-
const type = str2(step.node_type) ?? str2(obj(step.node_type)?.Custom);
|
|
3474
|
-
const cost = num(step.path_cost);
|
|
3475
|
-
const confidence = num(step.path_confidence);
|
|
3476
|
-
const prefix = [
|
|
3477
|
-
depth !== void 0 ? `depth ${depth}` : void 0,
|
|
3478
|
-
role,
|
|
3479
|
-
relation ? `\u2014${relation}\u2192` : void 0
|
|
3480
|
-
].filter(Boolean).join(" ");
|
|
3481
|
-
const meta = [
|
|
3482
|
-
type,
|
|
3483
|
-
parent ? `parent ${parent}` : void 0,
|
|
3484
|
-
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
3485
|
-
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
3486
|
-
].filter(Boolean).join(", ");
|
|
3487
|
-
return `- ${prefix ? `${prefix}: ` : ""}**${label}** [${uid}]${meta ? ` (${meta})` : ""}`;
|
|
3488
|
-
});
|
|
3489
|
-
if (stepItems.length > 0) {
|
|
3490
|
-
sections.push({ heading: "## Traversal steps", items: stepItems });
|
|
3491
|
-
} else {
|
|
3492
|
-
const start = str2(body.start_uid) || "?";
|
|
3493
|
-
const end = str2(body.end_uid);
|
|
3494
|
-
const mode2 = str2(body.mode);
|
|
3495
|
-
sections.push({
|
|
3496
|
-
heading: mode2 === "path" && end ? `No path found from [${start}] to [${end}].` : `No traversal steps returned from [${start}].`,
|
|
3497
|
-
items: []
|
|
3498
|
-
});
|
|
3638
|
+
return false;
|
|
3639
|
+
}
|
|
3640
|
+
function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
|
|
3641
|
+
try {
|
|
3642
|
+
if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
|
|
3643
|
+
return { refreshed: false, reason: "disabled" };
|
|
3499
3644
|
}
|
|
3645
|
+
const home = homeDir || import_node_os5.default.homedir();
|
|
3646
|
+
if (!import_node_fs7.default.existsSync(import_node_path6.default.join(home, RUNNER_RELATIVE))) {
|
|
3647
|
+
return { refreshed: false, reason: "no_runner" };
|
|
3648
|
+
}
|
|
3649
|
+
if (!import_node_fs7.default.existsSync(sourceBundle)) {
|
|
3650
|
+
return { refreshed: false, reason: "no_source" };
|
|
3651
|
+
}
|
|
3652
|
+
const server = parseSemver(serverVersion);
|
|
3653
|
+
if (!server) return { refreshed: false, reason: "unparseable" };
|
|
3654
|
+
const runnerVersion = installedHookRunnerVersion(home);
|
|
3655
|
+
if (runnerVersion !== void 0) {
|
|
3656
|
+
const pinned = parseSemver(runnerVersion);
|
|
3657
|
+
if (!pinned) {
|
|
3658
|
+
return { refreshed: false, reason: "unparseable", runnerVersion };
|
|
3659
|
+
}
|
|
3660
|
+
if (!semverNewer(server, pinned)) {
|
|
3661
|
+
return {
|
|
3662
|
+
refreshed: false,
|
|
3663
|
+
reason: runnerVersion === serverVersion ? "current" : "downgrade",
|
|
3664
|
+
runnerVersion
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
installHookRunner(sourceBundle, home, serverVersion);
|
|
3669
|
+
return { refreshed: true, reason: "updated", runnerVersion };
|
|
3670
|
+
} catch {
|
|
3671
|
+
return { refreshed: false, reason: "error" };
|
|
3500
3672
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3673
|
+
}
|
|
3674
|
+
function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
|
|
3675
|
+
const refreshed = [];
|
|
3676
|
+
const scopes = ["user", "project"];
|
|
3677
|
+
for (const scope of scopes) {
|
|
3678
|
+
try {
|
|
3679
|
+
const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
|
|
3680
|
+
if (!import_node_fs7.default.existsSync(file)) continue;
|
|
3681
|
+
if (!import_node_fs7.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
|
|
3682
|
+
installHooks(
|
|
3683
|
+
"claude-code",
|
|
3684
|
+
scope,
|
|
3685
|
+
projectDir,
|
|
3686
|
+
void 0,
|
|
3687
|
+
homeDir,
|
|
3688
|
+
storedFeatures(homeDir)
|
|
3689
|
+
);
|
|
3690
|
+
refreshed.push(file);
|
|
3691
|
+
} catch {
|
|
3510
3692
|
}
|
|
3511
3693
|
}
|
|
3512
|
-
|
|
3513
|
-
return assemble(
|
|
3514
|
-
`# Graph traversal${mode ? ` (${mode})` : ""}`,
|
|
3515
|
-
sections,
|
|
3516
|
-
state,
|
|
3517
|
-
maxChars
|
|
3518
|
-
);
|
|
3694
|
+
return refreshed;
|
|
3519
3695
|
}
|
|
3520
|
-
function
|
|
3521
|
-
const
|
|
3522
|
-
if (!
|
|
3523
|
-
|
|
3524
|
-
const uid = str2(node.uid);
|
|
3525
|
-
const type = str2(node.node_type);
|
|
3526
|
-
return `- ${label}${uid ? ` [${uid}]` : ""}${type ? ` (${type})` : ""}`;
|
|
3696
|
+
function versionSkewNote(serverVersion, homeDir) {
|
|
3697
|
+
const runnerVersion = installedHookRunnerVersion(homeDir);
|
|
3698
|
+
if (!runnerVersion || runnerVersion === serverVersion) return void 0;
|
|
3699
|
+
return `NOTE: the installed MindGraph hook runner is v${runnerVersion} but this MCP server is v${serverVersion} \u2014 hook behavior and tool contracts may be out of sync. Tell the user to run: npx -y mindgraph-mcp@latest install-hooks (refreshes the pinned runner).`;
|
|
3527
3700
|
}
|
|
3528
|
-
function
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
const objects = Array.isArray(payload.objects) ? payload.objects.filter(isNodeShaped) : isNodeShaped(payload.object) ? [payload.object] : [];
|
|
3532
|
-
const cognitive = obj(payload.cognitive_context);
|
|
3533
|
-
const provenance = Array.isArray(payload.provenance) ? payload.provenance : [];
|
|
3534
|
-
const hasQuerySignature = Array.isArray(payload.objects) || obj(payload.confidence) !== void 0;
|
|
3535
|
-
const cognitiveEmpty = !cognitive || Object.values(cognitive).every((v) => !Array.isArray(v) || v.length === 0);
|
|
3536
|
-
if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
|
|
3537
|
-
return hasQuerySignature ? "No matching domain objects." : void 0;
|
|
3538
|
-
}
|
|
3539
|
-
const state = { bounded: false, omitted: 0 };
|
|
3540
|
-
const sections = [];
|
|
3541
|
-
if (objects.length > 0) {
|
|
3542
|
-
sections.push(...groupedNodeSections(objects, state));
|
|
3701
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path6.default.join(import_node_os5.default.homedir(), ".codex"), homeDir) {
|
|
3702
|
+
if (harness === "codex") {
|
|
3703
|
+
return scope === "user" ? import_node_path6.default.join(codexHomeDir, "hooks.json") : import_node_path6.default.join(projectDir, ".codex", "hooks.json");
|
|
3543
3704
|
}
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3705
|
+
return scope === "user" ? import_node_path6.default.join(homeDir || import_node_os5.default.homedir(), ".claude", "settings.json") : import_node_path6.default.join(projectDir, ".claude", "settings.json");
|
|
3706
|
+
}
|
|
3707
|
+
function readSettings(file) {
|
|
3708
|
+
if (!import_node_fs7.default.existsSync(file)) return {};
|
|
3709
|
+
return JSON.parse(import_node_fs7.default.readFileSync(file, "utf8"));
|
|
3710
|
+
}
|
|
3711
|
+
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
3712
|
+
return {
|
|
3713
|
+
type: "command",
|
|
3714
|
+
command: command(harness),
|
|
3715
|
+
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
3716
|
+
timeout,
|
|
3717
|
+
statusMessage: "Syncing MindGraph session context",
|
|
3718
|
+
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
function desiredEntries(harness, features = {}) {
|
|
3722
|
+
const common = {
|
|
3723
|
+
SessionStart: [
|
|
3724
|
+
{
|
|
3725
|
+
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
3726
|
+
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
3727
|
+
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
3728
|
+
// open, but no brief reached the model. The same installed runner
|
|
3729
|
+
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
3730
|
+
// The declared Codex context limit must not undercut the brief's own
|
|
3731
|
+
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
3732
|
+
// truncation the renderer knew nothing about.
|
|
3733
|
+
hooks: [
|
|
3734
|
+
ownedHook(
|
|
3735
|
+
harness,
|
|
3736
|
+
30,
|
|
3737
|
+
harness === "codex" ? 1e4 : void 0
|
|
3738
|
+
)
|
|
3739
|
+
]
|
|
3740
|
+
}
|
|
3741
|
+
],
|
|
3742
|
+
PreToolUse: [
|
|
3743
|
+
{
|
|
3744
|
+
matcher: "mcp__mindgraph__.*",
|
|
3745
|
+
// Repository resolution over a large multi-repo workspace plus three
|
|
3746
|
+
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
3747
|
+
// PreToolUse means the call goes through untagged.
|
|
3748
|
+
hooks: [ownedHook(harness, 5)]
|
|
3749
|
+
}
|
|
3750
|
+
],
|
|
3751
|
+
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
3752
|
+
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
3753
|
+
// Claude Code: only the tools the hook acts on (MindGraph calls, edits,
|
|
3754
|
+
// and the shell/agent calls that keep a lease alive). Read, Grep and Glob
|
|
3755
|
+
// are most of a session's tool calls and the hook did nothing useful for
|
|
3756
|
+
// them but start a process (roadmap H8). Codex names its tools
|
|
3757
|
+
// differently, so it keeps the catch-all.
|
|
3758
|
+
PostToolUse: [
|
|
3759
|
+
{
|
|
3760
|
+
matcher: harness === "claude-code" ? CLAUDE_POST_TOOL_MATCHER : ".*",
|
|
3761
|
+
hooks: [ownedHook(harness, 10)]
|
|
3762
|
+
}
|
|
3763
|
+
],
|
|
3764
|
+
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
3765
|
+
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
3766
|
+
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
3767
|
+
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
3768
|
+
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
3769
|
+
// map), so the codex adapter instead makes at most ONE call there.
|
|
3770
|
+
SessionEnd: [
|
|
3771
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
3772
|
+
]
|
|
3773
|
+
};
|
|
3774
|
+
if (harness !== "claude-code") return common;
|
|
3775
|
+
return {
|
|
3776
|
+
...common,
|
|
3777
|
+
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
3778
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }],
|
|
3779
|
+
// Bounded recall per prompt: the core gives up at 4s and fails open, so
|
|
3780
|
+
// a 5s budget never holds the prompt longer than that.
|
|
3781
|
+
...features.promptRecall !== false ? { UserPromptSubmit: [{ hooks: [ownedHook(harness, 5)] }] } : {},
|
|
3782
|
+
// Opt-in transcript capture before compaction (cold-tenant ingest).
|
|
3783
|
+
...features.capture === true ? { PreCompact: [{ hooks: [ownedHook(harness, 30)] }] } : {}
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
function hookCommand(value) {
|
|
3787
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3788
|
+
const command2 = value.command;
|
|
3789
|
+
return typeof command2 === "string" ? command2 : void 0;
|
|
3790
|
+
}
|
|
3791
|
+
function entryHasOwnedHook(value) {
|
|
3792
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3793
|
+
const hooks = value.hooks;
|
|
3794
|
+
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
3795
|
+
}
|
|
3796
|
+
function writeSettings(file, settings) {
|
|
3797
|
+
import_node_fs7.default.mkdirSync(import_node_path6.default.dirname(file), { recursive: true });
|
|
3798
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
|
|
3799
|
+
import_node_fs7.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
3800
|
+
`, {
|
|
3801
|
+
mode: 384
|
|
3802
|
+
});
|
|
3803
|
+
import_node_fs7.default.renameSync(temporary, file);
|
|
3804
|
+
}
|
|
3805
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir, features = {}) {
|
|
3806
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
|
|
3807
|
+
const settings = readSettings(file);
|
|
3808
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
3809
|
+
let added = 0;
|
|
3810
|
+
let updated = 0;
|
|
3811
|
+
const desired = desiredEntries(harness, features);
|
|
3812
|
+
for (const optional of ["UserPromptSubmit", "PreCompact"]) {
|
|
3813
|
+
if (optional in desired || !Array.isArray(hooks[optional])) continue;
|
|
3814
|
+
const kept = hooks[optional].filter(
|
|
3815
|
+
(entry) => !entryHasOwnedHook(entry)
|
|
3816
|
+
);
|
|
3817
|
+
if (kept.length > 0) hooks[optional] = kept;
|
|
3818
|
+
else delete hooks[optional];
|
|
3551
3819
|
}
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3820
|
+
for (const [event, wanted] of Object.entries(desired)) {
|
|
3821
|
+
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
3822
|
+
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
3823
|
+
const hadOwned = kept.length !== current.length;
|
|
3824
|
+
for (const entry of wanted) {
|
|
3825
|
+
kept.push(entry);
|
|
3826
|
+
if (hadOwned) updated += 1;
|
|
3827
|
+
else added += 1;
|
|
3560
3828
|
}
|
|
3829
|
+
hooks[event] = kept;
|
|
3561
3830
|
}
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3831
|
+
settings.hooks = hooks;
|
|
3832
|
+
writeSettings(file, settings);
|
|
3833
|
+
return { path: file, added, updated };
|
|
3834
|
+
}
|
|
3835
|
+
function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
3836
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
3837
|
+
if (!import_node_fs7.default.existsSync(file)) return { path: file, removed: 0 };
|
|
3838
|
+
const settings = readSettings(file);
|
|
3839
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
3840
|
+
let removed = 0;
|
|
3841
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
3842
|
+
if (!Array.isArray(value)) continue;
|
|
3843
|
+
const retainedEntries = [];
|
|
3844
|
+
for (const rawEntry of value) {
|
|
3845
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
3846
|
+
retainedEntries.push(rawEntry);
|
|
3847
|
+
continue;
|
|
3848
|
+
}
|
|
3849
|
+
const entry = { ...rawEntry };
|
|
3850
|
+
if (!Array.isArray(entry.hooks)) {
|
|
3851
|
+
retainedEntries.push(entry);
|
|
3852
|
+
continue;
|
|
3853
|
+
}
|
|
3854
|
+
const retainedHooks = entry.hooks.filter((hook) => {
|
|
3855
|
+
const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
|
|
3856
|
+
if (owned) removed += 1;
|
|
3857
|
+
return !owned;
|
|
3858
|
+
});
|
|
3859
|
+
if (retainedHooks.length > 0) {
|
|
3860
|
+
entry.hooks = retainedHooks;
|
|
3861
|
+
retainedEntries.push(entry);
|
|
3573
3862
|
}
|
|
3574
3863
|
}
|
|
3864
|
+
if (retainedEntries.length > 0) hooks[event] = retainedEntries;
|
|
3865
|
+
else delete hooks[event];
|
|
3575
3866
|
}
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
const by = str2(entry.ingested_by_name);
|
|
3580
|
-
const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${str2(entry.source_uid) || "?"}]`;
|
|
3581
|
-
return str2(entry.span_kind) === "anchor" ? `- "${span}"${suffix}` : `- context: ${span}${suffix}`;
|
|
3582
|
-
});
|
|
3583
|
-
sections.push({ heading: "## Sources", items });
|
|
3584
|
-
}
|
|
3585
|
-
const confidence = num(obj(payload.confidence)?.overall);
|
|
3586
|
-
const notes = [];
|
|
3587
|
-
if (confidence !== void 0) notes.push(`overall confidence ${confidence}`);
|
|
3588
|
-
if (payload.has_more === true) notes.push("more results exist (has_more)");
|
|
3589
|
-
if (payload.seed_cap_hit === true) notes.push("seed cap hit \u2014 narrow the query for a complete set");
|
|
3590
|
-
if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
|
|
3591
|
-
return assemble(
|
|
3592
|
-
`# Ontology results (${objects.length} object${objects.length === 1 ? "" : "s"})`,
|
|
3593
|
-
sections,
|
|
3594
|
-
state
|
|
3595
|
-
);
|
|
3867
|
+
settings.hooks = hooks;
|
|
3868
|
+
writeSettings(file, settings);
|
|
3869
|
+
return { path: file, removed };
|
|
3596
3870
|
}
|
|
3597
|
-
function
|
|
3598
|
-
|
|
3599
|
-
if (!payload) return void 0;
|
|
3600
|
-
const rawItems = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.rows) && payload.rows.some((row) => unwrapSearchResult(row)) ? payload.rows : void 0;
|
|
3601
|
-
const items = (rawItems ?? []).map(unwrapSearchResult).filter((node) => Boolean(node));
|
|
3602
|
-
if (items.length === 0) {
|
|
3603
|
-
return rawItems ? `# ${title} (0 results)` : void 0;
|
|
3604
|
-
}
|
|
3605
|
-
const state = { bounded: false, omitted: 0 };
|
|
3606
|
-
const sections = groupedNodeSections(items, state);
|
|
3607
|
-
const total = num(payload.total_count);
|
|
3608
|
-
const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
|
|
3609
|
-
const notes = [];
|
|
3610
|
-
if (payload.has_more === true) notes.push("more results exist");
|
|
3611
|
-
if (truncations.length > 0) notes.push(`truncated: ${truncations.join(", ")}`);
|
|
3612
|
-
if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
|
|
3613
|
-
const count = total !== void 0 && total !== items.length ? `${items.length} of ${total}` : `${items.length}`;
|
|
3614
|
-
return assemble(`# ${title} (${count})`, sections, state);
|
|
3871
|
+
function installClaudeHooks(scope, projectDir = process.cwd(), features = {}) {
|
|
3872
|
+
return installHooks("claude-code", scope, projectDir, void 0, void 0, features);
|
|
3615
3873
|
}
|
|
3616
|
-
function
|
|
3617
|
-
|
|
3618
|
-
if (!Array.isArray(jobs)) return void 0;
|
|
3619
|
-
if (jobs.length === 0) return "No ingestion jobs.";
|
|
3620
|
-
const state = { bounded: false, omitted: 0 };
|
|
3621
|
-
const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
|
|
3622
|
-
const shown = sorted.slice(0, JOBS_RENDER_MAX);
|
|
3623
|
-
if (shown.length < sorted.length) state.bounded = true;
|
|
3624
|
-
const items = shown.map((job) => {
|
|
3625
|
-
const progress = obj(job.progress);
|
|
3626
|
-
const failure = obj(job.error_details);
|
|
3627
|
-
const message = str2(failure?.message) ?? str2(job.error);
|
|
3628
|
-
const done = num(progress?.processed_chunks);
|
|
3629
|
-
const totalChunks = num(progress?.total_chunks);
|
|
3630
|
-
const bits = [
|
|
3631
|
-
str2(job.status) || "?",
|
|
3632
|
-
done !== void 0 && totalChunks !== void 0 ? `${done}/${totalChunks} chunks` : void 0,
|
|
3633
|
-
num(progress?.nodes_created) !== void 0 ? `${num(progress?.nodes_created)} nodes` : void 0,
|
|
3634
|
-
num(job.queue_position) !== void 0 ? `queue #${num(job.queue_position)}` : void 0,
|
|
3635
|
-
message ? `error: ${clip(message, 160, state)}` : void 0,
|
|
3636
|
-
str2(failure?.code) ? `code: ${clip(failure?.code, 80, state)}` : void 0,
|
|
3637
|
-
num(failure?.status) !== void 0 ? `status: ${num(failure?.status)}` : void 0,
|
|
3638
|
-
typeof failure?.retriable === "boolean" ? `retriable: ${failure.retriable}` : void 0
|
|
3639
|
-
].filter(Boolean);
|
|
3640
|
-
return `- [${str2(job.id) || "?"}] ${clip(job.title, 120, state) || "(untitled)"} \u2014 ${bits.join(", ")}`;
|
|
3641
|
-
});
|
|
3642
|
-
return assemble(
|
|
3643
|
-
`# Ingestion jobs (${shown.length}${shown.length < sorted.length ? ` of ${sorted.length}` : ""})`,
|
|
3644
|
-
[{ heading: "", items }],
|
|
3645
|
-
state
|
|
3646
|
-
);
|
|
3874
|
+
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
3875
|
+
return uninstallHooks("claude-code", scope, projectDir);
|
|
3647
3876
|
}
|
|
3648
|
-
function
|
|
3649
|
-
|
|
3650
|
-
if (!payload) return void 0;
|
|
3651
|
-
const state = { bounded: false, omitted: 0 };
|
|
3652
|
-
const sections = [];
|
|
3653
|
-
for (const [key, value] of Object.entries(payload)) {
|
|
3654
|
-
if (!Array.isArray(value) || value.length === 0) continue;
|
|
3655
|
-
const items = value.map((raw) => obj(raw)).filter((item) => Boolean(item)).map((item) => {
|
|
3656
|
-
const fromLabel = str2(item.from_label);
|
|
3657
|
-
const toLabel = str2(item.to_label);
|
|
3658
|
-
const anyLabelKey = Object.keys(item).find(
|
|
3659
|
-
(k) => k.endsWith("_label") && str2(item[k])
|
|
3660
|
-
);
|
|
3661
|
-
const label = str2(item.label) ?? str2(item.title) ?? str2(item.name) ?? (fromLabel && toLabel ? `${fromLabel} \u2194 ${toLabel}` : void 0) ?? (anyLabelKey ? str2(item[anyLabelKey]) : void 0) ?? str2(item.uid) ?? str2(item.target_uid) ?? "(item)";
|
|
3662
|
-
const uid = str2(item.uid) ?? str2(item.target_uid);
|
|
3663
|
-
const numbers = Object.entries(item).filter(([, v]) => typeof v === "number").slice(0, 4).map(([k, v]) => `${k} ${Math.round(v * 100) / 100}`);
|
|
3664
|
-
const lists = Object.entries(item).filter(([, v]) => Array.isArray(v) && v.length > 0).slice(0, 2).map(([k, v]) => `${k}: ${v.length}`);
|
|
3665
|
-
const meta = [...numbers, ...lists].join(", ");
|
|
3666
|
-
return `- ${clip(label, 160, state)}${uid && uid !== label ? ` [${uid}]` : ""}${meta ? ` (${meta})` : ""}`;
|
|
3667
|
-
});
|
|
3668
|
-
if (items.length > 0) {
|
|
3669
|
-
sections.push({ heading: `## ${key.replace(/_/g, " ")}`, items });
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
if (sections.length === 0) return void 0;
|
|
3673
|
-
return assemble("# Synthesis signals", sections, state);
|
|
3877
|
+
function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
3878
|
+
return installHooks("codex", scope, projectDir, codexHomeDir);
|
|
3674
3879
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3880
|
+
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
3881
|
+
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
3882
|
+
}
|
|
3883
|
+
var import_node_fs7, import_node_os5, import_node_path6, import_node_crypto6, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE, CLAUDE_POST_TOOL_MATCHER;
|
|
3884
|
+
var init_hook_installer = __esm({
|
|
3885
|
+
"src/hook-installer.ts"() {
|
|
3678
3886
|
"use strict";
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
|
|
3689
|
-
INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
|
|
3690
|
-
JOBS_RENDER_MAX = 20;
|
|
3887
|
+
import_node_fs7 = __toESM(require("fs"));
|
|
3888
|
+
import_node_os5 = __toESM(require("os"));
|
|
3889
|
+
import_node_path6 = __toESM(require("path"));
|
|
3890
|
+
import_node_crypto6 = __toESM(require("crypto"));
|
|
3891
|
+
init_hook_env();
|
|
3892
|
+
OWNER_MARKER = "--owner mindgraph";
|
|
3893
|
+
RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
3894
|
+
RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
|
|
3895
|
+
CLAUDE_POST_TOOL_MATCHER = "mcp__mindgraph__.*|Edit|MultiEdit|Write|NotebookEdit|Bash|Task|Agent";
|
|
3691
3896
|
}
|
|
3692
3897
|
});
|
|
3693
3898
|
|
|
@@ -4583,7 +4788,8 @@ function compactItem(raw, fallbackType) {
|
|
|
4583
4788
|
type: fallbackType ?? nodeType2(value),
|
|
4584
4789
|
label: text(value.label) ?? text(value.document_title),
|
|
4585
4790
|
truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
|
|
4586
|
-
|
|
4791
|
+
// `true` from /retrieve, an object naming the replacement from /retrieve/context.
|
|
4792
|
+
superseded: value.superseded ? true : void 0,
|
|
4587
4793
|
currently_valid: value.currently_valid === false ? false : void 0,
|
|
4588
4794
|
valid_at_time: value.valid_at_time === false ? false : void 0,
|
|
4589
4795
|
retrieval_origin: text(value.retrieval_origin),
|
|
@@ -4644,6 +4850,10 @@ function resumeCandidates(raw) {
|
|
|
4644
4850
|
const item = compactItem(payload[key], key);
|
|
4645
4851
|
if (item) items.push(item);
|
|
4646
4852
|
}
|
|
4853
|
+
for (const value of Array.isArray(payload.other_eligible) ? payload.other_eligible : []) {
|
|
4854
|
+
const item = compactItem(value, "Task");
|
|
4855
|
+
if (item) items.push(item);
|
|
4856
|
+
}
|
|
4647
4857
|
for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
|
|
4648
4858
|
for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
|
|
4649
4859
|
const item = compactItem(value, key.replace(/s$/, ""));
|
|
@@ -4688,9 +4898,9 @@ function latestTimestamp(value) {
|
|
|
4688
4898
|
for (const item of current) visit(item);
|
|
4689
4899
|
return;
|
|
4690
4900
|
}
|
|
4691
|
-
const
|
|
4692
|
-
if (!
|
|
4693
|
-
for (const [key, item] of Object.entries(
|
|
4901
|
+
const record2 = obj2(current);
|
|
4902
|
+
if (!record2) return;
|
|
4903
|
+
for (const [key, item] of Object.entries(record2)) {
|
|
4694
4904
|
if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
|
|
4695
4905
|
latest = item;
|
|
4696
4906
|
}
|
|
@@ -4714,7 +4924,7 @@ function contextWarnings(raw, rendered2, available, returned) {
|
|
|
4714
4924
|
message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
|
|
4715
4925
|
});
|
|
4716
4926
|
}
|
|
4717
|
-
if (/"superseded":true/.test(serialized)) {
|
|
4927
|
+
if (/"superseded":(?:true|\{)/.test(serialized)) {
|
|
4718
4928
|
warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
|
|
4719
4929
|
}
|
|
4720
4930
|
if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
|
|
@@ -4763,6 +4973,7 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4763
4973
|
let rendered2;
|
|
4764
4974
|
let strategy;
|
|
4765
4975
|
let candidates;
|
|
4976
|
+
let workspace;
|
|
4766
4977
|
if (mode === "topic") {
|
|
4767
4978
|
const query = text(args.query);
|
|
4768
4979
|
if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
|
|
@@ -4785,10 +4996,27 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4785
4996
|
strategy = "retrieve_context";
|
|
4786
4997
|
candidates = topicCandidates(raw);
|
|
4787
4998
|
} else {
|
|
4999
|
+
if (args.scope !== void 0 && args.scope !== "workspace") {
|
|
5000
|
+
return error("scope must be 'workspace' when given");
|
|
5001
|
+
}
|
|
5002
|
+
if (args.scope === "workspace") {
|
|
5003
|
+
if (!config.resolveWorkspaceScope) {
|
|
5004
|
+
return error(
|
|
5005
|
+
"scope='workspace' needs the local MindGraph MCP server, which reads .mindgraph/workspace.json beside the checkout; this server has no working directory. Pass scope_uids instead."
|
|
5006
|
+
);
|
|
5007
|
+
}
|
|
5008
|
+
workspace = await config.resolveWorkspaceScope(agentId ?? "mcp");
|
|
5009
|
+
if (workspace.uids.length === 0) {
|
|
5010
|
+
return error(
|
|
5011
|
+
`scope='workspace' resolved no repositories${workspace.failures.length > 0 ? ` (${workspace.failures.join("; ")})` : ""}`
|
|
5012
|
+
);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
4788
5015
|
const scopeUids = Array.from(
|
|
4789
5016
|
/* @__PURE__ */ new Set([
|
|
4790
5017
|
...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
|
|
4791
|
-
...text(args.project_uid) ? [text(args.project_uid)] : []
|
|
5018
|
+
...text(args.project_uid) ? [text(args.project_uid)] : [],
|
|
5019
|
+
...workspace?.uids ?? []
|
|
4792
5020
|
])
|
|
4793
5021
|
);
|
|
4794
5022
|
raw = await client2.plan(
|
|
@@ -4797,11 +5025,11 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4797
5025
|
task_uid: text(args.task_uid),
|
|
4798
5026
|
session_uid: text(args.session_uid),
|
|
4799
5027
|
scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
|
|
4800
|
-
limit: 1,
|
|
5028
|
+
limit: workspace ? WORKSPACE_RESUME_LIMIT : 1,
|
|
4801
5029
|
agent_id: agentId
|
|
4802
5030
|
})
|
|
4803
5031
|
);
|
|
4804
|
-
rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
|
|
5032
|
+
rendered2 = renderResumeContext(raw, budget, workspace?.names) ?? "No eligible work was found in the requested scope.";
|
|
4805
5033
|
strategy = "resume_work";
|
|
4806
5034
|
candidates = resumeCandidates(raw);
|
|
4807
5035
|
}
|
|
@@ -4814,7 +5042,8 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4814
5042
|
project_uid: text(args.project_uid),
|
|
4815
5043
|
task_uid: text(args.task_uid),
|
|
4816
5044
|
session_uid: text(args.session_uid),
|
|
4817
|
-
scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
|
|
5045
|
+
scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0,
|
|
5046
|
+
workspace_repositories: workspace ? Object.values(workspace.names).sort() : void 0
|
|
4818
5047
|
});
|
|
4819
5048
|
const contextId = `ctx_${sha2562(
|
|
4820
5049
|
stableJson2({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
|
|
@@ -4855,7 +5084,15 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4855
5084
|
valid_at: text(args.valid_at),
|
|
4856
5085
|
latest_source_timestamp: latestTimestamp(raw)
|
|
4857
5086
|
}),
|
|
4858
|
-
warnings:
|
|
5087
|
+
warnings: [
|
|
5088
|
+
...contextWarnings(raw, rendered2, candidates.length, items.length),
|
|
5089
|
+
...workspace && workspace.failures.length > 0 ? [
|
|
5090
|
+
{
|
|
5091
|
+
code: "workspace_scope_partial",
|
|
5092
|
+
message: `${workspace.failures.length} declared repositories did not resolve and were left out: ${workspace.failures.join("; ")}`
|
|
5093
|
+
}
|
|
5094
|
+
] : []
|
|
5095
|
+
]
|
|
4859
5096
|
};
|
|
4860
5097
|
return {
|
|
4861
5098
|
content: [{ type: "text", text: rendered2 }],
|
|
@@ -4868,6 +5105,7 @@ var init_memory_tool = __esm({
|
|
|
4868
5105
|
"use strict";
|
|
4869
5106
|
import_node_crypto7 = require("crypto");
|
|
4870
5107
|
init_render();
|
|
5108
|
+
init_workspace_scope();
|
|
4871
5109
|
RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
|
|
4872
5110
|
DEFAULT_OUTPUT_CHARS = 8e3;
|
|
4873
5111
|
MIN_OUTPUT_CHARS = 512;
|
|
@@ -4902,6 +5140,11 @@ var init_memory_tool = __esm({
|
|
|
4902
5140
|
items: { type: "string" },
|
|
4903
5141
|
description: "Optional explicit graph scopes for continuity/resume"
|
|
4904
5142
|
},
|
|
5143
|
+
scope: {
|
|
5144
|
+
type: "string",
|
|
5145
|
+
enum: ["workspace"],
|
|
5146
|
+
description: "For continuity/resume on a local server: select work across every repository the working directory's .mindgraph/workspace.json declares, and list the other eligible tasks grouped by repository"
|
|
5147
|
+
},
|
|
4905
5148
|
node_types: {
|
|
4906
5149
|
type: "array",
|
|
4907
5150
|
items: { type: "string" },
|
|
@@ -5136,12 +5379,12 @@ var init_resources = __esm({
|
|
|
5136
5379
|
|
|
5137
5380
|
// src/sync-tool.ts
|
|
5138
5381
|
function safePath(root, candidate) {
|
|
5139
|
-
const absolute =
|
|
5140
|
-
const relative2 =
|
|
5141
|
-
if (relative2.startsWith("..") ||
|
|
5382
|
+
const absolute = import_node_path7.default.resolve(root, candidate);
|
|
5383
|
+
const relative2 = import_node_path7.default.relative(root, absolute);
|
|
5384
|
+
if (relative2.startsWith("..") || import_node_path7.default.isAbsolute(relative2)) {
|
|
5142
5385
|
throw new Error(`path escapes repository root: ${candidate}`);
|
|
5143
5386
|
}
|
|
5144
|
-
if (
|
|
5387
|
+
if (import_node_fs8.default.existsSync(absolute)) {
|
|
5145
5388
|
if (!pathContains(realPathOf(root), realPathOf(absolute))) {
|
|
5146
5389
|
throw new Error(`path escapes repository root via symlink: ${candidate}`);
|
|
5147
5390
|
}
|
|
@@ -5150,25 +5393,25 @@ function safePath(root, candidate) {
|
|
|
5150
5393
|
}
|
|
5151
5394
|
function realPathOf(target) {
|
|
5152
5395
|
try {
|
|
5153
|
-
return
|
|
5396
|
+
return import_node_fs8.default.realpathSync(target);
|
|
5154
5397
|
} catch {
|
|
5155
|
-
return
|
|
5398
|
+
return import_node_path7.default.resolve(target);
|
|
5156
5399
|
}
|
|
5157
5400
|
}
|
|
5158
5401
|
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
5159
|
-
if (!
|
|
5402
|
+
if (!import_node_fs8.default.existsSync(target)) return [];
|
|
5160
5403
|
const real = realPathOf(target);
|
|
5161
5404
|
if (!pathContains(realRoot, real)) return [];
|
|
5162
|
-
const stat =
|
|
5405
|
+
const stat = import_node_fs8.default.statSync(target);
|
|
5163
5406
|
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
5164
5407
|
if (!stat.isDirectory() || visited.has(real)) return [];
|
|
5165
5408
|
visited.add(real);
|
|
5166
|
-
return
|
|
5409
|
+
return import_node_fs8.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path7.default.join(target, entry.name), realRoot, visited));
|
|
5167
5410
|
}
|
|
5168
5411
|
function fileDescriptor(root, absolute) {
|
|
5169
|
-
const content =
|
|
5412
|
+
const content = import_node_fs8.default.readFileSync(absolute, "utf8");
|
|
5170
5413
|
return {
|
|
5171
|
-
logical_path:
|
|
5414
|
+
logical_path: import_node_path7.default.relative(root, absolute).split(import_node_path7.default.sep).join("/"),
|
|
5172
5415
|
content_hash: import_node_crypto8.default.createHash("sha256").update(content).digest("hex"),
|
|
5173
5416
|
bytes: Buffer.byteLength(content),
|
|
5174
5417
|
content
|
|
@@ -5238,7 +5481,7 @@ async function handleSyncTool(client2, args) {
|
|
|
5238
5481
|
}
|
|
5239
5482
|
if (action === "status" && request.logical_path && request.file_present !== false) {
|
|
5240
5483
|
const absolute = safePath(root, String(request.logical_path));
|
|
5241
|
-
request.file_present =
|
|
5484
|
+
request.file_present = import_node_fs8.default.existsSync(absolute);
|
|
5242
5485
|
if (request.file_present && !request.content_hash) {
|
|
5243
5486
|
request.content_hash = fileDescriptor(root, absolute).content_hash;
|
|
5244
5487
|
}
|
|
@@ -5282,13 +5525,13 @@ async function scanRepository(remote, repository, requested, provider, agentId)
|
|
|
5282
5525
|
files: results
|
|
5283
5526
|
};
|
|
5284
5527
|
}
|
|
5285
|
-
var import_node_crypto8,
|
|
5528
|
+
var import_node_crypto8, import_node_fs8, import_node_path7, ok3, err4, SYNC_TOOL;
|
|
5286
5529
|
var init_sync_tool = __esm({
|
|
5287
5530
|
"src/sync-tool.ts"() {
|
|
5288
5531
|
"use strict";
|
|
5289
5532
|
import_node_crypto8 = __toESM(require("crypto"));
|
|
5290
|
-
|
|
5291
|
-
|
|
5533
|
+
import_node_fs8 = __toESM(require("fs"));
|
|
5534
|
+
import_node_path7 = __toESM(require("path"));
|
|
5292
5535
|
init_codegraph();
|
|
5293
5536
|
init_error_detail();
|
|
5294
5537
|
ok3 = (data) => ({
|
|
@@ -7978,10 +8221,10 @@ async function main() {
|
|
|
7978
8221
|
onerror: (error2) => console.error("MindGraph MCP transport error:", error2.message)
|
|
7979
8222
|
});
|
|
7980
8223
|
console.error(
|
|
7981
|
-
`MindGraph MCP server v${"0.
|
|
8224
|
+
`MindGraph MCP server v${"0.26.0"} running on stdio (${BASE_URL})`
|
|
7982
8225
|
);
|
|
7983
8226
|
}
|
|
7984
|
-
var import_stdio,
|
|
8227
|
+
var import_stdio, import_node_path8, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, GOVERNANCE_CONFIG, MEMORY_RUNTIME_CONFIG, buildServer;
|
|
7985
8228
|
var init_src = __esm({
|
|
7986
8229
|
"src/index.ts"() {
|
|
7987
8230
|
"use strict";
|
|
@@ -7990,7 +8233,8 @@ var init_src = __esm({
|
|
|
7990
8233
|
init_dist();
|
|
7991
8234
|
init_hook_env();
|
|
7992
8235
|
init_server();
|
|
7993
|
-
|
|
8236
|
+
init_workspace_scope();
|
|
8237
|
+
import_node_path8 = __toESM(require("path"));
|
|
7994
8238
|
init_hook_installer();
|
|
7995
8239
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
7996
8240
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
@@ -8091,7 +8335,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
8091
8335
|
}
|
|
8092
8336
|
RUNNER_REFRESH = (() => {
|
|
8093
8337
|
try {
|
|
8094
|
-
return refreshHookRunner("0.
|
|
8338
|
+
return refreshHookRunner("0.26.0", import_node_path8.default.join(__dirname, "cli.js"));
|
|
8095
8339
|
} catch {
|
|
8096
8340
|
return void 0;
|
|
8097
8341
|
}
|
|
@@ -8105,12 +8349,12 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
8105
8349
|
}
|
|
8106
8350
|
})();
|
|
8107
8351
|
console.error(
|
|
8108
|
-
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.
|
|
8352
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.26.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
8109
8353
|
);
|
|
8110
8354
|
}
|
|
8111
8355
|
HOOK_SKEW_NOTE = (() => {
|
|
8112
8356
|
try {
|
|
8113
|
-
return versionSkewNote("0.
|
|
8357
|
+
return versionSkewNote("0.26.0");
|
|
8114
8358
|
} catch {
|
|
8115
8359
|
return void 0;
|
|
8116
8360
|
}
|
|
@@ -8131,16 +8375,21 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
8131
8375
|
}
|
|
8132
8376
|
};
|
|
8133
8377
|
MEMORY_RUNTIME_CONFIG = {
|
|
8134
|
-
serverVersion: "0.
|
|
8378
|
+
serverVersion: "0.26.0",
|
|
8135
8379
|
baseUrl: BASE_URL,
|
|
8136
8380
|
profile: PROFILE,
|
|
8137
8381
|
harness: HARNESS,
|
|
8138
8382
|
orgId: ORG_ID,
|
|
8139
|
-
agentId: AGENT_ID
|
|
8383
|
+
agentId: AGENT_ID,
|
|
8384
|
+
resolveWorkspaceScope: (agentId) => resolveRepositoryScope(
|
|
8385
|
+
process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
8386
|
+
client,
|
|
8387
|
+
agentId
|
|
8388
|
+
)
|
|
8140
8389
|
};
|
|
8141
8390
|
buildServer = () => buildMindGraphServer({
|
|
8142
8391
|
client,
|
|
8143
|
-
serverVersion: "0.
|
|
8392
|
+
serverVersion: "0.26.0",
|
|
8144
8393
|
instructions: INSTRUCTIONS,
|
|
8145
8394
|
profile: PROFILE,
|
|
8146
8395
|
agentId: AGENT_ID,
|
|
@@ -8156,8 +8405,8 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
8156
8405
|
});
|
|
8157
8406
|
|
|
8158
8407
|
// src/cli.ts
|
|
8159
|
-
var
|
|
8160
|
-
var
|
|
8408
|
+
var fs9 = __toESM(require("fs"));
|
|
8409
|
+
var path9 = __toESM(require("path"));
|
|
8161
8410
|
var os5 = __toESM(require("os"));
|
|
8162
8411
|
var crypto6 = __toESM(require("crypto"));
|
|
8163
8412
|
var import_child_process = require("child_process");
|
|
@@ -8642,18 +8891,19 @@ async function skillsPush(api, skillPath) {
|
|
|
8642
8891
|
|
|
8643
8892
|
// src/hook-core.ts
|
|
8644
8893
|
var import_node_crypto5 = __toESM(require("crypto"));
|
|
8645
|
-
var
|
|
8894
|
+
var import_node_fs6 = __toESM(require("fs"));
|
|
8646
8895
|
var import_node_os4 = __toESM(require("os"));
|
|
8647
|
-
var
|
|
8896
|
+
var import_node_path5 = __toESM(require("path"));
|
|
8648
8897
|
var import_node_child_process2 = require("child_process");
|
|
8649
8898
|
init_codegraph();
|
|
8650
|
-
|
|
8899
|
+
init_render();
|
|
8900
|
+
init_workspace_scope();
|
|
8651
8901
|
init_call_ledger();
|
|
8652
8902
|
init_error_detail();
|
|
8653
8903
|
init_hook_env();
|
|
8654
8904
|
|
|
8655
8905
|
// src/hook-memory.ts
|
|
8656
|
-
var
|
|
8906
|
+
var import_node_fs5 = __toESM(require("fs"));
|
|
8657
8907
|
var PROMPT_RECALL_LIMIT = 5;
|
|
8658
8908
|
var PROMPT_RECALL_MAX_CHARS = 4e3;
|
|
8659
8909
|
var MIN_PROMPT_RECALL_CHARS = 12;
|
|
@@ -8723,18 +8973,18 @@ function recallTermOverlap(query, text2) {
|
|
|
8723
8973
|
function relevant(query, text2) {
|
|
8724
8974
|
if (query === void 0) return true;
|
|
8725
8975
|
const { matched, usable } = recallTermOverlap(query, text2);
|
|
8726
|
-
return usable
|
|
8976
|
+
return usable >= 2 && matched >= 2;
|
|
8727
8977
|
}
|
|
8728
8978
|
var rec = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8729
|
-
var
|
|
8979
|
+
var str2 = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
8730
8980
|
var oneLine = (value, max) => {
|
|
8731
8981
|
const flat = value.replace(/\s+/g, " ").trim();
|
|
8732
8982
|
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
8733
8983
|
};
|
|
8734
8984
|
var INFRA_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article", "Session"]);
|
|
8735
8985
|
function nodeType(node) {
|
|
8736
|
-
const plain =
|
|
8737
|
-
return (plain && plain !== "Custom" ? plain : void 0) ??
|
|
8986
|
+
const plain = str2(node.node_type);
|
|
8987
|
+
return (plain && plain !== "Custom" ? plain : void 0) ?? str2(rec(node.node_type)?.Custom) ?? plain ?? "Node";
|
|
8738
8988
|
}
|
|
8739
8989
|
function renderPromptRecall(response, query) {
|
|
8740
8990
|
const payload = rec(response);
|
|
@@ -8747,16 +8997,16 @@ function renderPromptRecall(response, query) {
|
|
|
8747
8997
|
const type = nodeType(node);
|
|
8748
8998
|
if (INFRA_TYPES.has(type)) continue;
|
|
8749
8999
|
const props = rec(node.props);
|
|
8750
|
-
const label =
|
|
9000
|
+
const label = str2(node.label) ?? str2(props?.label) ?? str2(props?.name);
|
|
8751
9001
|
if (!label) continue;
|
|
8752
|
-
const summary =
|
|
9002
|
+
const summary = str2(node.summary) ?? str2(props?.summary) ?? str2(props?.description) ?? str2(props?.content);
|
|
8753
9003
|
const informative = summary && summary !== label ? summary : void 0;
|
|
8754
9004
|
if (!informative && type === "Entity") continue;
|
|
8755
9005
|
if (!relevant(query, `${label} ${informative ?? ""}`)) continue;
|
|
8756
9006
|
const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
|
|
8757
|
-
const source =
|
|
9007
|
+
const source = str2(rec(docs[0])?.title);
|
|
8758
9008
|
let line = `- [${type}] ${oneLine(label, 140)}`;
|
|
8759
|
-
if (node.superseded
|
|
9009
|
+
if (node.superseded) line += " (superseded)";
|
|
8760
9010
|
if (summary && summary !== label) line += ` \u2014 ${oneLine(summary, 240)}`;
|
|
8761
9011
|
if (source) line += ` (source: ${oneLine(source, 100)})`;
|
|
8762
9012
|
lines.push(line);
|
|
@@ -8765,9 +9015,9 @@ function renderPromptRecall(response, query) {
|
|
|
8765
9015
|
for (const raw of articles) {
|
|
8766
9016
|
const article = rec(raw);
|
|
8767
9017
|
if (!article || lines.length >= PROMPT_RECALL_LIMIT) continue;
|
|
8768
|
-
const label =
|
|
9018
|
+
const label = str2(article.label);
|
|
8769
9019
|
if (!label) continue;
|
|
8770
|
-
const content =
|
|
9020
|
+
const content = str2(article.content);
|
|
8771
9021
|
if (!relevant(query, `${label} ${content ?? ""}`)) continue;
|
|
8772
9022
|
lines.push(`- [Article] ${oneLine(label, 140)}${content ? ` \u2014 ${oneLine(content, 240)}` : ""}`);
|
|
8773
9023
|
}
|
|
@@ -8818,7 +9068,7 @@ function renderTranscript(turns) {
|
|
|
8818
9068
|
return start >= 0 ? tail.slice(start + 2) : tail;
|
|
8819
9069
|
}
|
|
8820
9070
|
function readTranscriptSince(file, offset = 0) {
|
|
8821
|
-
const buffer =
|
|
9071
|
+
const buffer = import_node_fs5.default.readFileSync(file);
|
|
8822
9072
|
const start = offset > buffer.length ? 0 : offset;
|
|
8823
9073
|
const lastNewline = buffer.lastIndexOf(10);
|
|
8824
9074
|
if (lastNewline < start) return { text: "", nextOffset: start };
|
|
@@ -8862,33 +9112,33 @@ function defaultLedger(sessionId, options) {
|
|
|
8862
9112
|
};
|
|
8863
9113
|
}
|
|
8864
9114
|
function runtimeDir(options) {
|
|
8865
|
-
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
9115
|
+
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path5.default.join(import_node_os4.default.homedir(), ".mindgraph", "runtime");
|
|
8866
9116
|
}
|
|
8867
9117
|
function ledgerPath(sessionId, options) {
|
|
8868
9118
|
const harness = "harness" in options ? options.harness : "claude-code";
|
|
8869
9119
|
const key = import_node_crypto5.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
8870
|
-
return
|
|
9120
|
+
return import_node_path5.default.join(runtimeDir(options), `${key}.json`);
|
|
8871
9121
|
}
|
|
8872
9122
|
function waitBriefly(ms) {
|
|
8873
9123
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
8874
9124
|
}
|
|
8875
9125
|
function withLedger(sessionId, options, update) {
|
|
8876
9126
|
const file = ledgerPath(sessionId, options);
|
|
8877
|
-
|
|
9127
|
+
import_node_fs6.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true, mode: 448 });
|
|
8878
9128
|
try {
|
|
8879
|
-
|
|
9129
|
+
import_node_fs6.default.chmodSync(import_node_path5.default.dirname(file), 448);
|
|
8880
9130
|
} catch {
|
|
8881
9131
|
}
|
|
8882
9132
|
const lock = `${file}.lock`;
|
|
8883
9133
|
let lockFd;
|
|
8884
9134
|
for (let attempt = 0; attempt < 400; attempt++) {
|
|
8885
9135
|
try {
|
|
8886
|
-
lockFd =
|
|
9136
|
+
lockFd = import_node_fs6.default.openSync(lock, "wx", 384);
|
|
8887
9137
|
break;
|
|
8888
9138
|
} catch (error2) {
|
|
8889
9139
|
if (error2.code !== "EEXIST") throw error2;
|
|
8890
9140
|
try {
|
|
8891
|
-
if (Date.now() -
|
|
9141
|
+
if (Date.now() - import_node_fs6.default.statSync(lock).mtimeMs > 3e3) import_node_fs6.default.unlinkSync(lock);
|
|
8892
9142
|
} catch {
|
|
8893
9143
|
}
|
|
8894
9144
|
waitBriefly(10);
|
|
@@ -8902,25 +9152,25 @@ function withLedger(sessionId, options, update) {
|
|
|
8902
9152
|
try {
|
|
8903
9153
|
ledger = {
|
|
8904
9154
|
...ledger,
|
|
8905
|
-
...JSON.parse(
|
|
9155
|
+
...JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"))
|
|
8906
9156
|
};
|
|
8907
9157
|
} catch {
|
|
8908
9158
|
}
|
|
8909
9159
|
const result = update(ledger);
|
|
8910
9160
|
ledger.updatedAt = Date.now();
|
|
8911
9161
|
const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
|
|
8912
|
-
|
|
9162
|
+
import_node_fs6.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
8913
9163
|
`, { mode: 384 });
|
|
8914
|
-
|
|
9164
|
+
import_node_fs6.default.renameSync(temporary, file);
|
|
8915
9165
|
try {
|
|
8916
|
-
|
|
9166
|
+
import_node_fs6.default.chmodSync(file, 384);
|
|
8917
9167
|
} catch {
|
|
8918
9168
|
}
|
|
8919
9169
|
return result;
|
|
8920
9170
|
} finally {
|
|
8921
|
-
|
|
9171
|
+
import_node_fs6.default.closeSync(lockFd);
|
|
8922
9172
|
try {
|
|
8923
|
-
|
|
9173
|
+
import_node_fs6.default.unlinkSync(lock);
|
|
8924
9174
|
} catch {
|
|
8925
9175
|
}
|
|
8926
9176
|
}
|
|
@@ -9030,6 +9280,11 @@ function renderBrief(brief, meta) {
|
|
|
9030
9280
|
);
|
|
9031
9281
|
const description = clipTracked(props?.description, 400);
|
|
9032
9282
|
if (description) lines.push(` ${description}`);
|
|
9283
|
+
const taskScopes = Array.isArray(brief?.task_scope_uids) ? brief.task_scope_uids.map(String) : [];
|
|
9284
|
+
const taskRepository = taskScopes.map((uid) => meta.repositoryNames?.[uid]).find(Boolean);
|
|
9285
|
+
if (taskRepository && Object.keys(meta.repositoryNames ?? {}).length > 1) {
|
|
9286
|
+
lines.push(`Repository: ${taskRepository}`);
|
|
9287
|
+
}
|
|
9033
9288
|
const reason = string(brief?.selection_reason);
|
|
9034
9289
|
if (reason) lines.push(`Selection: ${reason}`);
|
|
9035
9290
|
const lease = object(brief?.claim) || object(brief?.lease);
|
|
@@ -9075,6 +9330,8 @@ ${blockerLines.join("\n")}`);
|
|
|
9075
9330
|
}
|
|
9076
9331
|
}
|
|
9077
9332
|
const optional = [];
|
|
9333
|
+
const otherWork = renderOtherEligibleWork(brief, meta.repositoryNames);
|
|
9334
|
+
if (otherWork) optional.push(otherWork);
|
|
9078
9335
|
const knowledge = Array.isArray(brief?.knowledge) ? brief.knowledge : [];
|
|
9079
9336
|
if (knowledge.length > 5) bounded = true;
|
|
9080
9337
|
const knowledgeLines = knowledge.slice(0, 5).map((item) => nodeLine(item, 350)).filter(Boolean);
|
|
@@ -9111,62 +9368,6 @@ ${knowledgeLines.join("\n")}`);
|
|
|
9111
9368
|
return rendered2.length <= BRIEF_CHAR_BUDGET ? rendered2 : `${rendered2.slice(0, BRIEF_CHAR_BUDGET - 40)}
|
|
9112
9369
|
\u2026[bounded work brief truncated]`;
|
|
9113
9370
|
}
|
|
9114
|
-
async function repositoryScopeUids(input, client2, options, agentId) {
|
|
9115
|
-
if (!client2.entity) return { uids: [], failures: [] };
|
|
9116
|
-
const adapter = new CodegraphAdapter({
|
|
9117
|
-
cwd: input.cwd,
|
|
9118
|
-
workspaceFile: options.workspaceFile
|
|
9119
|
-
});
|
|
9120
|
-
const configured = await adapter.resolveRepositories();
|
|
9121
|
-
const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
|
|
9122
|
-
const failures = [];
|
|
9123
|
-
const resolved = await Promise.all(
|
|
9124
|
-
repositories.map(async (repository) => {
|
|
9125
|
-
try {
|
|
9126
|
-
const payload = object(
|
|
9127
|
-
await client2.entity({
|
|
9128
|
-
action: "resolve_identity",
|
|
9129
|
-
identity: {
|
|
9130
|
-
namespace: "external.code",
|
|
9131
|
-
key_version: 1,
|
|
9132
|
-
key: repositoryIdentityKey(repository)
|
|
9133
|
-
},
|
|
9134
|
-
agent_id: agentId
|
|
9135
|
-
})
|
|
9136
|
-
);
|
|
9137
|
-
if (payload?.status === "existing" && string(payload.uid)) {
|
|
9138
|
-
return string(payload.uid);
|
|
9139
|
-
}
|
|
9140
|
-
if (payload?.status === "absent") {
|
|
9141
|
-
const anchored = await anchorRepository(
|
|
9142
|
-
client2,
|
|
9143
|
-
repository,
|
|
9144
|
-
repository.spaceUid ?? `space:agent:${agentId}`,
|
|
9145
|
-
agentId
|
|
9146
|
-
);
|
|
9147
|
-
if (anchored?.uid) return anchored.uid;
|
|
9148
|
-
failures.push(
|
|
9149
|
-
`${repository.repoId}: anchor ${anchored?.status ?? "failed"}`
|
|
9150
|
-
);
|
|
9151
|
-
return void 0;
|
|
9152
|
-
}
|
|
9153
|
-
failures.push(
|
|
9154
|
-
`${repository.repoId}: ${string(payload?.status) || "unresolved"}`
|
|
9155
|
-
);
|
|
9156
|
-
return void 0;
|
|
9157
|
-
} catch (error2) {
|
|
9158
|
-
failures.push(
|
|
9159
|
-
`${repository.repoId}: ${error2?.message || "resolve failed"}`
|
|
9160
|
-
);
|
|
9161
|
-
return void 0;
|
|
9162
|
-
}
|
|
9163
|
-
})
|
|
9164
|
-
);
|
|
9165
|
-
return {
|
|
9166
|
-
uids: [...new Set(resolved.filter((uid) => Boolean(uid)))],
|
|
9167
|
-
failures
|
|
9168
|
-
};
|
|
9169
|
-
}
|
|
9170
9371
|
function isOwnPriorWork(brief, agentId, harness) {
|
|
9171
9372
|
const reason = string(brief?.selection_reason);
|
|
9172
9373
|
if (reason === "owned_live_lease" || reason === "same_agent_session_rebind") {
|
|
@@ -9190,30 +9391,30 @@ function hasMindgraphServer(servers) {
|
|
|
9190
9391
|
return false;
|
|
9191
9392
|
}
|
|
9192
9393
|
function pathIsWithin(ancestor, descendant) {
|
|
9193
|
-
const base =
|
|
9194
|
-
const target =
|
|
9195
|
-
return target === base || target.startsWith(base +
|
|
9394
|
+
const base = import_node_path5.default.resolve(ancestor);
|
|
9395
|
+
const target = import_node_path5.default.resolve(descendant);
|
|
9396
|
+
return target === base || target.startsWith(base + import_node_path5.default.sep);
|
|
9196
9397
|
}
|
|
9197
9398
|
function claudeMcpRegistrationNote(input, options) {
|
|
9198
9399
|
if (options.harness !== "claude-code") return void 0;
|
|
9199
9400
|
if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
|
|
9200
9401
|
try {
|
|
9201
|
-
const configPath = options.claudeConfigPath ||
|
|
9202
|
-
if (!
|
|
9203
|
-
const config = JSON.parse(
|
|
9402
|
+
const configPath = options.claudeConfigPath || import_node_path5.default.join(import_node_os4.default.homedir(), ".claude.json");
|
|
9403
|
+
if (!import_node_fs6.default.existsSync(configPath)) return void 0;
|
|
9404
|
+
const config = JSON.parse(import_node_fs6.default.readFileSync(configPath, "utf8"));
|
|
9204
9405
|
if (hasMindgraphServer(config.mcpServers)) return void 0;
|
|
9205
|
-
const cwd =
|
|
9406
|
+
const cwd = import_node_path5.default.resolve(input.cwd || ".");
|
|
9206
9407
|
for (const [projectDir, project] of Object.entries(config.projects || {})) {
|
|
9207
9408
|
if (!pathIsWithin(projectDir, cwd)) continue;
|
|
9208
9409
|
if (hasMindgraphServer(project?.mcpServers)) return void 0;
|
|
9209
9410
|
}
|
|
9210
|
-
for (let dir = cwd; ; dir =
|
|
9211
|
-
const candidate =
|
|
9212
|
-
if (
|
|
9213
|
-
const parsed = JSON.parse(
|
|
9411
|
+
for (let dir = cwd; ; dir = import_node_path5.default.dirname(dir)) {
|
|
9412
|
+
const candidate = import_node_path5.default.join(dir, ".mcp.json");
|
|
9413
|
+
if (import_node_fs6.default.existsSync(candidate)) {
|
|
9414
|
+
const parsed = JSON.parse(import_node_fs6.default.readFileSync(candidate, "utf8"));
|
|
9214
9415
|
if (hasMindgraphServer(parsed.mcpServers)) return void 0;
|
|
9215
9416
|
}
|
|
9216
|
-
if (dir ===
|
|
9417
|
+
if (dir === import_node_path5.default.dirname(dir)) break;
|
|
9217
9418
|
}
|
|
9218
9419
|
} catch {
|
|
9219
9420
|
return void 0;
|
|
@@ -9243,7 +9444,10 @@ async function sessionStart(input, client2, options) {
|
|
|
9243
9444
|
return void 0;
|
|
9244
9445
|
});
|
|
9245
9446
|
}
|
|
9246
|
-
const scope = await
|
|
9447
|
+
const scope = await resolveRepositoryScope(input.cwd, client2, agentId, {
|
|
9448
|
+
workspaceFile: options.workspaceFile,
|
|
9449
|
+
cache: options.scopeCacheTenant ? { dir: runtimeDir(options), tenant: options.scopeCacheTenant } : void 0
|
|
9450
|
+
});
|
|
9247
9451
|
if (scope.failures.length > 0) {
|
|
9248
9452
|
console.error(
|
|
9249
9453
|
`mindgraph-hook: ${scope.failures.length} repository scope(s) failed to resolve (${scope.failures.join("; ")}); resume runs ${scope.uids.length > 0 ? "narrowed" : "UNSCOPED"}`
|
|
@@ -9255,9 +9459,17 @@ async function sessionStart(input, client2, options) {
|
|
|
9255
9459
|
action: "resume_work",
|
|
9256
9460
|
session_uid: sessionUid,
|
|
9257
9461
|
...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
|
|
9462
|
+
// Above several repositories one task is not the picture: ask for the
|
|
9463
|
+
// work ranked behind it too. An older server ignores the field.
|
|
9464
|
+
...scopeUids.length > 1 ? { limit: WORKSPACE_RESUME_LIMIT } : {},
|
|
9258
9465
|
agent_id: agentId
|
|
9259
9466
|
})
|
|
9260
9467
|
);
|
|
9468
|
+
const workspaceList = {
|
|
9469
|
+
task_scope_uids: brief?.task_scope_uids,
|
|
9470
|
+
other_eligible: brief?.other_eligible,
|
|
9471
|
+
eligible_total: brief?.eligible_total
|
|
9472
|
+
};
|
|
9261
9473
|
const task = object(brief?.task);
|
|
9262
9474
|
const lease = object(brief?.lease);
|
|
9263
9475
|
const prior = readLedger(input.session_id, options);
|
|
@@ -9296,7 +9508,7 @@ async function sessionStart(input, client2, options) {
|
|
|
9296
9508
|
agent_id: agentId
|
|
9297
9509
|
})
|
|
9298
9510
|
);
|
|
9299
|
-
if (reResumed) brief = reResumed;
|
|
9511
|
+
if (reResumed) brief = { ...reResumed, ...workspaceList };
|
|
9300
9512
|
} catch {
|
|
9301
9513
|
}
|
|
9302
9514
|
if (brief) brief.claim = claimed;
|
|
@@ -9307,7 +9519,8 @@ async function sessionStart(input, client2, options) {
|
|
|
9307
9519
|
const briefText = renderBrief(brief || { status: "no_eligible_work" }, {
|
|
9308
9520
|
claimed: Boolean(claimed) || stillHeld,
|
|
9309
9521
|
claimFailed: claimFailed && !stillHeld,
|
|
9310
|
-
scopeNote
|
|
9522
|
+
scopeNote,
|
|
9523
|
+
repositoryNames: scope.names
|
|
9311
9524
|
});
|
|
9312
9525
|
const registrationNote = claudeMcpRegistrationNote(input, options);
|
|
9313
9526
|
if (registrationNote) {
|
|
@@ -9754,7 +9967,7 @@ function previewCapture(transcriptPath, sessionId, options = {}) {
|
|
|
9754
9967
|
let ledger = {};
|
|
9755
9968
|
try {
|
|
9756
9969
|
ledger = JSON.parse(
|
|
9757
|
-
|
|
9970
|
+
import_node_fs6.default.readFileSync(
|
|
9758
9971
|
ledgerPath(sessionId, { ...options, harness: "claude-code" }),
|
|
9759
9972
|
"utf8"
|
|
9760
9973
|
)
|
|
@@ -9775,13 +9988,13 @@ function readHookStats(sessionId, options = {}) {
|
|
|
9775
9988
|
let best;
|
|
9776
9989
|
let names = [];
|
|
9777
9990
|
try {
|
|
9778
|
-
names =
|
|
9991
|
+
names = import_node_fs6.default.readdirSync(dir).filter((name) => /^[0-9a-f]{64}\.json$/.test(name));
|
|
9779
9992
|
} catch {
|
|
9780
9993
|
return void 0;
|
|
9781
9994
|
}
|
|
9782
9995
|
for (const name of names) {
|
|
9783
9996
|
try {
|
|
9784
|
-
const ledger = JSON.parse(
|
|
9997
|
+
const ledger = JSON.parse(import_node_fs6.default.readFileSync(import_node_path5.default.join(dir, name), "utf8"));
|
|
9785
9998
|
if (!ledger.harnessSessionId || !ledger.hookStats) continue;
|
|
9786
9999
|
if (sessionId && ledger.harnessSessionId !== sessionId) continue;
|
|
9787
10000
|
const updatedAt = Number(ledger.updatedAt) || 0;
|
|
@@ -9860,15 +10073,15 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
9860
10073
|
init_hook_installer();
|
|
9861
10074
|
function recordHookHealth(harness, error2) {
|
|
9862
10075
|
try {
|
|
9863
|
-
const dir = process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
9864
|
-
|
|
9865
|
-
const file =
|
|
10076
|
+
const dir = process.env.MINDGRAPH_RUNTIME_DIR || path9.join(os5.homedir(), ".mindgraph", "runtime");
|
|
10077
|
+
fs9.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10078
|
+
const file = path9.join(dir, "hook-health.json");
|
|
9866
10079
|
let failures = 0;
|
|
9867
10080
|
try {
|
|
9868
|
-
failures = Number(JSON.parse(
|
|
10081
|
+
failures = Number(JSON.parse(fs9.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
9869
10082
|
} catch {
|
|
9870
10083
|
}
|
|
9871
|
-
|
|
10084
|
+
fs9.writeFileSync(
|
|
9872
10085
|
file,
|
|
9873
10086
|
`${JSON.stringify({
|
|
9874
10087
|
harness,
|
|
@@ -9884,14 +10097,14 @@ function recordHookHealth(harness, error2) {
|
|
|
9884
10097
|
}
|
|
9885
10098
|
function clearHookHealth(harness) {
|
|
9886
10099
|
try {
|
|
9887
|
-
const file =
|
|
9888
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
10100
|
+
const file = path9.join(
|
|
10101
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path9.join(os5.homedir(), ".mindgraph", "runtime"),
|
|
9889
10102
|
"hook-health.json"
|
|
9890
10103
|
);
|
|
9891
|
-
if (!
|
|
9892
|
-
const current = JSON.parse(
|
|
10104
|
+
if (!fs9.existsSync(file)) return;
|
|
10105
|
+
const current = JSON.parse(fs9.readFileSync(file, "utf8"));
|
|
9893
10106
|
if (!Number(current.consecutiveFailures)) return;
|
|
9894
|
-
|
|
10107
|
+
fs9.writeFileSync(
|
|
9895
10108
|
file,
|
|
9896
10109
|
`${JSON.stringify({
|
|
9897
10110
|
harness,
|
|
@@ -9907,7 +10120,7 @@ function clearHookHealth(harness) {
|
|
|
9907
10120
|
function getClaudeDesktopConfigPath() {
|
|
9908
10121
|
switch (process.platform) {
|
|
9909
10122
|
case "darwin":
|
|
9910
|
-
return
|
|
10123
|
+
return path9.join(
|
|
9911
10124
|
os5.homedir(),
|
|
9912
10125
|
"Library",
|
|
9913
10126
|
"Application Support",
|
|
@@ -9915,13 +10128,13 @@ function getClaudeDesktopConfigPath() {
|
|
|
9915
10128
|
"claude_desktop_config.json"
|
|
9916
10129
|
);
|
|
9917
10130
|
case "win32":
|
|
9918
|
-
return
|
|
9919
|
-
process.env.APPDATA ||
|
|
10131
|
+
return path9.join(
|
|
10132
|
+
process.env.APPDATA || path9.join(os5.homedir(), "AppData", "Roaming"),
|
|
9920
10133
|
"Claude",
|
|
9921
10134
|
"claude_desktop_config.json"
|
|
9922
10135
|
);
|
|
9923
10136
|
case "linux":
|
|
9924
|
-
return
|
|
10137
|
+
return path9.join(
|
|
9925
10138
|
os5.homedir(),
|
|
9926
10139
|
".config",
|
|
9927
10140
|
"Claude",
|
|
@@ -9932,16 +10145,16 @@ function getClaudeDesktopConfigPath() {
|
|
|
9932
10145
|
}
|
|
9933
10146
|
}
|
|
9934
10147
|
function readJsonFile(filePath) {
|
|
9935
|
-
if (!
|
|
9936
|
-
const content =
|
|
10148
|
+
if (!fs9.existsSync(filePath)) return {};
|
|
10149
|
+
const content = fs9.readFileSync(filePath, "utf-8");
|
|
9937
10150
|
return JSON.parse(content);
|
|
9938
10151
|
}
|
|
9939
10152
|
function writeJsonFile(filePath, data) {
|
|
9940
|
-
const dir =
|
|
9941
|
-
if (!
|
|
9942
|
-
|
|
10153
|
+
const dir = path9.dirname(filePath);
|
|
10154
|
+
if (!fs9.existsSync(dir)) {
|
|
10155
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
9943
10156
|
}
|
|
9944
|
-
|
|
10157
|
+
fs9.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
9945
10158
|
}
|
|
9946
10159
|
function getMcpServerEntry(apiKey, baseUrl) {
|
|
9947
10160
|
const env = { MINDGRAPH_API_KEY: apiKey };
|
|
@@ -10028,7 +10241,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
|
|
|
10028
10241
|
}
|
|
10029
10242
|
function uninstallClaudeDesktop() {
|
|
10030
10243
|
const configPath = getClaudeDesktopConfigPath();
|
|
10031
|
-
if (!
|
|
10244
|
+
if (!fs9.existsSync(configPath)) {
|
|
10032
10245
|
console.log("Claude Desktop config not found. Nothing to remove.");
|
|
10033
10246
|
return;
|
|
10034
10247
|
}
|
|
@@ -10055,7 +10268,7 @@ function uninstallClaudeCode() {
|
|
|
10055
10268
|
}
|
|
10056
10269
|
function printStatus() {
|
|
10057
10270
|
const desktopPath = getClaudeDesktopConfigPath();
|
|
10058
|
-
if (
|
|
10271
|
+
if (fs9.existsSync(desktopPath)) {
|
|
10059
10272
|
const config = readJsonFile(desktopPath);
|
|
10060
10273
|
const servers = config.mcpServers;
|
|
10061
10274
|
if (servers && "mindgraph" in servers) {
|
|
@@ -10073,12 +10286,12 @@ function printStatus() {
|
|
|
10073
10286
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
10074
10287
|
);
|
|
10075
10288
|
try {
|
|
10076
|
-
const healthFile =
|
|
10077
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
10289
|
+
const healthFile = path9.join(
|
|
10290
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path9.join(os5.homedir(), ".mindgraph", "runtime"),
|
|
10078
10291
|
"hook-health.json"
|
|
10079
10292
|
);
|
|
10080
|
-
if (
|
|
10081
|
-
const health = JSON.parse(
|
|
10293
|
+
if (fs9.existsSync(healthFile)) {
|
|
10294
|
+
const health = JSON.parse(fs9.readFileSync(healthFile, "utf8"));
|
|
10082
10295
|
const failures = Number(health.consecutiveFailures) || 0;
|
|
10083
10296
|
console.log(
|
|
10084
10297
|
failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
|
|
@@ -10107,21 +10320,21 @@ function printClaudeHookFeatures(features) {
|
|
|
10107
10320
|
);
|
|
10108
10321
|
}
|
|
10109
10322
|
function latestTranscript(cwd) {
|
|
10110
|
-
const dir =
|
|
10323
|
+
const dir = path9.join(
|
|
10111
10324
|
os5.homedir(),
|
|
10112
10325
|
".claude",
|
|
10113
10326
|
"projects",
|
|
10114
10327
|
cwd.replace(/[^A-Za-z0-9]/g, "-")
|
|
10115
10328
|
);
|
|
10116
10329
|
try {
|
|
10117
|
-
return
|
|
10330
|
+
return fs9.readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => path9.join(dir, name)).sort((a, b) => fs9.statSync(b).mtimeMs - fs9.statSync(a).mtimeMs)[0];
|
|
10118
10331
|
} catch {
|
|
10119
10332
|
return void 0;
|
|
10120
10333
|
}
|
|
10121
10334
|
}
|
|
10122
10335
|
function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
10123
10336
|
if (withHooks) {
|
|
10124
|
-
const runner = installHookRunner(__filename, void 0, "0.
|
|
10337
|
+
const runner = installHookRunner(__filename, void 0, "0.26.0");
|
|
10125
10338
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
10126
10339
|
const features = claudeHookFeatures();
|
|
10127
10340
|
const result = installClaudeHooks("user", void 0, features);
|
|
@@ -10449,7 +10662,7 @@ ${total} skill(s) synced.`);
|
|
|
10449
10662
|
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
10450
10663
|
throw new Error("--harness must be claude-code or codex");
|
|
10451
10664
|
}
|
|
10452
|
-
const runner = installHookRunner(__filename, void 0, "0.
|
|
10665
|
+
const runner = installHookRunner(__filename, void 0, "0.26.0");
|
|
10453
10666
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
10454
10667
|
const features = hookHarness === "claude-code" ? claudeHookFeatures() : void 0;
|
|
10455
10668
|
const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir, features);
|
|
@@ -10524,7 +10737,14 @@ ${total} skill(s) synced.`);
|
|
|
10524
10737
|
// No harness-name fallback here: hook-core derives a stable
|
|
10525
10738
|
// per-user default shared by both harnesses, which is what lets a
|
|
10526
10739
|
// task claimed under Codex rebind under Claude Code.
|
|
10527
|
-
agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId
|
|
10740
|
+
agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId,
|
|
10741
|
+
scopeCacheTenant: crypto6.createHash("sha256").update(
|
|
10742
|
+
[
|
|
10743
|
+
hookBaseUrl || "https://api.mindgraph.cloud",
|
|
10744
|
+
hookApiKey,
|
|
10745
|
+
process.env.MINDGRAPH_ORG_ID || stored.orgId || ""
|
|
10746
|
+
].join("\n")
|
|
10747
|
+
).digest("hex").slice(0, 16)
|
|
10528
10748
|
};
|
|
10529
10749
|
const output = hookHarness === "codex" ? await runCodexHook(
|
|
10530
10750
|
input,
|
|
@@ -10582,13 +10802,13 @@ ${total} skill(s) synced.`);
|
|
|
10582
10802
|
process.exit(1);
|
|
10583
10803
|
}
|
|
10584
10804
|
const transcript = positionals[1] || latestTranscript(process.cwd());
|
|
10585
|
-
if (!transcript || !
|
|
10805
|
+
if (!transcript || !fs9.existsSync(transcript)) {
|
|
10586
10806
|
console.error(
|
|
10587
10807
|
"No transcript found. Pass a Claude Code transcript path (~/.claude/projects/<dir>/<session>.jsonl)."
|
|
10588
10808
|
);
|
|
10589
10809
|
process.exit(1);
|
|
10590
10810
|
}
|
|
10591
|
-
const sessionId = parseArgs(process.argv).sessionId ||
|
|
10811
|
+
const sessionId = parseArgs(process.argv).sessionId || path9.basename(transcript, ".jsonl");
|
|
10592
10812
|
const preview = previewCapture(transcript, sessionId, {
|
|
10593
10813
|
agentId: process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId
|
|
10594
10814
|
});
|