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/README.md +8 -2
- package/dist/cli.js +467 -208
- package/dist/index.js +244 -148
- package/dist/server.d.mts +8 -3
- package/dist/server.d.ts +8 -3
- package/dist/server.js +54 -13
- package/dist/server.mjs +57 -19
- package/package.json +7 -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,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 ||
|
|
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
|
-
const
|
|
2631
|
-
if (!
|
|
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(
|
|
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 (!
|
|
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"
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
7890
|
+
server.setRequestHandler("tools/call", 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) {
|
|
@@ -7792,20 +7934,20 @@ function buildMindGraphServer(wiring) {
|
|
|
7792
7934
|
}
|
|
7793
7935
|
return handleTool(client2, name, toolArgs, wiring.memory);
|
|
7794
7936
|
});
|
|
7795
|
-
|
|
7937
|
+
server.setRequestHandler("prompts/list", async () => {
|
|
7796
7938
|
return { prompts: PROMPTS };
|
|
7797
7939
|
});
|
|
7798
|
-
|
|
7940
|
+
server.setRequestHandler("prompts/get", async (request) => {
|
|
7799
7941
|
const { name, arguments: args } = request.params;
|
|
7800
7942
|
return getPrompt(name, args);
|
|
7801
7943
|
});
|
|
7802
|
-
|
|
7944
|
+
server.setRequestHandler("resources/list", async () => {
|
|
7803
7945
|
return { resources: RESOURCES };
|
|
7804
7946
|
});
|
|
7805
|
-
|
|
7947
|
+
server.setRequestHandler("resources/templates/list", async () => {
|
|
7806
7948
|
return { resourceTemplates: RESOURCE_TEMPLATES };
|
|
7807
7949
|
});
|
|
7808
|
-
|
|
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
|
|
7958
|
+
return server;
|
|
7817
7959
|
}
|
|
7818
|
-
var import_server
|
|
7960
|
+
var import_server;
|
|
7819
7961
|
var init_server = __esm({
|
|
7820
7962
|
"src/server.ts"() {
|
|
7821
7963
|
"use strict";
|
|
7822
|
-
import_server = require("@modelcontextprotocol/
|
|
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
|
-
|
|
7838
|
-
|
|
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.
|
|
7981
|
+
`MindGraph MCP server v${"0.25.0"} running on stdio (${BASE_URL})`
|
|
7841
7982
|
);
|
|
7842
7983
|
}
|
|
7843
|
-
var import_stdio,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
8141
|
+
buildServer = () => buildMindGraphServer({
|
|
8000
8142
|
client,
|
|
8001
|
-
serverVersion: "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
|
|
8017
|
-
var
|
|
8018
|
-
var
|
|
8019
|
-
var
|
|
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
|
|
8502
|
-
var
|
|
8503
|
-
var
|
|
8504
|
-
var
|
|
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
|
|
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 =
|
|
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 ||
|
|
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 =
|
|
8701
|
-
return
|
|
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
|
-
|
|
8877
|
+
import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true, mode: 448 });
|
|
8709
8878
|
try {
|
|
8710
|
-
|
|
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 =
|
|
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() -
|
|
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(
|
|
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}.${
|
|
8743
|
-
|
|
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
|
-
|
|
8914
|
+
import_node_fs5.default.renameSync(temporary, file);
|
|
8746
8915
|
try {
|
|
8747
|
-
|
|
8916
|
+
import_node_fs5.default.chmodSync(file, 384);
|
|
8748
8917
|
} catch {
|
|
8749
8918
|
}
|
|
8750
8919
|
return result;
|
|
8751
8920
|
} finally {
|
|
8752
|
-
|
|
8921
|
+
import_node_fs5.default.closeSync(lockFd);
|
|
8753
8922
|
try {
|
|
8754
|
-
|
|
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 =
|
|
9025
|
-
const target =
|
|
9026
|
-
return target === base || target.startsWith(base +
|
|
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 ||
|
|
9033
|
-
if (!
|
|
9034
|
-
const config = JSON.parse(
|
|
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 =
|
|
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 =
|
|
9042
|
-
const candidate =
|
|
9043
|
-
if (
|
|
9044
|
-
const parsed = JSON.parse(
|
|
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 ===
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 ||
|
|
9632
|
-
|
|
9633
|
-
const file =
|
|
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(
|
|
9868
|
+
failures = Number(JSON.parse(fs8.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
9637
9869
|
} catch {
|
|
9638
9870
|
}
|
|
9639
|
-
|
|
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 =
|
|
9656
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
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 (!
|
|
9660
|
-
const current = JSON.parse(
|
|
9891
|
+
if (!fs8.existsSync(file)) return;
|
|
9892
|
+
const current = JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
9661
9893
|
if (!Number(current.consecutiveFailures)) return;
|
|
9662
|
-
|
|
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
|
|
9679
|
-
|
|
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
|
|
9687
|
-
process.env.APPDATA ||
|
|
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
|
|
9693
|
-
|
|
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 (!
|
|
9704
|
-
const content =
|
|
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 =
|
|
9709
|
-
if (!
|
|
9710
|
-
|
|
9940
|
+
const dir = path8.dirname(filePath);
|
|
9941
|
+
if (!fs8.existsSync(dir)) {
|
|
9942
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
9711
9943
|
}
|
|
9712
|
-
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
9845
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
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 (
|
|
9849
|
-
const health = JSON.parse(
|
|
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 =
|
|
9879
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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(
|
|
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 || !
|
|
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 ||
|
|
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
|
});
|