mindgraph-mcp 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/cli.js +448 -188
- package/dist/index.js +226 -129
- package/dist/server.d.mts +4 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +32 -2
- package/dist/server.mjs +32 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -35,14 +35,14 @@ function errorFields(body) {
|
|
|
35
35
|
...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
function canRetryRequest(method,
|
|
38
|
+
function canRetryRequest(method, path9, body) {
|
|
39
39
|
if (method === "GET" || method === "HEAD") return true;
|
|
40
40
|
if (method !== "POST") return false;
|
|
41
|
-
if (READ_POSTS.has(
|
|
41
|
+
if (READ_POSTS.has(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,
|
|
45
|
-
return
|
|
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,
|
|
183
|
-
const url = `${this.baseUrl}${
|
|
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
|
-
|
|
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,
|
|
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} ${
|
|
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,
|
|
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} ${
|
|
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(
|
|
259
|
-
return this.request("GET",
|
|
258
|
+
get(path9) {
|
|
259
|
+
return this.request("GET", path9);
|
|
260
260
|
}
|
|
261
|
-
post(
|
|
262
|
-
return this.request("POST",
|
|
261
|
+
post(path9, body) {
|
|
262
|
+
return this.request("POST", path9, body);
|
|
263
263
|
}
|
|
264
|
-
patch(
|
|
265
|
-
return this.request("PATCH",
|
|
264
|
+
patch(path9, body) {
|
|
265
|
+
return this.request("PATCH", path9, body);
|
|
266
266
|
}
|
|
267
|
-
del(
|
|
268
|
-
return this.request("DELETE",
|
|
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(
|
|
1244
|
-
return
|
|
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(
|
|
1250
|
-
const extension = (0, import_node_path2.extname)(
|
|
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
|
-
|
|
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 =
|
|
2582
|
-
|
|
2583
|
-
const temporary = `${target}.${process.pid}.${
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
const versionPath =
|
|
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
|
-
|
|
2689
|
+
import_node_fs6.default.writeFileSync(versionPath, `${version}
|
|
2589
2690
|
`);
|
|
2590
2691
|
} else {
|
|
2591
|
-
|
|
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 =
|
|
2598
|
-
|
|
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,11 +2721,11 @@ 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 ||
|
|
2624
|
-
if (!
|
|
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 (!
|
|
2728
|
+
if (!import_node_fs6.default.existsSync(sourceBundle)) {
|
|
2628
2729
|
return { refreshed: false, reason: "no_source" };
|
|
2629
2730
|
}
|
|
2630
2731
|
const server2 = parseSemver(serverVersion);
|
|
@@ -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 (!
|
|
2659
|
-
if (!
|
|
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 ||
|
|
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" ?
|
|
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" ?
|
|
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 (!
|
|
2687
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
2766
|
-
const temporary = `${file}.${process.pid}.${
|
|
2767
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
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((
|
|
3327
|
-
const uids = Array.isArray(
|
|
3328
|
-
const labels = Array.isArray(
|
|
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(
|
|
3334
|
-
const confidence = num(
|
|
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
|
|
4387
|
-
if (Array.isArray(value)) return `[${value.map(
|
|
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)}:${
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
5020
|
-
const relative2 =
|
|
5021
|
-
if (relative2.startsWith("..") ||
|
|
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 (
|
|
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
|
|
5153
|
+
return import_node_fs7.default.realpathSync(target);
|
|
5034
5154
|
} catch {
|
|
5035
|
-
return
|
|
5155
|
+
return import_node_path6.default.resolve(target);
|
|
5036
5156
|
}
|
|
5037
5157
|
}
|
|
5038
5158
|
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
5039
|
-
if (!
|
|
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 =
|
|
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
|
|
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 =
|
|
5169
|
+
const content = import_node_fs7.default.readFileSync(absolute, "utf8");
|
|
5050
5170
|
return {
|
|
5051
|
-
logical_path:
|
|
5052
|
-
content_hash:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
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
|
|
5254
|
-
if (Array.isArray(value)) return `[${value.map(
|
|
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)}:${
|
|
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,
|
|
5271
|
-
|
|
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
|
|
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
|
-
|
|
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"
|
|
@@ -7752,7 +7889,12 @@ function buildMindGraphServer(wiring) {
|
|
|
7752
7889
|
});
|
|
7753
7890
|
server2.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
7754
7891
|
const { name, arguments: args } = request.params;
|
|
7755
|
-
const rawArgs = {
|
|
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) {
|
|
@@ -7837,18 +7979,19 @@ async function main() {
|
|
|
7837
7979
|
const transport = new import_stdio.StdioServerTransport();
|
|
7838
7980
|
await server.connect(transport);
|
|
7839
7981
|
console.error(
|
|
7840
|
-
`MindGraph MCP server v${"0.
|
|
7982
|
+
`MindGraph MCP server v${"0.24.0"} running on stdio (${BASE_URL})`
|
|
7841
7983
|
);
|
|
7842
7984
|
}
|
|
7843
|
-
var import_stdio,
|
|
7985
|
+
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, server;
|
|
7844
7986
|
var init_src = __esm({
|
|
7845
7987
|
"src/index.ts"() {
|
|
7846
7988
|
"use strict";
|
|
7989
|
+
init_call_ledger();
|
|
7847
7990
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7848
7991
|
init_dist();
|
|
7849
7992
|
init_hook_env();
|
|
7850
7993
|
init_server();
|
|
7851
|
-
|
|
7994
|
+
import_node_path7 = __toESM(require("path"));
|
|
7852
7995
|
init_hook_installer();
|
|
7853
7996
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
7854
7997
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
@@ -7941,7 +8084,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
7941
8084
|
- Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
|
|
7942
8085
|
- If a write returns \`near_duplicate\`, choose \`reuse_uid\` or \`supersedes_uid\`; use \`confirm_new\` only for genuinely distinct facts.
|
|
7943
8086
|
- 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
|
|
8087
|
+
- Adapter context is owned by ${HARNESS}; omit invocation_context because the adapter supplies it.`;
|
|
7945
8088
|
} else {
|
|
7946
8089
|
INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
|
|
7947
8090
|
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
@@ -7949,7 +8092,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
7949
8092
|
}
|
|
7950
8093
|
RUNNER_REFRESH = (() => {
|
|
7951
8094
|
try {
|
|
7952
|
-
return refreshHookRunner("0.
|
|
8095
|
+
return refreshHookRunner("0.24.0", import_node_path7.default.join(__dirname, "cli.js"));
|
|
7953
8096
|
} catch {
|
|
7954
8097
|
return void 0;
|
|
7955
8098
|
}
|
|
@@ -7963,12 +8106,12 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
7963
8106
|
}
|
|
7964
8107
|
})();
|
|
7965
8108
|
console.error(
|
|
7966
|
-
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.
|
|
8109
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.24.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
7967
8110
|
);
|
|
7968
8111
|
}
|
|
7969
8112
|
HOOK_SKEW_NOTE = (() => {
|
|
7970
8113
|
try {
|
|
7971
|
-
return versionSkewNote("0.
|
|
8114
|
+
return versionSkewNote("0.24.0");
|
|
7972
8115
|
} catch {
|
|
7973
8116
|
return void 0;
|
|
7974
8117
|
}
|
|
@@ -7989,7 +8132,7 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
7989
8132
|
}
|
|
7990
8133
|
};
|
|
7991
8134
|
MEMORY_RUNTIME_CONFIG = {
|
|
7992
|
-
serverVersion: "0.
|
|
8135
|
+
serverVersion: "0.24.0",
|
|
7993
8136
|
baseUrl: BASE_URL,
|
|
7994
8137
|
profile: PROFILE,
|
|
7995
8138
|
harness: HARNESS,
|
|
@@ -7998,12 +8141,13 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
7998
8141
|
};
|
|
7999
8142
|
server = buildMindGraphServer({
|
|
8000
8143
|
client,
|
|
8001
|
-
serverVersion: "0.
|
|
8144
|
+
serverVersion: "0.24.0",
|
|
8002
8145
|
instructions: INSTRUCTIONS,
|
|
8003
8146
|
profile: PROFILE,
|
|
8004
8147
|
agentId: AGENT_ID,
|
|
8005
8148
|
governance: GOVERNANCE_CONFIG,
|
|
8006
|
-
memory: MEMORY_RUNTIME_CONFIG
|
|
8149
|
+
memory: MEMORY_RUNTIME_CONFIG,
|
|
8150
|
+
resolveHookFields: (name, args) => resolvePendingCall(name, args)
|
|
8007
8151
|
});
|
|
8008
8152
|
main().catch((error2) => {
|
|
8009
8153
|
console.error("Fatal error:", error2);
|
|
@@ -8013,10 +8157,10 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
8013
8157
|
});
|
|
8014
8158
|
|
|
8015
8159
|
// src/cli.ts
|
|
8016
|
-
var
|
|
8017
|
-
var
|
|
8018
|
-
var
|
|
8019
|
-
var
|
|
8160
|
+
var fs8 = __toESM(require("fs"));
|
|
8161
|
+
var path8 = __toESM(require("path"));
|
|
8162
|
+
var os5 = __toESM(require("os"));
|
|
8163
|
+
var crypto6 = __toESM(require("crypto"));
|
|
8020
8164
|
var import_child_process = require("child_process");
|
|
8021
8165
|
init_dist();
|
|
8022
8166
|
init_hook_env();
|
|
@@ -8498,24 +8642,25 @@ async function skillsPush(api, skillPath) {
|
|
|
8498
8642
|
}
|
|
8499
8643
|
|
|
8500
8644
|
// src/hook-core.ts
|
|
8501
|
-
var
|
|
8502
|
-
var
|
|
8503
|
-
var
|
|
8504
|
-
var
|
|
8645
|
+
var import_node_crypto5 = __toESM(require("crypto"));
|
|
8646
|
+
var import_node_fs5 = __toESM(require("fs"));
|
|
8647
|
+
var import_node_os4 = __toESM(require("os"));
|
|
8648
|
+
var import_node_path4 = __toESM(require("path"));
|
|
8505
8649
|
var import_node_child_process2 = require("child_process");
|
|
8506
8650
|
init_codegraph();
|
|
8507
8651
|
init_code_tool();
|
|
8652
|
+
init_call_ledger();
|
|
8508
8653
|
init_error_detail();
|
|
8509
8654
|
init_hook_env();
|
|
8510
8655
|
|
|
8511
8656
|
// src/hook-memory.ts
|
|
8512
|
-
var
|
|
8657
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
8513
8658
|
var PROMPT_RECALL_LIMIT = 5;
|
|
8514
8659
|
var PROMPT_RECALL_MAX_CHARS = 4e3;
|
|
8515
8660
|
var MIN_PROMPT_RECALL_CHARS = 12;
|
|
8516
8661
|
var CAPTURE_MAX_CHARS = 2e5;
|
|
8517
8662
|
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(" ")
|
|
8663
|
+
"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
8664
|
);
|
|
8520
8665
|
var WORD = /[A-Za-z0-9][A-Za-z0-9_.\-/#+]*/g;
|
|
8521
8666
|
var SECRET_PATTERNS = [
|
|
@@ -8560,6 +8705,27 @@ function promptRecallRequest(query) {
|
|
|
8560
8705
|
graph_max_depth: 2
|
|
8561
8706
|
};
|
|
8562
8707
|
}
|
|
8708
|
+
function wordParts(text2) {
|
|
8709
|
+
return text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
8710
|
+
}
|
|
8711
|
+
var sameWord = (left, right) => left === right || left.length >= 4 && right.length >= 4 && (left.startsWith(right) || right.startsWith(left));
|
|
8712
|
+
function recallTermOverlap(query, text2) {
|
|
8713
|
+
const hay = wordParts(text2);
|
|
8714
|
+
let matched = 0;
|
|
8715
|
+
let usable = 0;
|
|
8716
|
+
for (const term of new Set(query.split(/\s+/).filter(Boolean))) {
|
|
8717
|
+
const parts = wordParts(term).filter((part) => part.length >= 3 && !/^\d+$/.test(part));
|
|
8718
|
+
if (parts.length === 0) continue;
|
|
8719
|
+
usable += 1;
|
|
8720
|
+
if (parts.some((part) => hay.some((word) => sameWord(word, part)))) matched += 1;
|
|
8721
|
+
}
|
|
8722
|
+
return { matched, usable };
|
|
8723
|
+
}
|
|
8724
|
+
function relevant(query, text2) {
|
|
8725
|
+
if (query === void 0) return true;
|
|
8726
|
+
const { matched, usable } = recallTermOverlap(query, text2);
|
|
8727
|
+
return usable > 0 && matched >= Math.min(2, usable);
|
|
8728
|
+
}
|
|
8563
8729
|
var rec = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8564
8730
|
var str = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
8565
8731
|
var oneLine = (value, max) => {
|
|
@@ -8571,7 +8737,7 @@ function nodeType(node) {
|
|
|
8571
8737
|
const plain = str(node.node_type);
|
|
8572
8738
|
return (plain && plain !== "Custom" ? plain : void 0) ?? str(rec(node.node_type)?.Custom) ?? plain ?? "Node";
|
|
8573
8739
|
}
|
|
8574
|
-
function renderPromptRecall(response) {
|
|
8740
|
+
function renderPromptRecall(response, query) {
|
|
8575
8741
|
const payload = rec(response);
|
|
8576
8742
|
if (!payload) return void 0;
|
|
8577
8743
|
const lines = [];
|
|
@@ -8585,6 +8751,9 @@ function renderPromptRecall(response) {
|
|
|
8585
8751
|
const label = str(node.label) ?? str(props?.label) ?? str(props?.name);
|
|
8586
8752
|
if (!label) continue;
|
|
8587
8753
|
const summary = str(node.summary) ?? str(props?.summary) ?? str(props?.description) ?? str(props?.content);
|
|
8754
|
+
const informative = summary && summary !== label ? summary : void 0;
|
|
8755
|
+
if (!informative && type === "Entity") continue;
|
|
8756
|
+
if (!relevant(query, `${label} ${informative ?? ""}`)) continue;
|
|
8588
8757
|
const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
|
|
8589
8758
|
const source = str(rec(docs[0])?.title);
|
|
8590
8759
|
let line = `- [${type}] ${oneLine(label, 140)}`;
|
|
@@ -8600,6 +8769,7 @@ function renderPromptRecall(response) {
|
|
|
8600
8769
|
const label = str(article.label);
|
|
8601
8770
|
if (!label) continue;
|
|
8602
8771
|
const content = str(article.content);
|
|
8772
|
+
if (!relevant(query, `${label} ${content ?? ""}`)) continue;
|
|
8603
8773
|
lines.push(`- [Article] ${oneLine(label, 140)}${content ? ` \u2014 ${oneLine(content, 240)}` : ""}`);
|
|
8604
8774
|
}
|
|
8605
8775
|
if (lines.length === 0) return void 0;
|
|
@@ -8649,7 +8819,7 @@ function renderTranscript(turns) {
|
|
|
8649
8819
|
return start >= 0 ? tail.slice(start + 2) : tail;
|
|
8650
8820
|
}
|
|
8651
8821
|
function readTranscriptSince(file, offset = 0) {
|
|
8652
|
-
const buffer =
|
|
8822
|
+
const buffer = import_node_fs4.default.readFileSync(file);
|
|
8653
8823
|
const start = offset > buffer.length ? 0 : offset;
|
|
8654
8824
|
const lastNewline = buffer.lastIndexOf(10);
|
|
8655
8825
|
if (lastNewline < start) return { text: "", nextOffset: start };
|
|
@@ -8693,33 +8863,33 @@ function defaultLedger(sessionId, options) {
|
|
|
8693
8863
|
};
|
|
8694
8864
|
}
|
|
8695
8865
|
function runtimeDir(options) {
|
|
8696
|
-
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
8866
|
+
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path4.default.join(import_node_os4.default.homedir(), ".mindgraph", "runtime");
|
|
8697
8867
|
}
|
|
8698
8868
|
function ledgerPath(sessionId, options) {
|
|
8699
8869
|
const harness = "harness" in options ? options.harness : "claude-code";
|
|
8700
|
-
const key =
|
|
8701
|
-
return
|
|
8870
|
+
const key = import_node_crypto5.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
8871
|
+
return import_node_path4.default.join(runtimeDir(options), `${key}.json`);
|
|
8702
8872
|
}
|
|
8703
8873
|
function waitBriefly(ms) {
|
|
8704
8874
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
8705
8875
|
}
|
|
8706
8876
|
function withLedger(sessionId, options, update) {
|
|
8707
8877
|
const file = ledgerPath(sessionId, options);
|
|
8708
|
-
|
|
8878
|
+
import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true, mode: 448 });
|
|
8709
8879
|
try {
|
|
8710
|
-
|
|
8880
|
+
import_node_fs5.default.chmodSync(import_node_path4.default.dirname(file), 448);
|
|
8711
8881
|
} catch {
|
|
8712
8882
|
}
|
|
8713
8883
|
const lock = `${file}.lock`;
|
|
8714
8884
|
let lockFd;
|
|
8715
8885
|
for (let attempt = 0; attempt < 400; attempt++) {
|
|
8716
8886
|
try {
|
|
8717
|
-
lockFd =
|
|
8887
|
+
lockFd = import_node_fs5.default.openSync(lock, "wx", 384);
|
|
8718
8888
|
break;
|
|
8719
8889
|
} catch (error2) {
|
|
8720
8890
|
if (error2.code !== "EEXIST") throw error2;
|
|
8721
8891
|
try {
|
|
8722
|
-
if (Date.now() -
|
|
8892
|
+
if (Date.now() - import_node_fs5.default.statSync(lock).mtimeMs > 3e3) import_node_fs5.default.unlinkSync(lock);
|
|
8723
8893
|
} catch {
|
|
8724
8894
|
}
|
|
8725
8895
|
waitBriefly(10);
|
|
@@ -8733,25 +8903,25 @@ function withLedger(sessionId, options, update) {
|
|
|
8733
8903
|
try {
|
|
8734
8904
|
ledger = {
|
|
8735
8905
|
...ledger,
|
|
8736
|
-
...JSON.parse(
|
|
8906
|
+
...JSON.parse(import_node_fs5.default.readFileSync(file, "utf8"))
|
|
8737
8907
|
};
|
|
8738
8908
|
} catch {
|
|
8739
8909
|
}
|
|
8740
8910
|
const result = update(ledger);
|
|
8741
8911
|
ledger.updatedAt = Date.now();
|
|
8742
|
-
const temporary = `${file}.${process.pid}.${
|
|
8743
|
-
|
|
8912
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
|
|
8913
|
+
import_node_fs5.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
8744
8914
|
`, { mode: 384 });
|
|
8745
|
-
|
|
8915
|
+
import_node_fs5.default.renameSync(temporary, file);
|
|
8746
8916
|
try {
|
|
8747
|
-
|
|
8917
|
+
import_node_fs5.default.chmodSync(file, 384);
|
|
8748
8918
|
} catch {
|
|
8749
8919
|
}
|
|
8750
8920
|
return result;
|
|
8751
8921
|
} finally {
|
|
8752
|
-
|
|
8922
|
+
import_node_fs5.default.closeSync(lockFd);
|
|
8753
8923
|
try {
|
|
8754
|
-
|
|
8924
|
+
import_node_fs5.default.unlinkSync(lock);
|
|
8755
8925
|
} catch {
|
|
8756
8926
|
}
|
|
8757
8927
|
}
|
|
@@ -9021,30 +9191,30 @@ function hasMindgraphServer(servers) {
|
|
|
9021
9191
|
return false;
|
|
9022
9192
|
}
|
|
9023
9193
|
function pathIsWithin(ancestor, descendant) {
|
|
9024
|
-
const base =
|
|
9025
|
-
const target =
|
|
9026
|
-
return target === base || target.startsWith(base +
|
|
9194
|
+
const base = import_node_path4.default.resolve(ancestor);
|
|
9195
|
+
const target = import_node_path4.default.resolve(descendant);
|
|
9196
|
+
return target === base || target.startsWith(base + import_node_path4.default.sep);
|
|
9027
9197
|
}
|
|
9028
9198
|
function claudeMcpRegistrationNote(input, options) {
|
|
9029
9199
|
if (options.harness !== "claude-code") return void 0;
|
|
9030
9200
|
if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
|
|
9031
9201
|
try {
|
|
9032
|
-
const configPath = options.claudeConfigPath ||
|
|
9033
|
-
if (!
|
|
9034
|
-
const config = JSON.parse(
|
|
9202
|
+
const configPath = options.claudeConfigPath || import_node_path4.default.join(import_node_os4.default.homedir(), ".claude.json");
|
|
9203
|
+
if (!import_node_fs5.default.existsSync(configPath)) return void 0;
|
|
9204
|
+
const config = JSON.parse(import_node_fs5.default.readFileSync(configPath, "utf8"));
|
|
9035
9205
|
if (hasMindgraphServer(config.mcpServers)) return void 0;
|
|
9036
|
-
const cwd =
|
|
9206
|
+
const cwd = import_node_path4.default.resolve(input.cwd || ".");
|
|
9037
9207
|
for (const [projectDir, project] of Object.entries(config.projects || {})) {
|
|
9038
9208
|
if (!pathIsWithin(projectDir, cwd)) continue;
|
|
9039
9209
|
if (hasMindgraphServer(project?.mcpServers)) return void 0;
|
|
9040
9210
|
}
|
|
9041
|
-
for (let dir = cwd; ; dir =
|
|
9042
|
-
const candidate =
|
|
9043
|
-
if (
|
|
9044
|
-
const parsed = JSON.parse(
|
|
9211
|
+
for (let dir = cwd; ; dir = import_node_path4.default.dirname(dir)) {
|
|
9212
|
+
const candidate = import_node_path4.default.join(dir, ".mcp.json");
|
|
9213
|
+
if (import_node_fs5.default.existsSync(candidate)) {
|
|
9214
|
+
const parsed = JSON.parse(import_node_fs5.default.readFileSync(candidate, "utf8"));
|
|
9045
9215
|
if (hasMindgraphServer(parsed.mcpServers)) return void 0;
|
|
9046
9216
|
}
|
|
9047
|
-
if (dir ===
|
|
9217
|
+
if (dir === import_node_path4.default.dirname(dir)) break;
|
|
9048
9218
|
}
|
|
9049
9219
|
} catch {
|
|
9050
9220
|
return void 0;
|
|
@@ -9147,7 +9317,7 @@ async function sessionStart(input, client2, options) {
|
|
|
9147
9317
|
const contextText = registrationNote ? `${registrationNote}
|
|
9148
9318
|
|
|
9149
9319
|
${briefText}` : briefText;
|
|
9150
|
-
const briefHash =
|
|
9320
|
+
const briefHash = import_node_crypto5.default.createHash("sha256").update(contextText).digest("hex");
|
|
9151
9321
|
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
9152
9322
|
const changed = ledger.lastBriefHash !== briefHash;
|
|
9153
9323
|
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
@@ -9222,6 +9392,20 @@ async function preTool(input, options) {
|
|
|
9222
9392
|
if (targetsLedgerTask && fencedAction && ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
9223
9393
|
updatedInput.expected_version = ledger.taskVersion;
|
|
9224
9394
|
}
|
|
9395
|
+
if (options.harness === "claude-code") {
|
|
9396
|
+
const fills = Object.fromEntries(
|
|
9397
|
+
Object.entries(updatedInput).filter(
|
|
9398
|
+
([key, value]) => key === "invocation_context" || original[key] !== value
|
|
9399
|
+
)
|
|
9400
|
+
);
|
|
9401
|
+
recordPendingCall(
|
|
9402
|
+
input.tool_name || "",
|
|
9403
|
+
original,
|
|
9404
|
+
input.session_id,
|
|
9405
|
+
fills,
|
|
9406
|
+
{ runtimeDir: options.runtimeDir, now: options.now }
|
|
9407
|
+
);
|
|
9408
|
+
}
|
|
9225
9409
|
return { kind: "rewrite", updatedInput };
|
|
9226
9410
|
}
|
|
9227
9411
|
var FENCED_PLAN_ACTIONS = /* @__PURE__ */ new Set([
|
|
@@ -9448,9 +9632,9 @@ async function promptRecall(input, client2, options) {
|
|
|
9448
9632
|
client2.retrieveContext(promptRecallRequest(query)),
|
|
9449
9633
|
options.promptRecallTimeoutMs ?? 4e3
|
|
9450
9634
|
);
|
|
9451
|
-
const block = renderPromptRecall(response);
|
|
9635
|
+
const block = renderPromptRecall(response, query);
|
|
9452
9636
|
if (!block) return NOOP;
|
|
9453
|
-
const hash =
|
|
9637
|
+
const hash = import_node_crypto5.default.createHash("sha256").update(block).digest("hex");
|
|
9454
9638
|
const fresh = withLedger(input.session_id, options, (ledger) => {
|
|
9455
9639
|
if (ledger.lastRecallHash === hash) return false;
|
|
9456
9640
|
ledger.lastRecallHash = hash;
|
|
@@ -9501,6 +9685,32 @@ async function captureTranscript(input, client2, options) {
|
|
|
9501
9685
|
}
|
|
9502
9686
|
}
|
|
9503
9687
|
async function runHookCore(input, client2, options) {
|
|
9688
|
+
const started = performance.now();
|
|
9689
|
+
const output = await dispatchHook(input, client2, options);
|
|
9690
|
+
if (input.session_id && !(input.hook_event_name === "PreToolUse" && output.kind === "noop")) {
|
|
9691
|
+
try {
|
|
9692
|
+
const elapsed = Math.round(performance.now() - started);
|
|
9693
|
+
const injected = output.kind === "context" ? output.context.length : 0;
|
|
9694
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
9695
|
+
const stats = ledger.hookStats ??= {};
|
|
9696
|
+
const entry = stats[input.hook_event_name] ??= {
|
|
9697
|
+
count: 0,
|
|
9698
|
+
totalMs: 0,
|
|
9699
|
+
maxMs: 0,
|
|
9700
|
+
injectedChars: 0
|
|
9701
|
+
};
|
|
9702
|
+
entry.count += 1;
|
|
9703
|
+
entry.totalMs += elapsed;
|
|
9704
|
+
entry.maxMs = Math.max(entry.maxMs, elapsed);
|
|
9705
|
+
entry.injectedChars += injected;
|
|
9706
|
+
return void 0;
|
|
9707
|
+
});
|
|
9708
|
+
} catch {
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
return output;
|
|
9712
|
+
}
|
|
9713
|
+
async function dispatchHook(input, client2, options) {
|
|
9504
9714
|
switch (input.hook_event_name) {
|
|
9505
9715
|
case "SessionStart":
|
|
9506
9716
|
return sessionStart(input, client2, options);
|
|
@@ -9545,7 +9755,7 @@ function previewCapture(transcriptPath, sessionId, options = {}) {
|
|
|
9545
9755
|
let ledger = {};
|
|
9546
9756
|
try {
|
|
9547
9757
|
ledger = JSON.parse(
|
|
9548
|
-
|
|
9758
|
+
import_node_fs5.default.readFileSync(
|
|
9549
9759
|
ledgerPath(sessionId, { ...options, harness: "claude-code" }),
|
|
9550
9760
|
"utf8"
|
|
9551
9761
|
)
|
|
@@ -9561,6 +9771,34 @@ function previewCapture(transcriptPath, sessionId, options = {}) {
|
|
|
9561
9771
|
offset
|
|
9562
9772
|
};
|
|
9563
9773
|
}
|
|
9774
|
+
function readHookStats(sessionId, options = {}) {
|
|
9775
|
+
const dir = runtimeDir(options);
|
|
9776
|
+
let best;
|
|
9777
|
+
let names = [];
|
|
9778
|
+
try {
|
|
9779
|
+
names = import_node_fs5.default.readdirSync(dir).filter((name) => /^[0-9a-f]{64}\.json$/.test(name));
|
|
9780
|
+
} catch {
|
|
9781
|
+
return void 0;
|
|
9782
|
+
}
|
|
9783
|
+
for (const name of names) {
|
|
9784
|
+
try {
|
|
9785
|
+
const ledger = JSON.parse(import_node_fs5.default.readFileSync(import_node_path4.default.join(dir, name), "utf8"));
|
|
9786
|
+
if (!ledger.harnessSessionId || !ledger.hookStats) continue;
|
|
9787
|
+
if (sessionId && ledger.harnessSessionId !== sessionId) continue;
|
|
9788
|
+
const updatedAt = Number(ledger.updatedAt) || 0;
|
|
9789
|
+
if (!best || updatedAt > best.updatedAt) {
|
|
9790
|
+
best = {
|
|
9791
|
+
harness: String(ledger.harness ?? "unknown"),
|
|
9792
|
+
sessionId: ledger.harnessSessionId,
|
|
9793
|
+
updatedAt,
|
|
9794
|
+
events: ledger.hookStats
|
|
9795
|
+
};
|
|
9796
|
+
}
|
|
9797
|
+
} catch {
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
return best;
|
|
9801
|
+
}
|
|
9564
9802
|
|
|
9565
9803
|
// src/claude-hooks.ts
|
|
9566
9804
|
async function runClaudeHook(input, client2, options = {}) {
|
|
@@ -9580,12 +9818,7 @@ async function runClaudeHook(input, client2, options = {}) {
|
|
|
9580
9818
|
}
|
|
9581
9819
|
};
|
|
9582
9820
|
case "rewrite":
|
|
9583
|
-
return {
|
|
9584
|
-
hookSpecificOutput: {
|
|
9585
|
-
hookEventName: "PreToolUse",
|
|
9586
|
-
updatedInput: output.updatedInput
|
|
9587
|
-
}
|
|
9588
|
-
};
|
|
9821
|
+
return {};
|
|
9589
9822
|
case "block":
|
|
9590
9823
|
return { decision: "block", reason: output.reason };
|
|
9591
9824
|
case "noop":
|
|
@@ -9628,15 +9861,15 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
9628
9861
|
init_hook_installer();
|
|
9629
9862
|
function recordHookHealth(harness, error2) {
|
|
9630
9863
|
try {
|
|
9631
|
-
const dir = process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
9632
|
-
|
|
9633
|
-
const file =
|
|
9864
|
+
const dir = process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime");
|
|
9865
|
+
fs8.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9866
|
+
const file = path8.join(dir, "hook-health.json");
|
|
9634
9867
|
let failures = 0;
|
|
9635
9868
|
try {
|
|
9636
|
-
failures = Number(JSON.parse(
|
|
9869
|
+
failures = Number(JSON.parse(fs8.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
9637
9870
|
} catch {
|
|
9638
9871
|
}
|
|
9639
|
-
|
|
9872
|
+
fs8.writeFileSync(
|
|
9640
9873
|
file,
|
|
9641
9874
|
`${JSON.stringify({
|
|
9642
9875
|
harness,
|
|
@@ -9652,14 +9885,14 @@ function recordHookHealth(harness, error2) {
|
|
|
9652
9885
|
}
|
|
9653
9886
|
function clearHookHealth(harness) {
|
|
9654
9887
|
try {
|
|
9655
|
-
const file =
|
|
9656
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
9888
|
+
const file = path8.join(
|
|
9889
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime"),
|
|
9657
9890
|
"hook-health.json"
|
|
9658
9891
|
);
|
|
9659
|
-
if (!
|
|
9660
|
-
const current = JSON.parse(
|
|
9892
|
+
if (!fs8.existsSync(file)) return;
|
|
9893
|
+
const current = JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
9661
9894
|
if (!Number(current.consecutiveFailures)) return;
|
|
9662
|
-
|
|
9895
|
+
fs8.writeFileSync(
|
|
9663
9896
|
file,
|
|
9664
9897
|
`${JSON.stringify({
|
|
9665
9898
|
harness,
|
|
@@ -9675,22 +9908,22 @@ function clearHookHealth(harness) {
|
|
|
9675
9908
|
function getClaudeDesktopConfigPath() {
|
|
9676
9909
|
switch (process.platform) {
|
|
9677
9910
|
case "darwin":
|
|
9678
|
-
return
|
|
9679
|
-
|
|
9911
|
+
return path8.join(
|
|
9912
|
+
os5.homedir(),
|
|
9680
9913
|
"Library",
|
|
9681
9914
|
"Application Support",
|
|
9682
9915
|
"Claude",
|
|
9683
9916
|
"claude_desktop_config.json"
|
|
9684
9917
|
);
|
|
9685
9918
|
case "win32":
|
|
9686
|
-
return
|
|
9687
|
-
process.env.APPDATA ||
|
|
9919
|
+
return path8.join(
|
|
9920
|
+
process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming"),
|
|
9688
9921
|
"Claude",
|
|
9689
9922
|
"claude_desktop_config.json"
|
|
9690
9923
|
);
|
|
9691
9924
|
case "linux":
|
|
9692
|
-
return
|
|
9693
|
-
|
|
9925
|
+
return path8.join(
|
|
9926
|
+
os5.homedir(),
|
|
9694
9927
|
".config",
|
|
9695
9928
|
"Claude",
|
|
9696
9929
|
"claude_desktop_config.json"
|
|
@@ -9700,16 +9933,16 @@ function getClaudeDesktopConfigPath() {
|
|
|
9700
9933
|
}
|
|
9701
9934
|
}
|
|
9702
9935
|
function readJsonFile(filePath) {
|
|
9703
|
-
if (!
|
|
9704
|
-
const content =
|
|
9936
|
+
if (!fs8.existsSync(filePath)) return {};
|
|
9937
|
+
const content = fs8.readFileSync(filePath, "utf-8");
|
|
9705
9938
|
return JSON.parse(content);
|
|
9706
9939
|
}
|
|
9707
9940
|
function writeJsonFile(filePath, data) {
|
|
9708
|
-
const dir =
|
|
9709
|
-
if (!
|
|
9710
|
-
|
|
9941
|
+
const dir = path8.dirname(filePath);
|
|
9942
|
+
if (!fs8.existsSync(dir)) {
|
|
9943
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
9711
9944
|
}
|
|
9712
|
-
|
|
9945
|
+
fs8.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
9713
9946
|
}
|
|
9714
9947
|
function getMcpServerEntry(apiKey, baseUrl) {
|
|
9715
9948
|
const env = { MINDGRAPH_API_KEY: apiKey };
|
|
@@ -9796,7 +10029,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
|
|
|
9796
10029
|
}
|
|
9797
10030
|
function uninstallClaudeDesktop() {
|
|
9798
10031
|
const configPath = getClaudeDesktopConfigPath();
|
|
9799
|
-
if (!
|
|
10032
|
+
if (!fs8.existsSync(configPath)) {
|
|
9800
10033
|
console.log("Claude Desktop config not found. Nothing to remove.");
|
|
9801
10034
|
return;
|
|
9802
10035
|
}
|
|
@@ -9823,7 +10056,7 @@ function uninstallClaudeCode() {
|
|
|
9823
10056
|
}
|
|
9824
10057
|
function printStatus() {
|
|
9825
10058
|
const desktopPath = getClaudeDesktopConfigPath();
|
|
9826
|
-
if (
|
|
10059
|
+
if (fs8.existsSync(desktopPath)) {
|
|
9827
10060
|
const config = readJsonFile(desktopPath);
|
|
9828
10061
|
const servers = config.mcpServers;
|
|
9829
10062
|
if (servers && "mindgraph" in servers) {
|
|
@@ -9841,12 +10074,12 @@ function printStatus() {
|
|
|
9841
10074
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
9842
10075
|
);
|
|
9843
10076
|
try {
|
|
9844
|
-
const healthFile =
|
|
9845
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
10077
|
+
const healthFile = path8.join(
|
|
10078
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime"),
|
|
9846
10079
|
"hook-health.json"
|
|
9847
10080
|
);
|
|
9848
|
-
if (
|
|
9849
|
-
const health = JSON.parse(
|
|
10081
|
+
if (fs8.existsSync(healthFile)) {
|
|
10082
|
+
const health = JSON.parse(fs8.readFileSync(healthFile, "utf8"));
|
|
9850
10083
|
const failures = Number(health.consecutiveFailures) || 0;
|
|
9851
10084
|
console.log(
|
|
9852
10085
|
failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
|
|
@@ -9875,21 +10108,21 @@ function printClaudeHookFeatures(features) {
|
|
|
9875
10108
|
);
|
|
9876
10109
|
}
|
|
9877
10110
|
function latestTranscript(cwd) {
|
|
9878
|
-
const dir =
|
|
9879
|
-
|
|
10111
|
+
const dir = path8.join(
|
|
10112
|
+
os5.homedir(),
|
|
9880
10113
|
".claude",
|
|
9881
10114
|
"projects",
|
|
9882
10115
|
cwd.replace(/[^A-Za-z0-9]/g, "-")
|
|
9883
10116
|
);
|
|
9884
10117
|
try {
|
|
9885
|
-
return
|
|
10118
|
+
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
10119
|
} catch {
|
|
9887
10120
|
return void 0;
|
|
9888
10121
|
}
|
|
9889
10122
|
}
|
|
9890
10123
|
function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
9891
10124
|
if (withHooks) {
|
|
9892
|
-
const runner = installHookRunner(__filename, void 0, "0.
|
|
10125
|
+
const runner = installHookRunner(__filename, void 0, "0.24.0");
|
|
9893
10126
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
9894
10127
|
const features = claudeHookFeatures();
|
|
9895
10128
|
const result = installClaudeHooks("user", void 0, features);
|
|
@@ -9942,6 +10175,9 @@ USAGE:
|
|
|
9942
10175
|
mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]
|
|
9943
10176
|
Print exactly what transcript capture would
|
|
9944
10177
|
send (POST /ingest/session); sends nothing.
|
|
10178
|
+
mindgraph-mcp hooks stats [--session-id <id>]
|
|
10179
|
+
Per-event hook calls, time and characters
|
|
10180
|
+
injected for a session (latest by default).
|
|
9945
10181
|
mindgraph-mcp status Show installation status
|
|
9946
10182
|
mindgraph-mcp skills pull [--dir .claude/skills]
|
|
9947
10183
|
Write your org's published+granted skills as
|
|
@@ -9987,7 +10223,7 @@ var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cl
|
|
|
9987
10223
|
var POLL_INTERVAL = 2e3;
|
|
9988
10224
|
var POLL_TIMEOUT = 10 * 60 * 1e3;
|
|
9989
10225
|
function generateCode() {
|
|
9990
|
-
return
|
|
10226
|
+
return crypto6.randomBytes(32).toString("hex");
|
|
9991
10227
|
}
|
|
9992
10228
|
function openBrowser(url) {
|
|
9993
10229
|
try {
|
|
@@ -10214,7 +10450,7 @@ ${total} skill(s) synced.`);
|
|
|
10214
10450
|
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
10215
10451
|
throw new Error("--harness must be claude-code or codex");
|
|
10216
10452
|
}
|
|
10217
|
-
const runner = installHookRunner(__filename, void 0, "0.
|
|
10453
|
+
const runner = installHookRunner(__filename, void 0, "0.24.0");
|
|
10218
10454
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
10219
10455
|
const features = hookHarness === "claude-code" ? claudeHookFeatures() : void 0;
|
|
10220
10456
|
const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir, features);
|
|
@@ -10318,18 +10554,42 @@ ${total} skill(s) synced.`);
|
|
|
10318
10554
|
break;
|
|
10319
10555
|
}
|
|
10320
10556
|
case "hooks": {
|
|
10557
|
+
if (positionals[0] === "stats") {
|
|
10558
|
+
const stats = readHookStats(parseArgs(process.argv).sessionId);
|
|
10559
|
+
if (!stats) {
|
|
10560
|
+
console.error(
|
|
10561
|
+
"No hook statistics recorded yet. They are kept per session once the hooks from this version have run."
|
|
10562
|
+
);
|
|
10563
|
+
process.exit(1);
|
|
10564
|
+
}
|
|
10565
|
+
const rows = Object.entries(stats.events).sort(([a], [b]) => a.localeCompare(b));
|
|
10566
|
+
const injected = rows.reduce((sum, [, row]) => sum + row.injectedChars, 0);
|
|
10567
|
+
console.log(`Session ${stats.sessionId} (${stats.harness}), last active ${new Date(stats.updatedAt).toISOString()}`);
|
|
10568
|
+
console.log("event calls avg ms max ms injected chars");
|
|
10569
|
+
for (const [event, row] of rows) {
|
|
10570
|
+
console.log(
|
|
10571
|
+
`${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)}`
|
|
10572
|
+
);
|
|
10573
|
+
}
|
|
10574
|
+
console.log(
|
|
10575
|
+
`Injected into context: ${injected} characters (about ${Math.round(injected / 4)} tokens). Times are measured inside the hook and exclude process start.`
|
|
10576
|
+
);
|
|
10577
|
+
break;
|
|
10578
|
+
}
|
|
10321
10579
|
if (positionals[0] !== "preview") {
|
|
10322
|
-
console.error(
|
|
10580
|
+
console.error(
|
|
10581
|
+
"Usage: mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]\n mindgraph-mcp hooks stats [--session-id <id>]"
|
|
10582
|
+
);
|
|
10323
10583
|
process.exit(1);
|
|
10324
10584
|
}
|
|
10325
10585
|
const transcript = positionals[1] || latestTranscript(process.cwd());
|
|
10326
|
-
if (!transcript || !
|
|
10586
|
+
if (!transcript || !fs8.existsSync(transcript)) {
|
|
10327
10587
|
console.error(
|
|
10328
10588
|
"No transcript found. Pass a Claude Code transcript path (~/.claude/projects/<dir>/<session>.jsonl)."
|
|
10329
10589
|
);
|
|
10330
10590
|
process.exit(1);
|
|
10331
10591
|
}
|
|
10332
|
-
const sessionId = parseArgs(process.argv).sessionId ||
|
|
10592
|
+
const sessionId = parseArgs(process.argv).sessionId || path8.basename(transcript, ".jsonl");
|
|
10333
10593
|
const preview = previewCapture(transcript, sessionId, {
|
|
10334
10594
|
agentId: process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId
|
|
10335
10595
|
});
|