mindgraph-mcp 0.23.0 → 0.25.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/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, path8, body) {
38
+ function canRetryRequest(method, path9, body) {
39
39
  if (method === "GET" || method === "HEAD") return true;
40
40
  if (method !== "POST") return false;
41
- if (READ_POSTS.has(path8)) return true;
41
+ if (READ_POSTS.has(path9)) 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, path8) && READ_ACTIONS[path8].includes(fields.action)) return true;
45
- return path8 === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
44
+ if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path9) && READ_ACTIONS[path9].includes(fields.action)) return true;
45
+ return path9 === "/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, path8, body) {
183
- const url = `${this.baseUrl}${path8}`;
182
+ async request(method, path9, body) {
183
+ const url = `${this.baseUrl}${path9}`;
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
- path8,
198
+ path9,
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, path8);
203
+ const res = await this.fetchWithDeadline(url, init, method, path9);
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} ${path8} failed: ${res.status}`,
213
+ `${method} ${path9} 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, path8) {
237
+ async fetchWithDeadline(url, init, method, path9) {
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} ${path8} timed out after ${this.timeoutMs}ms`,
248
+ `${method} ${path9} 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(path8) {
259
- return this.request("GET", path8);
258
+ get(path9) {
259
+ return this.request("GET", path9);
260
260
  }
261
- post(path8, body) {
262
- return this.request("POST", path8, body);
261
+ post(path9, body) {
262
+ return this.request("POST", path9, body);
263
263
  }
264
- patch(path8, body) {
265
- return this.request("PATCH", path8, body);
264
+ patch(path9, body) {
265
+ return this.request("PATCH", path9, body);
266
266
  }
267
- del(path8) {
268
- return this.request("DELETE", path8);
267
+ del(path9) {
268
+ return this.request("DELETE", path9);
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(path8) {
1244
- return path8.replaceAll("\\", "/").replace(/^\.\//, "");
1243
+ function normalizePath(path9) {
1244
+ return path9.replaceAll("\\", "/").replace(/^\.\//, "");
1245
1245
  }
1246
1246
  function normalizeSignature(signature) {
1247
1247
  return signature.replace(/\s+/g, " ").trim();
1248
1248
  }
1249
- function languageFromPath(path8) {
1250
- const extension = (0, import_node_path2.extname)(path8).toLowerCase();
1249
+ function languageFromPath(path9) {
1250
+ const extension = (0, import_node_path2.extname)(path9).toLowerCase();
1251
1251
  return {
1252
1252
  ".c": "c",
1253
1253
  ".cc": "cpp",
@@ -2564,10 +2564,111 @@ var init_code_tool = __esm({
2564
2564
  }
2565
2565
  });
2566
2566
 
2567
+ // src/call-ledger.ts
2568
+ function callsDir(options) {
2569
+ return import_node_path3.default.join(
2570
+ options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime"),
2571
+ "calls"
2572
+ );
2573
+ }
2574
+ function stableJson(value) {
2575
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
2576
+ if (value && typeof value === "object") {
2577
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
2578
+ }
2579
+ return JSON.stringify(value) ?? "null";
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);
2590
+ }
2591
+ function prune(dir, now) {
2592
+ let names;
2593
+ try {
2594
+ names = import_node_fs3.default.readdirSync(dir);
2595
+ } catch {
2596
+ return;
2597
+ }
2598
+ for (const name of names) {
2599
+ const file = import_node_path3.default.join(dir, name);
2600
+ try {
2601
+ if (now - import_node_fs3.default.statSync(file).mtimeMs > TTL_MS) import_node_fs3.default.unlinkSync(file);
2602
+ } catch {
2603
+ }
2604
+ }
2605
+ }
2606
+ function recordPendingCall(toolName, toolInput, harnessSessionId, fills, options = {}) {
2607
+ const now = (options.now ?? Date.now)();
2608
+ const dir = callsDir(options);
2609
+ try {
2610
+ import_node_fs3.default.mkdirSync(dir, { recursive: true, mode: 448 });
2611
+ prune(dir, now);
2612
+ const record = {
2613
+ harnessSessionId,
2614
+ projectDir: options.projectDir ?? process.env.CLAUDE_PROJECT_DIR,
2615
+ fills,
2616
+ createdAt: now
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 {
2626
+ }
2627
+ }
2628
+ function resolvePendingCall(toolName, toolInput, options = {}) {
2629
+ const now = (options.now ?? Date.now)();
2630
+ const dir = callsDir(options);
2631
+ const prefix = `${callKey(toolName, toolInput)}.`;
2632
+ let names;
2633
+ try {
2634
+ names = import_node_fs3.default.readdirSync(dir).filter((name) => name.startsWith(prefix) && name.endsWith(".json"));
2635
+ } catch {
2636
+ return void 0;
2637
+ }
2638
+ let matches = [];
2639
+ for (const name of names) {
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);
2646
+ }
2647
+ } catch {
2648
+ }
2649
+ }
2650
+ if (matches.length > 1) {
2651
+ const projectDir = options.projectDir ?? process.env.CLAUDE_PROJECT_DIR;
2652
+ matches = projectDir ? matches.filter((record) => record.projectDir === projectDir) : [];
2653
+ }
2654
+ return matches.length === 1 ? matches[0].fills : void 0;
2655
+ }
2656
+ var import_node_crypto4, import_node_fs3, import_node_os3, import_node_path3, TTL_MS;
2657
+ var init_call_ledger = __esm({
2658
+ "src/call-ledger.ts"() {
2659
+ "use strict";
2660
+ import_node_crypto4 = __toESM(require("crypto"));
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
+
2567
2668
  // src/hook-installer.ts
2568
2669
  function storedFeatures(homeDir) {
2569
2670
  const { promptRecall: promptRecall2, capture } = loadHookEnv(
2570
- import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), ".mindgraph")
2671
+ import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), ".mindgraph")
2571
2672
  );
2572
2673
  return { promptRecall: promptRecall2, capture };
2573
2674
  }
@@ -2578,24 +2679,24 @@ function windowsCommand(harness) {
2578
2679
  return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
2579
2680
  }
2580
2681
  function installHookRunner(sourceFile, homeDir, version) {
2581
- const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
2582
- import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
2583
- const temporary = `${target}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
2584
- import_node_fs5.default.copyFileSync(sourceFile, temporary);
2585
- import_node_fs5.default.renameSync(temporary, target);
2586
- const versionPath = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
2682
+ const target = import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_RELATIVE);
2683
+ import_node_fs6.default.mkdirSync(import_node_path5.default.dirname(target), { recursive: true });
2684
+ const temporary = `${target}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
2685
+ import_node_fs6.default.copyFileSync(sourceFile, temporary);
2686
+ import_node_fs6.default.renameSync(temporary, target);
2687
+ const versionPath = import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_VERSION_RELATIVE);
2587
2688
  if (version) {
2588
- import_node_fs5.default.writeFileSync(versionPath, `${version}
2689
+ import_node_fs6.default.writeFileSync(versionPath, `${version}
2589
2690
  `);
2590
2691
  } else {
2591
- import_node_fs5.default.rmSync(versionPath, { force: true });
2692
+ import_node_fs6.default.rmSync(versionPath, { force: true });
2592
2693
  }
2593
2694
  return target;
2594
2695
  }
2595
2696
  function installedHookRunnerVersion(homeDir) {
2596
2697
  try {
2597
- const raw = import_node_fs5.default.readFileSync(
2598
- import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE),
2698
+ const raw = import_node_fs6.default.readFileSync(
2699
+ import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_VERSION_RELATIVE),
2599
2700
  "utf8"
2600
2701
  ).trim();
2601
2702
  return raw || void 0;
@@ -2620,22 +2721,22 @@ function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
2620
2721
  if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
2621
2722
  return { refreshed: false, reason: "disabled" };
2622
2723
  }
2623
- const home = homeDir || import_node_os4.default.homedir();
2624
- if (!import_node_fs5.default.existsSync(import_node_path4.default.join(home, RUNNER_RELATIVE))) {
2724
+ const home = homeDir || import_node_os5.default.homedir();
2725
+ if (!import_node_fs6.default.existsSync(import_node_path5.default.join(home, RUNNER_RELATIVE))) {
2625
2726
  return { refreshed: false, reason: "no_runner" };
2626
2727
  }
2627
- if (!import_node_fs5.default.existsSync(sourceBundle)) {
2728
+ if (!import_node_fs6.default.existsSync(sourceBundle)) {
2628
2729
  return { refreshed: false, reason: "no_source" };
2629
2730
  }
2630
- const server2 = parseSemver(serverVersion);
2631
- if (!server2) return { refreshed: false, reason: "unparseable" };
2731
+ const server = parseSemver(serverVersion);
2732
+ if (!server) return { refreshed: false, reason: "unparseable" };
2632
2733
  const runnerVersion = installedHookRunnerVersion(home);
2633
2734
  if (runnerVersion !== void 0) {
2634
2735
  const pinned = parseSemver(runnerVersion);
2635
2736
  if (!pinned) {
2636
2737
  return { refreshed: false, reason: "unparseable", runnerVersion };
2637
2738
  }
2638
- if (!semverNewer(server2, pinned)) {
2739
+ if (!semverNewer(server, pinned)) {
2639
2740
  return {
2640
2741
  refreshed: false,
2641
2742
  reason: runnerVersion === serverVersion ? "current" : "downgrade",
@@ -2655,8 +2756,8 @@ function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
2655
2756
  for (const scope of scopes) {
2656
2757
  try {
2657
2758
  const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
2658
- if (!import_node_fs5.default.existsSync(file)) continue;
2659
- if (!import_node_fs5.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
2759
+ if (!import_node_fs6.default.existsSync(file)) continue;
2760
+ if (!import_node_fs6.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
2660
2761
  installHooks(
2661
2762
  "claude-code",
2662
2763
  scope,
@@ -2676,15 +2777,15 @@ function versionSkewNote(serverVersion, homeDir) {
2676
2777
  if (!runnerVersion || runnerVersion === serverVersion) return void 0;
2677
2778
  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).`;
2678
2779
  }
2679
- function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex"), homeDir) {
2780
+ function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path5.default.join(import_node_os5.default.homedir(), ".codex"), homeDir) {
2680
2781
  if (harness === "codex") {
2681
- return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
2782
+ return scope === "user" ? import_node_path5.default.join(codexHomeDir, "hooks.json") : import_node_path5.default.join(projectDir, ".codex", "hooks.json");
2682
2783
  }
2683
- 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");
2784
+ return scope === "user" ? import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), ".claude", "settings.json") : import_node_path5.default.join(projectDir, ".claude", "settings.json");
2684
2785
  }
2685
2786
  function readSettings(file) {
2686
- if (!import_node_fs5.default.existsSync(file)) return {};
2687
- return JSON.parse(import_node_fs5.default.readFileSync(file, "utf8"));
2787
+ if (!import_node_fs6.default.existsSync(file)) return {};
2788
+ return JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
2688
2789
  }
2689
2790
  function ownedHook(harness, timeout, additionalContextLimit) {
2690
2791
  return {
@@ -2728,7 +2829,17 @@ function desiredEntries(harness, features = {}) {
2728
2829
  ],
2729
2830
  // PostToolUse awaits the lease renewal round-trip, which fires exactly
2730
2831
  // when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
2731
- PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
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
+ ],
2732
2843
  Stop: [{ hooks: [ownedHook(harness, 5)] }],
2733
2844
  // SessionEnd makes up to two sequential cloud calls (abandon_iteration +
2734
2845
  // session close) — the SAME cold-tenant latency SessionStart needs 30s
@@ -2762,13 +2873,13 @@ function entryHasOwnedHook(value) {
2762
2873
  return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
2763
2874
  }
2764
2875
  function writeSettings(file, settings) {
2765
- import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
2766
- const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
2767
- import_node_fs5.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
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)}
2768
2879
  `, {
2769
2880
  mode: 384
2770
2881
  });
2771
- import_node_fs5.default.renameSync(temporary, file);
2882
+ import_node_fs6.default.renameSync(temporary, file);
2772
2883
  }
2773
2884
  function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir, features = {}) {
2774
2885
  const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
@@ -2802,7 +2913,7 @@ function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir,
2802
2913
  }
2803
2914
  function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
2804
2915
  const file = settingsPath(harness, scope, projectDir, codexHomeDir);
2805
- if (!import_node_fs5.default.existsSync(file)) return { path: file, removed: 0 };
2916
+ if (!import_node_fs6.default.existsSync(file)) return { path: file, removed: 0 };
2806
2917
  const settings = readSettings(file);
2807
2918
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
2808
2919
  let removed = 0;
@@ -2848,18 +2959,19 @@ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2848
2959
  function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2849
2960
  return uninstallHooks("codex", scope, projectDir, codexHomeDir);
2850
2961
  }
2851
- var import_node_fs5, import_node_os4, import_node_path4, import_node_crypto5, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
2962
+ var import_node_fs6, import_node_os5, import_node_path5, import_node_crypto6, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE, CLAUDE_POST_TOOL_MATCHER;
2852
2963
  var init_hook_installer = __esm({
2853
2964
  "src/hook-installer.ts"() {
2854
2965
  "use strict";
2855
- import_node_fs5 = __toESM(require("fs"));
2856
- import_node_os4 = __toESM(require("os"));
2857
- import_node_path4 = __toESM(require("path"));
2858
- import_node_crypto5 = __toESM(require("crypto"));
2966
+ import_node_fs6 = __toESM(require("fs"));
2967
+ import_node_os5 = __toESM(require("os"));
2968
+ import_node_path5 = __toESM(require("path"));
2969
+ import_node_crypto6 = __toESM(require("crypto"));
2859
2970
  init_hook_env();
2860
2971
  OWNER_MARKER = "--owner mindgraph";
2861
2972
  RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
2862
2973
  RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
2974
+ CLAUDE_POST_TOOL_MATCHER = "mcp__mindgraph__.*|Edit|MultiEdit|Write|NotebookEdit|Bash|Task|Agent";
2863
2975
  }
2864
2976
  });
2865
2977
 
@@ -3101,6 +3213,14 @@ function hiddenSections(payload) {
3101
3213
  function renderContext(response, maxChars) {
3102
3214
  const payload = obj(response);
3103
3215
  if (!payload) return void 0;
3216
+ if (typeof payload.context_text === "string" && payload.context_text.length > 0) {
3217
+ const text2 = payload.context_text;
3218
+ if (maxChars !== void 0 && text2.length > maxChars) {
3219
+ return `${text2.slice(0, maxChars)}
3220
+ [truncated to ${maxChars} characters]`;
3221
+ }
3222
+ return text2;
3223
+ }
3104
3224
  const graph = obj(payload.graph);
3105
3225
  const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
3106
3226
  const shaped = rawNodes.filter(isNodeShaped);
@@ -3323,15 +3443,15 @@ function renderTraversal(response, maxChars) {
3323
3443
  const labelByUid = /* @__PURE__ */ new Map();
3324
3444
  for (const node of nodes) labelByUid.set(str2(node.uid), str2(node.label) || "?");
3325
3445
  if (paths.length > 0) {
3326
- const items = paths.map((raw) => obj(raw)).filter((path8) => Boolean(path8)).map((path8, index) => {
3327
- const uids = Array.isArray(path8.node_uids) ? path8.node_uids.map(String) : [];
3328
- const labels = Array.isArray(path8.labels) ? path8.labels.map(String) : Array.isArray(path8.nodes) ? path8.nodes.map((hop) => {
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) => {
3329
3449
  const hopNode = obj(hop);
3330
3450
  return str2(hopNode?.label) || labelByUid.get(str2(hopNode?.uid) || "") || str2(hop) || "?";
3331
3451
  }) : [];
3332
3452
  const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
3333
- const cost = num(path8.cost) ?? num(path8.path_cost);
3334
- const confidence = num(path8.path_confidence);
3453
+ const cost = num(path9.cost) ?? num(path9.path_cost);
3454
+ const confidence = num(path9.path_confidence);
3335
3455
  const meta = [
3336
3456
  cost !== void 0 ? `cost ${cost}` : void 0,
3337
3457
  confidence !== void 0 ? `confidence ${confidence}` : void 0
@@ -4383,15 +4503,15 @@ function obj2(value) {
4383
4503
  function text(value) {
4384
4504
  return typeof value === "string" && value.length > 0 ? value : void 0;
4385
4505
  }
4386
- function stableJson(value) {
4387
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
4506
+ function stableJson2(value) {
4507
+ if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
4388
4508
  if (value && typeof value === "object") {
4389
- return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
4509
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
4390
4510
  }
4391
4511
  return JSON.stringify(value);
4392
4512
  }
4393
4513
  function sha2562(value) {
4394
- return (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
4514
+ return (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
4395
4515
  }
4396
4516
  function outputBudget(value) {
4397
4517
  if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
@@ -4697,7 +4817,7 @@ async function handleMemoryTool(client2, args, config = {}) {
4697
4817
  scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
4698
4818
  });
4699
4819
  const contextId = `ctx_${sha2562(
4700
- stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
4820
+ stableJson2({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
4701
4821
  ).slice(0, 24)}`;
4702
4822
  const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
4703
4823
  const result = {
@@ -4742,11 +4862,11 @@ async function handleMemoryTool(client2, args, config = {}) {
4742
4862
  structuredContent: result
4743
4863
  };
4744
4864
  }
4745
- var import_node_crypto6, RESULT_SCHEMA_VERSION, DEFAULT_OUTPUT_CHARS, MIN_OUTPUT_CHARS, MAX_OUTPUT_CHARS, HIDDEN_CONTEXT_NODE_TYPES, HIDDEN_CONTEXT_EDGE_TYPES, MEMORY_TOOL;
4865
+ var import_node_crypto7, RESULT_SCHEMA_VERSION, DEFAULT_OUTPUT_CHARS, MIN_OUTPUT_CHARS, MAX_OUTPUT_CHARS, HIDDEN_CONTEXT_NODE_TYPES, HIDDEN_CONTEXT_EDGE_TYPES, MEMORY_TOOL;
4746
4866
  var init_memory_tool = __esm({
4747
4867
  "src/memory-tool.ts"() {
4748
4868
  "use strict";
4749
- import_node_crypto6 = require("crypto");
4869
+ import_node_crypto7 = require("crypto");
4750
4870
  init_render();
4751
4871
  RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
4752
4872
  DEFAULT_OUTPUT_CHARS = 8e3;
@@ -5016,12 +5136,12 @@ var init_resources = __esm({
5016
5136
 
5017
5137
  // src/sync-tool.ts
5018
5138
  function safePath(root, candidate) {
5019
- const absolute = import_node_path5.default.resolve(root, candidate);
5020
- const relative2 = import_node_path5.default.relative(root, absolute);
5021
- if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
5139
+ const absolute = import_node_path6.default.resolve(root, candidate);
5140
+ const relative2 = import_node_path6.default.relative(root, absolute);
5141
+ if (relative2.startsWith("..") || import_node_path6.default.isAbsolute(relative2)) {
5022
5142
  throw new Error(`path escapes repository root: ${candidate}`);
5023
5143
  }
5024
- if (import_node_fs6.default.existsSync(absolute)) {
5144
+ if (import_node_fs7.default.existsSync(absolute)) {
5025
5145
  if (!pathContains(realPathOf(root), realPathOf(absolute))) {
5026
5146
  throw new Error(`path escapes repository root via symlink: ${candidate}`);
5027
5147
  }
@@ -5030,26 +5150,26 @@ function safePath(root, candidate) {
5030
5150
  }
5031
5151
  function realPathOf(target) {
5032
5152
  try {
5033
- return import_node_fs6.default.realpathSync(target);
5153
+ return import_node_fs7.default.realpathSync(target);
5034
5154
  } catch {
5035
- return import_node_path5.default.resolve(target);
5155
+ return import_node_path6.default.resolve(target);
5036
5156
  }
5037
5157
  }
5038
5158
  function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
5039
- if (!import_node_fs6.default.existsSync(target)) return [];
5159
+ if (!import_node_fs7.default.existsSync(target)) return [];
5040
5160
  const real = realPathOf(target);
5041
5161
  if (!pathContains(realRoot, real)) return [];
5042
- const stat = import_node_fs6.default.statSync(target);
5162
+ const stat = import_node_fs7.default.statSync(target);
5043
5163
  if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
5044
5164
  if (!stat.isDirectory() || visited.has(real)) return [];
5045
5165
  visited.add(real);
5046
- return import_node_fs6.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name), realRoot, visited));
5166
+ return import_node_fs7.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path6.default.join(target, entry.name), realRoot, visited));
5047
5167
  }
5048
5168
  function fileDescriptor(root, absolute) {
5049
- const content = import_node_fs6.default.readFileSync(absolute, "utf8");
5169
+ const content = import_node_fs7.default.readFileSync(absolute, "utf8");
5050
5170
  return {
5051
- logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
5052
- content_hash: import_node_crypto7.default.createHash("sha256").update(content).digest("hex"),
5171
+ logical_path: import_node_path6.default.relative(root, absolute).split(import_node_path6.default.sep).join("/"),
5172
+ content_hash: import_node_crypto8.default.createHash("sha256").update(content).digest("hex"),
5053
5173
  bytes: Buffer.byteLength(content),
5054
5174
  content
5055
5175
  };
@@ -5118,7 +5238,7 @@ async function handleSyncTool(client2, args) {
5118
5238
  }
5119
5239
  if (action === "status" && request.logical_path && request.file_present !== false) {
5120
5240
  const absolute = safePath(root, String(request.logical_path));
5121
- request.file_present = import_node_fs6.default.existsSync(absolute);
5241
+ request.file_present = import_node_fs7.default.existsSync(absolute);
5122
5242
  if (request.file_present && !request.content_hash) {
5123
5243
  request.content_hash = fileDescriptor(root, absolute).content_hash;
5124
5244
  }
@@ -5162,13 +5282,13 @@ async function scanRepository(remote, repository, requested, provider, agentId)
5162
5282
  files: results
5163
5283
  };
5164
5284
  }
5165
- var import_node_crypto7, import_node_fs6, import_node_path5, ok3, err4, SYNC_TOOL;
5285
+ var import_node_crypto8, import_node_fs7, import_node_path6, ok3, err4, SYNC_TOOL;
5166
5286
  var init_sync_tool = __esm({
5167
5287
  "src/sync-tool.ts"() {
5168
5288
  "use strict";
5169
- import_node_crypto7 = __toESM(require("crypto"));
5170
- import_node_fs6 = __toESM(require("fs"));
5171
- import_node_path5 = __toESM(require("path"));
5289
+ import_node_crypto8 = __toESM(require("crypto"));
5290
+ import_node_fs7 = __toESM(require("fs"));
5291
+ import_node_path6 = __toESM(require("path"));
5172
5292
  init_codegraph();
5173
5293
  init_error_detail();
5174
5294
  ok3 = (data) => ({
@@ -5250,10 +5370,10 @@ var init_sync_tool = __esm({
5250
5370
  });
5251
5371
 
5252
5372
  // src/tools.ts
5253
- function stableJson2(value) {
5254
- if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
5373
+ function stableJson3(value) {
5374
+ if (Array.isArray(value)) return `[${value.map(stableJson3).join(",")}]`;
5255
5375
  if (value && typeof value === "object") {
5256
- return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
5376
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson3(item)}`).join(",")}}`;
5257
5377
  }
5258
5378
  return JSON.stringify(value);
5259
5379
  }
@@ -5267,8 +5387,8 @@ function defaultWriteIdempotencyKey(tool, args) {
5267
5387
  ([key]) => key !== "invocation_context" && key !== "idempotency_key"
5268
5388
  )
5269
5389
  );
5270
- return (0, import_node_crypto8.createHash)("sha256").update(
5271
- stableJson2({
5390
+ return (0, import_node_crypto9.createHash)("sha256").update(
5391
+ stableJson3({
5272
5392
  harness_session_id: sessionId,
5273
5393
  harness_turn_id: context?.harnessTurnId ?? null,
5274
5394
  tool,
@@ -6073,6 +6193,8 @@ async function handleRetrieve(client2, args, profile) {
6073
6193
  salience_min,
6074
6194
  article_limit,
6075
6195
  valid_at,
6196
+ as_of,
6197
+ retrieval_mode,
6076
6198
  created_after,
6077
6199
  created_before,
6078
6200
  explain,
@@ -6101,9 +6223,15 @@ async function handleRetrieve(client2, args, profile) {
6101
6223
  // the advertised knob must not get a silent no-op).
6102
6224
  ...article_limit !== void 0 ? { article_limit } : include_documents === false || profile === "coding" && !include_documents ? { article_limit: 0 } : {},
6103
6225
  valid_at,
6226
+ as_of,
6227
+ retrieval_mode,
6104
6228
  include_graph,
6105
6229
  graph_expansion_limit: graph_expansion_limit ?? 3,
6106
- graph_max_depth: graph_max_depth ?? 2
6230
+ graph_max_depth: graph_max_depth ?? 2,
6231
+ // Evidence plan R2: the server's canonical rendering under a token
6232
+ // budget derived from the character budget; the local renderer is
6233
+ // the fallback for servers that predate it.
6234
+ ...format === "json" ? {} : { format: "text", max_tokens: Math.max(256, Math.floor((max_output_chars ?? 24e3) / 4)) }
6107
6235
  }),
6108
6236
  (raw) => renderContext(raw, max_output_chars)
6109
6237
  );
@@ -6553,11 +6681,11 @@ async function handleOntology(client2, args) {
6553
6681
  return err5(`Unknown ontology action: ${action}`);
6554
6682
  }
6555
6683
  }
6556
- var import_node_crypto8, RESOLUTION_PROPERTIES, TOOLS, INVOCATION_CONTEXT_SCHEMA;
6684
+ var import_node_crypto9, RESOLUTION_PROPERTIES, TOOLS, INVOCATION_CONTEXT_SCHEMA;
6557
6685
  var init_tools = __esm({
6558
6686
  "src/tools.ts"() {
6559
6687
  "use strict";
6560
- import_node_crypto8 = require("crypto");
6688
+ import_node_crypto9 = require("crypto");
6561
6689
  init_error_detail();
6562
6690
  init_code_tool();
6563
6691
  init_codegraph();
@@ -7282,6 +7410,15 @@ var init_tools = __esm({
7282
7410
  type: "string",
7283
7411
  description: "ISO-8601 date (e.g. '2021-06') for context: nodes with validity windows are annotated as of this date instead of today"
7284
7412
  },
7413
+ as_of: {
7414
+ type: "string",
7415
+ description: "ISO-8601 date for context: answer with what had been STATED by this date (a node is admitted when one of its sources was stated on or before it; fact dates never exclude it). Use for 'what did we know / what was the value as of <date>' questions."
7416
+ },
7417
+ retrieval_mode: {
7418
+ type: "string",
7419
+ enum: ["normal", "exhaustive"],
7420
+ description: "context: 'exhaustive' widens retrieval for list/count questions ('how many different \u2026 across', 'what two \u2026'); omitted lets the server's cue decide."
7421
+ },
7285
7422
  created_after: {
7286
7423
  type: "number",
7287
7424
  description: "Unix seconds \u2014 'recent' action: only nodes ingested after this time"
@@ -7732,14 +7869,14 @@ var init_tools = __esm({
7732
7869
  // src/server.ts
7733
7870
  function buildMindGraphServer(wiring) {
7734
7871
  const { client: client2, hosted } = wiring;
7735
- const server2 = new import_server.Server(
7872
+ const server = new import_server.Server(
7736
7873
  { name: "mindgraph", version: wiring.serverVersion },
7737
7874
  {
7738
7875
  capabilities: { tools: {}, resources: {}, prompts: {} },
7739
7876
  instructions: wiring.instructions
7740
7877
  }
7741
7878
  );
7742
- server2.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
7879
+ server.setRequestHandler("tools/list", async () => {
7743
7880
  const generated = await getGeneratedTools(client2, wiring.generatedTools);
7744
7881
  const tools = [...toolsForProfile(wiring.profile), ...generated.tools];
7745
7882
  if (!hosted) return { tools };
@@ -7750,9 +7887,14 @@ function buildMindGraphServer(wiring) {
7750
7887
  ]
7751
7888
  };
7752
7889
  });
7753
- server2.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
7890
+ server.setRequestHandler("tools/call", async (request) => {
7754
7891
  const { name, arguments: args } = request.params;
7755
- const rawArgs = { ...args || {} };
7892
+ const rawArgs = {
7893
+ ...args || {},
7894
+ // H1: session, lease and invocation fields the local hook recorded for
7895
+ // this exact call (stdio only; hosted mode has no local hook).
7896
+ ...hosted ? {} : wiring.resolveHookFields?.(name, args || {})
7897
+ };
7756
7898
  const toolArgs = { ...rawArgs };
7757
7899
  let governanceConfig = wiring.governance;
7758
7900
  if (hosted) {
@@ -7792,20 +7934,20 @@ function buildMindGraphServer(wiring) {
7792
7934
  }
7793
7935
  return handleTool(client2, name, toolArgs, wiring.memory);
7794
7936
  });
7795
- server2.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
7937
+ server.setRequestHandler("prompts/list", async () => {
7796
7938
  return { prompts: PROMPTS };
7797
7939
  });
7798
- server2.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
7940
+ server.setRequestHandler("prompts/get", async (request) => {
7799
7941
  const { name, arguments: args } = request.params;
7800
7942
  return getPrompt(name, args);
7801
7943
  });
7802
- server2.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
7944
+ server.setRequestHandler("resources/list", async () => {
7803
7945
  return { resources: RESOURCES };
7804
7946
  });
7805
- server2.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => {
7947
+ server.setRequestHandler("resources/templates/list", async () => {
7806
7948
  return { resourceTemplates: RESOURCE_TEMPLATES };
7807
7949
  });
7808
- server2.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
7950
+ server.setRequestHandler("resources/read", async (request) => {
7809
7951
  return readGovernedResource(
7810
7952
  client2,
7811
7953
  request.params.uri,
@@ -7813,15 +7955,13 @@ function buildMindGraphServer(wiring) {
7813
7955
  wiring.memory
7814
7956
  );
7815
7957
  });
7816
- return server2;
7958
+ return server;
7817
7959
  }
7818
- var import_server, import_webStandardStreamableHttp, import_types;
7960
+ var import_server;
7819
7961
  var init_server = __esm({
7820
7962
  "src/server.ts"() {
7821
7963
  "use strict";
7822
- import_server = require("@modelcontextprotocol/sdk/server/index.js");
7823
- import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
7824
- import_types = require("@modelcontextprotocol/sdk/types.js");
7964
+ import_server = require("@modelcontextprotocol/server");
7825
7965
  init_generated_tools();
7826
7966
  init_governance();
7827
7967
  init_hosted_tools();
@@ -7834,21 +7974,23 @@ var init_server = __esm({
7834
7974
  // src/index.ts
7835
7975
  var src_exports = {};
7836
7976
  async function main() {
7837
- const transport = new import_stdio.StdioServerTransport();
7838
- await server.connect(transport);
7977
+ (0, import_stdio.serveStdio)(buildServer, {
7978
+ onerror: (error2) => console.error("MindGraph MCP transport error:", error2.message)
7979
+ });
7839
7980
  console.error(
7840
- `MindGraph MCP server v${"0.23.0"} running on stdio (${BASE_URL})`
7981
+ `MindGraph MCP server v${"0.25.0"} running on stdio (${BASE_URL})`
7841
7982
  );
7842
7983
  }
7843
- var import_stdio, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, GOVERNANCE_CONFIG, MEMORY_RUNTIME_CONFIG, server;
7984
+ var import_stdio, import_node_path7, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, GOVERNANCE_CONFIG, MEMORY_RUNTIME_CONFIG, buildServer;
7844
7985
  var init_src = __esm({
7845
7986
  "src/index.ts"() {
7846
7987
  "use strict";
7847
- import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
7988
+ init_call_ledger();
7989
+ import_stdio = require("@modelcontextprotocol/server/stdio");
7848
7990
  init_dist();
7849
7991
  init_hook_env();
7850
7992
  init_server();
7851
- import_node_path6 = __toESM(require("path"));
7993
+ import_node_path7 = __toESM(require("path"));
7852
7994
  init_hook_installer();
7853
7995
  API_KEY = process.env.MINDGRAPH_API_KEY;
7854
7996
  BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
@@ -7941,7 +8083,7 @@ Capture knowledge when the user shares something worth remembering:
7941
8083
  - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
7942
8084
  - If a write returns \`near_duplicate\`, choose \`reuse_uid\` or \`supersedes_uid\`; use \`confirm_new\` only for genuinely distinct facts.
7943
8085
  - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
7944
- - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
8086
+ - Adapter context is owned by ${HARNESS}; omit invocation_context because the adapter supplies it.`;
7945
8087
  } else {
7946
8088
  INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
7947
8089
  (line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
@@ -7949,7 +8091,7 @@ Capture knowledge when the user shares something worth remembering:
7949
8091
  }
7950
8092
  RUNNER_REFRESH = (() => {
7951
8093
  try {
7952
- return refreshHookRunner("0.23.0", import_node_path6.default.join(__dirname, "cli.js"));
8094
+ return refreshHookRunner("0.25.0", import_node_path7.default.join(__dirname, "cli.js"));
7953
8095
  } catch {
7954
8096
  return void 0;
7955
8097
  }
@@ -7963,12 +8105,12 @@ Capture knowledge when the user shares something worth remembering:
7963
8105
  }
7964
8106
  })();
7965
8107
  console.error(
7966
- `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.23.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
8108
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.25.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
7967
8109
  );
7968
8110
  }
7969
8111
  HOOK_SKEW_NOTE = (() => {
7970
8112
  try {
7971
- return versionSkewNote("0.23.0");
8113
+ return versionSkewNote("0.25.0");
7972
8114
  } catch {
7973
8115
  return void 0;
7974
8116
  }
@@ -7989,21 +8131,22 @@ ${HOOK_SKEW_NOTE}`;
7989
8131
  }
7990
8132
  };
7991
8133
  MEMORY_RUNTIME_CONFIG = {
7992
- serverVersion: "0.23.0",
8134
+ serverVersion: "0.25.0",
7993
8135
  baseUrl: BASE_URL,
7994
8136
  profile: PROFILE,
7995
8137
  harness: HARNESS,
7996
8138
  orgId: ORG_ID,
7997
8139
  agentId: AGENT_ID
7998
8140
  };
7999
- server = buildMindGraphServer({
8141
+ buildServer = () => buildMindGraphServer({
8000
8142
  client,
8001
- serverVersion: "0.23.0",
8143
+ serverVersion: "0.25.0",
8002
8144
  instructions: INSTRUCTIONS,
8003
8145
  profile: PROFILE,
8004
8146
  agentId: AGENT_ID,
8005
8147
  governance: GOVERNANCE_CONFIG,
8006
- memory: MEMORY_RUNTIME_CONFIG
8148
+ memory: MEMORY_RUNTIME_CONFIG,
8149
+ resolveHookFields: (name, args) => resolvePendingCall(name, args)
8007
8150
  });
8008
8151
  main().catch((error2) => {
8009
8152
  console.error("Fatal error:", error2);
@@ -8013,10 +8156,10 @@ ${HOOK_SKEW_NOTE}`;
8013
8156
  });
8014
8157
 
8015
8158
  // src/cli.ts
8016
- var fs7 = __toESM(require("fs"));
8017
- var path7 = __toESM(require("path"));
8018
- var os4 = __toESM(require("os"));
8019
- var crypto5 = __toESM(require("crypto"));
8159
+ var fs8 = __toESM(require("fs"));
8160
+ var path8 = __toESM(require("path"));
8161
+ var os5 = __toESM(require("os"));
8162
+ var crypto6 = __toESM(require("crypto"));
8020
8163
  var import_child_process = require("child_process");
8021
8164
  init_dist();
8022
8165
  init_hook_env();
@@ -8498,24 +8641,25 @@ async function skillsPush(api, skillPath) {
8498
8641
  }
8499
8642
 
8500
8643
  // src/hook-core.ts
8501
- var import_node_crypto4 = __toESM(require("crypto"));
8502
- var import_node_fs4 = __toESM(require("fs"));
8503
- var import_node_os3 = __toESM(require("os"));
8504
- var import_node_path3 = __toESM(require("path"));
8644
+ var import_node_crypto5 = __toESM(require("crypto"));
8645
+ var import_node_fs5 = __toESM(require("fs"));
8646
+ var import_node_os4 = __toESM(require("os"));
8647
+ var import_node_path4 = __toESM(require("path"));
8505
8648
  var import_node_child_process2 = require("child_process");
8506
8649
  init_codegraph();
8507
8650
  init_code_tool();
8651
+ init_call_ledger();
8508
8652
  init_error_detail();
8509
8653
  init_hook_env();
8510
8654
 
8511
8655
  // src/hook-memory.ts
8512
- var import_node_fs3 = __toESM(require("fs"));
8656
+ var import_node_fs4 = __toESM(require("fs"));
8513
8657
  var PROMPT_RECALL_LIMIT = 5;
8514
8658
  var PROMPT_RECALL_MAX_CHARS = 4e3;
8515
8659
  var MIN_PROMPT_RECALL_CHARS = 12;
8516
8660
  var CAPTURE_MAX_CHARS = 2e5;
8517
8661
  var STOPWORDS = new Set(
8518
- "a an and are as at be but by can could did do does for from had has have how i if in into is it its me my of on or our please should so that the their them then there these they this to us was we were what when where which who why will with would you your about just also any some tell know want need let lets like get got make".split(" ")
8662
+ "a an and are as at be but by can could did do does for from had has have how i if in into is it its me my of on or our please should so that the their them then there these they this to us was we were what when where which who why will with would you your about just also any some tell know want need let lets like get got make yes no not ok okay sure may might must up down out off over under again after before while without within including include use using used keep now next here than very more most other each both all one two been being am done going go".split(" ")
8519
8663
  );
8520
8664
  var WORD = /[A-Za-z0-9][A-Za-z0-9_.\-/#+]*/g;
8521
8665
  var SECRET_PATTERNS = [
@@ -8560,6 +8704,27 @@ function promptRecallRequest(query) {
8560
8704
  graph_max_depth: 2
8561
8705
  };
8562
8706
  }
8707
+ function wordParts(text2) {
8708
+ return text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
8709
+ }
8710
+ var sameWord = (left, right) => left === right || left.length >= 4 && right.length >= 4 && (left.startsWith(right) || right.startsWith(left));
8711
+ function recallTermOverlap(query, text2) {
8712
+ const hay = wordParts(text2);
8713
+ let matched = 0;
8714
+ let usable = 0;
8715
+ for (const term of new Set(query.split(/\s+/).filter(Boolean))) {
8716
+ const parts = wordParts(term).filter((part) => part.length >= 3 && !/^\d+$/.test(part));
8717
+ if (parts.length === 0) continue;
8718
+ usable += 1;
8719
+ if (parts.some((part) => hay.some((word) => sameWord(word, part)))) matched += 1;
8720
+ }
8721
+ return { matched, usable };
8722
+ }
8723
+ function relevant(query, text2) {
8724
+ if (query === void 0) return true;
8725
+ const { matched, usable } = recallTermOverlap(query, text2);
8726
+ return usable > 0 && matched >= Math.min(2, usable);
8727
+ }
8563
8728
  var rec = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8564
8729
  var str = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
8565
8730
  var oneLine = (value, max) => {
@@ -8571,7 +8736,7 @@ function nodeType(node) {
8571
8736
  const plain = str(node.node_type);
8572
8737
  return (plain && plain !== "Custom" ? plain : void 0) ?? str(rec(node.node_type)?.Custom) ?? plain ?? "Node";
8573
8738
  }
8574
- function renderPromptRecall(response) {
8739
+ function renderPromptRecall(response, query) {
8575
8740
  const payload = rec(response);
8576
8741
  if (!payload) return void 0;
8577
8742
  const lines = [];
@@ -8585,6 +8750,9 @@ function renderPromptRecall(response) {
8585
8750
  const label = str(node.label) ?? str(props?.label) ?? str(props?.name);
8586
8751
  if (!label) continue;
8587
8752
  const summary = str(node.summary) ?? str(props?.summary) ?? str(props?.description) ?? str(props?.content);
8753
+ const informative = summary && summary !== label ? summary : void 0;
8754
+ if (!informative && type === "Entity") continue;
8755
+ if (!relevant(query, `${label} ${informative ?? ""}`)) continue;
8588
8756
  const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
8589
8757
  const source = str(rec(docs[0])?.title);
8590
8758
  let line = `- [${type}] ${oneLine(label, 140)}`;
@@ -8600,6 +8768,7 @@ function renderPromptRecall(response) {
8600
8768
  const label = str(article.label);
8601
8769
  if (!label) continue;
8602
8770
  const content = str(article.content);
8771
+ if (!relevant(query, `${label} ${content ?? ""}`)) continue;
8603
8772
  lines.push(`- [Article] ${oneLine(label, 140)}${content ? ` \u2014 ${oneLine(content, 240)}` : ""}`);
8604
8773
  }
8605
8774
  if (lines.length === 0) return void 0;
@@ -8649,7 +8818,7 @@ function renderTranscript(turns) {
8649
8818
  return start >= 0 ? tail.slice(start + 2) : tail;
8650
8819
  }
8651
8820
  function readTranscriptSince(file, offset = 0) {
8652
- const buffer = import_node_fs3.default.readFileSync(file);
8821
+ const buffer = import_node_fs4.default.readFileSync(file);
8653
8822
  const start = offset > buffer.length ? 0 : offset;
8654
8823
  const lastNewline = buffer.lastIndexOf(10);
8655
8824
  if (lastNewline < start) return { text: "", nextOffset: start };
@@ -8693,33 +8862,33 @@ function defaultLedger(sessionId, options) {
8693
8862
  };
8694
8863
  }
8695
8864
  function runtimeDir(options) {
8696
- return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
8865
+ return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path4.default.join(import_node_os4.default.homedir(), ".mindgraph", "runtime");
8697
8866
  }
8698
8867
  function ledgerPath(sessionId, options) {
8699
8868
  const harness = "harness" in options ? options.harness : "claude-code";
8700
- const key = import_node_crypto4.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
8701
- return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
8869
+ const key = import_node_crypto5.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
8870
+ return import_node_path4.default.join(runtimeDir(options), `${key}.json`);
8702
8871
  }
8703
8872
  function waitBriefly(ms) {
8704
8873
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
8705
8874
  }
8706
8875
  function withLedger(sessionId, options, update) {
8707
8876
  const file = ledgerPath(sessionId, options);
8708
- import_node_fs4.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true, mode: 448 });
8877
+ import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true, mode: 448 });
8709
8878
  try {
8710
- import_node_fs4.default.chmodSync(import_node_path3.default.dirname(file), 448);
8879
+ import_node_fs5.default.chmodSync(import_node_path4.default.dirname(file), 448);
8711
8880
  } catch {
8712
8881
  }
8713
8882
  const lock = `${file}.lock`;
8714
8883
  let lockFd;
8715
8884
  for (let attempt = 0; attempt < 400; attempt++) {
8716
8885
  try {
8717
- lockFd = import_node_fs4.default.openSync(lock, "wx", 384);
8886
+ lockFd = import_node_fs5.default.openSync(lock, "wx", 384);
8718
8887
  break;
8719
8888
  } catch (error2) {
8720
8889
  if (error2.code !== "EEXIST") throw error2;
8721
8890
  try {
8722
- if (Date.now() - import_node_fs4.default.statSync(lock).mtimeMs > 3e3) import_node_fs4.default.unlinkSync(lock);
8891
+ if (Date.now() - import_node_fs5.default.statSync(lock).mtimeMs > 3e3) import_node_fs5.default.unlinkSync(lock);
8723
8892
  } catch {
8724
8893
  }
8725
8894
  waitBriefly(10);
@@ -8733,25 +8902,25 @@ function withLedger(sessionId, options, update) {
8733
8902
  try {
8734
8903
  ledger = {
8735
8904
  ...ledger,
8736
- ...JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"))
8905
+ ...JSON.parse(import_node_fs5.default.readFileSync(file, "utf8"))
8737
8906
  };
8738
8907
  } catch {
8739
8908
  }
8740
8909
  const result = update(ledger);
8741
8910
  ledger.updatedAt = Date.now();
8742
- const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
8743
- import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
8911
+ const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
8912
+ import_node_fs5.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
8744
8913
  `, { mode: 384 });
8745
- import_node_fs4.default.renameSync(temporary, file);
8914
+ import_node_fs5.default.renameSync(temporary, file);
8746
8915
  try {
8747
- import_node_fs4.default.chmodSync(file, 384);
8916
+ import_node_fs5.default.chmodSync(file, 384);
8748
8917
  } catch {
8749
8918
  }
8750
8919
  return result;
8751
8920
  } finally {
8752
- import_node_fs4.default.closeSync(lockFd);
8921
+ import_node_fs5.default.closeSync(lockFd);
8753
8922
  try {
8754
- import_node_fs4.default.unlinkSync(lock);
8923
+ import_node_fs5.default.unlinkSync(lock);
8755
8924
  } catch {
8756
8925
  }
8757
8926
  }
@@ -9021,30 +9190,30 @@ function hasMindgraphServer(servers) {
9021
9190
  return false;
9022
9191
  }
9023
9192
  function pathIsWithin(ancestor, descendant) {
9024
- const base = import_node_path3.default.resolve(ancestor);
9025
- const target = import_node_path3.default.resolve(descendant);
9026
- return target === base || target.startsWith(base + import_node_path3.default.sep);
9193
+ const base = import_node_path4.default.resolve(ancestor);
9194
+ const target = import_node_path4.default.resolve(descendant);
9195
+ return target === base || target.startsWith(base + import_node_path4.default.sep);
9027
9196
  }
9028
9197
  function claudeMcpRegistrationNote(input, options) {
9029
9198
  if (options.harness !== "claude-code") return void 0;
9030
9199
  if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
9031
9200
  try {
9032
- const configPath = options.claudeConfigPath || import_node_path3.default.join(import_node_os3.default.homedir(), ".claude.json");
9033
- if (!import_node_fs4.default.existsSync(configPath)) return void 0;
9034
- const config = JSON.parse(import_node_fs4.default.readFileSync(configPath, "utf8"));
9201
+ const configPath = options.claudeConfigPath || import_node_path4.default.join(import_node_os4.default.homedir(), ".claude.json");
9202
+ if (!import_node_fs5.default.existsSync(configPath)) return void 0;
9203
+ const config = JSON.parse(import_node_fs5.default.readFileSync(configPath, "utf8"));
9035
9204
  if (hasMindgraphServer(config.mcpServers)) return void 0;
9036
- const cwd = import_node_path3.default.resolve(input.cwd || ".");
9205
+ const cwd = import_node_path4.default.resolve(input.cwd || ".");
9037
9206
  for (const [projectDir, project] of Object.entries(config.projects || {})) {
9038
9207
  if (!pathIsWithin(projectDir, cwd)) continue;
9039
9208
  if (hasMindgraphServer(project?.mcpServers)) return void 0;
9040
9209
  }
9041
- for (let dir = cwd; ; dir = import_node_path3.default.dirname(dir)) {
9042
- const candidate = import_node_path3.default.join(dir, ".mcp.json");
9043
- if (import_node_fs4.default.existsSync(candidate)) {
9044
- const parsed = JSON.parse(import_node_fs4.default.readFileSync(candidate, "utf8"));
9210
+ for (let dir = cwd; ; dir = import_node_path4.default.dirname(dir)) {
9211
+ const candidate = import_node_path4.default.join(dir, ".mcp.json");
9212
+ if (import_node_fs5.default.existsSync(candidate)) {
9213
+ const parsed = JSON.parse(import_node_fs5.default.readFileSync(candidate, "utf8"));
9045
9214
  if (hasMindgraphServer(parsed.mcpServers)) return void 0;
9046
9215
  }
9047
- if (dir === import_node_path3.default.dirname(dir)) break;
9216
+ if (dir === import_node_path4.default.dirname(dir)) break;
9048
9217
  }
9049
9218
  } catch {
9050
9219
  return void 0;
@@ -9147,7 +9316,7 @@ async function sessionStart(input, client2, options) {
9147
9316
  const contextText = registrationNote ? `${registrationNote}
9148
9317
 
9149
9318
  ${briefText}` : briefText;
9150
- const briefHash = import_node_crypto4.default.createHash("sha256").update(contextText).digest("hex");
9319
+ const briefHash = import_node_crypto5.default.createHash("sha256").update(contextText).digest("hex");
9151
9320
  const shouldInject = withLedger(input.session_id, options, (ledger) => {
9152
9321
  const changed = ledger.lastBriefHash !== briefHash;
9153
9322
  const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
@@ -9222,6 +9391,20 @@ async function preTool(input, options) {
9222
9391
  if (targetsLedgerTask && fencedAction && ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
9223
9392
  updatedInput.expected_version = ledger.taskVersion;
9224
9393
  }
9394
+ if (options.harness === "claude-code") {
9395
+ const fills = Object.fromEntries(
9396
+ Object.entries(updatedInput).filter(
9397
+ ([key, value]) => key === "invocation_context" || original[key] !== value
9398
+ )
9399
+ );
9400
+ recordPendingCall(
9401
+ input.tool_name || "",
9402
+ original,
9403
+ input.session_id,
9404
+ fills,
9405
+ { runtimeDir: options.runtimeDir, now: options.now }
9406
+ );
9407
+ }
9225
9408
  return { kind: "rewrite", updatedInput };
9226
9409
  }
9227
9410
  var FENCED_PLAN_ACTIONS = /* @__PURE__ */ new Set([
@@ -9448,9 +9631,9 @@ async function promptRecall(input, client2, options) {
9448
9631
  client2.retrieveContext(promptRecallRequest(query)),
9449
9632
  options.promptRecallTimeoutMs ?? 4e3
9450
9633
  );
9451
- const block = renderPromptRecall(response);
9634
+ const block = renderPromptRecall(response, query);
9452
9635
  if (!block) return NOOP;
9453
- const hash = import_node_crypto4.default.createHash("sha256").update(block).digest("hex");
9636
+ const hash = import_node_crypto5.default.createHash("sha256").update(block).digest("hex");
9454
9637
  const fresh = withLedger(input.session_id, options, (ledger) => {
9455
9638
  if (ledger.lastRecallHash === hash) return false;
9456
9639
  ledger.lastRecallHash = hash;
@@ -9501,6 +9684,32 @@ async function captureTranscript(input, client2, options) {
9501
9684
  }
9502
9685
  }
9503
9686
  async function runHookCore(input, client2, options) {
9687
+ const started = performance.now();
9688
+ const output = await dispatchHook(input, client2, options);
9689
+ if (input.session_id && !(input.hook_event_name === "PreToolUse" && output.kind === "noop")) {
9690
+ try {
9691
+ const elapsed = Math.round(performance.now() - started);
9692
+ const injected = output.kind === "context" ? output.context.length : 0;
9693
+ withLedger(input.session_id, options, (ledger) => {
9694
+ const stats = ledger.hookStats ??= {};
9695
+ const entry = stats[input.hook_event_name] ??= {
9696
+ count: 0,
9697
+ totalMs: 0,
9698
+ maxMs: 0,
9699
+ injectedChars: 0
9700
+ };
9701
+ entry.count += 1;
9702
+ entry.totalMs += elapsed;
9703
+ entry.maxMs = Math.max(entry.maxMs, elapsed);
9704
+ entry.injectedChars += injected;
9705
+ return void 0;
9706
+ });
9707
+ } catch {
9708
+ }
9709
+ }
9710
+ return output;
9711
+ }
9712
+ async function dispatchHook(input, client2, options) {
9504
9713
  switch (input.hook_event_name) {
9505
9714
  case "SessionStart":
9506
9715
  return sessionStart(input, client2, options);
@@ -9545,7 +9754,7 @@ function previewCapture(transcriptPath, sessionId, options = {}) {
9545
9754
  let ledger = {};
9546
9755
  try {
9547
9756
  ledger = JSON.parse(
9548
- import_node_fs4.default.readFileSync(
9757
+ import_node_fs5.default.readFileSync(
9549
9758
  ledgerPath(sessionId, { ...options, harness: "claude-code" }),
9550
9759
  "utf8"
9551
9760
  )
@@ -9561,6 +9770,34 @@ function previewCapture(transcriptPath, sessionId, options = {}) {
9561
9770
  offset
9562
9771
  };
9563
9772
  }
9773
+ function readHookStats(sessionId, options = {}) {
9774
+ const dir = runtimeDir(options);
9775
+ let best;
9776
+ let names = [];
9777
+ try {
9778
+ names = import_node_fs5.default.readdirSync(dir).filter((name) => /^[0-9a-f]{64}\.json$/.test(name));
9779
+ } catch {
9780
+ return void 0;
9781
+ }
9782
+ for (const name of names) {
9783
+ try {
9784
+ const ledger = JSON.parse(import_node_fs5.default.readFileSync(import_node_path4.default.join(dir, name), "utf8"));
9785
+ if (!ledger.harnessSessionId || !ledger.hookStats) continue;
9786
+ if (sessionId && ledger.harnessSessionId !== sessionId) continue;
9787
+ const updatedAt = Number(ledger.updatedAt) || 0;
9788
+ if (!best || updatedAt > best.updatedAt) {
9789
+ best = {
9790
+ harness: String(ledger.harness ?? "unknown"),
9791
+ sessionId: ledger.harnessSessionId,
9792
+ updatedAt,
9793
+ events: ledger.hookStats
9794
+ };
9795
+ }
9796
+ } catch {
9797
+ }
9798
+ }
9799
+ return best;
9800
+ }
9564
9801
 
9565
9802
  // src/claude-hooks.ts
9566
9803
  async function runClaudeHook(input, client2, options = {}) {
@@ -9580,12 +9817,7 @@ async function runClaudeHook(input, client2, options = {}) {
9580
9817
  }
9581
9818
  };
9582
9819
  case "rewrite":
9583
- return {
9584
- hookSpecificOutput: {
9585
- hookEventName: "PreToolUse",
9586
- updatedInput: output.updatedInput
9587
- }
9588
- };
9820
+ return {};
9589
9821
  case "block":
9590
9822
  return { decision: "block", reason: output.reason };
9591
9823
  case "noop":
@@ -9628,15 +9860,15 @@ async function runCodexHook(input, client2, options = {}) {
9628
9860
  init_hook_installer();
9629
9861
  function recordHookHealth(harness, error2) {
9630
9862
  try {
9631
- const dir = process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime");
9632
- fs7.mkdirSync(dir, { recursive: true, mode: 448 });
9633
- const file = path7.join(dir, "hook-health.json");
9863
+ const dir = process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime");
9864
+ fs8.mkdirSync(dir, { recursive: true, mode: 448 });
9865
+ const file = path8.join(dir, "hook-health.json");
9634
9866
  let failures = 0;
9635
9867
  try {
9636
- failures = Number(JSON.parse(fs7.readFileSync(file, "utf8")).consecutiveFailures) || 0;
9868
+ failures = Number(JSON.parse(fs8.readFileSync(file, "utf8")).consecutiveFailures) || 0;
9637
9869
  } catch {
9638
9870
  }
9639
- fs7.writeFileSync(
9871
+ fs8.writeFileSync(
9640
9872
  file,
9641
9873
  `${JSON.stringify({
9642
9874
  harness,
@@ -9652,14 +9884,14 @@ function recordHookHealth(harness, error2) {
9652
9884
  }
9653
9885
  function clearHookHealth(harness) {
9654
9886
  try {
9655
- const file = path7.join(
9656
- process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
9887
+ const file = path8.join(
9888
+ process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime"),
9657
9889
  "hook-health.json"
9658
9890
  );
9659
- if (!fs7.existsSync(file)) return;
9660
- const current = JSON.parse(fs7.readFileSync(file, "utf8"));
9891
+ if (!fs8.existsSync(file)) return;
9892
+ const current = JSON.parse(fs8.readFileSync(file, "utf8"));
9661
9893
  if (!Number(current.consecutiveFailures)) return;
9662
- fs7.writeFileSync(
9894
+ fs8.writeFileSync(
9663
9895
  file,
9664
9896
  `${JSON.stringify({
9665
9897
  harness,
@@ -9675,22 +9907,22 @@ function clearHookHealth(harness) {
9675
9907
  function getClaudeDesktopConfigPath() {
9676
9908
  switch (process.platform) {
9677
9909
  case "darwin":
9678
- return path7.join(
9679
- os4.homedir(),
9910
+ return path8.join(
9911
+ os5.homedir(),
9680
9912
  "Library",
9681
9913
  "Application Support",
9682
9914
  "Claude",
9683
9915
  "claude_desktop_config.json"
9684
9916
  );
9685
9917
  case "win32":
9686
- return path7.join(
9687
- process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming"),
9918
+ return path8.join(
9919
+ process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming"),
9688
9920
  "Claude",
9689
9921
  "claude_desktop_config.json"
9690
9922
  );
9691
9923
  case "linux":
9692
- return path7.join(
9693
- os4.homedir(),
9924
+ return path8.join(
9925
+ os5.homedir(),
9694
9926
  ".config",
9695
9927
  "Claude",
9696
9928
  "claude_desktop_config.json"
@@ -9700,16 +9932,16 @@ function getClaudeDesktopConfigPath() {
9700
9932
  }
9701
9933
  }
9702
9934
  function readJsonFile(filePath) {
9703
- if (!fs7.existsSync(filePath)) return {};
9704
- const content = fs7.readFileSync(filePath, "utf-8");
9935
+ if (!fs8.existsSync(filePath)) return {};
9936
+ const content = fs8.readFileSync(filePath, "utf-8");
9705
9937
  return JSON.parse(content);
9706
9938
  }
9707
9939
  function writeJsonFile(filePath, data) {
9708
- const dir = path7.dirname(filePath);
9709
- if (!fs7.existsSync(dir)) {
9710
- fs7.mkdirSync(dir, { recursive: true });
9940
+ const dir = path8.dirname(filePath);
9941
+ if (!fs8.existsSync(dir)) {
9942
+ fs8.mkdirSync(dir, { recursive: true });
9711
9943
  }
9712
- fs7.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
9944
+ fs8.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
9713
9945
  }
9714
9946
  function getMcpServerEntry(apiKey, baseUrl) {
9715
9947
  const env = { MINDGRAPH_API_KEY: apiKey };
@@ -9796,7 +10028,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
9796
10028
  }
9797
10029
  function uninstallClaudeDesktop() {
9798
10030
  const configPath = getClaudeDesktopConfigPath();
9799
- if (!fs7.existsSync(configPath)) {
10031
+ if (!fs8.existsSync(configPath)) {
9800
10032
  console.log("Claude Desktop config not found. Nothing to remove.");
9801
10033
  return;
9802
10034
  }
@@ -9823,7 +10055,7 @@ function uninstallClaudeCode() {
9823
10055
  }
9824
10056
  function printStatus() {
9825
10057
  const desktopPath = getClaudeDesktopConfigPath();
9826
- if (fs7.existsSync(desktopPath)) {
10058
+ if (fs8.existsSync(desktopPath)) {
9827
10059
  const config = readJsonFile(desktopPath);
9828
10060
  const servers = config.mcpServers;
9829
10061
  if (servers && "mindgraph" in servers) {
@@ -9841,12 +10073,12 @@ function printStatus() {
9841
10073
  `MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
9842
10074
  );
9843
10075
  try {
9844
- const healthFile = path7.join(
9845
- process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
10076
+ const healthFile = path8.join(
10077
+ process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime"),
9846
10078
  "hook-health.json"
9847
10079
  );
9848
- if (fs7.existsSync(healthFile)) {
9849
- const health = JSON.parse(fs7.readFileSync(healthFile, "utf8"));
10080
+ if (fs8.existsSync(healthFile)) {
10081
+ const health = JSON.parse(fs8.readFileSync(healthFile, "utf8"));
9850
10082
  const failures = Number(health.consecutiveFailures) || 0;
9851
10083
  console.log(
9852
10084
  failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
@@ -9875,21 +10107,21 @@ function printClaudeHookFeatures(features) {
9875
10107
  );
9876
10108
  }
9877
10109
  function latestTranscript(cwd) {
9878
- const dir = path7.join(
9879
- os4.homedir(),
10110
+ const dir = path8.join(
10111
+ os5.homedir(),
9880
10112
  ".claude",
9881
10113
  "projects",
9882
10114
  cwd.replace(/[^A-Za-z0-9]/g, "-")
9883
10115
  );
9884
10116
  try {
9885
- return fs7.readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => path7.join(dir, name)).sort((a, b) => fs7.statSync(b).mtimeMs - fs7.statSync(a).mtimeMs)[0];
10117
+ return fs8.readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => path8.join(dir, name)).sort((a, b) => fs8.statSync(b).mtimeMs - fs8.statSync(a).mtimeMs)[0];
9886
10118
  } catch {
9887
10119
  return void 0;
9888
10120
  }
9889
10121
  }
9890
10122
  function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
9891
10123
  if (withHooks) {
9892
- const runner = installHookRunner(__filename, void 0, "0.23.0");
10124
+ const runner = installHookRunner(__filename, void 0, "0.25.0");
9893
10125
  console.log(`Installed pinned hook runner at ${runner}`);
9894
10126
  const features = claudeHookFeatures();
9895
10127
  const result = installClaudeHooks("user", void 0, features);
@@ -9942,6 +10174,9 @@ USAGE:
9942
10174
  mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]
9943
10175
  Print exactly what transcript capture would
9944
10176
  send (POST /ingest/session); sends nothing.
10177
+ mindgraph-mcp hooks stats [--session-id <id>]
10178
+ Per-event hook calls, time and characters
10179
+ injected for a session (latest by default).
9945
10180
  mindgraph-mcp status Show installation status
9946
10181
  mindgraph-mcp skills pull [--dir .claude/skills]
9947
10182
  Write your org's published+granted skills as
@@ -9987,7 +10222,7 @@ var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cl
9987
10222
  var POLL_INTERVAL = 2e3;
9988
10223
  var POLL_TIMEOUT = 10 * 60 * 1e3;
9989
10224
  function generateCode() {
9990
- return crypto5.randomBytes(32).toString("hex");
10225
+ return crypto6.randomBytes(32).toString("hex");
9991
10226
  }
9992
10227
  function openBrowser(url) {
9993
10228
  try {
@@ -10214,7 +10449,7 @@ ${total} skill(s) synced.`);
10214
10449
  if (hookHarness !== "claude-code" && hookHarness !== "codex") {
10215
10450
  throw new Error("--harness must be claude-code or codex");
10216
10451
  }
10217
- const runner = installHookRunner(__filename, void 0, "0.23.0");
10452
+ const runner = installHookRunner(__filename, void 0, "0.25.0");
10218
10453
  console.log(`Installed pinned hook runner at ${runner}`);
10219
10454
  const features = hookHarness === "claude-code" ? claudeHookFeatures() : void 0;
10220
10455
  const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir, features);
@@ -10318,18 +10553,42 @@ ${total} skill(s) synced.`);
10318
10553
  break;
10319
10554
  }
10320
10555
  case "hooks": {
10556
+ if (positionals[0] === "stats") {
10557
+ const stats = readHookStats(parseArgs(process.argv).sessionId);
10558
+ if (!stats) {
10559
+ console.error(
10560
+ "No hook statistics recorded yet. They are kept per session once the hooks from this version have run."
10561
+ );
10562
+ process.exit(1);
10563
+ }
10564
+ const rows = Object.entries(stats.events).sort(([a], [b]) => a.localeCompare(b));
10565
+ const injected = rows.reduce((sum, [, row]) => sum + row.injectedChars, 0);
10566
+ console.log(`Session ${stats.sessionId} (${stats.harness}), last active ${new Date(stats.updatedAt).toISOString()}`);
10567
+ console.log("event calls avg ms max ms injected chars");
10568
+ for (const [event, row] of rows) {
10569
+ console.log(
10570
+ `${event.padEnd(18)} ${String(row.count).padStart(6)} ${String(Math.round(row.totalMs / Math.max(row.count, 1))).padStart(8)} ${String(row.maxMs).padStart(8)} ${String(row.injectedChars).padStart(16)}`
10571
+ );
10572
+ }
10573
+ console.log(
10574
+ `Injected into context: ${injected} characters (about ${Math.round(injected / 4)} tokens). Times are measured inside the hook and exclude process start.`
10575
+ );
10576
+ break;
10577
+ }
10321
10578
  if (positionals[0] !== "preview") {
10322
- console.error("Usage: mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]");
10579
+ console.error(
10580
+ "Usage: mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]\n mindgraph-mcp hooks stats [--session-id <id>]"
10581
+ );
10323
10582
  process.exit(1);
10324
10583
  }
10325
10584
  const transcript = positionals[1] || latestTranscript(process.cwd());
10326
- if (!transcript || !fs7.existsSync(transcript)) {
10585
+ if (!transcript || !fs8.existsSync(transcript)) {
10327
10586
  console.error(
10328
10587
  "No transcript found. Pass a Claude Code transcript path (~/.claude/projects/<dir>/<session>.jsonl)."
10329
10588
  );
10330
10589
  process.exit(1);
10331
10590
  }
10332
- const sessionId = parseArgs(process.argv).sessionId || path7.basename(transcript, ".jsonl");
10591
+ const sessionId = parseArgs(process.argv).sessionId || path8.basename(transcript, ".jsonl");
10333
10592
  const preview = previewCapture(transcript, sessionId, {
10334
10593
  agentId: process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId
10335
10594
  });