mindgraph-mcp 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/cli.js +573 -257
- package/dist/index.js +418 -46
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -65,8 +65,8 @@ var init_dist = __esm({
|
|
|
65
65
|
this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
|
|
66
66
|
}
|
|
67
67
|
// ---- HTTP helpers ----
|
|
68
|
-
async request(method,
|
|
69
|
-
const url = `${this.baseUrl}${
|
|
68
|
+
async request(method, path7, body) {
|
|
69
|
+
const url = `${this.baseUrl}${path7}`;
|
|
70
70
|
const requestId = createRequestId();
|
|
71
71
|
const init = {
|
|
72
72
|
method,
|
|
@@ -91,7 +91,7 @@ var init_dist = __esm({
|
|
|
91
91
|
parsed = text2;
|
|
92
92
|
}
|
|
93
93
|
const err5 = new MindGraphError(
|
|
94
|
-
`${method} ${
|
|
94
|
+
`${method} ${path7} failed: ${res.status}`,
|
|
95
95
|
res.status,
|
|
96
96
|
parsed
|
|
97
97
|
);
|
|
@@ -109,17 +109,17 @@ var init_dist = __esm({
|
|
|
109
109
|
}
|
|
110
110
|
throw lastError;
|
|
111
111
|
}
|
|
112
|
-
get(
|
|
113
|
-
return this.request("GET",
|
|
112
|
+
get(path7) {
|
|
113
|
+
return this.request("GET", path7);
|
|
114
114
|
}
|
|
115
|
-
post(
|
|
116
|
-
return this.request("POST",
|
|
115
|
+
post(path7, body) {
|
|
116
|
+
return this.request("POST", path7, body);
|
|
117
117
|
}
|
|
118
|
-
patch(
|
|
119
|
-
return this.request("PATCH",
|
|
118
|
+
patch(path7, body) {
|
|
119
|
+
return this.request("PATCH", path7, body);
|
|
120
120
|
}
|
|
121
|
-
del(
|
|
122
|
-
return this.request("DELETE",
|
|
121
|
+
del(path7) {
|
|
122
|
+
return this.request("DELETE", path7);
|
|
123
123
|
}
|
|
124
124
|
// ---- Health ----
|
|
125
125
|
async health() {
|
|
@@ -971,6 +971,14 @@ var init_hook_env = __esm({
|
|
|
971
971
|
});
|
|
972
972
|
|
|
973
973
|
// src/codegraph.ts
|
|
974
|
+
function invocationCwd(args) {
|
|
975
|
+
const context = args.invocation_context;
|
|
976
|
+
if (context && typeof context === "object" && !Array.isArray(context)) {
|
|
977
|
+
const cwd = context.cwd;
|
|
978
|
+
if (typeof cwd === "string" && cwd) return cwd;
|
|
979
|
+
}
|
|
980
|
+
return process.cwd();
|
|
981
|
+
}
|
|
974
982
|
function unavailabilityCaveats(reason, message) {
|
|
975
983
|
const caveats = [message || "codegraph status failed"];
|
|
976
984
|
if (reason === "absent") {
|
|
@@ -980,14 +988,14 @@ function unavailabilityCaveats(reason, message) {
|
|
|
980
988
|
}
|
|
981
989
|
return caveats;
|
|
982
990
|
}
|
|
983
|
-
function normalizePath(
|
|
984
|
-
return
|
|
991
|
+
function normalizePath(path7) {
|
|
992
|
+
return path7.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
985
993
|
}
|
|
986
994
|
function normalizeSignature(signature) {
|
|
987
995
|
return signature.replace(/\s+/g, " ").trim();
|
|
988
996
|
}
|
|
989
|
-
function languageFromPath(
|
|
990
|
-
const extension = (0, import_node_path2.extname)(
|
|
997
|
+
function languageFromPath(path7) {
|
|
998
|
+
const extension = (0, import_node_path2.extname)(path7).toLowerCase();
|
|
991
999
|
return {
|
|
992
1000
|
".c": "c",
|
|
993
1001
|
".cc": "cpp",
|
|
@@ -1150,7 +1158,7 @@ function codeRefIdentityKey(ref) {
|
|
|
1150
1158
|
}
|
|
1151
1159
|
};
|
|
1152
1160
|
}
|
|
1153
|
-
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
|
|
1161
|
+
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
|
|
1154
1162
|
var init_codegraph = __esm({
|
|
1155
1163
|
"src/codegraph.ts"() {
|
|
1156
1164
|
"use strict";
|
|
@@ -1160,6 +1168,16 @@ var init_codegraph = __esm({
|
|
|
1160
1168
|
import_node_os2 = require("os");
|
|
1161
1169
|
import_node_path2 = require("path");
|
|
1162
1170
|
import_node_crypto2 = require("crypto");
|
|
1171
|
+
UnknownRepositoryError = class extends Error {
|
|
1172
|
+
constructor(repo) {
|
|
1173
|
+
super(
|
|
1174
|
+
`unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
|
|
1175
|
+
);
|
|
1176
|
+
this.repo = repo;
|
|
1177
|
+
this.name = "UnknownRepositoryError";
|
|
1178
|
+
}
|
|
1179
|
+
repo;
|
|
1180
|
+
};
|
|
1163
1181
|
DEFAULT_TIMEOUT_MS = 5e3;
|
|
1164
1182
|
DEFAULT_STATUS_TTL_MS = 6e4;
|
|
1165
1183
|
MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
@@ -1195,7 +1213,26 @@ var init_codegraph = __esm({
|
|
|
1195
1213
|
selected.spaceUid
|
|
1196
1214
|
) : selected;
|
|
1197
1215
|
}
|
|
1198
|
-
|
|
1216
|
+
if (repo) {
|
|
1217
|
+
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, repo)).catch(
|
|
1218
|
+
() => (0, import_node_path2.resolve)(this.cwd, repo)
|
|
1219
|
+
);
|
|
1220
|
+
if (!(0, import_node_fs2.existsSync)(requestedPath)) {
|
|
1221
|
+
const current = await this.currentRepository(fallbackSpaceUid);
|
|
1222
|
+
if (current.repoId === repo) return current;
|
|
1223
|
+
throw new UnknownRepositoryError(repo);
|
|
1224
|
+
}
|
|
1225
|
+
const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
|
|
1226
|
+
return this.resolveRepositoryIdentity(
|
|
1227
|
+
repoRoot,
|
|
1228
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
1229
|
+
this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
return this.currentRepository(fallbackSpaceUid);
|
|
1233
|
+
}
|
|
1234
|
+
async currentRepository(fallbackSpaceUid) {
|
|
1235
|
+
const requestedRoot = this.env.MINDGRAPH_REPO_ROOT || this.cwd;
|
|
1199
1236
|
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
|
|
1200
1237
|
() => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
|
|
1201
1238
|
);
|
|
@@ -1285,6 +1322,14 @@ var init_codegraph = __esm({
|
|
|
1285
1322
|
).catch(() => "");
|
|
1286
1323
|
return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
|
|
1287
1324
|
}
|
|
1325
|
+
/** Why the workspace map yielded no repositories — for callers that were
|
|
1326
|
+
* explicitly asked to fan out and would otherwise return silently empty. */
|
|
1327
|
+
workspaceDiagnostic() {
|
|
1328
|
+
if (!this.workspaceFile || !(0, import_node_fs2.existsSync)(this.workspaceFile)) {
|
|
1329
|
+
return "no .mindgraph/workspace.json was found between the working directory and the filesystem root";
|
|
1330
|
+
}
|
|
1331
|
+
return `${this.workspaceFile} is malformed or declares no repositories`;
|
|
1332
|
+
}
|
|
1288
1333
|
async availability(repository) {
|
|
1289
1334
|
const cached = this.statusCache.get(repository.repoRoot);
|
|
1290
1335
|
if (cached && cached.expiresAt > this.now()) return cached.availability;
|
|
@@ -1698,13 +1743,13 @@ var init_error_detail = __esm({
|
|
|
1698
1743
|
});
|
|
1699
1744
|
|
|
1700
1745
|
// src/code-tool.ts
|
|
1701
|
-
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
1746
|
+
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
|
|
1702
1747
|
const codeClient = client2;
|
|
1703
1748
|
const action = args.action;
|
|
1704
1749
|
const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
|
|
1705
1750
|
const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
|
|
1706
1751
|
const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
|
|
1707
|
-
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid :
|
|
1752
|
+
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
|
|
1708
1753
|
const options = {
|
|
1709
1754
|
repo: typeof args.repo === "string" ? args.repo : void 0,
|
|
1710
1755
|
fallbackSpaceUid
|
|
@@ -1828,6 +1873,7 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1828
1873
|
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
1829
1874
|
let graphContext;
|
|
1830
1875
|
const graphRefs = [];
|
|
1876
|
+
let unavailableRepositories = [];
|
|
1831
1877
|
if (targetUid) {
|
|
1832
1878
|
graphContext = await codeClient.traverse({
|
|
1833
1879
|
action: "neighborhood",
|
|
@@ -1835,9 +1881,13 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1835
1881
|
depth: 2,
|
|
1836
1882
|
max_nodes: 50
|
|
1837
1883
|
});
|
|
1838
|
-
|
|
1839
|
-
|
|
1884
|
+
const fromGraph = await codeRefsFromGraphContext(
|
|
1885
|
+
codeClient,
|
|
1886
|
+
adapter,
|
|
1887
|
+
graphContext
|
|
1840
1888
|
);
|
|
1889
|
+
graphRefs.push(...fromGraph.refs);
|
|
1890
|
+
unavailableRepositories = fromGraph.unavailable;
|
|
1841
1891
|
}
|
|
1842
1892
|
if (refs.length > 0 || anchorUids.length > 0) {
|
|
1843
1893
|
const target = await resolveSingleTarget(
|
|
@@ -1856,7 +1906,8 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1856
1906
|
target_uid: targetUid,
|
|
1857
1907
|
graph_context: graphContext,
|
|
1858
1908
|
affected: [],
|
|
1859
|
-
degradation: "no materialized code anchors were attached"
|
|
1909
|
+
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
1910
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
1860
1911
|
});
|
|
1861
1912
|
}
|
|
1862
1913
|
const impacts = await Promise.all(
|
|
@@ -1873,13 +1924,17 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1873
1924
|
target_uid: targetUid,
|
|
1874
1925
|
graph_context: graphContext,
|
|
1875
1926
|
impacts,
|
|
1876
|
-
materialized: joined
|
|
1927
|
+
materialized: joined,
|
|
1928
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
1877
1929
|
});
|
|
1878
1930
|
}
|
|
1879
1931
|
default:
|
|
1880
1932
|
return err("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
1881
1933
|
}
|
|
1882
1934
|
} catch (cause) {
|
|
1935
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
1936
|
+
return err("unknown_repository", cause.message);
|
|
1937
|
+
}
|
|
1883
1938
|
return err("code_tool_failed", errorDetail(cause));
|
|
1884
1939
|
}
|
|
1885
1940
|
}
|
|
@@ -1905,7 +1960,10 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
|
1905
1960
|
code_refs: args.code_refs,
|
|
1906
1961
|
repo: args.repo,
|
|
1907
1962
|
repo_space_uid: args.repo_space_uid,
|
|
1908
|
-
agent_id: args.agent_id
|
|
1963
|
+
agent_id: args.agent_id,
|
|
1964
|
+
// Keeps the default adapter anchored to the live session directory
|
|
1965
|
+
// rather than wherever the MCP server process was launched.
|
|
1966
|
+
invocation_context: args.invocation_context
|
|
1909
1967
|
},
|
|
1910
1968
|
adapter
|
|
1911
1969
|
);
|
|
@@ -1915,17 +1973,33 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
|
1915
1973
|
const anchorUids = (anchors.anchors ?? []).flatMap(
|
|
1916
1974
|
(anchor) => anchor.uid ? [anchor.uid] : []
|
|
1917
1975
|
);
|
|
1976
|
+
const relateFailures = [];
|
|
1918
1977
|
for (const targetUid of targetUids) {
|
|
1919
1978
|
for (const anchorUid of anchorUids) {
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1979
|
+
try {
|
|
1980
|
+
await codeClient.entity({
|
|
1981
|
+
action: "relate",
|
|
1982
|
+
source_uid: targetUid,
|
|
1983
|
+
target_uid: anchorUid,
|
|
1984
|
+
edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
|
|
1985
|
+
agent_id: args.agent_id
|
|
1986
|
+
});
|
|
1987
|
+
} catch (cause) {
|
|
1988
|
+
relateFailures.push(
|
|
1989
|
+
`${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1927
1992
|
}
|
|
1928
1993
|
}
|
|
1994
|
+
if (relateFailures.length > 0) {
|
|
1995
|
+
return ok({
|
|
1996
|
+
result: original,
|
|
1997
|
+
code_anchors: {
|
|
1998
|
+
...anchors,
|
|
1999
|
+
caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
1929
2003
|
}
|
|
1930
2004
|
return ok({ result: original, code_anchors: anchors });
|
|
1931
2005
|
}
|
|
@@ -1998,16 +2072,34 @@ async function codeRefFromAnchor(client2, adapter, uid) {
|
|
|
1998
2072
|
if (!attributes || typeof attributes !== "object") return void 0;
|
|
1999
2073
|
const codeRef = attributes.code_ref;
|
|
2000
2074
|
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
2001
|
-
|
|
2002
|
-
|
|
2075
|
+
try {
|
|
2076
|
+
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
2077
|
+
return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
|
|
2078
|
+
} catch (cause) {
|
|
2079
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2080
|
+
return { missingRepo: codeRef.repoId };
|
|
2081
|
+
}
|
|
2082
|
+
throw cause;
|
|
2083
|
+
}
|
|
2003
2084
|
}
|
|
2004
2085
|
async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
|
|
2005
2086
|
if (refs.length > 1 || anchorUids.length > 1) {
|
|
2006
2087
|
return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
|
|
2007
2088
|
}
|
|
2008
2089
|
if (anchorUids.length === 1) {
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2090
|
+
const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
|
|
2091
|
+
if (!outcome) {
|
|
2092
|
+
return { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
|
|
2093
|
+
}
|
|
2094
|
+
if ("missingRepo" in outcome) {
|
|
2095
|
+
return {
|
|
2096
|
+
error: err(
|
|
2097
|
+
"repository_unavailable",
|
|
2098
|
+
`repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
|
|
2099
|
+
)
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
return { ref: outcome.ref };
|
|
2011
2103
|
}
|
|
2012
2104
|
if (refs.length !== 1) {
|
|
2013
2105
|
return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
@@ -2046,7 +2138,7 @@ async function joinMaterialized(client2, refs, agentId, limit) {
|
|
|
2046
2138
|
async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
2047
2139
|
const uids = /* @__PURE__ */ new Set();
|
|
2048
2140
|
collectUids(context, uids);
|
|
2049
|
-
if (uids.size === 0) return [];
|
|
2141
|
+
if (uids.size === 0) return { refs: [], unavailable: [] };
|
|
2050
2142
|
const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
|
|
2051
2143
|
const stored = nodes.flatMap((node) => {
|
|
2052
2144
|
if (!node) return [];
|
|
@@ -2055,12 +2147,21 @@ async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
|
2055
2147
|
const codeRef = attributes.code_ref;
|
|
2056
2148
|
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
2057
2149
|
});
|
|
2058
|
-
|
|
2059
|
-
|
|
2150
|
+
const refs = [];
|
|
2151
|
+
const unavailable = /* @__PURE__ */ new Set();
|
|
2152
|
+
for (const ref of stored) {
|
|
2153
|
+
try {
|
|
2060
2154
|
const repository = await adapter.resolveRepository(ref.repoId);
|
|
2061
|
-
|
|
2062
|
-
})
|
|
2063
|
-
|
|
2155
|
+
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
2156
|
+
} catch (cause) {
|
|
2157
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2158
|
+
unavailable.add(ref.repoId);
|
|
2159
|
+
} else {
|
|
2160
|
+
throw cause;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return { refs, unavailable: [...unavailable].sort() };
|
|
2064
2165
|
}
|
|
2065
2166
|
function collectUids(value, uids) {
|
|
2066
2167
|
if (Array.isArray(value)) {
|
|
@@ -2136,7 +2237,7 @@ var init_code_tool = __esm({
|
|
|
2136
2237
|
});
|
|
2137
2238
|
CODE_TOOL = {
|
|
2138
2239
|
name: "mindgraph_code",
|
|
2139
|
-
description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing.",
|
|
2240
|
+
description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
|
|
2140
2241
|
inputSchema: {
|
|
2141
2242
|
type: "object",
|
|
2142
2243
|
properties: {
|
|
@@ -2178,7 +2279,7 @@ var init_code_tool = __esm({
|
|
|
2178
2279
|
},
|
|
2179
2280
|
repo_space_uid: {
|
|
2180
2281
|
type: "string",
|
|
2181
|
-
description: "Writable shared Project/repository Space for new anchors"
|
|
2282
|
+
description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
|
|
2182
2283
|
},
|
|
2183
2284
|
task_uid: { type: "string" },
|
|
2184
2285
|
execution_uid: { type: "string" },
|
|
@@ -2197,6 +2298,276 @@ var init_code_tool = __esm({
|
|
|
2197
2298
|
}
|
|
2198
2299
|
});
|
|
2199
2300
|
|
|
2301
|
+
// src/hook-installer.ts
|
|
2302
|
+
function command(harness) {
|
|
2303
|
+
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
2304
|
+
}
|
|
2305
|
+
function windowsCommand(harness) {
|
|
2306
|
+
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
2307
|
+
}
|
|
2308
|
+
function installHookRunner(sourceFile, homeDir, version) {
|
|
2309
|
+
const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
|
|
2310
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
|
|
2311
|
+
const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
2312
|
+
import_node_fs4.default.copyFileSync(sourceFile, temporary);
|
|
2313
|
+
import_node_fs4.default.renameSync(temporary, target);
|
|
2314
|
+
const versionPath = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
|
|
2315
|
+
if (version) {
|
|
2316
|
+
import_node_fs4.default.writeFileSync(versionPath, `${version}
|
|
2317
|
+
`);
|
|
2318
|
+
} else {
|
|
2319
|
+
import_node_fs4.default.rmSync(versionPath, { force: true });
|
|
2320
|
+
}
|
|
2321
|
+
return target;
|
|
2322
|
+
}
|
|
2323
|
+
function installedHookRunnerVersion(homeDir) {
|
|
2324
|
+
try {
|
|
2325
|
+
const raw = import_node_fs4.default.readFileSync(
|
|
2326
|
+
import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE),
|
|
2327
|
+
"utf8"
|
|
2328
|
+
).trim();
|
|
2329
|
+
return raw || void 0;
|
|
2330
|
+
} catch {
|
|
2331
|
+
return void 0;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function parseSemver(value) {
|
|
2335
|
+
if (!value) return void 0;
|
|
2336
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
|
|
2337
|
+
if (!match) return void 0;
|
|
2338
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
2339
|
+
}
|
|
2340
|
+
function semverNewer(a, b) {
|
|
2341
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2342
|
+
if (a[index] !== b[index]) return a[index] > b[index];
|
|
2343
|
+
}
|
|
2344
|
+
return false;
|
|
2345
|
+
}
|
|
2346
|
+
function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
|
|
2347
|
+
try {
|
|
2348
|
+
if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
|
|
2349
|
+
return { refreshed: false, reason: "disabled" };
|
|
2350
|
+
}
|
|
2351
|
+
const home = homeDir || import_node_os4.default.homedir();
|
|
2352
|
+
if (!import_node_fs4.default.existsSync(import_node_path4.default.join(home, RUNNER_RELATIVE))) {
|
|
2353
|
+
return { refreshed: false, reason: "no_runner" };
|
|
2354
|
+
}
|
|
2355
|
+
if (!import_node_fs4.default.existsSync(sourceBundle)) {
|
|
2356
|
+
return { refreshed: false, reason: "no_source" };
|
|
2357
|
+
}
|
|
2358
|
+
const server2 = parseSemver(serverVersion);
|
|
2359
|
+
if (!server2) return { refreshed: false, reason: "unparseable" };
|
|
2360
|
+
const runnerVersion = installedHookRunnerVersion(home);
|
|
2361
|
+
if (runnerVersion !== void 0) {
|
|
2362
|
+
const pinned = parseSemver(runnerVersion);
|
|
2363
|
+
if (!pinned) {
|
|
2364
|
+
return { refreshed: false, reason: "unparseable", runnerVersion };
|
|
2365
|
+
}
|
|
2366
|
+
if (!semverNewer(server2, pinned)) {
|
|
2367
|
+
return {
|
|
2368
|
+
refreshed: false,
|
|
2369
|
+
reason: runnerVersion === serverVersion ? "current" : "downgrade",
|
|
2370
|
+
runnerVersion
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
installHookRunner(sourceBundle, home, serverVersion);
|
|
2375
|
+
return { refreshed: true, reason: "updated", runnerVersion };
|
|
2376
|
+
} catch {
|
|
2377
|
+
return { refreshed: false, reason: "error" };
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
|
|
2381
|
+
const refreshed = [];
|
|
2382
|
+
const scopes = ["user", "project"];
|
|
2383
|
+
for (const scope of scopes) {
|
|
2384
|
+
try {
|
|
2385
|
+
const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
|
|
2386
|
+
if (!import_node_fs4.default.existsSync(file)) continue;
|
|
2387
|
+
if (!import_node_fs4.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
|
|
2388
|
+
installHooks("claude-code", scope, projectDir, void 0, homeDir);
|
|
2389
|
+
refreshed.push(file);
|
|
2390
|
+
} catch {
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
return refreshed;
|
|
2394
|
+
}
|
|
2395
|
+
function versionSkewNote(serverVersion, homeDir) {
|
|
2396
|
+
const runnerVersion = installedHookRunnerVersion(homeDir);
|
|
2397
|
+
if (!runnerVersion || runnerVersion === serverVersion) return void 0;
|
|
2398
|
+
return `NOTE: the installed MindGraph hook runner is v${runnerVersion} but this MCP server is v${serverVersion} \u2014 hook behavior and tool contracts may be out of sync. Tell the user to run: npx -y mindgraph-mcp@latest install-hooks (refreshes the pinned runner).`;
|
|
2399
|
+
}
|
|
2400
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex"), homeDir) {
|
|
2401
|
+
if (harness === "codex") {
|
|
2402
|
+
return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
|
|
2403
|
+
}
|
|
2404
|
+
return scope === "user" ? import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
|
|
2405
|
+
}
|
|
2406
|
+
function readSettings(file) {
|
|
2407
|
+
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
2408
|
+
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
2409
|
+
}
|
|
2410
|
+
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
2411
|
+
return {
|
|
2412
|
+
type: "command",
|
|
2413
|
+
command: command(harness),
|
|
2414
|
+
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
2415
|
+
timeout,
|
|
2416
|
+
statusMessage: "Syncing MindGraph session context",
|
|
2417
|
+
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
function desiredEntries(harness) {
|
|
2421
|
+
const common = {
|
|
2422
|
+
SessionStart: [
|
|
2423
|
+
{
|
|
2424
|
+
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
2425
|
+
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
2426
|
+
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
2427
|
+
// open, but no brief reached the model. The same installed runner
|
|
2428
|
+
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
2429
|
+
// The declared Codex context limit must not undercut the brief's own
|
|
2430
|
+
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
2431
|
+
// truncation the renderer knew nothing about.
|
|
2432
|
+
hooks: [
|
|
2433
|
+
ownedHook(
|
|
2434
|
+
harness,
|
|
2435
|
+
30,
|
|
2436
|
+
harness === "codex" ? 1e4 : void 0
|
|
2437
|
+
)
|
|
2438
|
+
]
|
|
2439
|
+
}
|
|
2440
|
+
],
|
|
2441
|
+
PreToolUse: [
|
|
2442
|
+
{
|
|
2443
|
+
matcher: "mcp__mindgraph__.*",
|
|
2444
|
+
// Repository resolution over a large multi-repo workspace plus three
|
|
2445
|
+
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
2446
|
+
// PreToolUse means the call goes through untagged.
|
|
2447
|
+
hooks: [ownedHook(harness, 5)]
|
|
2448
|
+
}
|
|
2449
|
+
],
|
|
2450
|
+
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
2451
|
+
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
2452
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
|
|
2453
|
+
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
2454
|
+
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
2455
|
+
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
2456
|
+
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
2457
|
+
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
2458
|
+
// map), so the codex adapter instead makes at most ONE call there.
|
|
2459
|
+
SessionEnd: [
|
|
2460
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
2461
|
+
]
|
|
2462
|
+
};
|
|
2463
|
+
return harness === "claude-code" ? {
|
|
2464
|
+
...common,
|
|
2465
|
+
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
2466
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
2467
|
+
} : common;
|
|
2468
|
+
}
|
|
2469
|
+
function hookCommand(value) {
|
|
2470
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2471
|
+
const command2 = value.command;
|
|
2472
|
+
return typeof command2 === "string" ? command2 : void 0;
|
|
2473
|
+
}
|
|
2474
|
+
function entryHasOwnedHook(value) {
|
|
2475
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2476
|
+
const hooks = value.hooks;
|
|
2477
|
+
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
2478
|
+
}
|
|
2479
|
+
function writeSettings(file, settings) {
|
|
2480
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
2481
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
2482
|
+
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
2483
|
+
`, {
|
|
2484
|
+
mode: 384
|
|
2485
|
+
});
|
|
2486
|
+
import_node_fs4.default.renameSync(temporary, file);
|
|
2487
|
+
}
|
|
2488
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir) {
|
|
2489
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
|
|
2490
|
+
const settings = readSettings(file);
|
|
2491
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2492
|
+
let added = 0;
|
|
2493
|
+
let updated = 0;
|
|
2494
|
+
for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
|
|
2495
|
+
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
2496
|
+
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
2497
|
+
const hadOwned = kept.length !== current.length;
|
|
2498
|
+
for (const entry of wanted) {
|
|
2499
|
+
kept.push(entry);
|
|
2500
|
+
if (hadOwned) updated += 1;
|
|
2501
|
+
else added += 1;
|
|
2502
|
+
}
|
|
2503
|
+
hooks[event] = kept;
|
|
2504
|
+
}
|
|
2505
|
+
settings.hooks = hooks;
|
|
2506
|
+
writeSettings(file, settings);
|
|
2507
|
+
return { path: file, added, updated };
|
|
2508
|
+
}
|
|
2509
|
+
function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2510
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
2511
|
+
if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
|
|
2512
|
+
const settings = readSettings(file);
|
|
2513
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2514
|
+
let removed = 0;
|
|
2515
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
2516
|
+
if (!Array.isArray(value)) continue;
|
|
2517
|
+
const retainedEntries = [];
|
|
2518
|
+
for (const rawEntry of value) {
|
|
2519
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
2520
|
+
retainedEntries.push(rawEntry);
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
const entry = { ...rawEntry };
|
|
2524
|
+
if (!Array.isArray(entry.hooks)) {
|
|
2525
|
+
retainedEntries.push(entry);
|
|
2526
|
+
continue;
|
|
2527
|
+
}
|
|
2528
|
+
const retainedHooks = entry.hooks.filter((hook) => {
|
|
2529
|
+
const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
|
|
2530
|
+
if (owned) removed += 1;
|
|
2531
|
+
return !owned;
|
|
2532
|
+
});
|
|
2533
|
+
if (retainedHooks.length > 0) {
|
|
2534
|
+
entry.hooks = retainedHooks;
|
|
2535
|
+
retainedEntries.push(entry);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
if (retainedEntries.length > 0) hooks[event] = retainedEntries;
|
|
2539
|
+
else delete hooks[event];
|
|
2540
|
+
}
|
|
2541
|
+
settings.hooks = hooks;
|
|
2542
|
+
writeSettings(file, settings);
|
|
2543
|
+
return { path: file, removed };
|
|
2544
|
+
}
|
|
2545
|
+
function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2546
|
+
return installHooks("claude-code", scope, projectDir);
|
|
2547
|
+
}
|
|
2548
|
+
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2549
|
+
return uninstallHooks("claude-code", scope, projectDir);
|
|
2550
|
+
}
|
|
2551
|
+
function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2552
|
+
return installHooks("codex", scope, projectDir, codexHomeDir);
|
|
2553
|
+
}
|
|
2554
|
+
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2555
|
+
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
2556
|
+
}
|
|
2557
|
+
var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto4, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
|
|
2558
|
+
var init_hook_installer = __esm({
|
|
2559
|
+
"src/hook-installer.ts"() {
|
|
2560
|
+
"use strict";
|
|
2561
|
+
import_node_fs4 = __toESM(require("fs"));
|
|
2562
|
+
import_node_os4 = __toESM(require("os"));
|
|
2563
|
+
import_node_path4 = __toESM(require("path"));
|
|
2564
|
+
import_node_crypto4 = __toESM(require("crypto"));
|
|
2565
|
+
OWNER_MARKER = "--owner mindgraph";
|
|
2566
|
+
RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
2567
|
+
RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2200
2571
|
// src/sync-tool.ts
|
|
2201
2572
|
function safePath(root, candidate) {
|
|
2202
2573
|
const absolute = import_node_path5.default.resolve(root, candidate);
|
|
@@ -2204,14 +2575,29 @@ function safePath(root, candidate) {
|
|
|
2204
2575
|
if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
|
|
2205
2576
|
throw new Error(`path escapes repository root: ${candidate}`);
|
|
2206
2577
|
}
|
|
2578
|
+
if (import_node_fs5.default.existsSync(absolute)) {
|
|
2579
|
+
if (!pathContains(realPathOf(root), realPathOf(absolute))) {
|
|
2580
|
+
throw new Error(`path escapes repository root via symlink: ${candidate}`);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2207
2583
|
return absolute;
|
|
2208
2584
|
}
|
|
2209
|
-
function
|
|
2585
|
+
function realPathOf(target) {
|
|
2586
|
+
try {
|
|
2587
|
+
return import_node_fs5.default.realpathSync(target);
|
|
2588
|
+
} catch {
|
|
2589
|
+
return import_node_path5.default.resolve(target);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
2210
2593
|
if (!import_node_fs5.default.existsSync(target)) return [];
|
|
2594
|
+
const real = realPathOf(target);
|
|
2595
|
+
if (!pathContains(realRoot, real)) return [];
|
|
2211
2596
|
const stat = import_node_fs5.default.statSync(target);
|
|
2212
2597
|
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
2213
|
-
if (!stat.isDirectory()) return [];
|
|
2214
|
-
|
|
2598
|
+
if (!stat.isDirectory() || visited.has(real)) return [];
|
|
2599
|
+
visited.add(real);
|
|
2600
|
+
return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name), realRoot, visited));
|
|
2215
2601
|
}
|
|
2216
2602
|
function fileDescriptor(root, absolute) {
|
|
2217
2603
|
const content = import_node_fs5.default.readFileSync(absolute, "utf8");
|
|
@@ -2226,9 +2612,7 @@ async function handleSyncTool(client2, args) {
|
|
|
2226
2612
|
try {
|
|
2227
2613
|
const action = String(args.action || "");
|
|
2228
2614
|
const context = args.invocation_context || {};
|
|
2229
|
-
const adapter = new CodegraphAdapter({
|
|
2230
|
-
cwd: context.cwd || process.cwd()
|
|
2231
|
-
});
|
|
2615
|
+
const adapter = new CodegraphAdapter({ cwd: invocationCwd(args) });
|
|
2232
2616
|
const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
|
|
2233
2617
|
const repository = await adapter.resolveRepository(requestedRepo);
|
|
2234
2618
|
const root = repository.repoRoot;
|
|
@@ -2237,6 +2621,12 @@ async function handleSyncTool(client2, args) {
|
|
|
2237
2621
|
const remote = client2;
|
|
2238
2622
|
if (action === "scan") {
|
|
2239
2623
|
const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
|
|
2624
|
+
if (args.workspace === true && repositories.length === 0) {
|
|
2625
|
+
return err2(
|
|
2626
|
+
"workspace_not_configured",
|
|
2627
|
+
`workspace scan found no declared repositories: ${adapter.workspaceDiagnostic()}`
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2240
2630
|
const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
|
|
2241
2631
|
const scans = [];
|
|
2242
2632
|
for (const selected of repositories) {
|
|
@@ -2293,10 +2683,11 @@ async function handleSyncTool(client2, args) {
|
|
|
2293
2683
|
}
|
|
2294
2684
|
}
|
|
2295
2685
|
async function scanRepository(remote, repository, requested, provider, agentId) {
|
|
2686
|
+
const realRoot = realPathOf(repository.repoRoot);
|
|
2296
2687
|
const files = [
|
|
2297
2688
|
...new Set(
|
|
2298
2689
|
requested.flatMap(
|
|
2299
|
-
(candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
|
|
2690
|
+
(candidate) => markdownFiles(safePath(repository.repoRoot, candidate), realRoot)
|
|
2300
2691
|
)
|
|
2301
2692
|
)
|
|
2302
2693
|
].sort();
|
|
@@ -2730,15 +3121,15 @@ function renderTraversal(response) {
|
|
|
2730
3121
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
2731
3122
|
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
2732
3123
|
if (paths.length > 0) {
|
|
2733
|
-
const items = paths.map((raw) => obj(raw)).filter((
|
|
2734
|
-
const uids = Array.isArray(
|
|
2735
|
-
const labels = Array.isArray(
|
|
3124
|
+
const items = paths.map((raw) => obj(raw)).filter((path7) => Boolean(path7)).map((path7, index) => {
|
|
3125
|
+
const uids = Array.isArray(path7.node_uids) ? path7.node_uids.map(String) : [];
|
|
3126
|
+
const labels = Array.isArray(path7.labels) ? path7.labels.map(String) : Array.isArray(path7.nodes) ? path7.nodes.map((hop) => {
|
|
2736
3127
|
const hopNode = obj(hop);
|
|
2737
3128
|
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
2738
3129
|
}) : [];
|
|
2739
3130
|
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
2740
|
-
const cost = num(
|
|
2741
|
-
const confidence = num(
|
|
3131
|
+
const cost = num(path7.cost) ?? num(path7.path_cost);
|
|
3132
|
+
const confidence = num(path7.path_confidence);
|
|
2742
3133
|
const meta = [
|
|
2743
3134
|
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
2744
3135
|
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
@@ -5343,7 +5734,7 @@ async function checkMcpGovernance(toolName, args, config) {
|
|
|
5343
5734
|
if (!response) {
|
|
5344
5735
|
return {
|
|
5345
5736
|
allowed: false,
|
|
5346
|
-
message: `MindGraph
|
|
5737
|
+
message: `MindGraph at ${config.baseUrl} is unreachable (${lastFailure}; ${retryDelays.length + 1} attempts). The tool call was NOT evaluated against policy and NOT executed \u2014 this is a connectivity failure, not a governance denial. Check the network and MINDGRAPH_BASE_URL, then retry.`
|
|
5347
5738
|
};
|
|
5348
5739
|
}
|
|
5349
5740
|
if (!response.ok) {
|
|
@@ -5587,10 +5978,10 @@ async function main() {
|
|
|
5587
5978
|
const transport = new import_stdio.StdioServerTransport();
|
|
5588
5979
|
await server.connect(transport);
|
|
5589
5980
|
console.error(
|
|
5590
|
-
`MindGraph MCP server v${"0.
|
|
5981
|
+
`MindGraph MCP server v${"0.18.0"} running on stdio (${BASE_URL})`
|
|
5591
5982
|
);
|
|
5592
5983
|
}
|
|
5593
|
-
var import_server, import_stdio, import_types, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
|
|
5984
|
+
var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, PROMPTS;
|
|
5594
5985
|
var init_src = __esm({
|
|
5595
5986
|
"src/index.ts"() {
|
|
5596
5987
|
"use strict";
|
|
@@ -5603,6 +5994,8 @@ var init_src = __esm({
|
|
|
5603
5994
|
init_generated_tools();
|
|
5604
5995
|
init_governance();
|
|
5605
5996
|
init_resources();
|
|
5997
|
+
import_node_path6 = __toESM(require("path"));
|
|
5998
|
+
init_hook_installer();
|
|
5606
5999
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
5607
6000
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
5608
6001
|
AGENT_ID = process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId();
|
|
@@ -5724,10 +6117,42 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
5724
6117
|
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
5725
6118
|
).join("\n");
|
|
5726
6119
|
}
|
|
6120
|
+
RUNNER_REFRESH = (() => {
|
|
6121
|
+
try {
|
|
6122
|
+
return refreshHookRunner("0.18.0", import_node_path6.default.join(__dirname, "cli.js"));
|
|
6123
|
+
} catch {
|
|
6124
|
+
return void 0;
|
|
6125
|
+
}
|
|
6126
|
+
})();
|
|
6127
|
+
if (RUNNER_REFRESH?.refreshed) {
|
|
6128
|
+
const refreshedSettings = (() => {
|
|
6129
|
+
try {
|
|
6130
|
+
return refreshOwnedClaudeHooks();
|
|
6131
|
+
} catch {
|
|
6132
|
+
return [];
|
|
6133
|
+
}
|
|
6134
|
+
})();
|
|
6135
|
+
console.error(
|
|
6136
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.18.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
6137
|
+
);
|
|
6138
|
+
}
|
|
6139
|
+
HOOK_SKEW_NOTE = (() => {
|
|
6140
|
+
try {
|
|
6141
|
+
return versionSkewNote("0.18.0");
|
|
6142
|
+
} catch {
|
|
6143
|
+
return void 0;
|
|
6144
|
+
}
|
|
6145
|
+
})();
|
|
6146
|
+
if (HOOK_SKEW_NOTE) {
|
|
6147
|
+
INSTRUCTIONS += `
|
|
6148
|
+
|
|
6149
|
+
${HOOK_SKEW_NOTE}`;
|
|
6150
|
+
console.error(`[mindgraph-mcp] ${HOOK_SKEW_NOTE}`);
|
|
6151
|
+
}
|
|
5727
6152
|
server = new import_server.Server(
|
|
5728
6153
|
{
|
|
5729
6154
|
name: "mindgraph",
|
|
5730
|
-
version: "0.
|
|
6155
|
+
version: "0.18.0"
|
|
5731
6156
|
},
|
|
5732
6157
|
{
|
|
5733
6158
|
capabilities: {
|
|
@@ -5946,7 +6371,7 @@ ${conversation}`
|
|
|
5946
6371
|
|
|
5947
6372
|
// src/cli.ts
|
|
5948
6373
|
var fs5 = __toESM(require("fs"));
|
|
5949
|
-
var
|
|
6374
|
+
var path6 = __toESM(require("path"));
|
|
5950
6375
|
var os4 = __toESM(require("os"));
|
|
5951
6376
|
var crypto5 = __toESM(require("crypto"));
|
|
5952
6377
|
var import_child_process = require("child_process");
|
|
@@ -6029,6 +6454,7 @@ var import_node_path3 = __toESM(require("path"));
|
|
|
6029
6454
|
var import_node_child_process2 = require("child_process");
|
|
6030
6455
|
init_codegraph();
|
|
6031
6456
|
init_code_tool();
|
|
6457
|
+
init_error_detail();
|
|
6032
6458
|
init_hook_env();
|
|
6033
6459
|
var NOOP = { kind: "noop" };
|
|
6034
6460
|
function defaultLedger(sessionId, options) {
|
|
@@ -6360,6 +6786,51 @@ function isOwnPriorWork(brief, agentId, harness) {
|
|
|
6360
6786
|
const owner = string(object(brief?.lease)?.lease_owner_agent_id);
|
|
6361
6787
|
return owner === agentId || owner === harness;
|
|
6362
6788
|
}
|
|
6789
|
+
function hasMindgraphServer(servers) {
|
|
6790
|
+
if (!servers || typeof servers !== "object") return false;
|
|
6791
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
6792
|
+
if (name === "mindgraph") return true;
|
|
6793
|
+
const cfg = entry && typeof entry === "object" ? entry : void 0;
|
|
6794
|
+
const text = [
|
|
6795
|
+
String(cfg?.command ?? ""),
|
|
6796
|
+
Array.isArray(cfg?.args) ? cfg.args.join(" ") : "",
|
|
6797
|
+
String(cfg?.url ?? "")
|
|
6798
|
+
].join(" ");
|
|
6799
|
+
if (text.includes("mindgraph-mcp")) return true;
|
|
6800
|
+
}
|
|
6801
|
+
return false;
|
|
6802
|
+
}
|
|
6803
|
+
function pathIsWithin(ancestor, descendant) {
|
|
6804
|
+
const base = import_node_path3.default.resolve(ancestor);
|
|
6805
|
+
const target = import_node_path3.default.resolve(descendant);
|
|
6806
|
+
return target === base || target.startsWith(base + import_node_path3.default.sep);
|
|
6807
|
+
}
|
|
6808
|
+
function claudeMcpRegistrationNote(input, options) {
|
|
6809
|
+
if (options.harness !== "claude-code") return void 0;
|
|
6810
|
+
if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
|
|
6811
|
+
try {
|
|
6812
|
+
const configPath = options.claudeConfigPath || import_node_path3.default.join(import_node_os3.default.homedir(), ".claude.json");
|
|
6813
|
+
if (!import_node_fs3.default.existsSync(configPath)) return void 0;
|
|
6814
|
+
const config = JSON.parse(import_node_fs3.default.readFileSync(configPath, "utf8"));
|
|
6815
|
+
if (hasMindgraphServer(config.mcpServers)) return void 0;
|
|
6816
|
+
const cwd = import_node_path3.default.resolve(input.cwd || ".");
|
|
6817
|
+
for (const [projectDir, project] of Object.entries(config.projects || {})) {
|
|
6818
|
+
if (!pathIsWithin(projectDir, cwd)) continue;
|
|
6819
|
+
if (hasMindgraphServer(project?.mcpServers)) return void 0;
|
|
6820
|
+
}
|
|
6821
|
+
for (let dir = cwd; ; dir = import_node_path3.default.dirname(dir)) {
|
|
6822
|
+
const candidate = import_node_path3.default.join(dir, ".mcp.json");
|
|
6823
|
+
if (import_node_fs3.default.existsSync(candidate)) {
|
|
6824
|
+
const parsed = JSON.parse(import_node_fs3.default.readFileSync(candidate, "utf8"));
|
|
6825
|
+
if (hasMindgraphServer(parsed.mcpServers)) return void 0;
|
|
6826
|
+
}
|
|
6827
|
+
if (dir === import_node_path3.default.dirname(dir)) break;
|
|
6828
|
+
}
|
|
6829
|
+
} catch {
|
|
6830
|
+
return void 0;
|
|
6831
|
+
}
|
|
6832
|
+
return "NOTE: no MindGraph MCP server appears to be registered for Claude Code, so this brief's continuity actions (claim, checkpoint, capture) have no mcp__mindgraph__* tools to run through and durable memory cannot be written this session. Tell the user to run: npx -y mindgraph-mcp@latest install-code \u2014 then start a new session.";
|
|
6833
|
+
}
|
|
6363
6834
|
async function sessionStart(input, client2, options) {
|
|
6364
6835
|
const agentId = resolveAgentId(options);
|
|
6365
6836
|
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
@@ -6449,7 +6920,14 @@ async function sessionStart(input, client2, options) {
|
|
|
6449
6920
|
claimFailed: claimFailed && !stillHeld,
|
|
6450
6921
|
scopeNote
|
|
6451
6922
|
});
|
|
6452
|
-
const
|
|
6923
|
+
const registrationNote = claudeMcpRegistrationNote(input, options);
|
|
6924
|
+
if (registrationNote) {
|
|
6925
|
+
console.error(`mindgraph-hook: ${registrationNote}`);
|
|
6926
|
+
}
|
|
6927
|
+
const contextText = registrationNote ? `${registrationNote}
|
|
6928
|
+
|
|
6929
|
+
${briefText}` : briefText;
|
|
6930
|
+
const briefHash = import_node_crypto3.default.createHash("sha256").update(contextText).digest("hex");
|
|
6453
6931
|
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
6454
6932
|
const changed = ledger.lastBriefHash !== briefHash;
|
|
6455
6933
|
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
@@ -6482,7 +6960,7 @@ async function sessionStart(input, client2, options) {
|
|
|
6482
6960
|
ledger.lastBriefHash = briefHash;
|
|
6483
6961
|
return changed || reinjectCompact;
|
|
6484
6962
|
});
|
|
6485
|
-
return shouldInject ? { kind: "context", context:
|
|
6963
|
+
return shouldInject ? { kind: "context", context: contextText } : NOOP;
|
|
6486
6964
|
}
|
|
6487
6965
|
async function preTool(input, options) {
|
|
6488
6966
|
const original = { ...input.tool_input || {} };
|
|
@@ -6589,9 +7067,7 @@ async function postTool(input, client2, options) {
|
|
|
6589
7067
|
}
|
|
6590
7068
|
}
|
|
6591
7069
|
} else if (aboutLedgerTask) {
|
|
6592
|
-
ledger
|
|
6593
|
-
ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
|
|
6594
|
-
ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
7070
|
+
adoptConflictFence(ledger, payload);
|
|
6595
7071
|
}
|
|
6596
7072
|
}
|
|
6597
7073
|
if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 && // Only ledgers whose lease state was written by a claim THIS code made
|
|
@@ -6648,19 +7124,35 @@ async function postTool(input, client2, options) {
|
|
|
6648
7124
|
})
|
|
6649
7125
|
);
|
|
6650
7126
|
withLedger(input.session_id, options, (ledger) => {
|
|
6651
|
-
if (payloadIsError(response)) return void 0;
|
|
6652
7127
|
if (ledger.taskUid !== active.taskUid) return void 0;
|
|
7128
|
+
if (payloadIsError(response)) {
|
|
7129
|
+
adoptConflictFence(ledger, response);
|
|
7130
|
+
return void 0;
|
|
7131
|
+
}
|
|
6653
7132
|
ledger.taskVersion = number(response?.task_version) ?? ledger.taskVersion;
|
|
6654
7133
|
ledger.leaseEpoch = number(response?.lease_epoch) ?? ledger.leaseEpoch;
|
|
6655
7134
|
ledger.leaseExpiresAt = number(response?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
6656
7135
|
ledger.lastLeaseRenewalAt = nowSeconds;
|
|
6657
7136
|
return void 0;
|
|
6658
7137
|
});
|
|
6659
|
-
} catch {
|
|
7138
|
+
} catch (cause) {
|
|
7139
|
+
const conflict = conflictState(cause);
|
|
7140
|
+
if (Object.keys(conflict).length > 0) {
|
|
7141
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
7142
|
+
if (ledger.taskUid !== active.taskUid) return void 0;
|
|
7143
|
+
adoptConflictFence(ledger, conflict);
|
|
7144
|
+
return void 0;
|
|
7145
|
+
});
|
|
7146
|
+
}
|
|
6660
7147
|
}
|
|
6661
7148
|
}
|
|
6662
7149
|
return NOOP;
|
|
6663
7150
|
}
|
|
7151
|
+
function adoptConflictFence(ledger, payload) {
|
|
7152
|
+
ledger.taskVersion = number(payload?.current_version) ?? ledger.taskVersion;
|
|
7153
|
+
ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
|
|
7154
|
+
ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
7155
|
+
}
|
|
6664
7156
|
function payloadIsError(payload) {
|
|
6665
7157
|
return Boolean(payload && payload.error);
|
|
6666
7158
|
}
|
|
@@ -6813,189 +7305,13 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
6813
7305
|
}
|
|
6814
7306
|
}
|
|
6815
7307
|
|
|
6816
|
-
// src/hook-installer.ts
|
|
6817
|
-
var import_node_fs4 = __toESM(require("fs"));
|
|
6818
|
-
var import_node_os4 = __toESM(require("os"));
|
|
6819
|
-
var import_node_path4 = __toESM(require("path"));
|
|
6820
|
-
var import_node_crypto4 = __toESM(require("crypto"));
|
|
6821
|
-
var OWNER_MARKER = "--owner mindgraph";
|
|
6822
|
-
var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
6823
|
-
function command(harness) {
|
|
6824
|
-
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
6825
|
-
}
|
|
6826
|
-
function windowsCommand(harness) {
|
|
6827
|
-
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
6828
|
-
}
|
|
6829
|
-
function installHookRunner(sourceFile, homeDir) {
|
|
6830
|
-
const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
|
|
6831
|
-
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
|
|
6832
|
-
import_node_fs4.default.copyFileSync(sourceFile, target);
|
|
6833
|
-
return target;
|
|
6834
|
-
}
|
|
6835
|
-
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex")) {
|
|
6836
|
-
if (harness === "codex") {
|
|
6837
|
-
return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
|
|
6838
|
-
}
|
|
6839
|
-
return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
|
|
6840
|
-
}
|
|
6841
|
-
function readSettings(file) {
|
|
6842
|
-
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
6843
|
-
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
6844
|
-
}
|
|
6845
|
-
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
6846
|
-
return {
|
|
6847
|
-
type: "command",
|
|
6848
|
-
command: command(harness),
|
|
6849
|
-
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
6850
|
-
timeout,
|
|
6851
|
-
statusMessage: "Syncing MindGraph session context",
|
|
6852
|
-
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
6853
|
-
};
|
|
6854
|
-
}
|
|
6855
|
-
function desiredEntries(harness) {
|
|
6856
|
-
const common = {
|
|
6857
|
-
SessionStart: [
|
|
6858
|
-
{
|
|
6859
|
-
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
6860
|
-
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
6861
|
-
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
6862
|
-
// open, but no brief reached the model. The same installed runner
|
|
6863
|
-
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
6864
|
-
// The declared Codex context limit must not undercut the brief's own
|
|
6865
|
-
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
6866
|
-
// truncation the renderer knew nothing about.
|
|
6867
|
-
hooks: [
|
|
6868
|
-
ownedHook(
|
|
6869
|
-
harness,
|
|
6870
|
-
30,
|
|
6871
|
-
harness === "codex" ? 1e4 : void 0
|
|
6872
|
-
)
|
|
6873
|
-
]
|
|
6874
|
-
}
|
|
6875
|
-
],
|
|
6876
|
-
PreToolUse: [
|
|
6877
|
-
{
|
|
6878
|
-
matcher: "mcp__mindgraph__.*",
|
|
6879
|
-
// Repository resolution over a large multi-repo workspace plus three
|
|
6880
|
-
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
6881
|
-
// PreToolUse means the call goes through untagged.
|
|
6882
|
-
hooks: [ownedHook(harness, 5)]
|
|
6883
|
-
}
|
|
6884
|
-
],
|
|
6885
|
-
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
6886
|
-
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
6887
|
-
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
|
|
6888
|
-
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
6889
|
-
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
6890
|
-
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
6891
|
-
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
6892
|
-
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
6893
|
-
// map), so the codex adapter instead makes at most ONE call there.
|
|
6894
|
-
SessionEnd: [
|
|
6895
|
-
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
6896
|
-
]
|
|
6897
|
-
};
|
|
6898
|
-
return harness === "claude-code" ? {
|
|
6899
|
-
...common,
|
|
6900
|
-
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
6901
|
-
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
6902
|
-
} : common;
|
|
6903
|
-
}
|
|
6904
|
-
function hookCommand(value) {
|
|
6905
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
6906
|
-
const command2 = value.command;
|
|
6907
|
-
return typeof command2 === "string" ? command2 : void 0;
|
|
6908
|
-
}
|
|
6909
|
-
function entryHasOwnedHook(value) {
|
|
6910
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
6911
|
-
const hooks = value.hooks;
|
|
6912
|
-
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
6913
|
-
}
|
|
6914
|
-
function writeSettings(file, settings) {
|
|
6915
|
-
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
6916
|
-
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
6917
|
-
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
6918
|
-
`, {
|
|
6919
|
-
mode: 384
|
|
6920
|
-
});
|
|
6921
|
-
import_node_fs4.default.renameSync(temporary, file);
|
|
6922
|
-
}
|
|
6923
|
-
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
6924
|
-
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
6925
|
-
const settings = readSettings(file);
|
|
6926
|
-
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
6927
|
-
let added = 0;
|
|
6928
|
-
let updated = 0;
|
|
6929
|
-
for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
|
|
6930
|
-
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
6931
|
-
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
6932
|
-
const hadOwned = kept.length !== current.length;
|
|
6933
|
-
for (const entry of wanted) {
|
|
6934
|
-
kept.push(entry);
|
|
6935
|
-
if (hadOwned) updated += 1;
|
|
6936
|
-
else added += 1;
|
|
6937
|
-
}
|
|
6938
|
-
hooks[event] = kept;
|
|
6939
|
-
}
|
|
6940
|
-
settings.hooks = hooks;
|
|
6941
|
-
writeSettings(file, settings);
|
|
6942
|
-
return { path: file, added, updated };
|
|
6943
|
-
}
|
|
6944
|
-
function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
6945
|
-
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
6946
|
-
if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
|
|
6947
|
-
const settings = readSettings(file);
|
|
6948
|
-
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
6949
|
-
let removed = 0;
|
|
6950
|
-
for (const [event, value] of Object.entries(hooks)) {
|
|
6951
|
-
if (!Array.isArray(value)) continue;
|
|
6952
|
-
const retainedEntries = [];
|
|
6953
|
-
for (const rawEntry of value) {
|
|
6954
|
-
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
6955
|
-
retainedEntries.push(rawEntry);
|
|
6956
|
-
continue;
|
|
6957
|
-
}
|
|
6958
|
-
const entry = { ...rawEntry };
|
|
6959
|
-
if (!Array.isArray(entry.hooks)) {
|
|
6960
|
-
retainedEntries.push(entry);
|
|
6961
|
-
continue;
|
|
6962
|
-
}
|
|
6963
|
-
const retainedHooks = entry.hooks.filter((hook) => {
|
|
6964
|
-
const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
|
|
6965
|
-
if (owned) removed += 1;
|
|
6966
|
-
return !owned;
|
|
6967
|
-
});
|
|
6968
|
-
if (retainedHooks.length > 0) {
|
|
6969
|
-
entry.hooks = retainedHooks;
|
|
6970
|
-
retainedEntries.push(entry);
|
|
6971
|
-
}
|
|
6972
|
-
}
|
|
6973
|
-
if (retainedEntries.length > 0) hooks[event] = retainedEntries;
|
|
6974
|
-
else delete hooks[event];
|
|
6975
|
-
}
|
|
6976
|
-
settings.hooks = hooks;
|
|
6977
|
-
writeSettings(file, settings);
|
|
6978
|
-
return { path: file, removed };
|
|
6979
|
-
}
|
|
6980
|
-
function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
6981
|
-
return installHooks("claude-code", scope, projectDir);
|
|
6982
|
-
}
|
|
6983
|
-
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
6984
|
-
return uninstallHooks("claude-code", scope, projectDir);
|
|
6985
|
-
}
|
|
6986
|
-
function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
6987
|
-
return installHooks("codex", scope, projectDir, codexHomeDir);
|
|
6988
|
-
}
|
|
6989
|
-
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
6990
|
-
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
6991
|
-
}
|
|
6992
|
-
|
|
6993
7308
|
// src/cli.ts
|
|
7309
|
+
init_hook_installer();
|
|
6994
7310
|
function recordHookHealth(harness, error) {
|
|
6995
7311
|
try {
|
|
6996
|
-
const dir = process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
7312
|
+
const dir = process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime");
|
|
6997
7313
|
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
6998
|
-
const file =
|
|
7314
|
+
const file = path6.join(dir, "hook-health.json");
|
|
6999
7315
|
let failures = 0;
|
|
7000
7316
|
try {
|
|
7001
7317
|
failures = Number(JSON.parse(fs5.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
@@ -7017,8 +7333,8 @@ function recordHookHealth(harness, error) {
|
|
|
7017
7333
|
}
|
|
7018
7334
|
function clearHookHealth(harness) {
|
|
7019
7335
|
try {
|
|
7020
|
-
const file =
|
|
7021
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
7336
|
+
const file = path6.join(
|
|
7337
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
|
|
7022
7338
|
"hook-health.json"
|
|
7023
7339
|
);
|
|
7024
7340
|
if (!fs5.existsSync(file)) return;
|
|
@@ -7040,7 +7356,7 @@ function clearHookHealth(harness) {
|
|
|
7040
7356
|
function getClaudeDesktopConfigPath() {
|
|
7041
7357
|
switch (process.platform) {
|
|
7042
7358
|
case "darwin":
|
|
7043
|
-
return
|
|
7359
|
+
return path6.join(
|
|
7044
7360
|
os4.homedir(),
|
|
7045
7361
|
"Library",
|
|
7046
7362
|
"Application Support",
|
|
@@ -7048,13 +7364,13 @@ function getClaudeDesktopConfigPath() {
|
|
|
7048
7364
|
"claude_desktop_config.json"
|
|
7049
7365
|
);
|
|
7050
7366
|
case "win32":
|
|
7051
|
-
return
|
|
7052
|
-
process.env.APPDATA ||
|
|
7367
|
+
return path6.join(
|
|
7368
|
+
process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming"),
|
|
7053
7369
|
"Claude",
|
|
7054
7370
|
"claude_desktop_config.json"
|
|
7055
7371
|
);
|
|
7056
7372
|
case "linux":
|
|
7057
|
-
return
|
|
7373
|
+
return path6.join(
|
|
7058
7374
|
os4.homedir(),
|
|
7059
7375
|
".config",
|
|
7060
7376
|
"Claude",
|
|
@@ -7070,7 +7386,7 @@ function readJsonFile(filePath) {
|
|
|
7070
7386
|
return JSON.parse(content);
|
|
7071
7387
|
}
|
|
7072
7388
|
function writeJsonFile(filePath, data) {
|
|
7073
|
-
const dir =
|
|
7389
|
+
const dir = path6.dirname(filePath);
|
|
7074
7390
|
if (!fs5.existsSync(dir)) {
|
|
7075
7391
|
fs5.mkdirSync(dir, { recursive: true });
|
|
7076
7392
|
}
|
|
@@ -7206,8 +7522,8 @@ function printStatus() {
|
|
|
7206
7522
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
7207
7523
|
);
|
|
7208
7524
|
try {
|
|
7209
|
-
const healthFile =
|
|
7210
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
7525
|
+
const healthFile = path6.join(
|
|
7526
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
|
|
7211
7527
|
"hook-health.json"
|
|
7212
7528
|
);
|
|
7213
7529
|
if (fs5.existsSync(healthFile)) {
|
|
@@ -7228,7 +7544,7 @@ function withHooksFlag() {
|
|
|
7228
7544
|
}
|
|
7229
7545
|
function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
7230
7546
|
if (withHooks) {
|
|
7231
|
-
const runner = installHookRunner(__filename);
|
|
7547
|
+
const runner = installHookRunner(__filename, void 0, "0.18.0");
|
|
7232
7548
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
7233
7549
|
const result = installClaudeHooks("user");
|
|
7234
7550
|
console.log(
|
|
@@ -7458,7 +7774,7 @@ async function main2() {
|
|
|
7458
7774
|
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
7459
7775
|
throw new Error("--harness must be claude-code or codex");
|
|
7460
7776
|
}
|
|
7461
|
-
const runner = installHookRunner(__filename);
|
|
7777
|
+
const runner = installHookRunner(__filename, void 0, "0.18.0");
|
|
7462
7778
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
7463
7779
|
const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
|
|
7464
7780
|
console.log(
|