mindgraph-mcp 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +57 -4
  2. package/dist/cli.js +879 -335
  3. package/dist/index.js +718 -119
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -65,8 +65,8 @@ var init_dist = __esm({
65
65
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
66
66
  }
67
67
  // ---- HTTP helpers ----
68
- async request(method, path6, body) {
69
- const url = `${this.baseUrl}${path6}`;
68
+ async request(method, path7, body) {
69
+ const url = `${this.baseUrl}${path7}`;
70
70
  const requestId = createRequestId();
71
71
  const init = {
72
72
  method,
@@ -91,7 +91,7 @@ var init_dist = __esm({
91
91
  parsed = text2;
92
92
  }
93
93
  const err5 = new MindGraphError(
94
- `${method} ${path6} failed: ${res.status}`,
94
+ `${method} ${path7} failed: ${res.status}`,
95
95
  res.status,
96
96
  parsed
97
97
  );
@@ -109,17 +109,17 @@ var init_dist = __esm({
109
109
  }
110
110
  throw lastError;
111
111
  }
112
- get(path6) {
113
- return this.request("GET", path6);
112
+ get(path7) {
113
+ return this.request("GET", path7);
114
114
  }
115
- post(path6, body) {
116
- return this.request("POST", path6, body);
115
+ post(path7, body) {
116
+ return this.request("POST", path7, body);
117
117
  }
118
- patch(path6, body) {
119
- return this.request("PATCH", path6, body);
118
+ patch(path7, body) {
119
+ return this.request("PATCH", path7, body);
120
120
  }
121
- del(path6) {
122
- return this.request("DELETE", path6);
121
+ del(path7) {
122
+ return this.request("DELETE", path7);
123
123
  }
124
124
  // ---- Health ----
125
125
  async health() {
@@ -971,6 +971,14 @@ var init_hook_env = __esm({
971
971
  });
972
972
 
973
973
  // src/codegraph.ts
974
+ function invocationCwd(args) {
975
+ const context = args.invocation_context;
976
+ if (context && typeof context === "object" && !Array.isArray(context)) {
977
+ const cwd = context.cwd;
978
+ if (typeof cwd === "string" && cwd) return cwd;
979
+ }
980
+ return process.cwd();
981
+ }
974
982
  function unavailabilityCaveats(reason, message) {
975
983
  const caveats = [message || "codegraph status failed"];
976
984
  if (reason === "absent") {
@@ -980,14 +988,14 @@ function unavailabilityCaveats(reason, message) {
980
988
  }
981
989
  return caveats;
982
990
  }
983
- function normalizePath(path6) {
984
- return path6.replaceAll("\\", "/").replace(/^\.\//, "");
991
+ function normalizePath(path7) {
992
+ return path7.replaceAll("\\", "/").replace(/^\.\//, "");
985
993
  }
986
994
  function normalizeSignature(signature) {
987
995
  return signature.replace(/\s+/g, " ").trim();
988
996
  }
989
- function languageFromPath(path6) {
990
- const extension = (0, import_node_path2.extname)(path6).toLowerCase();
997
+ function languageFromPath(path7) {
998
+ const extension = (0, import_node_path2.extname)(path7).toLowerCase();
991
999
  return {
992
1000
  ".c": "c",
993
1001
  ".cc": "cpp",
@@ -1150,7 +1158,7 @@ function codeRefIdentityKey(ref) {
1150
1158
  }
1151
1159
  };
1152
1160
  }
1153
- var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
1161
+ var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
1154
1162
  var init_codegraph = __esm({
1155
1163
  "src/codegraph.ts"() {
1156
1164
  "use strict";
@@ -1160,6 +1168,16 @@ var init_codegraph = __esm({
1160
1168
  import_node_os2 = require("os");
1161
1169
  import_node_path2 = require("path");
1162
1170
  import_node_crypto2 = require("crypto");
1171
+ UnknownRepositoryError = class extends Error {
1172
+ constructor(repo) {
1173
+ super(
1174
+ `unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
1175
+ );
1176
+ this.repo = repo;
1177
+ this.name = "UnknownRepositoryError";
1178
+ }
1179
+ repo;
1180
+ };
1163
1181
  DEFAULT_TIMEOUT_MS = 5e3;
1164
1182
  DEFAULT_STATUS_TTL_MS = 6e4;
1165
1183
  MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
@@ -1195,7 +1213,26 @@ var init_codegraph = __esm({
1195
1213
  selected.spaceUid
1196
1214
  ) : selected;
1197
1215
  }
1198
- const requestedRoot = repo || this.env.MINDGRAPH_REPO_ROOT || this.cwd;
1216
+ if (repo) {
1217
+ const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, repo)).catch(
1218
+ () => (0, import_node_path2.resolve)(this.cwd, repo)
1219
+ );
1220
+ if (!(0, import_node_fs2.existsSync)(requestedPath)) {
1221
+ const current = await this.currentRepository(fallbackSpaceUid);
1222
+ if (current.repoId === repo) return current;
1223
+ throw new UnknownRepositoryError(repo);
1224
+ }
1225
+ const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
1226
+ return this.resolveRepositoryIdentity(
1227
+ repoRoot,
1228
+ this.env.MINDGRAPH_REPO_ID,
1229
+ this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
1230
+ );
1231
+ }
1232
+ return this.currentRepository(fallbackSpaceUid);
1233
+ }
1234
+ async currentRepository(fallbackSpaceUid) {
1235
+ const requestedRoot = this.env.MINDGRAPH_REPO_ROOT || this.cwd;
1199
1236
  const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
1200
1237
  () => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
1201
1238
  );
@@ -1285,6 +1322,14 @@ var init_codegraph = __esm({
1285
1322
  ).catch(() => "");
1286
1323
  return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
1287
1324
  }
1325
+ /** Why the workspace map yielded no repositories — for callers that were
1326
+ * explicitly asked to fan out and would otherwise return silently empty. */
1327
+ workspaceDiagnostic() {
1328
+ if (!this.workspaceFile || !(0, import_node_fs2.existsSync)(this.workspaceFile)) {
1329
+ return "no .mindgraph/workspace.json was found between the working directory and the filesystem root";
1330
+ }
1331
+ return `${this.workspaceFile} is malformed or declares no repositories`;
1332
+ }
1288
1333
  async availability(repository) {
1289
1334
  const cached = this.statusCache.get(repository.repoRoot);
1290
1335
  if (cached && cached.expiresAt > this.now()) return cached.availability;
@@ -1698,13 +1743,13 @@ var init_error_detail = __esm({
1698
1743
  });
1699
1744
 
1700
1745
  // src/code-tool.ts
1701
- async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
1746
+ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
1702
1747
  const codeClient = client2;
1703
1748
  const action = args.action;
1704
1749
  const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
1705
1750
  const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
1706
1751
  const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
1707
- const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : `space:agent:${agentId}`;
1752
+ const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
1708
1753
  const options = {
1709
1754
  repo: typeof args.repo === "string" ? args.repo : void 0,
1710
1755
  fallbackSpaceUid
@@ -1828,6 +1873,7 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
1828
1873
  const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
1829
1874
  let graphContext;
1830
1875
  const graphRefs = [];
1876
+ let unavailableRepositories = [];
1831
1877
  if (targetUid) {
1832
1878
  graphContext = await codeClient.traverse({
1833
1879
  action: "neighborhood",
@@ -1835,9 +1881,13 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
1835
1881
  depth: 2,
1836
1882
  max_nodes: 50
1837
1883
  });
1838
- graphRefs.push(
1839
- ...await codeRefsFromGraphContext(codeClient, adapter, graphContext)
1884
+ const fromGraph = await codeRefsFromGraphContext(
1885
+ codeClient,
1886
+ adapter,
1887
+ graphContext
1840
1888
  );
1889
+ graphRefs.push(...fromGraph.refs);
1890
+ unavailableRepositories = fromGraph.unavailable;
1841
1891
  }
1842
1892
  if (refs.length > 0 || anchorUids.length > 0) {
1843
1893
  const target = await resolveSingleTarget(
@@ -1856,7 +1906,8 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
1856
1906
  target_uid: targetUid,
1857
1907
  graph_context: graphContext,
1858
1908
  affected: [],
1859
- degradation: "no materialized code anchors were attached"
1909
+ degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
1910
+ ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
1860
1911
  });
1861
1912
  }
1862
1913
  const impacts = await Promise.all(
@@ -1873,13 +1924,17 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
1873
1924
  target_uid: targetUid,
1874
1925
  graph_context: graphContext,
1875
1926
  impacts,
1876
- materialized: joined
1927
+ materialized: joined,
1928
+ ...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
1877
1929
  });
1878
1930
  }
1879
1931
  default:
1880
1932
  return err("unknown_action", `unknown mindgraph_code action: ${action}`);
1881
1933
  }
1882
1934
  } catch (cause) {
1935
+ if (cause instanceof UnknownRepositoryError) {
1936
+ return err("unknown_repository", cause.message);
1937
+ }
1883
1938
  return err("code_tool_failed", errorDetail(cause));
1884
1939
  }
1885
1940
  }
@@ -1905,7 +1960,10 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
1905
1960
  code_refs: args.code_refs,
1906
1961
  repo: args.repo,
1907
1962
  repo_space_uid: args.repo_space_uid,
1908
- agent_id: args.agent_id
1963
+ agent_id: args.agent_id,
1964
+ // Keeps the default adapter anchored to the live session directory
1965
+ // rather than wherever the MCP server process was launched.
1966
+ invocation_context: args.invocation_context
1909
1967
  },
1910
1968
  adapter
1911
1969
  );
@@ -1915,17 +1973,33 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
1915
1973
  const anchorUids = (anchors.anchors ?? []).flatMap(
1916
1974
  (anchor) => anchor.uid ? [anchor.uid] : []
1917
1975
  );
1976
+ const relateFailures = [];
1918
1977
  for (const targetUid of targetUids) {
1919
1978
  for (const anchorUid of anchorUids) {
1920
- await codeClient.entity({
1921
- action: "relate",
1922
- source_uid: targetUid,
1923
- target_uid: anchorUid,
1924
- edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
1925
- agent_id: args.agent_id
1926
- });
1979
+ try {
1980
+ await codeClient.entity({
1981
+ action: "relate",
1982
+ source_uid: targetUid,
1983
+ target_uid: anchorUid,
1984
+ edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
1985
+ agent_id: args.agent_id
1986
+ });
1987
+ } catch (cause) {
1988
+ relateFailures.push(
1989
+ `${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
1990
+ );
1991
+ }
1927
1992
  }
1928
1993
  }
1994
+ if (relateFailures.length > 0) {
1995
+ return ok({
1996
+ result: original,
1997
+ code_anchors: {
1998
+ ...anchors,
1999
+ caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
2000
+ }
2001
+ });
2002
+ }
1929
2003
  }
1930
2004
  return ok({ result: original, code_anchors: anchors });
1931
2005
  }
@@ -1998,16 +2072,34 @@ async function codeRefFromAnchor(client2, adapter, uid) {
1998
2072
  if (!attributes || typeof attributes !== "object") return void 0;
1999
2073
  const codeRef = attributes.code_ref;
2000
2074
  if (!isStoredCodeRef(codeRef)) return void 0;
2001
- const repository = await adapter.resolveRepository(codeRef.repoId);
2002
- return { ...codeRef, repoRoot: repository.repoRoot };
2075
+ try {
2076
+ const repository = await adapter.resolveRepository(codeRef.repoId);
2077
+ return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
2078
+ } catch (cause) {
2079
+ if (cause instanceof UnknownRepositoryError) {
2080
+ return { missingRepo: codeRef.repoId };
2081
+ }
2082
+ throw cause;
2083
+ }
2003
2084
  }
2004
2085
  async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
2005
2086
  if (refs.length > 1 || anchorUids.length > 1) {
2006
2087
  return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
2007
2088
  }
2008
2089
  if (anchorUids.length === 1) {
2009
- const ref = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
2010
- return ref ? { ref } : { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
2090
+ const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
2091
+ if (!outcome) {
2092
+ return { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
2093
+ }
2094
+ if ("missingRepo" in outcome) {
2095
+ return {
2096
+ error: err(
2097
+ "repository_unavailable",
2098
+ `repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
2099
+ )
2100
+ };
2101
+ }
2102
+ return { ref: outcome.ref };
2011
2103
  }
2012
2104
  if (refs.length !== 1) {
2013
2105
  return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
@@ -2046,7 +2138,7 @@ async function joinMaterialized(client2, refs, agentId, limit) {
2046
2138
  async function codeRefsFromGraphContext(client2, adapter, context) {
2047
2139
  const uids = /* @__PURE__ */ new Set();
2048
2140
  collectUids(context, uids);
2049
- if (uids.size === 0) return [];
2141
+ if (uids.size === 0) return { refs: [], unavailable: [] };
2050
2142
  const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
2051
2143
  const stored = nodes.flatMap((node) => {
2052
2144
  if (!node) return [];
@@ -2055,12 +2147,21 @@ async function codeRefsFromGraphContext(client2, adapter, context) {
2055
2147
  const codeRef = attributes.code_ref;
2056
2148
  return isStoredCodeRef(codeRef) ? [codeRef] : [];
2057
2149
  });
2058
- return Promise.all(
2059
- stored.map(async (ref) => {
2150
+ const refs = [];
2151
+ const unavailable = /* @__PURE__ */ new Set();
2152
+ for (const ref of stored) {
2153
+ try {
2060
2154
  const repository = await adapter.resolveRepository(ref.repoId);
2061
- return { ...ref, repoRoot: repository.repoRoot };
2062
- })
2063
- );
2155
+ refs.push({ ...ref, repoRoot: repository.repoRoot });
2156
+ } catch (cause) {
2157
+ if (cause instanceof UnknownRepositoryError) {
2158
+ unavailable.add(ref.repoId);
2159
+ } else {
2160
+ throw cause;
2161
+ }
2162
+ }
2163
+ }
2164
+ return { refs, unavailable: [...unavailable].sort() };
2064
2165
  }
2065
2166
  function collectUids(value, uids) {
2066
2167
  if (Array.isArray(value)) {
@@ -2123,20 +2224,20 @@ var init_code_tool = __esm({
2123
2224
  init_error_detail();
2124
2225
  init_codegraph();
2125
2226
  ok = (data) => ({
2126
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
2227
+ content: [{ type: "text", text: JSON.stringify(data) }]
2127
2228
  });
2128
2229
  err = (code, message, details) => ({
2129
2230
  content: [
2130
2231
  {
2131
2232
  type: "text",
2132
- text: JSON.stringify({ error: message, code, details }, null, 2)
2233
+ text: JSON.stringify({ error: message, code, details })
2133
2234
  }
2134
2235
  ],
2135
2236
  isError: true
2136
2237
  });
2137
2238
  CODE_TOOL = {
2138
2239
  name: "mindgraph_code",
2139
- 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.",
2240
+ 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.",
2140
2241
  inputSchema: {
2141
2242
  type: "object",
2142
2243
  properties: {
@@ -2178,7 +2279,7 @@ var init_code_tool = __esm({
2178
2279
  },
2179
2280
  repo_space_uid: {
2180
2281
  type: "string",
2181
- description: "Writable shared Project/repository Space for new anchors"
2282
+ description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
2182
2283
  },
2183
2284
  task_uid: { type: "string" },
2184
2285
  execution_uid: { type: "string" },
@@ -2197,6 +2298,276 @@ var init_code_tool = __esm({
2197
2298
  }
2198
2299
  });
2199
2300
 
2301
+ // src/hook-installer.ts
2302
+ function command(harness) {
2303
+ return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
2304
+ }
2305
+ function windowsCommand(harness) {
2306
+ return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
2307
+ }
2308
+ function installHookRunner(sourceFile, homeDir, version) {
2309
+ const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
2310
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
2311
+ const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
2312
+ import_node_fs4.default.copyFileSync(sourceFile, temporary);
2313
+ import_node_fs4.default.renameSync(temporary, target);
2314
+ const versionPath = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
2315
+ if (version) {
2316
+ import_node_fs4.default.writeFileSync(versionPath, `${version}
2317
+ `);
2318
+ } else {
2319
+ import_node_fs4.default.rmSync(versionPath, { force: true });
2320
+ }
2321
+ return target;
2322
+ }
2323
+ function installedHookRunnerVersion(homeDir) {
2324
+ try {
2325
+ const raw = import_node_fs4.default.readFileSync(
2326
+ import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE),
2327
+ "utf8"
2328
+ ).trim();
2329
+ return raw || void 0;
2330
+ } catch {
2331
+ return void 0;
2332
+ }
2333
+ }
2334
+ function parseSemver(value) {
2335
+ if (!value) return void 0;
2336
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
2337
+ if (!match) return void 0;
2338
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
2339
+ }
2340
+ function semverNewer(a, b) {
2341
+ for (let index = 0; index < 3; index += 1) {
2342
+ if (a[index] !== b[index]) return a[index] > b[index];
2343
+ }
2344
+ return false;
2345
+ }
2346
+ function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
2347
+ try {
2348
+ if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
2349
+ return { refreshed: false, reason: "disabled" };
2350
+ }
2351
+ const home = homeDir || import_node_os4.default.homedir();
2352
+ if (!import_node_fs4.default.existsSync(import_node_path4.default.join(home, RUNNER_RELATIVE))) {
2353
+ return { refreshed: false, reason: "no_runner" };
2354
+ }
2355
+ if (!import_node_fs4.default.existsSync(sourceBundle)) {
2356
+ return { refreshed: false, reason: "no_source" };
2357
+ }
2358
+ const server2 = parseSemver(serverVersion);
2359
+ if (!server2) return { refreshed: false, reason: "unparseable" };
2360
+ const runnerVersion = installedHookRunnerVersion(home);
2361
+ if (runnerVersion !== void 0) {
2362
+ const pinned = parseSemver(runnerVersion);
2363
+ if (!pinned) {
2364
+ return { refreshed: false, reason: "unparseable", runnerVersion };
2365
+ }
2366
+ if (!semverNewer(server2, pinned)) {
2367
+ return {
2368
+ refreshed: false,
2369
+ reason: runnerVersion === serverVersion ? "current" : "downgrade",
2370
+ runnerVersion
2371
+ };
2372
+ }
2373
+ }
2374
+ installHookRunner(sourceBundle, home, serverVersion);
2375
+ return { refreshed: true, reason: "updated", runnerVersion };
2376
+ } catch {
2377
+ return { refreshed: false, reason: "error" };
2378
+ }
2379
+ }
2380
+ function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
2381
+ const refreshed = [];
2382
+ const scopes = ["user", "project"];
2383
+ for (const scope of scopes) {
2384
+ try {
2385
+ const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
2386
+ if (!import_node_fs4.default.existsSync(file)) continue;
2387
+ if (!import_node_fs4.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
2388
+ installHooks("claude-code", scope, projectDir, void 0, homeDir);
2389
+ refreshed.push(file);
2390
+ } catch {
2391
+ }
2392
+ }
2393
+ return refreshed;
2394
+ }
2395
+ function versionSkewNote(serverVersion, homeDir) {
2396
+ const runnerVersion = installedHookRunnerVersion(homeDir);
2397
+ if (!runnerVersion || runnerVersion === serverVersion) return void 0;
2398
+ 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).`;
2399
+ }
2400
+ function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex"), homeDir) {
2401
+ if (harness === "codex") {
2402
+ return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
2403
+ }
2404
+ return scope === "user" ? import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
2405
+ }
2406
+ function readSettings(file) {
2407
+ if (!import_node_fs4.default.existsSync(file)) return {};
2408
+ return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
2409
+ }
2410
+ function ownedHook(harness, timeout, additionalContextLimit) {
2411
+ return {
2412
+ type: "command",
2413
+ command: command(harness),
2414
+ ...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
2415
+ timeout,
2416
+ statusMessage: "Syncing MindGraph session context",
2417
+ ...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
2418
+ };
2419
+ }
2420
+ function desiredEntries(harness) {
2421
+ const common = {
2422
+ SessionStart: [
2423
+ {
2424
+ matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
2425
+ // B7 live acceptance caught cold SessionStart calls crossing the
2426
+ // inherited 20s edge in both harnesses: the hooks correctly failed
2427
+ // open, but no brief reached the model. The same installed runner
2428
+ // completed in 16.84s once warm. Keep a 30s cold-tenant margin.
2429
+ // The declared Codex context limit must not undercut the brief's own
2430
+ // 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
2431
+ // truncation the renderer knew nothing about.
2432
+ hooks: [
2433
+ ownedHook(
2434
+ harness,
2435
+ 30,
2436
+ harness === "codex" ? 1e4 : void 0
2437
+ )
2438
+ ]
2439
+ }
2440
+ ],
2441
+ PreToolUse: [
2442
+ {
2443
+ matcher: "mcp__mindgraph__.*",
2444
+ // Repository resolution over a large multi-repo workspace plus three
2445
+ // git probes can exceed 3s on the first (uncached) call; a killed
2446
+ // PreToolUse means the call goes through untagged.
2447
+ hooks: [ownedHook(harness, 5)]
2448
+ }
2449
+ ],
2450
+ // PostToolUse awaits the lease renewal round-trip, which fires exactly
2451
+ // when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
2452
+ PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
2453
+ Stop: [{ hooks: [ownedHook(harness, 5)] }],
2454
+ // SessionEnd makes up to two sequential cloud calls (abandon_iteration +
2455
+ // session close) — the SAME cold-tenant latency SessionStart needs 30s
2456
+ // for. Claude honors the configured budget; Codex clamps SessionEnd to a
2457
+ // 3s platform cap regardless of this value (see codex-hooks.ts wire
2458
+ // map), so the codex adapter instead makes at most ONE call there.
2459
+ SessionEnd: [
2460
+ { hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
2461
+ ]
2462
+ };
2463
+ return harness === "claude-code" ? {
2464
+ ...common,
2465
+ TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
2466
+ TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
2467
+ } : common;
2468
+ }
2469
+ function hookCommand(value) {
2470
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2471
+ const command2 = value.command;
2472
+ return typeof command2 === "string" ? command2 : void 0;
2473
+ }
2474
+ function entryHasOwnedHook(value) {
2475
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2476
+ const hooks = value.hooks;
2477
+ return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
2478
+ }
2479
+ function writeSettings(file, settings) {
2480
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
2481
+ const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
2482
+ import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
2483
+ `, {
2484
+ mode: 384
2485
+ });
2486
+ import_node_fs4.default.renameSync(temporary, file);
2487
+ }
2488
+ function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir) {
2489
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
2490
+ const settings = readSettings(file);
2491
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
2492
+ let added = 0;
2493
+ let updated = 0;
2494
+ for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
2495
+ const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
2496
+ const kept = current.filter((entry) => !entryHasOwnedHook(entry));
2497
+ const hadOwned = kept.length !== current.length;
2498
+ for (const entry of wanted) {
2499
+ kept.push(entry);
2500
+ if (hadOwned) updated += 1;
2501
+ else added += 1;
2502
+ }
2503
+ hooks[event] = kept;
2504
+ }
2505
+ settings.hooks = hooks;
2506
+ writeSettings(file, settings);
2507
+ return { path: file, added, updated };
2508
+ }
2509
+ function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
2510
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
2511
+ if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
2512
+ const settings = readSettings(file);
2513
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
2514
+ let removed = 0;
2515
+ for (const [event, value] of Object.entries(hooks)) {
2516
+ if (!Array.isArray(value)) continue;
2517
+ const retainedEntries = [];
2518
+ for (const rawEntry of value) {
2519
+ if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
2520
+ retainedEntries.push(rawEntry);
2521
+ continue;
2522
+ }
2523
+ const entry = { ...rawEntry };
2524
+ if (!Array.isArray(entry.hooks)) {
2525
+ retainedEntries.push(entry);
2526
+ continue;
2527
+ }
2528
+ const retainedHooks = entry.hooks.filter((hook) => {
2529
+ const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
2530
+ if (owned) removed += 1;
2531
+ return !owned;
2532
+ });
2533
+ if (retainedHooks.length > 0) {
2534
+ entry.hooks = retainedHooks;
2535
+ retainedEntries.push(entry);
2536
+ }
2537
+ }
2538
+ if (retainedEntries.length > 0) hooks[event] = retainedEntries;
2539
+ else delete hooks[event];
2540
+ }
2541
+ settings.hooks = hooks;
2542
+ writeSettings(file, settings);
2543
+ return { path: file, removed };
2544
+ }
2545
+ function installClaudeHooks(scope, projectDir = process.cwd()) {
2546
+ return installHooks("claude-code", scope, projectDir);
2547
+ }
2548
+ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
2549
+ return uninstallHooks("claude-code", scope, projectDir);
2550
+ }
2551
+ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2552
+ return installHooks("codex", scope, projectDir, codexHomeDir);
2553
+ }
2554
+ function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2555
+ return uninstallHooks("codex", scope, projectDir, codexHomeDir);
2556
+ }
2557
+ var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto4, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
2558
+ var init_hook_installer = __esm({
2559
+ "src/hook-installer.ts"() {
2560
+ "use strict";
2561
+ import_node_fs4 = __toESM(require("fs"));
2562
+ import_node_os4 = __toESM(require("os"));
2563
+ import_node_path4 = __toESM(require("path"));
2564
+ import_node_crypto4 = __toESM(require("crypto"));
2565
+ OWNER_MARKER = "--owner mindgraph";
2566
+ RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
2567
+ RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
2568
+ }
2569
+ });
2570
+
2200
2571
  // src/sync-tool.ts
2201
2572
  function safePath(root, candidate) {
2202
2573
  const absolute = import_node_path5.default.resolve(root, candidate);
@@ -2204,14 +2575,29 @@ function safePath(root, candidate) {
2204
2575
  if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
2205
2576
  throw new Error(`path escapes repository root: ${candidate}`);
2206
2577
  }
2578
+ if (import_node_fs5.default.existsSync(absolute)) {
2579
+ if (!pathContains(realPathOf(root), realPathOf(absolute))) {
2580
+ throw new Error(`path escapes repository root via symlink: ${candidate}`);
2581
+ }
2582
+ }
2207
2583
  return absolute;
2208
2584
  }
2209
- function markdownFiles(target) {
2585
+ function realPathOf(target) {
2586
+ try {
2587
+ return import_node_fs5.default.realpathSync(target);
2588
+ } catch {
2589
+ return import_node_path5.default.resolve(target);
2590
+ }
2591
+ }
2592
+ function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
2210
2593
  if (!import_node_fs5.default.existsSync(target)) return [];
2594
+ const real = realPathOf(target);
2595
+ if (!pathContains(realRoot, real)) return [];
2211
2596
  const stat = import_node_fs5.default.statSync(target);
2212
2597
  if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
2213
- if (!stat.isDirectory()) return [];
2214
- return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name)));
2598
+ if (!stat.isDirectory() || visited.has(real)) return [];
2599
+ visited.add(real);
2600
+ return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name), realRoot, visited));
2215
2601
  }
2216
2602
  function fileDescriptor(root, absolute) {
2217
2603
  const content = import_node_fs5.default.readFileSync(absolute, "utf8");
@@ -2226,9 +2612,7 @@ async function handleSyncTool(client2, args) {
2226
2612
  try {
2227
2613
  const action = String(args.action || "");
2228
2614
  const context = args.invocation_context || {};
2229
- const adapter = new CodegraphAdapter({
2230
- cwd: context.cwd || process.cwd()
2231
- });
2615
+ const adapter = new CodegraphAdapter({ cwd: invocationCwd(args) });
2232
2616
  const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
2233
2617
  const repository = await adapter.resolveRepository(requestedRepo);
2234
2618
  const root = repository.repoRoot;
@@ -2237,6 +2621,12 @@ async function handleSyncTool(client2, args) {
2237
2621
  const remote = client2;
2238
2622
  if (action === "scan") {
2239
2623
  const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
2624
+ if (args.workspace === true && repositories.length === 0) {
2625
+ return err2(
2626
+ "workspace_not_configured",
2627
+ `workspace scan found no declared repositories: ${adapter.workspaceDiagnostic()}`
2628
+ );
2629
+ }
2240
2630
  const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
2241
2631
  const scans = [];
2242
2632
  for (const selected of repositories) {
@@ -2293,10 +2683,11 @@ async function handleSyncTool(client2, args) {
2293
2683
  }
2294
2684
  }
2295
2685
  async function scanRepository(remote, repository, requested, provider, agentId) {
2686
+ const realRoot = realPathOf(repository.repoRoot);
2296
2687
  const files = [
2297
2688
  ...new Set(
2298
2689
  requested.flatMap(
2299
- (candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
2690
+ (candidate) => markdownFiles(safePath(repository.repoRoot, candidate), realRoot)
2300
2691
  )
2301
2692
  )
2302
2693
  ].sort();
@@ -2335,7 +2726,7 @@ var init_sync_tool = __esm({
2335
2726
  init_codegraph();
2336
2727
  init_error_detail();
2337
2728
  ok2 = (data) => ({
2338
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
2729
+ content: [{ type: "text", text: JSON.stringify(data) }]
2339
2730
  });
2340
2731
  err2 = (code, message) => ({
2341
2732
  content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
@@ -2431,12 +2822,23 @@ function clip(value, max, state) {
2431
2822
  }
2432
2823
  function isNodeShaped(value) {
2433
2824
  const node = obj(value);
2434
- return Boolean(node && str(node.uid) && (str(node.label) || str(node.node_type)));
2825
+ return Boolean(
2826
+ node && str(node.uid) && (str(node.label) || str(node.node_type) || obj(node.node_type))
2827
+ );
2828
+ }
2829
+ function nodeDisplayType(node) {
2830
+ const props = obj(node.props);
2831
+ const plain = str(node.node_type);
2832
+ 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";
2833
+ }
2834
+ function nodeFields(node) {
2835
+ const props = obj(node.props);
2836
+ return obj(props?.fields) ?? obj(obj(props?.data)?.fields);
2435
2837
  }
2436
2838
  function unwrapSearchResult(value) {
2437
2839
  if (isNodeShaped(value)) return value;
2438
2840
  const wrapper = obj(value);
2439
- const inner = obj(wrapper?.node);
2841
+ const inner = obj(wrapper?.node) ?? obj(wrapper?.object);
2440
2842
  if (inner && isNodeShaped(inner)) {
2441
2843
  const score = num(wrapper.score);
2442
2844
  return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
@@ -2474,7 +2876,7 @@ function sourceTag(node) {
2474
2876
  return `from: ${title}${extra}`;
2475
2877
  }
2476
2878
  function layer7Fields(node, state) {
2477
- const fields = obj(obj(node.props)?.fields);
2879
+ const fields = nodeFields(node);
2478
2880
  if (!fields) return void 0;
2479
2881
  const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
2480
2882
  if (pairs.length === 0) return void 0;
@@ -2486,7 +2888,7 @@ function nodeSummaryText(node, state) {
2486
2888
  }
2487
2889
  function nodeLines(node, state) {
2488
2890
  const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
2489
- const type = str(node.node_type) || "Node";
2891
+ const type = nodeDisplayType(node);
2490
2892
  const confidence = num(node.confidence);
2491
2893
  const score = num(node.retrieval_score);
2492
2894
  const parenthetical = [
@@ -2505,19 +2907,19 @@ function nodeLines(node, state) {
2505
2907
  if (source) lines.push(` - ${source}`);
2506
2908
  const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
2507
2909
  if (believers.length > 0) {
2508
- const rendered = believers.slice(0, 3).map((raw) => {
2910
+ const rendered2 = believers.slice(0, 3).map((raw) => {
2509
2911
  const believer = obj(raw);
2510
2912
  const conf = num(believer?.confidence);
2511
2913
  return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
2512
2914
  }).join(", ");
2513
- lines.push(` - believed by: ${rendered}`);
2915
+ lines.push(` - believed by: ${rendered2}`);
2514
2916
  }
2515
2917
  return lines.join("\n");
2516
2918
  }
2517
2919
  function groupedNodeSections(nodes, state, headingPrefix = "") {
2518
2920
  const groups = /* @__PURE__ */ new Map();
2519
2921
  for (const node of nodes) {
2520
- const type = str(node.node_type) || "Node";
2922
+ const type = nodeDisplayType(node);
2521
2923
  const group = groups.get(type) ?? [];
2522
2924
  group.push(node);
2523
2925
  groups.set(type, group);
@@ -2533,7 +2935,7 @@ function groupedNodeSections(nodes, state, headingPrefix = "") {
2533
2935
  }
2534
2936
  return sections;
2535
2937
  }
2536
- function relationshipItems(edges, labelByUid, state) {
2938
+ function relationshipItems(edges, labelByUid, state, options) {
2537
2939
  const lines = [];
2538
2940
  for (const raw of edges) {
2539
2941
  const edge = obj(raw);
@@ -2543,8 +2945,8 @@ function relationshipItems(edges, labelByUid, state) {
2543
2945
  const to = str(edge.to_uid);
2544
2946
  if (CURATION_EDGE_TYPES.has(type)) continue;
2545
2947
  if (!from || !to || from === to) continue;
2546
- const fromLabel = labelByUid.get(from);
2547
- const toLabel = labelByUid.get(to);
2948
+ const fromLabel = labelByUid.get(from) ?? (options?.fallbackToUid ? `[${from}]` : void 0);
2949
+ const toLabel = labelByUid.get(to) ?? (options?.fallbackToUid ? `[${to}]` : void 0);
2548
2950
  if (!fromLabel || !toLabel) continue;
2549
2951
  lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
2550
2952
  }
@@ -2587,8 +2989,8 @@ function assemble(header, sections, state) {
2587
2989
  sectionUsed += marker.length + 1;
2588
2990
  }
2589
2991
  lines.push(
2590
- section.items.length === 0 ? section.heading : `${section.heading}
2591
- ${kept.join("\n")}`
2992
+ section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
2993
+ ${kept.join("\n")}` : kept.join("\n")
2592
2994
  );
2593
2995
  used += headingCost + sectionUsed;
2594
2996
  }
@@ -2719,15 +3121,15 @@ function renderTraversal(response) {
2719
3121
  const labelByUid = /* @__PURE__ */ new Map();
2720
3122
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
2721
3123
  if (paths.length > 0) {
2722
- const items = paths.map((raw) => obj(raw)).filter((path6) => Boolean(path6)).map((path6, index) => {
2723
- const uids = Array.isArray(path6.node_uids) ? path6.node_uids.map(String) : [];
2724
- const labels = Array.isArray(path6.labels) ? path6.labels.map(String) : Array.isArray(path6.nodes) ? path6.nodes.map((hop) => {
3124
+ const items = paths.map((raw) => obj(raw)).filter((path7) => Boolean(path7)).map((path7, index) => {
3125
+ const uids = Array.isArray(path7.node_uids) ? path7.node_uids.map(String) : [];
3126
+ const labels = Array.isArray(path7.labels) ? path7.labels.map(String) : Array.isArray(path7.nodes) ? path7.nodes.map((hop) => {
2725
3127
  const hopNode = obj(hop);
2726
3128
  return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
2727
3129
  }) : [];
2728
3130
  const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
2729
- const cost = num(path6.cost) ?? num(path6.path_cost);
2730
- const confidence = num(path6.path_confidence);
3131
+ const cost = num(path7.cost) ?? num(path7.path_cost);
3132
+ const confidence = num(path7.path_confidence);
2731
3133
  const meta = [
2732
3134
  cost !== void 0 ? `cost ${cost}` : void 0,
2733
3135
  confidence !== void 0 ? `confidence ${confidence}` : void 0
@@ -2749,9 +3151,159 @@ function renderTraversal(response) {
2749
3151
  sections.push({ heading: "## Relationships", items: relationships });
2750
3152
  }
2751
3153
  }
2752
- return assemble("# Graph traversal", sections, state);
3154
+ return assemble("# Graph traversal", sections, state);
3155
+ }
3156
+ function neighborLine(value) {
3157
+ const node = obj(value);
3158
+ if (!node) return void 0;
3159
+ const label = str(node.label) || "(unlabeled)";
3160
+ const uid = str(node.uid);
3161
+ const type = str(node.node_type);
3162
+ return `- ${label}${uid ? ` [${uid}]` : ""}${type ? ` (${type})` : ""}`;
3163
+ }
3164
+ function renderOntologyAnswer(response) {
3165
+ const payload = obj(response);
3166
+ if (!payload) return void 0;
3167
+ const objects = Array.isArray(payload.objects) ? payload.objects.filter(isNodeShaped) : isNodeShaped(payload.object) ? [payload.object] : [];
3168
+ const cognitive = obj(payload.cognitive_context);
3169
+ const provenance = Array.isArray(payload.provenance) ? payload.provenance : [];
3170
+ const hasQuerySignature = Array.isArray(payload.objects) || obj(payload.confidence) !== void 0;
3171
+ const cognitiveEmpty = !cognitive || Object.values(cognitive).every((v) => !Array.isArray(v) || v.length === 0);
3172
+ if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
3173
+ return hasQuerySignature ? "No matching domain objects." : void 0;
3174
+ }
3175
+ const state = { bounded: false };
3176
+ const sections = [];
3177
+ if (objects.length > 0) {
3178
+ sections.push(...groupedNodeSections(objects, state));
3179
+ }
3180
+ const labelByUid = /* @__PURE__ */ new Map();
3181
+ for (const node of objects) labelByUid.set(str(node.uid), str(node.label) || "?");
3182
+ const graph = obj(payload.graph);
3183
+ for (const raw of Array.isArray(graph?.nodes) ? graph.nodes : []) {
3184
+ const node = obj(raw);
3185
+ const uid = str(node?.uid);
3186
+ if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
3187
+ }
3188
+ if (cognitive) {
3189
+ for (const rawNodes of Object.values(cognitive)) {
3190
+ if (!Array.isArray(rawNodes)) continue;
3191
+ for (const raw of rawNodes) {
3192
+ const node = obj(raw);
3193
+ const uid = str(node?.uid);
3194
+ if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
3195
+ }
3196
+ }
3197
+ }
3198
+ const relations = Array.isArray(payload.relations) ? payload.relations : [];
3199
+ const relationLines = relationshipItems(relations, labelByUid, state, { fallbackToUid: true });
3200
+ if (relationLines.length > 0) {
3201
+ sections.push({ heading: "## Relations", items: relationLines });
3202
+ }
3203
+ if (cognitive) {
3204
+ for (const [category, rawNodes] of Object.entries(cognitive)) {
3205
+ if (!Array.isArray(rawNodes) || rawNodes.length === 0) continue;
3206
+ const items = rawNodes.map(neighborLine).filter((line) => Boolean(line));
3207
+ if (items.length > 0) {
3208
+ sections.push({ heading: `### Cognitive context \u2014 ${category}`, items });
3209
+ }
3210
+ }
3211
+ }
3212
+ if (provenance.length > 0) {
3213
+ const items = provenance.map((raw) => obj(raw)).filter((entry) => Boolean(entry)).map((entry) => {
3214
+ const span = clip(entry.text_span, QUOTE_MAX, state) || "";
3215
+ const by = str(entry.ingested_by_name);
3216
+ const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${str(entry.source_uid) || "?"}]`;
3217
+ return str(entry.span_kind) === "anchor" ? `- "${span}"${suffix}` : `- context: ${span}${suffix}`;
3218
+ });
3219
+ sections.push({ heading: "## Sources", items });
3220
+ }
3221
+ const confidence = num(obj(payload.confidence)?.overall);
3222
+ const notes = [];
3223
+ if (confidence !== void 0) notes.push(`overall confidence ${confidence}`);
3224
+ if (payload.has_more === true) notes.push("more results exist (has_more)");
3225
+ if (payload.seed_cap_hit === true) notes.push("seed cap hit \u2014 narrow the query for a complete set");
3226
+ if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
3227
+ return assemble(
3228
+ `# Ontology results (${objects.length} object${objects.length === 1 ? "" : "s"})`,
3229
+ sections,
3230
+ state
3231
+ );
3232
+ }
3233
+ function renderObjectList(response, title) {
3234
+ const payload = obj(response);
3235
+ if (!payload) return void 0;
3236
+ const rawItems = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.rows) && payload.rows.some((row) => unwrapSearchResult(row)) ? payload.rows : void 0;
3237
+ const items = (rawItems ?? []).map(unwrapSearchResult).filter((node) => Boolean(node));
3238
+ if (items.length === 0) {
3239
+ return rawItems ? `# ${title} (0 results)` : void 0;
3240
+ }
3241
+ const state = { bounded: false };
3242
+ const sections = groupedNodeSections(items, state);
3243
+ const total = num(payload.total_count);
3244
+ const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
3245
+ const notes = [];
3246
+ if (payload.has_more === true) notes.push("more results exist");
3247
+ if (truncations.length > 0) notes.push(`truncated: ${truncations.join(", ")}`);
3248
+ if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
3249
+ const count = total !== void 0 && total !== items.length ? `${items.length} of ${total}` : `${items.length}`;
3250
+ return assemble(`# ${title} (${count})`, sections, state);
3251
+ }
3252
+ function renderJobs(response) {
3253
+ const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
3254
+ if (!Array.isArray(jobs)) return void 0;
3255
+ if (jobs.length === 0) return "No ingestion jobs.";
3256
+ const state = { bounded: false };
3257
+ const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
3258
+ const shown = sorted.slice(0, JOBS_RENDER_MAX);
3259
+ if (shown.length < sorted.length) state.bounded = true;
3260
+ const items = shown.map((job) => {
3261
+ const progress = obj(job.progress);
3262
+ const done = num(progress?.processed_chunks);
3263
+ const totalChunks = num(progress?.total_chunks);
3264
+ const bits = [
3265
+ str(job.status) || "?",
3266
+ done !== void 0 && totalChunks !== void 0 ? `${done}/${totalChunks} chunks` : void 0,
3267
+ num(progress?.nodes_created) !== void 0 ? `${num(progress?.nodes_created)} nodes` : void 0,
3268
+ num(job.queue_position) !== void 0 ? `queue #${num(job.queue_position)}` : void 0,
3269
+ str(job.error) ? `error: ${clip(job.error, 160, state)}` : void 0
3270
+ ].filter(Boolean);
3271
+ return `- [${str(job.id) || "?"}] ${clip(job.title, 120, state) || "(untitled)"} \u2014 ${bits.join(", ")}`;
3272
+ });
3273
+ return assemble(
3274
+ `# Ingestion jobs (${shown.length}${shown.length < sorted.length ? ` of ${sorted.length}` : ""})`,
3275
+ [{ heading: "", items }],
3276
+ state
3277
+ );
3278
+ }
3279
+ function renderSignals(response) {
3280
+ const payload = obj(response);
3281
+ if (!payload) return void 0;
3282
+ const state = { bounded: false };
3283
+ const sections = [];
3284
+ for (const [key, value] of Object.entries(payload)) {
3285
+ if (!Array.isArray(value) || value.length === 0) continue;
3286
+ const items = value.map((raw) => obj(raw)).filter((item) => Boolean(item)).map((item) => {
3287
+ const fromLabel = str(item.from_label);
3288
+ const toLabel = str(item.to_label);
3289
+ const anyLabelKey = Object.keys(item).find(
3290
+ (k) => k.endsWith("_label") && str(item[k])
3291
+ );
3292
+ 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)";
3293
+ const uid = str(item.uid) ?? str(item.target_uid);
3294
+ const numbers = Object.entries(item).filter(([, v]) => typeof v === "number").slice(0, 4).map(([k, v]) => `${k} ${Math.round(v * 100) / 100}`);
3295
+ const lists = Object.entries(item).filter(([, v]) => Array.isArray(v) && v.length > 0).slice(0, 2).map(([k, v]) => `${k}: ${v.length}`);
3296
+ const meta = [...numbers, ...lists].join(", ");
3297
+ return `- ${clip(label, 160, state)}${uid && uid !== label ? ` [${uid}]` : ""}${meta ? ` (${meta})` : ""}`;
3298
+ });
3299
+ if (items.length > 0) {
3300
+ sections.push({ heading: `## ${key.replace(/_/g, " ")}`, items });
3301
+ }
3302
+ }
3303
+ if (sections.length === 0) return void 0;
3304
+ return assemble("# Synthesis signals", sections, state);
2753
3305
  }
2754
- var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, CURATION_EDGE_TYPES, INFRA_NODE_TYPES;
3306
+ var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, CURATION_EDGE_TYPES, INFRA_NODE_TYPES, JOBS_RENDER_MAX;
2755
3307
  var init_render = __esm({
2756
3308
  "src/render.ts"() {
2757
3309
  "use strict";
@@ -2764,6 +3316,7 @@ var init_render = __esm({
2764
3316
  RENDER_CHAR_BUDGET = 24e3;
2765
3317
  CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
2766
3318
  INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
3319
+ JOBS_RENDER_MAX = 20;
2767
3320
  }
2768
3321
  });
2769
3322
 
@@ -2776,9 +3329,14 @@ function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
2776
3329
  }
2777
3330
  function ok3(data) {
2778
3331
  return {
2779
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
3332
+ content: [{ type: "text", text: JSON.stringify(data) }]
2780
3333
  };
2781
3334
  }
3335
+ function renderedResult(format, raw, render) {
3336
+ if (format === "json") return ok3(raw);
3337
+ const text = render(raw);
3338
+ return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
3339
+ }
2782
3340
  function err3(message) {
2783
3341
  return {
2784
3342
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
@@ -3390,15 +3948,11 @@ async function handleRetrieve(client2, args) {
3390
3948
  max_cost,
3391
3949
  agent_id
3392
3950
  } = args;
3393
- const rendered = (raw, render) => {
3394
- if (format === "json") return ok3(raw);
3395
- const text = render(raw);
3396
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
3397
- };
3951
+ const rendered2 = (raw, render) => renderedResult(format, raw, render);
3398
3952
  switch (action) {
3399
3953
  case "context":
3400
3954
  if (!query) return err3("query is required for context retrieval");
3401
- return rendered(
3955
+ return rendered2(
3402
3956
  await client2.retrieveContext({
3403
3957
  query,
3404
3958
  node_limit: limit,
@@ -3423,7 +3977,7 @@ async function handleRetrieve(client2, args) {
3423
3977
  );
3424
3978
  case "text":
3425
3979
  if (!query) return err3("query is required for text search");
3426
- return rendered(
3980
+ return rendered2(
3427
3981
  await client2.retrieve({
3428
3982
  action: "text",
3429
3983
  query,
@@ -3437,7 +3991,7 @@ async function handleRetrieve(client2, args) {
3437
3991
  );
3438
3992
  case "semantic":
3439
3993
  if (!query) return err3("query is required for semantic search");
3440
- return rendered(
3994
+ return rendered2(
3441
3995
  await client2.retrieve({
3442
3996
  action: "semantic",
3443
3997
  query,
@@ -3451,7 +4005,7 @@ async function handleRetrieve(client2, args) {
3451
4005
  );
3452
4006
  case "hybrid":
3453
4007
  if (!query) return err3("query is required for hybrid search");
3454
- return rendered(
4008
+ return rendered2(
3455
4009
  await client2.retrieve({
3456
4010
  action: "hybrid",
3457
4011
  query,
@@ -3473,7 +4027,7 @@ async function handleRetrieve(client2, args) {
3473
4027
  case "weak_claims":
3474
4028
  case "pending_approvals":
3475
4029
  case "unresolved_contradictions":
3476
- return rendered(
4030
+ return rendered2(
3477
4031
  await client2.retrieve({
3478
4032
  action,
3479
4033
  limit,
@@ -3483,7 +4037,7 @@ async function handleRetrieve(client2, args) {
3483
4037
  (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
3484
4038
  );
3485
4039
  case "stale_derivations":
3486
- return rendered(
4040
+ return rendered2(
3487
4041
  await client2.retrieve({
3488
4042
  action: "stale_derivations",
3489
4043
  limit,
@@ -3492,7 +4046,7 @@ async function handleRetrieve(client2, args) {
3492
4046
  (raw) => renderNodeList(raw, "stale derivations", limit)
3493
4047
  );
3494
4048
  case "preferences":
3495
- return rendered(
4049
+ return rendered2(
3496
4050
  await client2.retrieve({
3497
4051
  action: "preferences",
3498
4052
  ...query ? { query, k: limit } : {},
@@ -3503,7 +4057,7 @@ async function handleRetrieve(client2, args) {
3503
4057
  );
3504
4058
  case "layer":
3505
4059
  if (!layer) return err3("layer is required for layer retrieval");
3506
- return rendered(
4060
+ return rendered2(
3507
4061
  await client2.retrieve({
3508
4062
  action: "layer",
3509
4063
  layer,
@@ -3514,7 +4068,7 @@ async function handleRetrieve(client2, args) {
3514
4068
  (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
3515
4069
  );
3516
4070
  case "recent":
3517
- return rendered(
4071
+ return rendered2(
3518
4072
  await client2.retrieve({
3519
4073
  action: "recent",
3520
4074
  limit,
@@ -3529,7 +4083,7 @@ async function handleRetrieve(client2, args) {
3529
4083
  );
3530
4084
  // Document inventory
3531
4085
  case "document_index":
3532
- return rendered(
4086
+ return rendered2(
3533
4087
  await client2.getNodes({
3534
4088
  node_type: "Document",
3535
4089
  limit: limit ?? 100
@@ -3539,7 +4093,7 @@ async function handleRetrieve(client2, args) {
3539
4093
  // Graph traversal
3540
4094
  case "chain":
3541
4095
  if (!start_uid) return err3("start_uid is required for chain traversal");
3542
- return rendered(
4096
+ return rendered2(
3543
4097
  await client2.traverse({
3544
4098
  action: "chain",
3545
4099
  start_uid,
@@ -3552,7 +4106,7 @@ async function handleRetrieve(client2, args) {
3552
4106
  );
3553
4107
  case "neighborhood":
3554
4108
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
3555
- return rendered(
4109
+ return rendered2(
3556
4110
  await client2.traverse({
3557
4111
  action: "neighborhood",
3558
4112
  start_uid,
@@ -3568,7 +4122,7 @@ async function handleRetrieve(client2, args) {
3568
4122
  case "path":
3569
4123
  if (!start_uid) return err3("start_uid is required for path traversal");
3570
4124
  if (!end_uid) return err3("end_uid is required for path traversal");
3571
- return rendered(
4125
+ return rendered2(
3572
4126
  await client2.traverse({
3573
4127
  action: "path",
3574
4128
  start_uid,
@@ -3584,7 +4138,7 @@ async function handleRetrieve(client2, args) {
3584
4138
  );
3585
4139
  case "subgraph":
3586
4140
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
3587
- return rendered(
4141
+ return rendered2(
3588
4142
  await client2.traverse({
3589
4143
  action: "subgraph",
3590
4144
  start_uid,
@@ -3600,7 +4154,7 @@ async function handleRetrieve(client2, args) {
3600
4154
  case "top_k_paths":
3601
4155
  if (!start_uid) return err3("start_uid is required for top_k_paths");
3602
4156
  if (!end_uid) return err3("end_uid is required for top_k_paths");
3603
- return rendered(
4157
+ return rendered2(
3604
4158
  await client2.traverse({
3605
4159
  action: "top_k_paths",
3606
4160
  start_uid,
@@ -3637,6 +4191,7 @@ async function handleIngest(client2, args) {
3637
4191
  chunk_size,
3638
4192
  chunk_overlap,
3639
4193
  job_id,
4194
+ format,
3640
4195
  agent_id
3641
4196
  } = args;
3642
4197
  const src = source_uri ?? source;
@@ -3706,19 +4261,21 @@ async function handleIngest(client2, args) {
3706
4261
  if (job_id) {
3707
4262
  return ok3(await client2.getJob(job_id));
3708
4263
  }
3709
- return ok3(await client2.listJobs());
4264
+ return renderedResult(format, await client2.listJobs(), renderJobs);
3710
4265
  default:
3711
4266
  return err3(`Unknown ingest action: ${action}`);
3712
4267
  }
3713
4268
  }
3714
4269
  async function handleSynthesize(client2, args) {
3715
- const { action, project_uid, signals, target_types, job_id } = args;
4270
+ const { action, project_uid, signals, target_types, job_id, format } = args;
3716
4271
  switch (action) {
3717
4272
  case "signals":
3718
4273
  if (!project_uid)
3719
4274
  return err3("project_uid is required for action='signals'");
3720
- return ok3(
3721
- await client2.signals(project_uid, { signals, target_types })
4275
+ return renderedResult(
4276
+ format,
4277
+ await client2.signals(project_uid, { signals, target_types }),
4278
+ renderSignals
3722
4279
  );
3723
4280
  case "run":
3724
4281
  if (!project_uid)
@@ -3752,6 +4309,7 @@ async function handleOntology(client2, args) {
3752
4309
  relation_type,
3753
4310
  fields,
3754
4311
  source_uids,
4312
+ format,
3755
4313
  agent_id
3756
4314
  } = args;
3757
4315
  switch (action) {
@@ -3767,7 +4325,8 @@ async function handleOntology(client2, args) {
3767
4325
  "schema_id is required for action='query' (use action='schemas' to find the active schema)"
3768
4326
  );
3769
4327
  }
3770
- return ok3(
4328
+ return renderedResult(
4329
+ format,
3771
4330
  await client2.queryOntology({
3772
4331
  query,
3773
4332
  schema_id,
@@ -3776,29 +4335,42 @@ async function handleOntology(client2, args) {
3776
4335
  include_sources: include_sources ?? true,
3777
4336
  depth,
3778
4337
  limit
3779
- })
4338
+ }),
4339
+ renderOntologyAnswer
3780
4340
  );
3781
4341
  }
3782
4342
  case "search":
3783
4343
  if (!query) return err3("query is required for action='search'");
3784
- return ok3(
4344
+ return renderedResult(
4345
+ format,
3785
4346
  await client2.searchDomainObjects(query, {
3786
4347
  schema_id,
3787
4348
  object_types,
3788
4349
  limit
3789
- })
4350
+ }),
4351
+ (raw) => renderObjectList(raw, `Domain search: ${query}`)
3790
4352
  );
3791
4353
  case "objects":
3792
4354
  if (!schema_id) return err3("schema_id is required for action='objects'");
3793
- return ok3(
3794
- await client2.listDomainObjects({ schema_id, object_type, limit })
4355
+ return renderedResult(
4356
+ format,
4357
+ await client2.listDomainObjects({ schema_id, object_type, limit }),
4358
+ (raw) => renderObjectList(raw, `Domain objects${object_type ? `: ${object_type}` : ""}`)
3795
4359
  );
3796
4360
  case "object":
3797
4361
  if (!uid) return err3("uid is required for action='object'");
3798
- return ok3(await client2.getDomainObject(uid));
4362
+ return renderedResult(
4363
+ format,
4364
+ await client2.getDomainObject(uid),
4365
+ (raw) => renderNodeList([raw], "Domain object")
4366
+ );
3799
4367
  case "object_context":
3800
4368
  if (!uid) return err3("uid is required for action='object_context'");
3801
- return ok3(await client2.getDomainObjectContext(uid, depth));
4369
+ return renderedResult(
4370
+ format,
4371
+ await client2.getDomainObjectContext(uid, depth),
4372
+ renderOntologyAnswer
4373
+ );
3802
4374
  case "proposals":
3803
4375
  return ok3(
3804
4376
  await client2.listOntologyProposals({
@@ -4665,6 +5237,11 @@ var init_tools = __esm({
4665
5237
  type: "string",
4666
5238
  description: "Job ID to check status (for job_status action)"
4667
5239
  },
5240
+ format: {
5241
+ type: "string",
5242
+ enum: ["text", "json"],
5243
+ description: "job_status without job_id: 'text' (default) renders the 20 most recent jobs; 'json' returns the full raw list."
5244
+ },
4668
5245
  agent_id: {
4669
5246
  type: "string",
4670
5247
  description: "Agent identity"
@@ -4700,6 +5277,11 @@ var init_tools = __esm({
4700
5277
  type: "string",
4701
5278
  description: "Job ID to poll (for action='job_status')"
4702
5279
  },
5280
+ format: {
5281
+ type: "string",
5282
+ enum: ["text", "json"],
5283
+ description: "signals action: 'text' (default) renders signal sections; 'json' returns the raw payload."
5284
+ },
4703
5285
  agent_id: {
4704
5286
  type: "string",
4705
5287
  description: "Agent identity"
@@ -4808,6 +5390,11 @@ var init_tools = __esm({
4808
5390
  items: { type: "string" },
4809
5391
  description: "Chunk/document UIDs to extract from (for action=extract)"
4810
5392
  },
5393
+ format: {
5394
+ type: "string",
5395
+ enum: ["text", "json"],
5396
+ description: "Output format for the read actions query/search/objects/object/object_context (default 'text'): a compact rendered block. Pass 'json' for the raw server response."
5397
+ },
4811
5398
  agent_id: {
4812
5399
  type: "string",
4813
5400
  description: "Agent identity"
@@ -4870,10 +5457,21 @@ async function getGeneratedTools(client2) {
4870
5457
  );
4871
5458
  }
4872
5459
  byName.set(d.name, d);
5460
+ const inputSchema = d.maps_to === "structured_query" ? d.input_schema : {
5461
+ ...d.input_schema,
5462
+ properties: {
5463
+ ...d.input_schema.properties,
5464
+ format: {
5465
+ type: "string",
5466
+ enum: ["text", "json"],
5467
+ description: "Output format (default 'text': compact rendered block). Pass 'json' for the raw server response."
5468
+ }
5469
+ }
5470
+ };
4873
5471
  tools.push({
4874
5472
  name: d.name,
4875
5473
  description: d.description,
4876
- inputSchema: d.input_schema
5474
+ inputSchema
4877
5475
  });
4878
5476
  }
4879
5477
  cache = { tools, byName, fetchedAt: Date.now() };
@@ -4894,31 +5492,41 @@ async function handleGeneratedTool(client2, desc, args) {
4894
5492
  if (!query) return err4(`query is required for ${desc.name}`);
4895
5493
  const limit = typeof args.limit === "number" ? args.limit : void 0;
4896
5494
  const filters = Array.isArray(args.filters) ? args.filters : void 0;
4897
- return ok4(
5495
+ return rendered(
5496
+ args.format,
4898
5497
  await ontologyClient.searchDomainObjects(query, {
4899
5498
  schema_id: desc.schema_id,
4900
5499
  object_types: [desc.object_type],
4901
5500
  filters,
4902
5501
  limit
4903
- })
5502
+ }),
5503
+ (raw) => renderObjectList(raw, `${desc.object_type} search: ${query}`)
4904
5504
  );
4905
5505
  }
4906
5506
  case "object": {
4907
5507
  const uid = args.uid;
4908
5508
  if (!uid) return err4(`uid is required for ${desc.name}`);
4909
- return ok4(await ontologyClient.getDomainObject(uid, {
4910
- schema_id: desc.schema_id,
4911
- object_type: desc.object_type
4912
- }));
5509
+ return rendered(
5510
+ args.format,
5511
+ await ontologyClient.getDomainObject(uid, {
5512
+ schema_id: desc.schema_id,
5513
+ object_type: desc.object_type
5514
+ }),
5515
+ (raw) => renderNodeList([raw], desc.object_type)
5516
+ );
4913
5517
  }
4914
5518
  case "object_context": {
4915
5519
  const uid = args.uid;
4916
5520
  if (!uid) return err4(`uid is required for ${desc.name}`);
4917
5521
  const depth = typeof args.depth === "number" ? args.depth : void 0;
4918
- return ok4(await ontologyClient.getDomainObjectContext(uid, depth, {
4919
- schema_id: desc.schema_id,
4920
- object_type: desc.object_type
4921
- }));
5522
+ return rendered(
5523
+ args.format,
5524
+ await ontologyClient.getDomainObjectContext(uid, depth, {
5525
+ schema_id: desc.schema_id,
5526
+ object_type: desc.object_type
5527
+ }),
5528
+ renderOntologyAnswer
5529
+ );
4922
5530
  }
4923
5531
  case "related": {
4924
5532
  const uid = args.uid;
@@ -4931,18 +5539,22 @@ async function handleGeneratedTool(client2, desc, args) {
4931
5539
  "related ontology tools require a graph-aware version of the mindgraph SDK"
4932
5540
  );
4933
5541
  }
4934
- return ok4(await ontologyClient.queryRelatedDomainObjects({
4935
- schema_id: desc.schema_id,
4936
- entry_type: desc.object_type,
4937
- uid,
4938
- relation: desc.relation,
4939
- entry_role: desc.entry_role,
4940
- far_type: desc.far_type,
4941
- where: Array.isArray(args.where) ? args.where : void 0,
4942
- include_provenance: args.include_provenance === true,
4943
- limit: typeof args.limit === "number" ? args.limit : void 0,
4944
- offset: typeof args.offset === "number" ? args.offset : void 0
4945
- }));
5542
+ return rendered(
5543
+ args.format,
5544
+ await ontologyClient.queryRelatedDomainObjects({
5545
+ schema_id: desc.schema_id,
5546
+ entry_type: desc.object_type,
5547
+ uid,
5548
+ relation: desc.relation,
5549
+ entry_role: desc.entry_role,
5550
+ far_type: desc.far_type,
5551
+ where: Array.isArray(args.where) ? args.where : void 0,
5552
+ include_provenance: args.include_provenance === true,
5553
+ limit: typeof args.limit === "number" ? args.limit : void 0,
5554
+ offset: typeof args.offset === "number" ? args.offset : void 0
5555
+ }),
5556
+ (raw) => renderObjectList(raw, `${desc.far_type} related via ${desc.relation}`)
5557
+ );
4946
5558
  }
4947
5559
  case "structured_query": {
4948
5560
  const select = args.select;
@@ -4968,13 +5580,20 @@ async function handleGeneratedTool(client2, desc, args) {
4968
5580
  return err4(e instanceof Error ? e.message : String(e));
4969
5581
  }
4970
5582
  }
4971
- var ok4, err4, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
5583
+ var ok4, rendered, err4, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
4972
5584
  var init_generated_tools = __esm({
4973
5585
  "src/generated-tools.ts"() {
4974
5586
  "use strict";
5587
+ init_render();
4975
5588
  ok4 = (data) => ({
4976
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
5589
+ // Compact on purpose — pretty-printing doubled the token cost.
5590
+ content: [{ type: "text", text: JSON.stringify(data) }]
4977
5591
  });
5592
+ rendered = (format, raw, render) => {
5593
+ if (format === "json") return ok4(raw);
5594
+ const text = render(raw);
5595
+ return text !== void 0 ? { content: [{ type: "text", text }] } : ok4(raw);
5596
+ };
4978
5597
  err4 = (message) => ({
4979
5598
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
4980
5599
  isError: true
@@ -5115,7 +5734,7 @@ async function checkMcpGovernance(toolName, args, config) {
5115
5734
  if (!response) {
5116
5735
  return {
5117
5736
  allowed: false,
5118
- message: `MindGraph governance could not establish permission after ${retryDelays.length + 1} attempts (${lastFailure}). Set MINDGRAPH_GOVERNANCE=off to run this adapter ungoverned.`
5737
+ message: `MindGraph at ${config.baseUrl} is unreachable (${lastFailure}; ${retryDelays.length + 1} attempts). The tool call was NOT evaluated against policy and NOT executed \u2014 this is a connectivity failure, not a governance denial. Check the network and MINDGRAPH_BASE_URL, then retry.`
5119
5738
  };
5120
5739
  }
5121
5740
  if (!response.ok) {
@@ -5245,7 +5864,7 @@ var init_governance = __esm({
5245
5864
  // src/resources.ts
5246
5865
  function json(uri, data) {
5247
5866
  return {
5248
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
5867
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
5249
5868
  };
5250
5869
  }
5251
5870
  async function readResource(client2, uri) {
@@ -5359,10 +5978,10 @@ async function main() {
5359
5978
  const transport = new import_stdio.StdioServerTransport();
5360
5979
  await server.connect(transport);
5361
5980
  console.error(
5362
- `MindGraph MCP server v${"0.16.0"} running on stdio (${BASE_URL})`
5981
+ `MindGraph MCP server v${"0.18.0"} running on stdio (${BASE_URL})`
5363
5982
  );
5364
5983
  }
5365
- var import_server, import_stdio, import_types, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
5984
+ var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, PROMPTS;
5366
5985
  var init_src = __esm({
5367
5986
  "src/index.ts"() {
5368
5987
  "use strict";
@@ -5375,6 +5994,8 @@ var init_src = __esm({
5375
5994
  init_generated_tools();
5376
5995
  init_governance();
5377
5996
  init_resources();
5997
+ import_node_path6 = __toESM(require("path"));
5998
+ init_hook_installer();
5378
5999
  API_KEY = process.env.MINDGRAPH_API_KEY;
5379
6000
  BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
5380
6001
  AGENT_ID = process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId();
@@ -5496,10 +6117,42 @@ Capture knowledge when the user shares something worth remembering:
5496
6117
  (line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
5497
6118
  ).join("\n");
5498
6119
  }
6120
+ RUNNER_REFRESH = (() => {
6121
+ try {
6122
+ return refreshHookRunner("0.18.0", import_node_path6.default.join(__dirname, "cli.js"));
6123
+ } catch {
6124
+ return void 0;
6125
+ }
6126
+ })();
6127
+ if (RUNNER_REFRESH?.refreshed) {
6128
+ const refreshedSettings = (() => {
6129
+ try {
6130
+ return refreshOwnedClaudeHooks();
6131
+ } catch {
6132
+ return [];
6133
+ }
6134
+ })();
6135
+ console.error(
6136
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.18.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6137
+ );
6138
+ }
6139
+ HOOK_SKEW_NOTE = (() => {
6140
+ try {
6141
+ return versionSkewNote("0.18.0");
6142
+ } catch {
6143
+ return void 0;
6144
+ }
6145
+ })();
6146
+ if (HOOK_SKEW_NOTE) {
6147
+ INSTRUCTIONS += `
6148
+
6149
+ ${HOOK_SKEW_NOTE}`;
6150
+ console.error(`[mindgraph-mcp] ${HOOK_SKEW_NOTE}`);
6151
+ }
5499
6152
  server = new import_server.Server(
5500
6153
  {
5501
6154
  name: "mindgraph",
5502
- version: "0.16.0"
6155
+ version: "0.18.0"
5503
6156
  },
5504
6157
  {
5505
6158
  capabilities: {
@@ -5718,7 +6371,7 @@ ${conversation}`
5718
6371
 
5719
6372
  // src/cli.ts
5720
6373
  var fs5 = __toESM(require("fs"));
5721
- var path5 = __toESM(require("path"));
6374
+ var path6 = __toESM(require("path"));
5722
6375
  var os4 = __toESM(require("os"));
5723
6376
  var crypto5 = __toESM(require("crypto"));
5724
6377
  var import_child_process = require("child_process");
@@ -5801,6 +6454,7 @@ var import_node_path3 = __toESM(require("path"));
5801
6454
  var import_node_child_process2 = require("child_process");
5802
6455
  init_codegraph();
5803
6456
  init_code_tool();
6457
+ init_error_detail();
5804
6458
  init_hook_env();
5805
6459
  var NOOP = { kind: "noop" };
5806
6460
  function defaultLedger(sessionId, options) {
@@ -6064,8 +6718,8 @@ ${knowledgeLines.join("\n")}`);
6064
6718
  );
6065
6719
  }
6066
6720
  if (footer) lines.push(footer);
6067
- const rendered = lines.join("\n");
6068
- return rendered.length <= BRIEF_CHAR_BUDGET ? rendered : `${rendered.slice(0, BRIEF_CHAR_BUDGET - 40)}
6721
+ const rendered2 = lines.join("\n");
6722
+ return rendered2.length <= BRIEF_CHAR_BUDGET ? rendered2 : `${rendered2.slice(0, BRIEF_CHAR_BUDGET - 40)}
6069
6723
  \u2026[bounded work brief truncated]`;
6070
6724
  }
6071
6725
  async function repositoryScopeUids(input, client2, options, agentId) {
@@ -6132,6 +6786,51 @@ function isOwnPriorWork(brief, agentId, harness) {
6132
6786
  const owner = string(object(brief?.lease)?.lease_owner_agent_id);
6133
6787
  return owner === agentId || owner === harness;
6134
6788
  }
6789
+ function hasMindgraphServer(servers) {
6790
+ if (!servers || typeof servers !== "object") return false;
6791
+ for (const [name, entry] of Object.entries(servers)) {
6792
+ if (name === "mindgraph") return true;
6793
+ const cfg = entry && typeof entry === "object" ? entry : void 0;
6794
+ const text = [
6795
+ String(cfg?.command ?? ""),
6796
+ Array.isArray(cfg?.args) ? cfg.args.join(" ") : "",
6797
+ String(cfg?.url ?? "")
6798
+ ].join(" ");
6799
+ if (text.includes("mindgraph-mcp")) return true;
6800
+ }
6801
+ return false;
6802
+ }
6803
+ function pathIsWithin(ancestor, descendant) {
6804
+ const base = import_node_path3.default.resolve(ancestor);
6805
+ const target = import_node_path3.default.resolve(descendant);
6806
+ return target === base || target.startsWith(base + import_node_path3.default.sep);
6807
+ }
6808
+ function claudeMcpRegistrationNote(input, options) {
6809
+ if (options.harness !== "claude-code") return void 0;
6810
+ if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
6811
+ try {
6812
+ const configPath = options.claudeConfigPath || import_node_path3.default.join(import_node_os3.default.homedir(), ".claude.json");
6813
+ if (!import_node_fs3.default.existsSync(configPath)) return void 0;
6814
+ const config = JSON.parse(import_node_fs3.default.readFileSync(configPath, "utf8"));
6815
+ if (hasMindgraphServer(config.mcpServers)) return void 0;
6816
+ const cwd = import_node_path3.default.resolve(input.cwd || ".");
6817
+ for (const [projectDir, project] of Object.entries(config.projects || {})) {
6818
+ if (!pathIsWithin(projectDir, cwd)) continue;
6819
+ if (hasMindgraphServer(project?.mcpServers)) return void 0;
6820
+ }
6821
+ for (let dir = cwd; ; dir = import_node_path3.default.dirname(dir)) {
6822
+ const candidate = import_node_path3.default.join(dir, ".mcp.json");
6823
+ if (import_node_fs3.default.existsSync(candidate)) {
6824
+ const parsed = JSON.parse(import_node_fs3.default.readFileSync(candidate, "utf8"));
6825
+ if (hasMindgraphServer(parsed.mcpServers)) return void 0;
6826
+ }
6827
+ if (dir === import_node_path3.default.dirname(dir)) break;
6828
+ }
6829
+ } catch {
6830
+ return void 0;
6831
+ }
6832
+ return "NOTE: no MindGraph MCP server appears to be registered for Claude Code, so this brief's continuity actions (claim, checkpoint, capture) have no mcp__mindgraph__* tools to run through and durable memory cannot be written this session. Tell the user to run: npx -y mindgraph-mcp@latest install-code \u2014 then start a new session.";
6833
+ }
6135
6834
  async function sessionStart(input, client2, options) {
6136
6835
  const agentId = resolveAgentId(options);
6137
6836
  const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
@@ -6221,7 +6920,14 @@ async function sessionStart(input, client2, options) {
6221
6920
  claimFailed: claimFailed && !stillHeld,
6222
6921
  scopeNote
6223
6922
  });
6224
- const briefHash = import_node_crypto3.default.createHash("sha256").update(briefText).digest("hex");
6923
+ const registrationNote = claudeMcpRegistrationNote(input, options);
6924
+ if (registrationNote) {
6925
+ console.error(`mindgraph-hook: ${registrationNote}`);
6926
+ }
6927
+ const contextText = registrationNote ? `${registrationNote}
6928
+
6929
+ ${briefText}` : briefText;
6930
+ const briefHash = import_node_crypto3.default.createHash("sha256").update(contextText).digest("hex");
6225
6931
  const shouldInject = withLedger(input.session_id, options, (ledger) => {
6226
6932
  const changed = ledger.lastBriefHash !== briefHash;
6227
6933
  const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
@@ -6254,7 +6960,7 @@ async function sessionStart(input, client2, options) {
6254
6960
  ledger.lastBriefHash = briefHash;
6255
6961
  return changed || reinjectCompact;
6256
6962
  });
6257
- return shouldInject ? { kind: "context", context: briefText } : NOOP;
6963
+ return shouldInject ? { kind: "context", context: contextText } : NOOP;
6258
6964
  }
6259
6965
  async function preTool(input, options) {
6260
6966
  const original = { ...input.tool_input || {} };
@@ -6361,9 +7067,7 @@ async function postTool(input, client2, options) {
6361
7067
  }
6362
7068
  }
6363
7069
  } else if (aboutLedgerTask) {
6364
- ledger.taskVersion = number(payload?.current_version) ?? ledger.taskVersion;
6365
- ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
6366
- ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
7070
+ adoptConflictFence(ledger, payload);
6367
7071
  }
6368
7072
  }
6369
7073
  if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 && // Only ledgers whose lease state was written by a claim THIS code made
@@ -6420,19 +7124,35 @@ async function postTool(input, client2, options) {
6420
7124
  })
6421
7125
  );
6422
7126
  withLedger(input.session_id, options, (ledger) => {
6423
- if (payloadIsError(response)) return void 0;
6424
7127
  if (ledger.taskUid !== active.taskUid) return void 0;
7128
+ if (payloadIsError(response)) {
7129
+ adoptConflictFence(ledger, response);
7130
+ return void 0;
7131
+ }
6425
7132
  ledger.taskVersion = number(response?.task_version) ?? ledger.taskVersion;
6426
7133
  ledger.leaseEpoch = number(response?.lease_epoch) ?? ledger.leaseEpoch;
6427
7134
  ledger.leaseExpiresAt = number(response?.lease_expires_at) ?? ledger.leaseExpiresAt;
6428
7135
  ledger.lastLeaseRenewalAt = nowSeconds;
6429
7136
  return void 0;
6430
7137
  });
6431
- } catch {
7138
+ } catch (cause) {
7139
+ const conflict = conflictState(cause);
7140
+ if (Object.keys(conflict).length > 0) {
7141
+ withLedger(input.session_id, options, (ledger) => {
7142
+ if (ledger.taskUid !== active.taskUid) return void 0;
7143
+ adoptConflictFence(ledger, conflict);
7144
+ return void 0;
7145
+ });
7146
+ }
6432
7147
  }
6433
7148
  }
6434
7149
  return NOOP;
6435
7150
  }
7151
+ function adoptConflictFence(ledger, payload) {
7152
+ ledger.taskVersion = number(payload?.current_version) ?? ledger.taskVersion;
7153
+ ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
7154
+ ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
7155
+ }
6436
7156
  function payloadIsError(payload) {
6437
7157
  return Boolean(payload && payload.error);
6438
7158
  }
@@ -6585,189 +7305,13 @@ async function runCodexHook(input, client2, options = {}) {
6585
7305
  }
6586
7306
  }
6587
7307
 
6588
- // src/hook-installer.ts
6589
- var import_node_fs4 = __toESM(require("fs"));
6590
- var import_node_os4 = __toESM(require("os"));
6591
- var import_node_path4 = __toESM(require("path"));
6592
- var import_node_crypto4 = __toESM(require("crypto"));
6593
- var OWNER_MARKER = "--owner mindgraph";
6594
- var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
6595
- function command(harness) {
6596
- return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
6597
- }
6598
- function windowsCommand(harness) {
6599
- return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
6600
- }
6601
- function installHookRunner(sourceFile, homeDir) {
6602
- const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
6603
- import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
6604
- import_node_fs4.default.copyFileSync(sourceFile, target);
6605
- return target;
6606
- }
6607
- function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex")) {
6608
- if (harness === "codex") {
6609
- return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
6610
- }
6611
- return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
6612
- }
6613
- function readSettings(file) {
6614
- if (!import_node_fs4.default.existsSync(file)) return {};
6615
- return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
6616
- }
6617
- function ownedHook(harness, timeout, additionalContextLimit) {
6618
- return {
6619
- type: "command",
6620
- command: command(harness),
6621
- ...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
6622
- timeout,
6623
- statusMessage: "Syncing MindGraph session context",
6624
- ...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
6625
- };
6626
- }
6627
- function desiredEntries(harness) {
6628
- const common = {
6629
- SessionStart: [
6630
- {
6631
- matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
6632
- // B7 live acceptance caught cold SessionStart calls crossing the
6633
- // inherited 20s edge in both harnesses: the hooks correctly failed
6634
- // open, but no brief reached the model. The same installed runner
6635
- // completed in 16.84s once warm. Keep a 30s cold-tenant margin.
6636
- // The declared Codex context limit must not undercut the brief's own
6637
- // 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
6638
- // truncation the renderer knew nothing about.
6639
- hooks: [
6640
- ownedHook(
6641
- harness,
6642
- 30,
6643
- harness === "codex" ? 1e4 : void 0
6644
- )
6645
- ]
6646
- }
6647
- ],
6648
- PreToolUse: [
6649
- {
6650
- matcher: "mcp__mindgraph__.*",
6651
- // Repository resolution over a large multi-repo workspace plus three
6652
- // git probes can exceed 3s on the first (uncached) call; a killed
6653
- // PreToolUse means the call goes through untagged.
6654
- hooks: [ownedHook(harness, 5)]
6655
- }
6656
- ],
6657
- // PostToolUse awaits the lease renewal round-trip, which fires exactly
6658
- // when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
6659
- PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
6660
- Stop: [{ hooks: [ownedHook(harness, 5)] }],
6661
- // SessionEnd makes up to two sequential cloud calls (abandon_iteration +
6662
- // session close) — the SAME cold-tenant latency SessionStart needs 30s
6663
- // for. Claude honors the configured budget; Codex clamps SessionEnd to a
6664
- // 3s platform cap regardless of this value (see codex-hooks.ts wire
6665
- // map), so the codex adapter instead makes at most ONE call there.
6666
- SessionEnd: [
6667
- { hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
6668
- ]
6669
- };
6670
- return harness === "claude-code" ? {
6671
- ...common,
6672
- TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
6673
- TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
6674
- } : common;
6675
- }
6676
- function hookCommand(value) {
6677
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
6678
- const command2 = value.command;
6679
- return typeof command2 === "string" ? command2 : void 0;
6680
- }
6681
- function entryHasOwnedHook(value) {
6682
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
6683
- const hooks = value.hooks;
6684
- return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
6685
- }
6686
- function writeSettings(file, settings) {
6687
- import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
6688
- const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
6689
- import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
6690
- `, {
6691
- mode: 384
6692
- });
6693
- import_node_fs4.default.renameSync(temporary, file);
6694
- }
6695
- function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
6696
- const file = settingsPath(harness, scope, projectDir, codexHomeDir);
6697
- const settings = readSettings(file);
6698
- const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
6699
- let added = 0;
6700
- let updated = 0;
6701
- for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
6702
- const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
6703
- const kept = current.filter((entry) => !entryHasOwnedHook(entry));
6704
- const hadOwned = kept.length !== current.length;
6705
- for (const entry of wanted) {
6706
- kept.push(entry);
6707
- if (hadOwned) updated += 1;
6708
- else added += 1;
6709
- }
6710
- hooks[event] = kept;
6711
- }
6712
- settings.hooks = hooks;
6713
- writeSettings(file, settings);
6714
- return { path: file, added, updated };
6715
- }
6716
- function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
6717
- const file = settingsPath(harness, scope, projectDir, codexHomeDir);
6718
- if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
6719
- const settings = readSettings(file);
6720
- const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
6721
- let removed = 0;
6722
- for (const [event, value] of Object.entries(hooks)) {
6723
- if (!Array.isArray(value)) continue;
6724
- const retainedEntries = [];
6725
- for (const rawEntry of value) {
6726
- if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
6727
- retainedEntries.push(rawEntry);
6728
- continue;
6729
- }
6730
- const entry = { ...rawEntry };
6731
- if (!Array.isArray(entry.hooks)) {
6732
- retainedEntries.push(entry);
6733
- continue;
6734
- }
6735
- const retainedHooks = entry.hooks.filter((hook) => {
6736
- const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
6737
- if (owned) removed += 1;
6738
- return !owned;
6739
- });
6740
- if (retainedHooks.length > 0) {
6741
- entry.hooks = retainedHooks;
6742
- retainedEntries.push(entry);
6743
- }
6744
- }
6745
- if (retainedEntries.length > 0) hooks[event] = retainedEntries;
6746
- else delete hooks[event];
6747
- }
6748
- settings.hooks = hooks;
6749
- writeSettings(file, settings);
6750
- return { path: file, removed };
6751
- }
6752
- function installClaudeHooks(scope, projectDir = process.cwd()) {
6753
- return installHooks("claude-code", scope, projectDir);
6754
- }
6755
- function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
6756
- return uninstallHooks("claude-code", scope, projectDir);
6757
- }
6758
- function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
6759
- return installHooks("codex", scope, projectDir, codexHomeDir);
6760
- }
6761
- function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
6762
- return uninstallHooks("codex", scope, projectDir, codexHomeDir);
6763
- }
6764
-
6765
7308
  // src/cli.ts
7309
+ init_hook_installer();
6766
7310
  function recordHookHealth(harness, error) {
6767
7311
  try {
6768
- const dir = process.env.MINDGRAPH_RUNTIME_DIR || path5.join(os4.homedir(), ".mindgraph", "runtime");
7312
+ const dir = process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime");
6769
7313
  fs5.mkdirSync(dir, { recursive: true, mode: 448 });
6770
- const file = path5.join(dir, "hook-health.json");
7314
+ const file = path6.join(dir, "hook-health.json");
6771
7315
  let failures = 0;
6772
7316
  try {
6773
7317
  failures = Number(JSON.parse(fs5.readFileSync(file, "utf8")).consecutiveFailures) || 0;
@@ -6789,8 +7333,8 @@ function recordHookHealth(harness, error) {
6789
7333
  }
6790
7334
  function clearHookHealth(harness) {
6791
7335
  try {
6792
- const file = path5.join(
6793
- process.env.MINDGRAPH_RUNTIME_DIR || path5.join(os4.homedir(), ".mindgraph", "runtime"),
7336
+ const file = path6.join(
7337
+ process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
6794
7338
  "hook-health.json"
6795
7339
  );
6796
7340
  if (!fs5.existsSync(file)) return;
@@ -6812,7 +7356,7 @@ function clearHookHealth(harness) {
6812
7356
  function getClaudeDesktopConfigPath() {
6813
7357
  switch (process.platform) {
6814
7358
  case "darwin":
6815
- return path5.join(
7359
+ return path6.join(
6816
7360
  os4.homedir(),
6817
7361
  "Library",
6818
7362
  "Application Support",
@@ -6820,13 +7364,13 @@ function getClaudeDesktopConfigPath() {
6820
7364
  "claude_desktop_config.json"
6821
7365
  );
6822
7366
  case "win32":
6823
- return path5.join(
6824
- process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"),
7367
+ return path6.join(
7368
+ process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming"),
6825
7369
  "Claude",
6826
7370
  "claude_desktop_config.json"
6827
7371
  );
6828
7372
  case "linux":
6829
- return path5.join(
7373
+ return path6.join(
6830
7374
  os4.homedir(),
6831
7375
  ".config",
6832
7376
  "Claude",
@@ -6842,7 +7386,7 @@ function readJsonFile(filePath) {
6842
7386
  return JSON.parse(content);
6843
7387
  }
6844
7388
  function writeJsonFile(filePath, data) {
6845
- const dir = path5.dirname(filePath);
7389
+ const dir = path6.dirname(filePath);
6846
7390
  if (!fs5.existsSync(dir)) {
6847
7391
  fs5.mkdirSync(dir, { recursive: true });
6848
7392
  }
@@ -6978,8 +7522,8 @@ function printStatus() {
6978
7522
  `MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
6979
7523
  );
6980
7524
  try {
6981
- const healthFile = path5.join(
6982
- process.env.MINDGRAPH_RUNTIME_DIR || path5.join(os4.homedir(), ".mindgraph", "runtime"),
7525
+ const healthFile = path6.join(
7526
+ process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
6983
7527
  "hook-health.json"
6984
7528
  );
6985
7529
  if (fs5.existsSync(healthFile)) {
@@ -7000,7 +7544,7 @@ function withHooksFlag() {
7000
7544
  }
7001
7545
  function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
7002
7546
  if (withHooks) {
7003
- const runner = installHookRunner(__filename);
7547
+ const runner = installHookRunner(__filename, void 0, "0.18.0");
7004
7548
  console.log(`Installed pinned hook runner at ${runner}`);
7005
7549
  const result = installClaudeHooks("user");
7006
7550
  console.log(
@@ -7230,7 +7774,7 @@ async function main2() {
7230
7774
  if (hookHarness !== "claude-code" && hookHarness !== "codex") {
7231
7775
  throw new Error("--harness must be claude-code or codex");
7232
7776
  }
7233
- const runner = installHookRunner(__filename);
7777
+ const runner = installHookRunner(__filename, void 0, "0.18.0");
7234
7778
  console.log(`Installed pinned hook runner at ${runner}`);
7235
7779
  const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
7236
7780
  console.log(