mindgraph-mcp 0.17.0 → 0.19.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 +58 -2
- package/dist/cli.js +1440 -452
- package/dist/index.js +563 -52
- package/package.json +2 -2
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, path8, body) {
|
|
69
|
+
const url = `${this.baseUrl}${path8}`;
|
|
70
70
|
const requestId = createRequestId();
|
|
71
71
|
const init = {
|
|
72
72
|
method,
|
|
@@ -91,13 +91,14 @@ var init_dist = __esm({
|
|
|
91
91
|
parsed = text2;
|
|
92
92
|
}
|
|
93
93
|
const err5 = new MindGraphError(
|
|
94
|
-
`${method} ${
|
|
94
|
+
`${method} ${path8} failed: ${res.status}`,
|
|
95
95
|
res.status,
|
|
96
96
|
parsed
|
|
97
97
|
);
|
|
98
98
|
if (res.status === 503 && attempt < this.maxRetries) {
|
|
99
99
|
lastError = err5;
|
|
100
|
-
const
|
|
100
|
+
const retryAfterSecs = Number(res.headers.get("retry-after"));
|
|
101
|
+
const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
|
|
101
102
|
await new Promise((r) => setTimeout(r, delay));
|
|
102
103
|
continue;
|
|
103
104
|
}
|
|
@@ -109,17 +110,17 @@ var init_dist = __esm({
|
|
|
109
110
|
}
|
|
110
111
|
throw lastError;
|
|
111
112
|
}
|
|
112
|
-
get(
|
|
113
|
-
return this.request("GET",
|
|
113
|
+
get(path8) {
|
|
114
|
+
return this.request("GET", path8);
|
|
114
115
|
}
|
|
115
|
-
post(
|
|
116
|
-
return this.request("POST",
|
|
116
|
+
post(path8, body) {
|
|
117
|
+
return this.request("POST", path8, body);
|
|
117
118
|
}
|
|
118
|
-
patch(
|
|
119
|
-
return this.request("PATCH",
|
|
119
|
+
patch(path8, body) {
|
|
120
|
+
return this.request("PATCH", path8, body);
|
|
120
121
|
}
|
|
121
|
-
del(
|
|
122
|
-
return this.request("DELETE",
|
|
122
|
+
del(path8) {
|
|
123
|
+
return this.request("DELETE", path8);
|
|
123
124
|
}
|
|
124
125
|
// ---- Health ----
|
|
125
126
|
async health() {
|
|
@@ -148,6 +149,52 @@ var init_dist = __esm({
|
|
|
148
149
|
async entity(req) {
|
|
149
150
|
return this.post("/reality/entity", req);
|
|
150
151
|
}
|
|
152
|
+
async series(req) {
|
|
153
|
+
return this.post("/reality/series", req);
|
|
154
|
+
}
|
|
155
|
+
async createSeries(req) {
|
|
156
|
+
return this.post("/reality/series", { action: "create", ...req });
|
|
157
|
+
}
|
|
158
|
+
async appendSeries(req) {
|
|
159
|
+
return this.post("/reality/series", { action: "append", ...req });
|
|
160
|
+
}
|
|
161
|
+
async seriesWindow(req) {
|
|
162
|
+
return this.post("/reality/series", { action: "window", ...req });
|
|
163
|
+
}
|
|
164
|
+
async aggregateSeries(req) {
|
|
165
|
+
return this.post("/reality/series", { action: "aggregate", ...req });
|
|
166
|
+
}
|
|
167
|
+
async latestSeries(seriesUid, options) {
|
|
168
|
+
return this.post("/reality/series", {
|
|
169
|
+
action: "latest",
|
|
170
|
+
series_uid: seriesUid,
|
|
171
|
+
...options
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async listSeriesForEntity(entityUid, options) {
|
|
175
|
+
return this.post("/reality/series", {
|
|
176
|
+
action: "list_for_entity",
|
|
177
|
+
entity_uid: entityUid,
|
|
178
|
+
...options
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async batchLatestSeries(entityUids, options) {
|
|
182
|
+
return this.post("/reality/series", {
|
|
183
|
+
action: "batch_latest",
|
|
184
|
+
entity_uids: entityUids,
|
|
185
|
+
...options
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
async batchAggregateSeries(req) {
|
|
189
|
+
return this.post("/reality/series", { action: "batch_aggregate", ...req });
|
|
190
|
+
}
|
|
191
|
+
async deleteSeries(seriesUid, options) {
|
|
192
|
+
return this.post("/reality/series", {
|
|
193
|
+
action: "delete_series",
|
|
194
|
+
series_uid: seriesUid,
|
|
195
|
+
...options
|
|
196
|
+
});
|
|
197
|
+
}
|
|
151
198
|
/** Convenience: create or find an entity by label. */
|
|
152
199
|
async findOrCreateEntity(label, props, agentId) {
|
|
153
200
|
return this.post("/reality/entity", {
|
|
@@ -717,6 +764,20 @@ var init_dist = __esm({
|
|
|
717
764
|
async getOntologySchema(id) {
|
|
718
765
|
return this.get(`/v1/ontology/schemas/${seg(id)}`);
|
|
719
766
|
}
|
|
767
|
+
async createOntologySeriesBinding(schemaId, request) {
|
|
768
|
+
return this.post(`/v1/ontology/schemas/${seg(schemaId)}/series-bindings`, request);
|
|
769
|
+
}
|
|
770
|
+
async syncOntologySeriesBinding(bindingId, mode) {
|
|
771
|
+
return this.post(
|
|
772
|
+
`/v1/ontology/series-bindings/${seg(bindingId)}/sync`,
|
|
773
|
+
mode ? { mode } : {}
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
async archiveOntologySeriesBinding(schemaId, bindingId) {
|
|
777
|
+
return this.del(
|
|
778
|
+
`/v1/ontology/schemas/${seg(schemaId)}/series-bindings/${seg(bindingId)}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
720
781
|
async createOntologySchema(req) {
|
|
721
782
|
return this.post("/v1/ontology/schemas", req);
|
|
722
783
|
}
|
|
@@ -971,6 +1032,14 @@ var init_hook_env = __esm({
|
|
|
971
1032
|
});
|
|
972
1033
|
|
|
973
1034
|
// src/codegraph.ts
|
|
1035
|
+
function invocationCwd(args) {
|
|
1036
|
+
const context = args.invocation_context;
|
|
1037
|
+
if (context && typeof context === "object" && !Array.isArray(context)) {
|
|
1038
|
+
const cwd = context.cwd;
|
|
1039
|
+
if (typeof cwd === "string" && cwd) return cwd;
|
|
1040
|
+
}
|
|
1041
|
+
return process.cwd();
|
|
1042
|
+
}
|
|
974
1043
|
function unavailabilityCaveats(reason, message) {
|
|
975
1044
|
const caveats = [message || "codegraph status failed"];
|
|
976
1045
|
if (reason === "absent") {
|
|
@@ -980,14 +1049,14 @@ function unavailabilityCaveats(reason, message) {
|
|
|
980
1049
|
}
|
|
981
1050
|
return caveats;
|
|
982
1051
|
}
|
|
983
|
-
function normalizePath(
|
|
984
|
-
return
|
|
1052
|
+
function normalizePath(path8) {
|
|
1053
|
+
return path8.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
985
1054
|
}
|
|
986
1055
|
function normalizeSignature(signature) {
|
|
987
1056
|
return signature.replace(/\s+/g, " ").trim();
|
|
988
1057
|
}
|
|
989
|
-
function languageFromPath(
|
|
990
|
-
const extension = (0, import_node_path2.extname)(
|
|
1058
|
+
function languageFromPath(path8) {
|
|
1059
|
+
const extension = (0, import_node_path2.extname)(path8).toLowerCase();
|
|
991
1060
|
return {
|
|
992
1061
|
".c": "c",
|
|
993
1062
|
".cc": "cpp",
|
|
@@ -1150,7 +1219,7 @@ function codeRefIdentityKey(ref) {
|
|
|
1150
1219
|
}
|
|
1151
1220
|
};
|
|
1152
1221
|
}
|
|
1153
|
-
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2,
|
|
1222
|
+
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto3, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
|
|
1154
1223
|
var init_codegraph = __esm({
|
|
1155
1224
|
"src/codegraph.ts"() {
|
|
1156
1225
|
"use strict";
|
|
@@ -1159,7 +1228,17 @@ var init_codegraph = __esm({
|
|
|
1159
1228
|
import_promises = require("fs/promises");
|
|
1160
1229
|
import_node_os2 = require("os");
|
|
1161
1230
|
import_node_path2 = require("path");
|
|
1162
|
-
|
|
1231
|
+
import_node_crypto3 = require("crypto");
|
|
1232
|
+
UnknownRepositoryError = class extends Error {
|
|
1233
|
+
constructor(repo) {
|
|
1234
|
+
super(
|
|
1235
|
+
`unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
|
|
1236
|
+
);
|
|
1237
|
+
this.repo = repo;
|
|
1238
|
+
this.name = "UnknownRepositoryError";
|
|
1239
|
+
}
|
|
1240
|
+
repo;
|
|
1241
|
+
};
|
|
1163
1242
|
DEFAULT_TIMEOUT_MS = 5e3;
|
|
1164
1243
|
DEFAULT_STATUS_TTL_MS = 6e4;
|
|
1165
1244
|
MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
@@ -1195,7 +1274,26 @@ var init_codegraph = __esm({
|
|
|
1195
1274
|
selected.spaceUid
|
|
1196
1275
|
) : selected;
|
|
1197
1276
|
}
|
|
1198
|
-
|
|
1277
|
+
if (repo) {
|
|
1278
|
+
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, repo)).catch(
|
|
1279
|
+
() => (0, import_node_path2.resolve)(this.cwd, repo)
|
|
1280
|
+
);
|
|
1281
|
+
if (!(0, import_node_fs2.existsSync)(requestedPath)) {
|
|
1282
|
+
const current = await this.currentRepository(fallbackSpaceUid);
|
|
1283
|
+
if (current.repoId === repo) return current;
|
|
1284
|
+
throw new UnknownRepositoryError(repo);
|
|
1285
|
+
}
|
|
1286
|
+
const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
|
|
1287
|
+
return this.resolveRepositoryIdentity(
|
|
1288
|
+
repoRoot,
|
|
1289
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
1290
|
+
this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
return this.currentRepository(fallbackSpaceUid);
|
|
1294
|
+
}
|
|
1295
|
+
async currentRepository(fallbackSpaceUid) {
|
|
1296
|
+
const requestedRoot = this.env.MINDGRAPH_REPO_ROOT || this.cwd;
|
|
1199
1297
|
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
|
|
1200
1298
|
() => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
|
|
1201
1299
|
);
|
|
@@ -1259,7 +1357,7 @@ var init_codegraph = __esm({
|
|
|
1259
1357
|
} catch {
|
|
1260
1358
|
}
|
|
1261
1359
|
(0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
|
|
1262
|
-
const generated = (0,
|
|
1360
|
+
const generated = (0, import_node_crypto3.randomUUID)();
|
|
1263
1361
|
try {
|
|
1264
1362
|
(0, import_node_fs2.writeFileSync)(identityFile, `${generated}
|
|
1265
1363
|
`, {
|
|
@@ -1285,6 +1383,14 @@ var init_codegraph = __esm({
|
|
|
1285
1383
|
).catch(() => "");
|
|
1286
1384
|
return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
|
|
1287
1385
|
}
|
|
1386
|
+
/** Why the workspace map yielded no repositories — for callers that were
|
|
1387
|
+
* explicitly asked to fan out and would otherwise return silently empty. */
|
|
1388
|
+
workspaceDiagnostic() {
|
|
1389
|
+
if (!this.workspaceFile || !(0, import_node_fs2.existsSync)(this.workspaceFile)) {
|
|
1390
|
+
return "no .mindgraph/workspace.json was found between the working directory and the filesystem root";
|
|
1391
|
+
}
|
|
1392
|
+
return `${this.workspaceFile} is malformed or declares no repositories`;
|
|
1393
|
+
}
|
|
1288
1394
|
async availability(repository) {
|
|
1289
1395
|
const cached = this.statusCache.get(repository.repoRoot);
|
|
1290
1396
|
if (cached && cached.expiresAt > this.now()) return cached.availability;
|
|
@@ -1698,13 +1804,13 @@ var init_error_detail = __esm({
|
|
|
1698
1804
|
});
|
|
1699
1805
|
|
|
1700
1806
|
// src/code-tool.ts
|
|
1701
|
-
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
1807
|
+
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
|
|
1702
1808
|
const codeClient = client2;
|
|
1703
1809
|
const action = args.action;
|
|
1704
1810
|
const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
|
|
1705
1811
|
const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
|
|
1706
1812
|
const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
|
|
1707
|
-
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid :
|
|
1813
|
+
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
|
|
1708
1814
|
const options = {
|
|
1709
1815
|
repo: typeof args.repo === "string" ? args.repo : void 0,
|
|
1710
1816
|
fallbackSpaceUid
|
|
@@ -1828,6 +1934,7 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1828
1934
|
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
1829
1935
|
let graphContext;
|
|
1830
1936
|
const graphRefs = [];
|
|
1937
|
+
let unavailableRepositories = [];
|
|
1831
1938
|
if (targetUid) {
|
|
1832
1939
|
graphContext = await codeClient.traverse({
|
|
1833
1940
|
action: "neighborhood",
|
|
@@ -1835,9 +1942,13 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1835
1942
|
depth: 2,
|
|
1836
1943
|
max_nodes: 50
|
|
1837
1944
|
});
|
|
1838
|
-
|
|
1839
|
-
|
|
1945
|
+
const fromGraph = await codeRefsFromGraphContext(
|
|
1946
|
+
codeClient,
|
|
1947
|
+
adapter,
|
|
1948
|
+
graphContext
|
|
1840
1949
|
);
|
|
1950
|
+
graphRefs.push(...fromGraph.refs);
|
|
1951
|
+
unavailableRepositories = fromGraph.unavailable;
|
|
1841
1952
|
}
|
|
1842
1953
|
if (refs.length > 0 || anchorUids.length > 0) {
|
|
1843
1954
|
const target = await resolveSingleTarget(
|
|
@@ -1856,7 +1967,8 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1856
1967
|
target_uid: targetUid,
|
|
1857
1968
|
graph_context: graphContext,
|
|
1858
1969
|
affected: [],
|
|
1859
|
-
degradation: "no materialized code anchors were attached"
|
|
1970
|
+
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
1971
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
1860
1972
|
});
|
|
1861
1973
|
}
|
|
1862
1974
|
const impacts = await Promise.all(
|
|
@@ -1873,13 +1985,17 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
|
1873
1985
|
target_uid: targetUid,
|
|
1874
1986
|
graph_context: graphContext,
|
|
1875
1987
|
impacts,
|
|
1876
|
-
materialized: joined
|
|
1988
|
+
materialized: joined,
|
|
1989
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
1877
1990
|
});
|
|
1878
1991
|
}
|
|
1879
1992
|
default:
|
|
1880
1993
|
return err("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
1881
1994
|
}
|
|
1882
1995
|
} catch (cause) {
|
|
1996
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
1997
|
+
return err("unknown_repository", cause.message);
|
|
1998
|
+
}
|
|
1883
1999
|
return err("code_tool_failed", errorDetail(cause));
|
|
1884
2000
|
}
|
|
1885
2001
|
}
|
|
@@ -1905,7 +2021,10 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
|
1905
2021
|
code_refs: args.code_refs,
|
|
1906
2022
|
repo: args.repo,
|
|
1907
2023
|
repo_space_uid: args.repo_space_uid,
|
|
1908
|
-
agent_id: args.agent_id
|
|
2024
|
+
agent_id: args.agent_id,
|
|
2025
|
+
// Keeps the default adapter anchored to the live session directory
|
|
2026
|
+
// rather than wherever the MCP server process was launched.
|
|
2027
|
+
invocation_context: args.invocation_context
|
|
1909
2028
|
},
|
|
1910
2029
|
adapter
|
|
1911
2030
|
);
|
|
@@ -1915,17 +2034,33 @@ async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
|
1915
2034
|
const anchorUids = (anchors.anchors ?? []).flatMap(
|
|
1916
2035
|
(anchor) => anchor.uid ? [anchor.uid] : []
|
|
1917
2036
|
);
|
|
2037
|
+
const relateFailures = [];
|
|
1918
2038
|
for (const targetUid of targetUids) {
|
|
1919
2039
|
for (const anchorUid of anchorUids) {
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2040
|
+
try {
|
|
2041
|
+
await codeClient.entity({
|
|
2042
|
+
action: "relate",
|
|
2043
|
+
source_uid: targetUid,
|
|
2044
|
+
target_uid: anchorUid,
|
|
2045
|
+
edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
|
|
2046
|
+
agent_id: args.agent_id
|
|
2047
|
+
});
|
|
2048
|
+
} catch (cause) {
|
|
2049
|
+
relateFailures.push(
|
|
2050
|
+
`${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
1927
2053
|
}
|
|
1928
2054
|
}
|
|
2055
|
+
if (relateFailures.length > 0) {
|
|
2056
|
+
return ok({
|
|
2057
|
+
result: original,
|
|
2058
|
+
code_anchors: {
|
|
2059
|
+
...anchors,
|
|
2060
|
+
caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
1929
2064
|
}
|
|
1930
2065
|
return ok({ result: original, code_anchors: anchors });
|
|
1931
2066
|
}
|
|
@@ -1998,16 +2133,34 @@ async function codeRefFromAnchor(client2, adapter, uid) {
|
|
|
1998
2133
|
if (!attributes || typeof attributes !== "object") return void 0;
|
|
1999
2134
|
const codeRef = attributes.code_ref;
|
|
2000
2135
|
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
2001
|
-
|
|
2002
|
-
|
|
2136
|
+
try {
|
|
2137
|
+
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
2138
|
+
return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
|
|
2139
|
+
} catch (cause) {
|
|
2140
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2141
|
+
return { missingRepo: codeRef.repoId };
|
|
2142
|
+
}
|
|
2143
|
+
throw cause;
|
|
2144
|
+
}
|
|
2003
2145
|
}
|
|
2004
2146
|
async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
|
|
2005
2147
|
if (refs.length > 1 || anchorUids.length > 1) {
|
|
2006
2148
|
return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
|
|
2007
2149
|
}
|
|
2008
2150
|
if (anchorUids.length === 1) {
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2151
|
+
const outcome = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
|
|
2152
|
+
if (!outcome) {
|
|
2153
|
+
return { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
|
|
2154
|
+
}
|
|
2155
|
+
if ("missingRepo" in outcome) {
|
|
2156
|
+
return {
|
|
2157
|
+
error: err(
|
|
2158
|
+
"repository_unavailable",
|
|
2159
|
+
`repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
|
|
2160
|
+
)
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
return { ref: outcome.ref };
|
|
2011
2164
|
}
|
|
2012
2165
|
if (refs.length !== 1) {
|
|
2013
2166
|
return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
@@ -2046,7 +2199,7 @@ async function joinMaterialized(client2, refs, agentId, limit) {
|
|
|
2046
2199
|
async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
2047
2200
|
const uids = /* @__PURE__ */ new Set();
|
|
2048
2201
|
collectUids(context, uids);
|
|
2049
|
-
if (uids.size === 0) return [];
|
|
2202
|
+
if (uids.size === 0) return { refs: [], unavailable: [] };
|
|
2050
2203
|
const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
|
|
2051
2204
|
const stored = nodes.flatMap((node) => {
|
|
2052
2205
|
if (!node) return [];
|
|
@@ -2055,12 +2208,21 @@ async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
|
2055
2208
|
const codeRef = attributes.code_ref;
|
|
2056
2209
|
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
2057
2210
|
});
|
|
2058
|
-
|
|
2059
|
-
|
|
2211
|
+
const refs = [];
|
|
2212
|
+
const unavailable = /* @__PURE__ */ new Set();
|
|
2213
|
+
for (const ref of stored) {
|
|
2214
|
+
try {
|
|
2060
2215
|
const repository = await adapter.resolveRepository(ref.repoId);
|
|
2061
|
-
|
|
2062
|
-
})
|
|
2063
|
-
|
|
2216
|
+
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
2217
|
+
} catch (cause) {
|
|
2218
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2219
|
+
unavailable.add(ref.repoId);
|
|
2220
|
+
} else {
|
|
2221
|
+
throw cause;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
return { refs, unavailable: [...unavailable].sort() };
|
|
2064
2226
|
}
|
|
2065
2227
|
function collectUids(value, uids) {
|
|
2066
2228
|
if (Array.isArray(value)) {
|
|
@@ -2136,7 +2298,7 @@ var init_code_tool = __esm({
|
|
|
2136
2298
|
});
|
|
2137
2299
|
CODE_TOOL = {
|
|
2138
2300
|
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.",
|
|
2301
|
+
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
2302
|
inputSchema: {
|
|
2141
2303
|
type: "object",
|
|
2142
2304
|
properties: {
|
|
@@ -2178,7 +2340,7 @@ var init_code_tool = __esm({
|
|
|
2178
2340
|
},
|
|
2179
2341
|
repo_space_uid: {
|
|
2180
2342
|
type: "string",
|
|
2181
|
-
description: "Writable shared Project/repository Space for new anchors"
|
|
2343
|
+
description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
|
|
2182
2344
|
},
|
|
2183
2345
|
task_uid: { type: "string" },
|
|
2184
2346
|
execution_uid: { type: "string" },
|
|
@@ -2197,179 +2359,469 @@ var init_code_tool = __esm({
|
|
|
2197
2359
|
}
|
|
2198
2360
|
});
|
|
2199
2361
|
|
|
2200
|
-
// src/
|
|
2201
|
-
function
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2362
|
+
// src/hook-installer.ts
|
|
2363
|
+
function command(harness) {
|
|
2364
|
+
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
2365
|
+
}
|
|
2366
|
+
function windowsCommand(harness) {
|
|
2367
|
+
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
2368
|
+
}
|
|
2369
|
+
function installHookRunner(sourceFile, homeDir, version) {
|
|
2370
|
+
const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
|
|
2371
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
|
|
2372
|
+
const temporary = `${target}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
|
|
2373
|
+
import_node_fs4.default.copyFileSync(sourceFile, temporary);
|
|
2374
|
+
import_node_fs4.default.renameSync(temporary, target);
|
|
2375
|
+
const versionPath = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
|
|
2376
|
+
if (version) {
|
|
2377
|
+
import_node_fs4.default.writeFileSync(versionPath, `${version}
|
|
2378
|
+
`);
|
|
2379
|
+
} else {
|
|
2380
|
+
import_node_fs4.default.rmSync(versionPath, { force: true });
|
|
2206
2381
|
}
|
|
2207
|
-
return
|
|
2382
|
+
return target;
|
|
2208
2383
|
}
|
|
2209
|
-
function
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2384
|
+
function installedHookRunnerVersion(homeDir) {
|
|
2385
|
+
try {
|
|
2386
|
+
const raw = import_node_fs4.default.readFileSync(
|
|
2387
|
+
import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE),
|
|
2388
|
+
"utf8"
|
|
2389
|
+
).trim();
|
|
2390
|
+
return raw || void 0;
|
|
2391
|
+
} catch {
|
|
2392
|
+
return void 0;
|
|
2393
|
+
}
|
|
2215
2394
|
}
|
|
2216
|
-
function
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
bytes: Buffer.byteLength(content),
|
|
2222
|
-
content
|
|
2223
|
-
};
|
|
2395
|
+
function parseSemver(value) {
|
|
2396
|
+
if (!value) return void 0;
|
|
2397
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
|
|
2398
|
+
if (!match) return void 0;
|
|
2399
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
2224
2400
|
}
|
|
2225
|
-
|
|
2401
|
+
function semverNewer(a, b) {
|
|
2402
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2403
|
+
if (a[index] !== b[index]) return a[index] > b[index];
|
|
2404
|
+
}
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
|
|
2226
2408
|
try {
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
const adapter = new CodegraphAdapter({
|
|
2230
|
-
cwd: context.cwd || process.cwd()
|
|
2231
|
-
});
|
|
2232
|
-
const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
|
|
2233
|
-
const repository = await adapter.resolveRepository(requestedRepo);
|
|
2234
|
-
const root = repository.repoRoot;
|
|
2235
|
-
const repoId = repository.repoId;
|
|
2236
|
-
const provider = String(args.provider || "claude_project");
|
|
2237
|
-
const remote = client2;
|
|
2238
|
-
if (action === "scan") {
|
|
2239
|
-
const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
|
|
2240
|
-
const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
|
|
2241
|
-
const scans = [];
|
|
2242
|
-
for (const selected of repositories) {
|
|
2243
|
-
scans.push(
|
|
2244
|
-
await scanRepository(
|
|
2245
|
-
remote,
|
|
2246
|
-
selected,
|
|
2247
|
-
requested,
|
|
2248
|
-
provider,
|
|
2249
|
-
args.agent_id
|
|
2250
|
-
)
|
|
2251
|
-
);
|
|
2252
|
-
}
|
|
2253
|
-
if (args.workspace === true) {
|
|
2254
|
-
return ok2({
|
|
2255
|
-
provider,
|
|
2256
|
-
workspace: true,
|
|
2257
|
-
repositories: scans,
|
|
2258
|
-
one_way: "files_to_graph"
|
|
2259
|
-
});
|
|
2260
|
-
}
|
|
2261
|
-
return ok2(scans[0]);
|
|
2409
|
+
if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
|
|
2410
|
+
return { refreshed: false, reason: "disabled" };
|
|
2262
2411
|
}
|
|
2263
|
-
const
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
repo_id: repoId
|
|
2267
|
-
};
|
|
2268
|
-
delete request.invocation_context;
|
|
2269
|
-
delete request.paths;
|
|
2270
|
-
delete request.repo;
|
|
2271
|
-
delete request.workspace;
|
|
2272
|
-
if (action === "begin" && !request.content) {
|
|
2273
|
-
if (!request.logical_path) {
|
|
2274
|
-
return err2("missing_field", "logical_path is required for begin");
|
|
2275
|
-
}
|
|
2276
|
-
const file = fileDescriptor(
|
|
2277
|
-
root,
|
|
2278
|
-
safePath(root, String(request.logical_path))
|
|
2279
|
-
);
|
|
2280
|
-
request.content = file.content;
|
|
2281
|
-
request.content_hash ||= file.content_hash;
|
|
2412
|
+
const home = homeDir || import_node_os4.default.homedir();
|
|
2413
|
+
if (!import_node_fs4.default.existsSync(import_node_path4.default.join(home, RUNNER_RELATIVE))) {
|
|
2414
|
+
return { refreshed: false, reason: "no_runner" };
|
|
2282
2415
|
}
|
|
2283
|
-
if (
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2416
|
+
if (!import_node_fs4.default.existsSync(sourceBundle)) {
|
|
2417
|
+
return { refreshed: false, reason: "no_source" };
|
|
2418
|
+
}
|
|
2419
|
+
const server2 = parseSemver(serverVersion);
|
|
2420
|
+
if (!server2) return { refreshed: false, reason: "unparseable" };
|
|
2421
|
+
const runnerVersion = installedHookRunnerVersion(home);
|
|
2422
|
+
if (runnerVersion !== void 0) {
|
|
2423
|
+
const pinned = parseSemver(runnerVersion);
|
|
2424
|
+
if (!pinned) {
|
|
2425
|
+
return { refreshed: false, reason: "unparseable", runnerVersion };
|
|
2426
|
+
}
|
|
2427
|
+
if (!semverNewer(server2, pinned)) {
|
|
2428
|
+
return {
|
|
2429
|
+
refreshed: false,
|
|
2430
|
+
reason: runnerVersion === serverVersion ? "current" : "downgrade",
|
|
2431
|
+
runnerVersion
|
|
2432
|
+
};
|
|
2288
2433
|
}
|
|
2289
2434
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2435
|
+
installHookRunner(sourceBundle, home, serverVersion);
|
|
2436
|
+
return { refreshed: true, reason: "updated", runnerVersion };
|
|
2437
|
+
} catch {
|
|
2438
|
+
return { refreshed: false, reason: "error" };
|
|
2293
2439
|
}
|
|
2294
2440
|
}
|
|
2295
|
-
|
|
2296
|
-
const
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
)
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
action: "scan",
|
|
2308
|
-
provider,
|
|
2309
|
-
repo_id: repository.repoId,
|
|
2310
|
-
logical_path: file.logical_path,
|
|
2311
|
-
content_hash: file.content_hash,
|
|
2312
|
-
file_present: true,
|
|
2313
|
-
agent_id: agentId
|
|
2314
|
-
});
|
|
2315
|
-
results.push({
|
|
2316
|
-
logical_path: file.logical_path,
|
|
2317
|
-
content_hash: file.content_hash,
|
|
2318
|
-
bytes: file.bytes,
|
|
2319
|
-
status
|
|
2320
|
-
});
|
|
2441
|
+
function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
|
|
2442
|
+
const refreshed = [];
|
|
2443
|
+
const scopes = ["user", "project"];
|
|
2444
|
+
for (const scope of scopes) {
|
|
2445
|
+
try {
|
|
2446
|
+
const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
|
|
2447
|
+
if (!import_node_fs4.default.existsSync(file)) continue;
|
|
2448
|
+
if (!import_node_fs4.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
|
|
2449
|
+
installHooks("claude-code", scope, projectDir, void 0, homeDir);
|
|
2450
|
+
refreshed.push(file);
|
|
2451
|
+
} catch {
|
|
2452
|
+
}
|
|
2321
2453
|
}
|
|
2454
|
+
return refreshed;
|
|
2455
|
+
}
|
|
2456
|
+
function versionSkewNote(serverVersion, homeDir) {
|
|
2457
|
+
const runnerVersion = installedHookRunnerVersion(homeDir);
|
|
2458
|
+
if (!runnerVersion || runnerVersion === serverVersion) return void 0;
|
|
2459
|
+
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).`;
|
|
2460
|
+
}
|
|
2461
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex"), homeDir) {
|
|
2462
|
+
if (harness === "codex") {
|
|
2463
|
+
return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
|
|
2464
|
+
}
|
|
2465
|
+
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");
|
|
2466
|
+
}
|
|
2467
|
+
function readSettings(file) {
|
|
2468
|
+
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
2469
|
+
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
2470
|
+
}
|
|
2471
|
+
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
2322
2472
|
return {
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2473
|
+
type: "command",
|
|
2474
|
+
command: command(harness),
|
|
2475
|
+
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
2476
|
+
timeout,
|
|
2477
|
+
statusMessage: "Syncing MindGraph session context",
|
|
2478
|
+
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
2326
2479
|
};
|
|
2327
2480
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2481
|
+
function desiredEntries(harness) {
|
|
2482
|
+
const common = {
|
|
2483
|
+
SessionStart: [
|
|
2484
|
+
{
|
|
2485
|
+
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
2486
|
+
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
2487
|
+
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
2488
|
+
// open, but no brief reached the model. The same installed runner
|
|
2489
|
+
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
2490
|
+
// The declared Codex context limit must not undercut the brief's own
|
|
2491
|
+
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
2492
|
+
// truncation the renderer knew nothing about.
|
|
2493
|
+
hooks: [
|
|
2494
|
+
ownedHook(
|
|
2495
|
+
harness,
|
|
2496
|
+
30,
|
|
2497
|
+
harness === "codex" ? 1e4 : void 0
|
|
2498
|
+
)
|
|
2499
|
+
]
|
|
2500
|
+
}
|
|
2501
|
+
],
|
|
2502
|
+
PreToolUse: [
|
|
2503
|
+
{
|
|
2504
|
+
matcher: "mcp__mindgraph__.*",
|
|
2505
|
+
// Repository resolution over a large multi-repo workspace plus three
|
|
2506
|
+
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
2507
|
+
// PreToolUse means the call goes through untagged.
|
|
2508
|
+
hooks: [ownedHook(harness, 5)]
|
|
2509
|
+
}
|
|
2510
|
+
],
|
|
2511
|
+
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
2512
|
+
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
2513
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
|
|
2514
|
+
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
2515
|
+
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
2516
|
+
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
2517
|
+
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
2518
|
+
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
2519
|
+
// map), so the codex adapter instead makes at most ONE call there.
|
|
2520
|
+
SessionEnd: [
|
|
2521
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
2522
|
+
]
|
|
2523
|
+
};
|
|
2524
|
+
return harness === "claude-code" ? {
|
|
2525
|
+
...common,
|
|
2526
|
+
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
2527
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
2528
|
+
} : common;
|
|
2529
|
+
}
|
|
2530
|
+
function hookCommand(value) {
|
|
2531
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2532
|
+
const command2 = value.command;
|
|
2533
|
+
return typeof command2 === "string" ? command2 : void 0;
|
|
2534
|
+
}
|
|
2535
|
+
function entryHasOwnedHook(value) {
|
|
2536
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2537
|
+
const hooks = value.hooks;
|
|
2538
|
+
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
2539
|
+
}
|
|
2540
|
+
function writeSettings(file, settings) {
|
|
2541
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
2542
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
|
|
2543
|
+
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
2544
|
+
`, {
|
|
2545
|
+
mode: 384
|
|
2546
|
+
});
|
|
2547
|
+
import_node_fs4.default.renameSync(temporary, file);
|
|
2548
|
+
}
|
|
2549
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir) {
|
|
2550
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
|
|
2551
|
+
const settings = readSettings(file);
|
|
2552
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2553
|
+
let added = 0;
|
|
2554
|
+
let updated = 0;
|
|
2555
|
+
for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
|
|
2556
|
+
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
2557
|
+
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
2558
|
+
const hadOwned = kept.length !== current.length;
|
|
2559
|
+
for (const entry of wanted) {
|
|
2560
|
+
kept.push(entry);
|
|
2561
|
+
if (hadOwned) updated += 1;
|
|
2562
|
+
else added += 1;
|
|
2563
|
+
}
|
|
2564
|
+
hooks[event] = kept;
|
|
2565
|
+
}
|
|
2566
|
+
settings.hooks = hooks;
|
|
2567
|
+
writeSettings(file, settings);
|
|
2568
|
+
return { path: file, added, updated };
|
|
2569
|
+
}
|
|
2570
|
+
function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2571
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
2572
|
+
if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
|
|
2573
|
+
const settings = readSettings(file);
|
|
2574
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2575
|
+
let removed = 0;
|
|
2576
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
2577
|
+
if (!Array.isArray(value)) continue;
|
|
2578
|
+
const retainedEntries = [];
|
|
2579
|
+
for (const rawEntry of value) {
|
|
2580
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
2581
|
+
retainedEntries.push(rawEntry);
|
|
2582
|
+
continue;
|
|
2583
|
+
}
|
|
2584
|
+
const entry = { ...rawEntry };
|
|
2585
|
+
if (!Array.isArray(entry.hooks)) {
|
|
2586
|
+
retainedEntries.push(entry);
|
|
2587
|
+
continue;
|
|
2588
|
+
}
|
|
2589
|
+
const retainedHooks = entry.hooks.filter((hook) => {
|
|
2590
|
+
const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
|
|
2591
|
+
if (owned) removed += 1;
|
|
2592
|
+
return !owned;
|
|
2593
|
+
});
|
|
2594
|
+
if (retainedHooks.length > 0) {
|
|
2595
|
+
entry.hooks = retainedHooks;
|
|
2596
|
+
retainedEntries.push(entry);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
if (retainedEntries.length > 0) hooks[event] = retainedEntries;
|
|
2600
|
+
else delete hooks[event];
|
|
2601
|
+
}
|
|
2602
|
+
settings.hooks = hooks;
|
|
2603
|
+
writeSettings(file, settings);
|
|
2604
|
+
return { path: file, removed };
|
|
2605
|
+
}
|
|
2606
|
+
function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2607
|
+
return installHooks("claude-code", scope, projectDir);
|
|
2608
|
+
}
|
|
2609
|
+
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2610
|
+
return uninstallHooks("claude-code", scope, projectDir);
|
|
2611
|
+
}
|
|
2612
|
+
function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2613
|
+
return installHooks("codex", scope, projectDir, codexHomeDir);
|
|
2614
|
+
}
|
|
2615
|
+
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2616
|
+
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
2617
|
+
}
|
|
2618
|
+
var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto5, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
|
|
2619
|
+
var init_hook_installer = __esm({
|
|
2620
|
+
"src/hook-installer.ts"() {
|
|
2621
|
+
"use strict";
|
|
2622
|
+
import_node_fs4 = __toESM(require("fs"));
|
|
2623
|
+
import_node_os4 = __toESM(require("os"));
|
|
2624
|
+
import_node_path4 = __toESM(require("path"));
|
|
2625
|
+
import_node_crypto5 = __toESM(require("crypto"));
|
|
2626
|
+
OWNER_MARKER = "--owner mindgraph";
|
|
2627
|
+
RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
2628
|
+
RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
|
|
2632
|
+
// src/sync-tool.ts
|
|
2633
|
+
function safePath(root, candidate) {
|
|
2634
|
+
const absolute = import_node_path5.default.resolve(root, candidate);
|
|
2635
|
+
const relative2 = import_node_path5.default.relative(root, absolute);
|
|
2636
|
+
if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
|
|
2637
|
+
throw new Error(`path escapes repository root: ${candidate}`);
|
|
2638
|
+
}
|
|
2639
|
+
if (import_node_fs5.default.existsSync(absolute)) {
|
|
2640
|
+
if (!pathContains(realPathOf(root), realPathOf(absolute))) {
|
|
2641
|
+
throw new Error(`path escapes repository root via symlink: ${candidate}`);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
return absolute;
|
|
2645
|
+
}
|
|
2646
|
+
function realPathOf(target) {
|
|
2647
|
+
try {
|
|
2648
|
+
return import_node_fs5.default.realpathSync(target);
|
|
2649
|
+
} catch {
|
|
2650
|
+
return import_node_path5.default.resolve(target);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
2654
|
+
if (!import_node_fs5.default.existsSync(target)) return [];
|
|
2655
|
+
const real = realPathOf(target);
|
|
2656
|
+
if (!pathContains(realRoot, real)) return [];
|
|
2657
|
+
const stat = import_node_fs5.default.statSync(target);
|
|
2658
|
+
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
2659
|
+
if (!stat.isDirectory() || visited.has(real)) return [];
|
|
2660
|
+
visited.add(real);
|
|
2661
|
+
return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name), realRoot, visited));
|
|
2662
|
+
}
|
|
2663
|
+
function fileDescriptor(root, absolute) {
|
|
2664
|
+
const content = import_node_fs5.default.readFileSync(absolute, "utf8");
|
|
2665
|
+
return {
|
|
2666
|
+
logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
|
|
2667
|
+
content_hash: import_node_crypto6.default.createHash("sha256").update(content).digest("hex"),
|
|
2668
|
+
bytes: Buffer.byteLength(content),
|
|
2669
|
+
content
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
async function handleSyncTool(client2, args) {
|
|
2673
|
+
try {
|
|
2674
|
+
const action = String(args.action || "");
|
|
2675
|
+
const context = args.invocation_context || {};
|
|
2676
|
+
const adapter = new CodegraphAdapter({ cwd: invocationCwd(args) });
|
|
2677
|
+
const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
|
|
2678
|
+
const repository = await adapter.resolveRepository(requestedRepo);
|
|
2679
|
+
const root = repository.repoRoot;
|
|
2680
|
+
const repoId = repository.repoId;
|
|
2681
|
+
const provider = String(args.provider || "claude_project");
|
|
2682
|
+
const remote = client2;
|
|
2683
|
+
if (action === "scan") {
|
|
2684
|
+
const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
|
|
2685
|
+
if (args.workspace === true && repositories.length === 0) {
|
|
2686
|
+
return err2(
|
|
2687
|
+
"workspace_not_configured",
|
|
2688
|
+
`workspace scan found no declared repositories: ${adapter.workspaceDiagnostic()}`
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
|
|
2692
|
+
const scans = [];
|
|
2693
|
+
for (const selected of repositories) {
|
|
2694
|
+
scans.push(
|
|
2695
|
+
await scanRepository(
|
|
2696
|
+
remote,
|
|
2697
|
+
selected,
|
|
2698
|
+
requested,
|
|
2699
|
+
provider,
|
|
2700
|
+
args.agent_id
|
|
2701
|
+
)
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
if (args.workspace === true) {
|
|
2705
|
+
return ok2({
|
|
2706
|
+
provider,
|
|
2707
|
+
workspace: true,
|
|
2708
|
+
repositories: scans,
|
|
2709
|
+
one_way: "files_to_graph"
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
return ok2(scans[0]);
|
|
2713
|
+
}
|
|
2714
|
+
const request = {
|
|
2715
|
+
...args,
|
|
2716
|
+
provider,
|
|
2717
|
+
repo_id: repoId
|
|
2718
|
+
};
|
|
2719
|
+
delete request.invocation_context;
|
|
2720
|
+
delete request.paths;
|
|
2721
|
+
delete request.repo;
|
|
2722
|
+
delete request.workspace;
|
|
2723
|
+
if (action === "begin" && !request.content) {
|
|
2724
|
+
if (!request.logical_path) {
|
|
2725
|
+
return err2("missing_field", "logical_path is required for begin");
|
|
2726
|
+
}
|
|
2727
|
+
const file = fileDescriptor(
|
|
2728
|
+
root,
|
|
2729
|
+
safePath(root, String(request.logical_path))
|
|
2730
|
+
);
|
|
2731
|
+
request.content = file.content;
|
|
2732
|
+
request.content_hash ||= file.content_hash;
|
|
2733
|
+
}
|
|
2734
|
+
if (action === "status" && request.logical_path && request.file_present !== false) {
|
|
2735
|
+
const absolute = safePath(root, String(request.logical_path));
|
|
2736
|
+
request.file_present = import_node_fs5.default.existsSync(absolute);
|
|
2737
|
+
if (request.file_present && !request.content_hash) {
|
|
2738
|
+
request.content_hash = fileDescriptor(root, absolute).content_hash;
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
return ok2(await remote.memorySync(request));
|
|
2742
|
+
} catch (error) {
|
|
2743
|
+
return err2("sync_failed", errorDetail(error));
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
async function scanRepository(remote, repository, requested, provider, agentId) {
|
|
2747
|
+
const realRoot = realPathOf(repository.repoRoot);
|
|
2748
|
+
const files = [
|
|
2749
|
+
...new Set(
|
|
2750
|
+
requested.flatMap(
|
|
2751
|
+
(candidate) => markdownFiles(safePath(repository.repoRoot, candidate), realRoot)
|
|
2752
|
+
)
|
|
2753
|
+
)
|
|
2754
|
+
].sort();
|
|
2755
|
+
const results = [];
|
|
2756
|
+
for (const absolute of files) {
|
|
2757
|
+
const file = fileDescriptor(repository.repoRoot, absolute);
|
|
2758
|
+
const status = await remote.memorySync({
|
|
2759
|
+
action: "scan",
|
|
2760
|
+
provider,
|
|
2761
|
+
repo_id: repository.repoId,
|
|
2762
|
+
logical_path: file.logical_path,
|
|
2763
|
+
content_hash: file.content_hash,
|
|
2764
|
+
file_present: true,
|
|
2765
|
+
agent_id: agentId
|
|
2766
|
+
});
|
|
2767
|
+
results.push({
|
|
2768
|
+
logical_path: file.logical_path,
|
|
2769
|
+
content_hash: file.content_hash,
|
|
2770
|
+
bytes: file.bytes,
|
|
2771
|
+
status
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
return {
|
|
2775
|
+
repo_id: repository.repoId,
|
|
2776
|
+
root: ".",
|
|
2777
|
+
files: results
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
var import_node_crypto6, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
|
|
2781
|
+
var init_sync_tool = __esm({
|
|
2782
|
+
"src/sync-tool.ts"() {
|
|
2783
|
+
"use strict";
|
|
2784
|
+
import_node_crypto6 = __toESM(require("crypto"));
|
|
2785
|
+
import_node_fs5 = __toESM(require("fs"));
|
|
2786
|
+
import_node_path5 = __toESM(require("path"));
|
|
2787
|
+
init_codegraph();
|
|
2788
|
+
init_error_detail();
|
|
2789
|
+
ok2 = (data) => ({
|
|
2790
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
2791
|
+
});
|
|
2792
|
+
err2 = (code, message) => ({
|
|
2793
|
+
content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
|
|
2794
|
+
isError: true
|
|
2795
|
+
});
|
|
2796
|
+
SYNC_TOOL = {
|
|
2797
|
+
name: "mindgraph_sync",
|
|
2798
|
+
description: "Transactional one-way project memory-file sync. scan/status are reads; begin/record/finalize/abandon are writes. Discovery and hashing are automatic; semantic assertions are deliberately supplied through record. This never writes graph state back to files.",
|
|
2799
|
+
annotations: {
|
|
2800
|
+
readOnlyHint: false,
|
|
2801
|
+
destructiveHint: false,
|
|
2802
|
+
idempotentHint: true,
|
|
2803
|
+
openWorldHint: true
|
|
2804
|
+
},
|
|
2805
|
+
inputSchema: {
|
|
2806
|
+
type: "object",
|
|
2807
|
+
properties: {
|
|
2808
|
+
action: {
|
|
2809
|
+
type: "string",
|
|
2810
|
+
enum: ["scan", "begin", "record", "finalize", "status", "abandon"]
|
|
2811
|
+
},
|
|
2812
|
+
paths: { type: "array", items: { type: "string" } },
|
|
2813
|
+
workspace: {
|
|
2814
|
+
type: "boolean",
|
|
2815
|
+
description: "For scan only: fan out across repositories explicitly listed in .mindgraph/workspace.json"
|
|
2816
|
+
},
|
|
2817
|
+
repo: {
|
|
2818
|
+
type: "string",
|
|
2819
|
+
description: "Configured repository id, name, or root"
|
|
2820
|
+
},
|
|
2821
|
+
logical_path: { type: "string" },
|
|
2822
|
+
content: { type: "string" },
|
|
2823
|
+
content_hash: { type: "string" },
|
|
2824
|
+
source_uid: { type: "string" },
|
|
2373
2825
|
execution_uid: { type: "string" },
|
|
2374
2826
|
assertion_fingerprint: { type: "string" },
|
|
2375
2827
|
planned_fingerprints: { type: "array", items: { type: "string" } },
|
|
@@ -2730,15 +3182,15 @@ function renderTraversal(response) {
|
|
|
2730
3182
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
2731
3183
|
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
2732
3184
|
if (paths.length > 0) {
|
|
2733
|
-
const items = paths.map((raw) => obj(raw)).filter((
|
|
2734
|
-
const uids = Array.isArray(
|
|
2735
|
-
const labels = Array.isArray(
|
|
3185
|
+
const items = paths.map((raw) => obj(raw)).filter((path8) => Boolean(path8)).map((path8, index) => {
|
|
3186
|
+
const uids = Array.isArray(path8.node_uids) ? path8.node_uids.map(String) : [];
|
|
3187
|
+
const labels = Array.isArray(path8.labels) ? path8.labels.map(String) : Array.isArray(path8.nodes) ? path8.nodes.map((hop) => {
|
|
2736
3188
|
const hopNode = obj(hop);
|
|
2737
3189
|
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
2738
3190
|
}) : [];
|
|
2739
3191
|
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
2740
|
-
const cost = num(
|
|
2741
|
-
const confidence = num(
|
|
3192
|
+
const cost = num(path8.cost) ?? num(path8.path_cost);
|
|
3193
|
+
const confidence = num(path8.path_confidence);
|
|
2742
3194
|
const meta = [
|
|
2743
3195
|
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
2744
3196
|
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
@@ -2971,6 +3423,9 @@ async function handleTool(client2, name, args) {
|
|
|
2971
3423
|
case "mindgraph_retrieve":
|
|
2972
3424
|
result = await handleRetrieve(client2, args);
|
|
2973
3425
|
break;
|
|
3426
|
+
case "mindgraph_series_query":
|
|
3427
|
+
result = await handleSeriesQuery(client2, args);
|
|
3428
|
+
break;
|
|
2974
3429
|
case "mindgraph_ingest":
|
|
2975
3430
|
result = await handleIngest(client2, args);
|
|
2976
3431
|
break;
|
|
@@ -3015,12 +3470,36 @@ async function handleTool(client2, name, args) {
|
|
|
3015
3470
|
return err3(errorDetail(e));
|
|
3016
3471
|
}
|
|
3017
3472
|
}
|
|
3473
|
+
async function handleSeriesQuery(client2, args) {
|
|
3474
|
+
const action = typeof args.action === "string" ? args.action : "";
|
|
3475
|
+
if (!["list_for_entity", "latest", "window", "aggregate"].includes(action)) {
|
|
3476
|
+
return err3(`Unknown series query action: ${action}`);
|
|
3477
|
+
}
|
|
3478
|
+
if (action === "list_for_entity" && typeof args.entity_uid !== "string") {
|
|
3479
|
+
return err3("entity_uid is required for action='list_for_entity'");
|
|
3480
|
+
}
|
|
3481
|
+
if (action !== "list_for_entity" && typeof args.series_uid !== "string") {
|
|
3482
|
+
return err3(`series_uid is required for action='${action}'`);
|
|
3483
|
+
}
|
|
3484
|
+
if ((action === "window" || action === "aggregate") && (typeof args.from !== "number" || typeof args.to !== "number")) {
|
|
3485
|
+
return err3(`from and to are required for action='${action}'`);
|
|
3486
|
+
}
|
|
3487
|
+
if (action === "aggregate" && (typeof args.bucket !== "string" || typeof args.agg !== "string")) {
|
|
3488
|
+
return err3("bucket and agg are required for action='aggregate'");
|
|
3489
|
+
}
|
|
3490
|
+
return ok3(
|
|
3491
|
+
await client2.series(args)
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3018
3494
|
async function handleCapture(client2, args) {
|
|
3019
3495
|
const {
|
|
3020
3496
|
action,
|
|
3021
3497
|
label,
|
|
3022
3498
|
summary,
|
|
3023
3499
|
content,
|
|
3500
|
+
name,
|
|
3501
|
+
description,
|
|
3502
|
+
license,
|
|
3024
3503
|
session_uid,
|
|
3025
3504
|
work_uid,
|
|
3026
3505
|
execution_uid,
|
|
@@ -3055,6 +3534,41 @@ async function handleCapture(client2, args) {
|
|
|
3055
3534
|
agent_id
|
|
3056
3535
|
})
|
|
3057
3536
|
);
|
|
3537
|
+
case "skill": {
|
|
3538
|
+
if (!name) return err3("name is required for action=skill");
|
|
3539
|
+
if (!description) return err3("description is required for action=skill");
|
|
3540
|
+
if (content === void 0) return err3("content is required for action=skill");
|
|
3541
|
+
const hasProvenance = [session_uid, work_uid, execution_uid].some(
|
|
3542
|
+
(uid) => typeof uid === "string" && uid.length > 0
|
|
3543
|
+
) || Array.isArray(summarizes_uids) && summarizes_uids.some((uid) => typeof uid === "string" && uid.length > 0);
|
|
3544
|
+
if (!hasProvenance) {
|
|
3545
|
+
return err3(
|
|
3546
|
+
"at least one of session_uid, work_uid, execution_uid, or summarizes_uids is required for action=skill"
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
return ok3(
|
|
3550
|
+
await client2.distill({
|
|
3551
|
+
label,
|
|
3552
|
+
output_type: "skill",
|
|
3553
|
+
summary,
|
|
3554
|
+
confidence,
|
|
3555
|
+
salience,
|
|
3556
|
+
session_uid,
|
|
3557
|
+
work_uid,
|
|
3558
|
+
execution_uid,
|
|
3559
|
+
idempotency_key,
|
|
3560
|
+
supersedes_uid,
|
|
3561
|
+
summarizes_uids,
|
|
3562
|
+
props: {
|
|
3563
|
+
name,
|
|
3564
|
+
description,
|
|
3565
|
+
content,
|
|
3566
|
+
...license ? { license } : {}
|
|
3567
|
+
},
|
|
3568
|
+
agent_id
|
|
3569
|
+
})
|
|
3570
|
+
);
|
|
3571
|
+
}
|
|
3058
3572
|
case "journal": {
|
|
3059
3573
|
if (!content) return err3("content is required for action=journal");
|
|
3060
3574
|
const journalProps = { content };
|
|
@@ -4042,7 +4556,7 @@ var init_tools = __esm({
|
|
|
4042
4556
|
TOOLS = [
|
|
4043
4557
|
{
|
|
4044
4558
|
name: "mindgraph_capture",
|
|
4045
|
-
description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes
|
|
4559
|
+
description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes, 'lesson' for a durable learning, and 'skill' for a reusable procedure authored from completed work. Skill capture always creates a candidate requiring admin review; it never publishes. Skill names must be 1-64 lowercase a-z, digits, or hyphens, with no leading/trailing/consecutive hyphens and no 'anthropic' or 'claude'. Provide at least one provenance field and write instructions, not a transcript dump.",
|
|
4046
4560
|
inputSchema: {
|
|
4047
4561
|
type: "object",
|
|
4048
4562
|
properties: {
|
|
@@ -4055,7 +4569,8 @@ var init_tools = __esm({
|
|
|
4055
4569
|
"snippet",
|
|
4056
4570
|
"concept",
|
|
4057
4571
|
"journal",
|
|
4058
|
-
"lesson"
|
|
4572
|
+
"lesson",
|
|
4573
|
+
"skill"
|
|
4059
4574
|
],
|
|
4060
4575
|
description: "Type of knowledge to capture"
|
|
4061
4576
|
},
|
|
@@ -4069,11 +4584,30 @@ var init_tools = __esm({
|
|
|
4069
4584
|
},
|
|
4070
4585
|
content: {
|
|
4071
4586
|
type: "string",
|
|
4072
|
-
|
|
4587
|
+
maxLength: 65536,
|
|
4588
|
+
description: "Journal body (action=journal) or complete SKILL.md instructions, at most 64 KiB UTF-8 (action=skill)"
|
|
4589
|
+
},
|
|
4590
|
+
name: {
|
|
4591
|
+
type: "string",
|
|
4592
|
+
minLength: 1,
|
|
4593
|
+
maxLength: 64,
|
|
4594
|
+
pattern: "^(?!.*(?:anthropic|claude))(?!-)(?!.*--)[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
4595
|
+
description: "Stable skill identifier using lowercase a-z, digits, and hyphens (for action=skill)"
|
|
4596
|
+
},
|
|
4597
|
+
description: {
|
|
4598
|
+
type: "string",
|
|
4599
|
+
minLength: 1,
|
|
4600
|
+
maxLength: 1024,
|
|
4601
|
+
description: "When the skill should be used; XML tags are rejected (for action=skill)"
|
|
4602
|
+
},
|
|
4603
|
+
license: {
|
|
4604
|
+
type: "string",
|
|
4605
|
+
description: "Optional skill license (for action=skill)"
|
|
4073
4606
|
},
|
|
4074
4607
|
session_uid: {
|
|
4075
4608
|
type: "string",
|
|
4076
|
-
|
|
4609
|
+
minLength: 1,
|
|
4610
|
+
description: "Session UID that produced a lesson or skill"
|
|
4077
4611
|
},
|
|
4078
4612
|
work_uid: {
|
|
4079
4613
|
type: "string",
|
|
@@ -4094,7 +4628,8 @@ var init_tools = __esm({
|
|
|
4094
4628
|
summarizes_uids: {
|
|
4095
4629
|
type: "array",
|
|
4096
4630
|
items: { type: "string" },
|
|
4097
|
-
|
|
4631
|
+
minItems: 1,
|
|
4632
|
+
description: "Source node UIDs the lesson or skill was learned from"
|
|
4098
4633
|
},
|
|
4099
4634
|
entity_type: {
|
|
4100
4635
|
type: "string",
|
|
@@ -4161,7 +4696,24 @@ var init_tools = __esm({
|
|
|
4161
4696
|
description: "Agent identity"
|
|
4162
4697
|
}
|
|
4163
4698
|
},
|
|
4164
|
-
required: ["action", "label"]
|
|
4699
|
+
required: ["action", "label"],
|
|
4700
|
+
allOf: [
|
|
4701
|
+
{
|
|
4702
|
+
if: {
|
|
4703
|
+
properties: { action: { const: "skill" } },
|
|
4704
|
+
required: ["action"]
|
|
4705
|
+
},
|
|
4706
|
+
then: {
|
|
4707
|
+
required: ["name", "description", "content"],
|
|
4708
|
+
anyOf: [
|
|
4709
|
+
{ required: ["session_uid"] },
|
|
4710
|
+
{ required: ["work_uid"] },
|
|
4711
|
+
{ required: ["execution_uid"] },
|
|
4712
|
+
{ required: ["summarizes_uids"] }
|
|
4713
|
+
]
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
]
|
|
4165
4717
|
}
|
|
4166
4718
|
},
|
|
4167
4719
|
{
|
|
@@ -4747,6 +5299,42 @@ var init_tools = __esm({
|
|
|
4747
5299
|
required: ["action"]
|
|
4748
5300
|
}
|
|
4749
5301
|
},
|
|
5302
|
+
{
|
|
5303
|
+
name: "mindgraph_series_query",
|
|
5304
|
+
description: "Read dense numeric time series without placing raw points in general retrieval context. Use list_for_entity to discover series, latest for cached statistics, window for [from,to) keyset pages, and aggregate for bounded calendar buckets.",
|
|
5305
|
+
inputSchema: {
|
|
5306
|
+
type: "object",
|
|
5307
|
+
additionalProperties: false,
|
|
5308
|
+
properties: {
|
|
5309
|
+
action: {
|
|
5310
|
+
type: "string",
|
|
5311
|
+
enum: ["list_for_entity", "latest", "window", "aggregate"]
|
|
5312
|
+
},
|
|
5313
|
+
entity_uid: { type: "string" },
|
|
5314
|
+
series_uid: { type: "string" },
|
|
5315
|
+
from: { type: "number" },
|
|
5316
|
+
to: { type: "number" },
|
|
5317
|
+
cursor: { type: "number" },
|
|
5318
|
+
limit: { type: "number", minimum: 1, maximum: 1e4 },
|
|
5319
|
+
bucket: {
|
|
5320
|
+
type: "string",
|
|
5321
|
+
enum: ["year", "quarter", "month", "week", "day", "hour"]
|
|
5322
|
+
},
|
|
5323
|
+
agg: {
|
|
5324
|
+
type: "string",
|
|
5325
|
+
enum: ["sum", "mean", "min", "max", "count", "std_dev", "last"]
|
|
5326
|
+
},
|
|
5327
|
+
fill: { type: "string", enum: ["none", "null", "prev"] }
|
|
5328
|
+
},
|
|
5329
|
+
required: ["action"]
|
|
5330
|
+
},
|
|
5331
|
+
annotations: {
|
|
5332
|
+
readOnlyHint: true,
|
|
5333
|
+
destructiveHint: false,
|
|
5334
|
+
idempotentHint: true,
|
|
5335
|
+
openWorldHint: false
|
|
5336
|
+
}
|
|
5337
|
+
},
|
|
4750
5338
|
{
|
|
4751
5339
|
name: "mindgraph_ingest",
|
|
4752
5340
|
description: "Ingest long-form content (articles, transcripts, documents, meeting notes) into the knowledge graph. The pipeline automatically chunks the text, extracts entities/claims/relationships via LLM, and deduplicates against existing nodes. Use 'chunk' for a single passage (synchronous), 'document' for full documents (async \u2014 returns a job ID), 'session' for conversation transcripts. Use 'job_status' to check progress. For short individual facts, use the specific cognitive tools instead.",
|
|
@@ -5233,7 +5821,7 @@ function isTransientStatus(status) {
|
|
|
5233
5821
|
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
5234
5822
|
}
|
|
5235
5823
|
function sleep(ms) {
|
|
5236
|
-
return new Promise((
|
|
5824
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5237
5825
|
}
|
|
5238
5826
|
function headers(apiKey, orgId) {
|
|
5239
5827
|
const value = {
|
|
@@ -5251,6 +5839,7 @@ function unsupportedResponse(status, body) {
|
|
|
5251
5839
|
}
|
|
5252
5840
|
function toolMutability(toolName, action) {
|
|
5253
5841
|
if (toolName === "mindgraph_retrieve") return "read";
|
|
5842
|
+
if (toolName === "mindgraph_series_query") return "read";
|
|
5254
5843
|
if (toolName === "mindgraph_plan") {
|
|
5255
5844
|
return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
|
|
5256
5845
|
}
|
|
@@ -5343,7 +5932,7 @@ async function checkMcpGovernance(toolName, args, config) {
|
|
|
5343
5932
|
if (!response) {
|
|
5344
5933
|
return {
|
|
5345
5934
|
allowed: false,
|
|
5346
|
-
message: `MindGraph
|
|
5935
|
+
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
5936
|
};
|
|
5348
5937
|
}
|
|
5349
5938
|
if (!response.ok) {
|
|
@@ -5435,6 +6024,8 @@ var init_governance = __esm({
|
|
|
5435
6024
|
UID_FIELDS = [
|
|
5436
6025
|
"uid",
|
|
5437
6026
|
"source_uid",
|
|
6027
|
+
"entity_uid",
|
|
6028
|
+
"series_uid",
|
|
5438
6029
|
"session_uid",
|
|
5439
6030
|
"work_uid",
|
|
5440
6031
|
"goal_uid",
|
|
@@ -5587,10 +6178,10 @@ async function main() {
|
|
|
5587
6178
|
const transport = new import_stdio.StdioServerTransport();
|
|
5588
6179
|
await server.connect(transport);
|
|
5589
6180
|
console.error(
|
|
5590
|
-
`MindGraph MCP server v${"0.
|
|
6181
|
+
`MindGraph MCP server v${"0.19.0"} running on stdio (${BASE_URL})`
|
|
5591
6182
|
);
|
|
5592
6183
|
}
|
|
5593
|
-
var import_server, import_stdio, import_types, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
|
|
6184
|
+
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
6185
|
var init_src = __esm({
|
|
5595
6186
|
"src/index.ts"() {
|
|
5596
6187
|
"use strict";
|
|
@@ -5603,6 +6194,8 @@ var init_src = __esm({
|
|
|
5603
6194
|
init_generated_tools();
|
|
5604
6195
|
init_governance();
|
|
5605
6196
|
init_resources();
|
|
6197
|
+
import_node_path6 = __toESM(require("path"));
|
|
6198
|
+
init_hook_installer();
|
|
5606
6199
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
5607
6200
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
5608
6201
|
AGENT_ID = process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId();
|
|
@@ -5724,10 +6317,42 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
5724
6317
|
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
5725
6318
|
).join("\n");
|
|
5726
6319
|
}
|
|
6320
|
+
RUNNER_REFRESH = (() => {
|
|
6321
|
+
try {
|
|
6322
|
+
return refreshHookRunner("0.19.0", import_node_path6.default.join(__dirname, "cli.js"));
|
|
6323
|
+
} catch {
|
|
6324
|
+
return void 0;
|
|
6325
|
+
}
|
|
6326
|
+
})();
|
|
6327
|
+
if (RUNNER_REFRESH?.refreshed) {
|
|
6328
|
+
const refreshedSettings = (() => {
|
|
6329
|
+
try {
|
|
6330
|
+
return refreshOwnedClaudeHooks();
|
|
6331
|
+
} catch {
|
|
6332
|
+
return [];
|
|
6333
|
+
}
|
|
6334
|
+
})();
|
|
6335
|
+
console.error(
|
|
6336
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.19.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
6337
|
+
);
|
|
6338
|
+
}
|
|
6339
|
+
HOOK_SKEW_NOTE = (() => {
|
|
6340
|
+
try {
|
|
6341
|
+
return versionSkewNote("0.19.0");
|
|
6342
|
+
} catch {
|
|
6343
|
+
return void 0;
|
|
6344
|
+
}
|
|
6345
|
+
})();
|
|
6346
|
+
if (HOOK_SKEW_NOTE) {
|
|
6347
|
+
INSTRUCTIONS += `
|
|
6348
|
+
|
|
6349
|
+
${HOOK_SKEW_NOTE}`;
|
|
6350
|
+
console.error(`[mindgraph-mcp] ${HOOK_SKEW_NOTE}`);
|
|
6351
|
+
}
|
|
5727
6352
|
server = new import_server.Server(
|
|
5728
6353
|
{
|
|
5729
6354
|
name: "mindgraph",
|
|
5730
|
-
version: "0.
|
|
6355
|
+
version: "0.19.0"
|
|
5731
6356
|
},
|
|
5732
6357
|
{
|
|
5733
6358
|
capabilities: {
|
|
@@ -5945,8 +6570,8 @@ ${conversation}`
|
|
|
5945
6570
|
});
|
|
5946
6571
|
|
|
5947
6572
|
// src/cli.ts
|
|
5948
|
-
var
|
|
5949
|
-
var
|
|
6573
|
+
var fs6 = __toESM(require("fs"));
|
|
6574
|
+
var path7 = __toESM(require("path"));
|
|
5950
6575
|
var os4 = __toESM(require("os"));
|
|
5951
6576
|
var crypto5 = __toESM(require("crypto"));
|
|
5952
6577
|
var import_child_process = require("child_process");
|
|
@@ -5964,6 +6589,8 @@ function parseArgs(argv) {
|
|
|
5964
6589
|
let harness;
|
|
5965
6590
|
let hooks = false;
|
|
5966
6591
|
let agentId = process.env.MINDGRAPH_AGENT_ID;
|
|
6592
|
+
const positionals = [];
|
|
6593
|
+
let dir;
|
|
5967
6594
|
for (let i = 0; i < args.length; i++) {
|
|
5968
6595
|
const arg = args[i];
|
|
5969
6596
|
switch (arg) {
|
|
@@ -5984,6 +6611,9 @@ function parseArgs(argv) {
|
|
|
5984
6611
|
case "--project-dir":
|
|
5985
6612
|
projectDir = args[++i];
|
|
5986
6613
|
break;
|
|
6614
|
+
case "--dir":
|
|
6615
|
+
dir = args[++i];
|
|
6616
|
+
break;
|
|
5987
6617
|
case "--harness":
|
|
5988
6618
|
harness = args[++i];
|
|
5989
6619
|
break;
|
|
@@ -6002,33 +6632,416 @@ function parseArgs(argv) {
|
|
|
6002
6632
|
break;
|
|
6003
6633
|
default:
|
|
6004
6634
|
if (!arg.startsWith("-")) {
|
|
6005
|
-
command2
|
|
6635
|
+
if (command2 === "serve") {
|
|
6636
|
+
command2 = arg;
|
|
6637
|
+
} else {
|
|
6638
|
+
positionals.push(arg);
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
break;
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6644
|
+
return {
|
|
6645
|
+
command: command2,
|
|
6646
|
+
apiKey,
|
|
6647
|
+
baseUrl,
|
|
6648
|
+
scope,
|
|
6649
|
+
projectDir,
|
|
6650
|
+
harness,
|
|
6651
|
+
hooks,
|
|
6652
|
+
agentId,
|
|
6653
|
+
positionals,
|
|
6654
|
+
dir
|
|
6655
|
+
};
|
|
6656
|
+
}
|
|
6657
|
+
function mcpAddFailureOutput(e) {
|
|
6658
|
+
const err5 = e;
|
|
6659
|
+
return `${err5.stdout ?? ""}${err5.stderr ?? ""}`.trim();
|
|
6660
|
+
}
|
|
6661
|
+
function classifyMcpAddFailure(e) {
|
|
6662
|
+
if (e?.code === "ENOENT") return "missing-cli";
|
|
6663
|
+
if (mcpAddFailureOutput(e).toLowerCase().includes("already exists")) {
|
|
6664
|
+
return "already-exists";
|
|
6665
|
+
}
|
|
6666
|
+
return "other";
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
// src/skills-cli.ts
|
|
6670
|
+
var import_node_crypto2 = require("crypto");
|
|
6671
|
+
var fs2 = __toESM(require("fs"));
|
|
6672
|
+
var path2 = __toESM(require("path"));
|
|
6673
|
+
function httpSkillsApi(baseUrl, apiKey, agentId) {
|
|
6674
|
+
const endpoint = `${baseUrl.replace(/\/$/, "")}/action/skill`;
|
|
6675
|
+
const call = async (body) => {
|
|
6676
|
+
const response = await fetch(endpoint, {
|
|
6677
|
+
method: "POST",
|
|
6678
|
+
headers: {
|
|
6679
|
+
"content-type": "application/json",
|
|
6680
|
+
authorization: `Bearer ${apiKey}`
|
|
6681
|
+
},
|
|
6682
|
+
body: JSON.stringify(
|
|
6683
|
+
agentId ? { agent_id: agentId, ...body } : body
|
|
6684
|
+
)
|
|
6685
|
+
});
|
|
6686
|
+
let parsed = {};
|
|
6687
|
+
try {
|
|
6688
|
+
parsed = await response.json();
|
|
6689
|
+
} catch {
|
|
6690
|
+
parsed = { error: `non-JSON response (HTTP ${response.status})` };
|
|
6691
|
+
}
|
|
6692
|
+
return { status: response.status, body: parsed };
|
|
6693
|
+
};
|
|
6694
|
+
return {
|
|
6695
|
+
async list(grantedTo) {
|
|
6696
|
+
const { status, body } = await call({
|
|
6697
|
+
action: "list",
|
|
6698
|
+
...grantedTo ? { granted_to: grantedTo } : {}
|
|
6699
|
+
});
|
|
6700
|
+
if (status !== 200) {
|
|
6701
|
+
throw new Error(
|
|
6702
|
+
`skill list failed (HTTP ${status}): ${String(body.error ?? "")}`
|
|
6703
|
+
);
|
|
6704
|
+
}
|
|
6705
|
+
return body.skills ?? [];
|
|
6706
|
+
},
|
|
6707
|
+
async get(uid) {
|
|
6708
|
+
const { status, body } = await call({ action: "get", uid });
|
|
6709
|
+
if (status === 404) return null;
|
|
6710
|
+
if (status !== 200) {
|
|
6711
|
+
throw new Error(
|
|
6712
|
+
`skill get failed (HTTP ${status}): ${String(body.error ?? "")}`
|
|
6713
|
+
);
|
|
6714
|
+
}
|
|
6715
|
+
const props = body.props ?? {};
|
|
6716
|
+
return {
|
|
6717
|
+
uid: String(body.uid ?? uid),
|
|
6718
|
+
name: String(props.name ?? ""),
|
|
6719
|
+
description: String(props.description ?? ""),
|
|
6720
|
+
content: String(props.content ?? ""),
|
|
6721
|
+
license: props.license ?? null,
|
|
6722
|
+
status: String(props.status ?? "candidate"),
|
|
6723
|
+
status_reason: props.status_reason ?? null,
|
|
6724
|
+
content_hash: String(props.content_hash ?? ""),
|
|
6725
|
+
version: Number(body.version ?? 0)
|
|
6726
|
+
};
|
|
6727
|
+
},
|
|
6728
|
+
send: call
|
|
6729
|
+
};
|
|
6730
|
+
}
|
|
6731
|
+
function isValidSkillName(name) {
|
|
6732
|
+
return name.length >= 1 && name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
|
|
6733
|
+
}
|
|
6734
|
+
function renderSkillMd(skill) {
|
|
6735
|
+
const lines = [
|
|
6736
|
+
"---",
|
|
6737
|
+
`name: ${JSON.stringify(skill.name)}`,
|
|
6738
|
+
`description: ${JSON.stringify(skill.description)}`
|
|
6739
|
+
];
|
|
6740
|
+
if (skill.license) {
|
|
6741
|
+
lines.push(`license: ${JSON.stringify(skill.license)}`);
|
|
6742
|
+
}
|
|
6743
|
+
lines.push(
|
|
6744
|
+
"metadata:",
|
|
6745
|
+
` mindgraph-uid: ${JSON.stringify(skill.uid)}`,
|
|
6746
|
+
` mindgraph-version: ${JSON.stringify(String(skill.version))}`,
|
|
6747
|
+
` mindgraph-content-hash: ${JSON.stringify(skill.content_hash)}`,
|
|
6748
|
+
"---",
|
|
6749
|
+
"",
|
|
6750
|
+
skill.content
|
|
6751
|
+
);
|
|
6752
|
+
return `${lines.join("\n")}
|
|
6753
|
+
`;
|
|
6754
|
+
}
|
|
6755
|
+
function scalar(raw) {
|
|
6756
|
+
const trimmed = raw.trim();
|
|
6757
|
+
try {
|
|
6758
|
+
const parsed = JSON.parse(trimmed);
|
|
6759
|
+
if (typeof parsed === "string") return parsed;
|
|
6760
|
+
} catch {
|
|
6761
|
+
}
|
|
6762
|
+
return trimmed;
|
|
6763
|
+
}
|
|
6764
|
+
function parseSkillMd(text) {
|
|
6765
|
+
if (!text.startsWith("---\n")) return null;
|
|
6766
|
+
const end = text.indexOf("\n---\n", 4);
|
|
6767
|
+
if (end < 0) return null;
|
|
6768
|
+
const frontmatter = text.slice(4, end + 1);
|
|
6769
|
+
const body = text.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
|
|
6770
|
+
const entries = [];
|
|
6771
|
+
for (const line of frontmatter.split("\n")) {
|
|
6772
|
+
const top = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
|
|
6773
|
+
if (top) {
|
|
6774
|
+
entries.push({ key: top[1], raw: top[2] });
|
|
6775
|
+
} else if (entries.length > 0) {
|
|
6776
|
+
entries[entries.length - 1].raw += `
|
|
6777
|
+
${line}`;
|
|
6778
|
+
}
|
|
6779
|
+
}
|
|
6780
|
+
const result = { unknown: {}, body };
|
|
6781
|
+
for (const { key, raw } of entries) {
|
|
6782
|
+
switch (key) {
|
|
6783
|
+
case "name":
|
|
6784
|
+
result.name = scalar(raw);
|
|
6785
|
+
break;
|
|
6786
|
+
case "description":
|
|
6787
|
+
result.description = scalar(raw);
|
|
6788
|
+
break;
|
|
6789
|
+
case "license":
|
|
6790
|
+
result.license = scalar(raw);
|
|
6791
|
+
break;
|
|
6792
|
+
case "metadata": {
|
|
6793
|
+
const uid = /mindgraph-uid:\s*(.+)/.exec(raw);
|
|
6794
|
+
const version = /mindgraph-version:\s*(.+)/.exec(raw);
|
|
6795
|
+
const hash = /mindgraph-content-hash:\s*(.+)/.exec(raw);
|
|
6796
|
+
if (uid) {
|
|
6797
|
+
result.pin = {
|
|
6798
|
+
uid: scalar(uid[1]),
|
|
6799
|
+
version: version ? scalar(version[1]) : void 0,
|
|
6800
|
+
contentHash: hash ? scalar(hash[1]) : void 0
|
|
6801
|
+
};
|
|
6802
|
+
} else if (raw.trim().length > 0) {
|
|
6803
|
+
result.unknown[key] = raw;
|
|
6006
6804
|
}
|
|
6007
6805
|
break;
|
|
6806
|
+
}
|
|
6807
|
+
default:
|
|
6808
|
+
result.unknown[key] = raw;
|
|
6809
|
+
break;
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
return result;
|
|
6813
|
+
}
|
|
6814
|
+
var STATE_FILE = ".mindgraph-skills-state.json";
|
|
6815
|
+
function sha256(text) {
|
|
6816
|
+
return (0, import_node_crypto2.createHash)("sha256").update(text).digest("hex");
|
|
6817
|
+
}
|
|
6818
|
+
function loadState(dir) {
|
|
6819
|
+
try {
|
|
6820
|
+
return JSON.parse(
|
|
6821
|
+
fs2.readFileSync(path2.join(dir, STATE_FILE), "utf8")
|
|
6822
|
+
);
|
|
6823
|
+
} catch {
|
|
6824
|
+
return {};
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
function saveState(dir, state) {
|
|
6828
|
+
fs2.writeFileSync(
|
|
6829
|
+
path2.join(dir, STATE_FILE),
|
|
6830
|
+
`${JSON.stringify(state, null, 2)}
|
|
6831
|
+
`
|
|
6832
|
+
);
|
|
6833
|
+
}
|
|
6834
|
+
function isSymlink(target) {
|
|
6835
|
+
try {
|
|
6836
|
+
return fs2.lstatSync(target).isSymbolicLink();
|
|
6837
|
+
} catch {
|
|
6838
|
+
return false;
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
function safeSkillPaths(dir, name) {
|
|
6842
|
+
if (!isValidSkillName(name)) {
|
|
6843
|
+
return `invalid skill name ${JSON.stringify(name)}`;
|
|
6844
|
+
}
|
|
6845
|
+
const root = path2.resolve(dir);
|
|
6846
|
+
const skillDir = path2.resolve(root, name);
|
|
6847
|
+
if (skillDir !== path2.join(root, name) || !skillDir.startsWith(root + path2.sep)) {
|
|
6848
|
+
return "resolved path escapes the target directory";
|
|
6849
|
+
}
|
|
6850
|
+
if (isSymlink(skillDir)) {
|
|
6851
|
+
return "target directory is a symlink";
|
|
6852
|
+
}
|
|
6853
|
+
const skillFile = path2.join(skillDir, "SKILL.md");
|
|
6854
|
+
if (isSymlink(skillFile)) {
|
|
6855
|
+
return "target SKILL.md is a symlink";
|
|
6856
|
+
}
|
|
6857
|
+
return { skillDir, skillFile };
|
|
6858
|
+
}
|
|
6859
|
+
async function skillsPull(api, dir, grantedTo) {
|
|
6860
|
+
const report = {
|
|
6861
|
+
written: [],
|
|
6862
|
+
unchanged: [],
|
|
6863
|
+
locallyEdited: [],
|
|
6864
|
+
conflicts: [],
|
|
6865
|
+
removed: [],
|
|
6866
|
+
refused: []
|
|
6867
|
+
};
|
|
6868
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
6869
|
+
const state = loadState(dir);
|
|
6870
|
+
const summaries = await api.list(grantedTo);
|
|
6871
|
+
const fetched = /* @__PURE__ */ new Set();
|
|
6872
|
+
for (const summary of summaries) {
|
|
6873
|
+
const detail = await api.get(summary.uid);
|
|
6874
|
+
if (!detail || detail.status !== "published") continue;
|
|
6875
|
+
fetched.add(detail.name);
|
|
6876
|
+
const paths = safeSkillPaths(dir, detail.name);
|
|
6877
|
+
if (typeof paths === "string") {
|
|
6878
|
+
report.refused.push({ name: detail.name, reason: paths });
|
|
6879
|
+
continue;
|
|
6880
|
+
}
|
|
6881
|
+
const { skillDir, skillFile } = paths;
|
|
6882
|
+
const rendered2 = renderSkillMd(detail);
|
|
6883
|
+
let existing = null;
|
|
6884
|
+
try {
|
|
6885
|
+
existing = fs2.readFileSync(skillFile, "utf8");
|
|
6886
|
+
} catch {
|
|
6887
|
+
existing = null;
|
|
6888
|
+
}
|
|
6889
|
+
if (existing === rendered2) {
|
|
6890
|
+
state[detail.name] = {
|
|
6891
|
+
uid: detail.uid,
|
|
6892
|
+
content_hash: detail.content_hash,
|
|
6893
|
+
file_sha256: sha256(rendered2)
|
|
6894
|
+
};
|
|
6895
|
+
report.unchanged.push(detail.name);
|
|
6896
|
+
continue;
|
|
6897
|
+
}
|
|
6898
|
+
const recorded = state[detail.name];
|
|
6899
|
+
const locallyModified = existing !== null && (!recorded || sha256(existing) !== recorded.file_sha256);
|
|
6900
|
+
if (locallyModified) {
|
|
6901
|
+
if (recorded && recorded.content_hash === detail.content_hash) {
|
|
6902
|
+
report.locallyEdited.push(detail.name);
|
|
6903
|
+
continue;
|
|
6904
|
+
}
|
|
6905
|
+
fs2.writeFileSync(`${skillFile}.conflict`, rendered2);
|
|
6906
|
+
report.conflicts.push(detail.name);
|
|
6907
|
+
continue;
|
|
6008
6908
|
}
|
|
6909
|
+
fs2.mkdirSync(skillDir, { recursive: true });
|
|
6910
|
+
fs2.writeFileSync(skillFile, rendered2);
|
|
6911
|
+
state[detail.name] = {
|
|
6912
|
+
uid: detail.uid,
|
|
6913
|
+
content_hash: detail.content_hash,
|
|
6914
|
+
file_sha256: sha256(rendered2)
|
|
6915
|
+
};
|
|
6916
|
+
report.written.push(detail.name);
|
|
6009
6917
|
}
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6918
|
+
for (const [name, recorded] of Object.entries(state)) {
|
|
6919
|
+
if (fetched.has(name)) continue;
|
|
6920
|
+
const paths = safeSkillPaths(dir, name);
|
|
6921
|
+
if (typeof paths === "string") {
|
|
6922
|
+
delete state[name];
|
|
6923
|
+
continue;
|
|
6924
|
+
}
|
|
6925
|
+
const { skillDir, skillFile } = paths;
|
|
6926
|
+
let statusReason = null;
|
|
6927
|
+
try {
|
|
6928
|
+
const gone = await api.get(recorded.uid);
|
|
6929
|
+
statusReason = gone?.status_reason ?? (gone ? `status: ${gone.status}` : null);
|
|
6930
|
+
} catch {
|
|
6931
|
+
statusReason = null;
|
|
6932
|
+
}
|
|
6933
|
+
let existing = null;
|
|
6934
|
+
try {
|
|
6935
|
+
existing = fs2.readFileSync(skillFile, "utf8");
|
|
6936
|
+
} catch {
|
|
6937
|
+
existing = null;
|
|
6938
|
+
}
|
|
6939
|
+
if (existing === null) {
|
|
6940
|
+
delete state[name];
|
|
6941
|
+
continue;
|
|
6942
|
+
}
|
|
6943
|
+
const locallyModified = sha256(existing) !== recorded.file_sha256;
|
|
6944
|
+
if (locallyModified) {
|
|
6945
|
+
fs2.renameSync(skillFile, `${skillFile}.conflict`);
|
|
6946
|
+
report.removed.push({ name, statusReason, kept: true });
|
|
6947
|
+
} else {
|
|
6948
|
+
fs2.rmSync(skillDir, { recursive: true, force: true });
|
|
6949
|
+
report.removed.push({ name, statusReason, kept: false });
|
|
6950
|
+
}
|
|
6951
|
+
delete state[name];
|
|
6952
|
+
}
|
|
6953
|
+
saveState(dir, state);
|
|
6954
|
+
return report;
|
|
6015
6955
|
}
|
|
6016
|
-
function
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6956
|
+
async function skillsPush(api, skillPath) {
|
|
6957
|
+
const resolved = path2.resolve(skillPath);
|
|
6958
|
+
const file = resolved.endsWith("SKILL.md") ? resolved : path2.join(resolved, "SKILL.md");
|
|
6959
|
+
if (isSymlink(file)) {
|
|
6960
|
+
return { outcome: "error", message: `${file} is a symlink; refusing` };
|
|
6020
6961
|
}
|
|
6021
|
-
|
|
6962
|
+
let text;
|
|
6963
|
+
try {
|
|
6964
|
+
text = fs2.readFileSync(file, "utf8");
|
|
6965
|
+
} catch {
|
|
6966
|
+
return { outcome: "error", message: `could not read ${file}` };
|
|
6967
|
+
}
|
|
6968
|
+
const parsed = parseSkillMd(text);
|
|
6969
|
+
if (!parsed) {
|
|
6970
|
+
return {
|
|
6971
|
+
outcome: "error",
|
|
6972
|
+
message: "SKILL.md must start with a --- frontmatter block"
|
|
6973
|
+
};
|
|
6974
|
+
}
|
|
6975
|
+
if (!parsed.name || !parsed.description) {
|
|
6976
|
+
return {
|
|
6977
|
+
outcome: "error",
|
|
6978
|
+
message: "SKILL.md frontmatter requires name and description"
|
|
6979
|
+
};
|
|
6980
|
+
}
|
|
6981
|
+
if (parsed.pin?.uid) {
|
|
6982
|
+
const { status: status2, body: body2 } = await api.send({
|
|
6983
|
+
action: "update",
|
|
6984
|
+
uid: parsed.pin.uid,
|
|
6985
|
+
name: parsed.name,
|
|
6986
|
+
description: parsed.description,
|
|
6987
|
+
content: parsed.body,
|
|
6988
|
+
...parsed.license ? { license: parsed.license } : {}
|
|
6989
|
+
});
|
|
6990
|
+
if (status2 === 200) {
|
|
6991
|
+
return {
|
|
6992
|
+
outcome: "updated",
|
|
6993
|
+
message: `updated candidate ${parsed.name} (${parsed.pin.uid})`,
|
|
6994
|
+
uid: parsed.pin.uid
|
|
6995
|
+
};
|
|
6996
|
+
}
|
|
6997
|
+
return {
|
|
6998
|
+
outcome: status2 === 409 ? "conflict" : "error",
|
|
6999
|
+
message: `update failed (HTTP ${status2}): ${String(body2.error ?? "")}`,
|
|
7000
|
+
existingUid: body2.existing_uid ?? void 0,
|
|
7001
|
+
existingStatus: body2.existing_status ?? void 0
|
|
7002
|
+
};
|
|
7003
|
+
}
|
|
7004
|
+
const { status, body } = await api.send({
|
|
7005
|
+
action: "create",
|
|
7006
|
+
skill_source: "imported",
|
|
7007
|
+
name: parsed.name,
|
|
7008
|
+
description: parsed.description,
|
|
7009
|
+
content: parsed.body,
|
|
7010
|
+
...parsed.license ? { license: parsed.license } : {},
|
|
7011
|
+
...Object.keys(parsed.unknown).length > 0 ? { imported_frontmatter: parsed.unknown } : {},
|
|
7012
|
+
import_origin: `skills-push:${path2.basename(resolved)}`
|
|
7013
|
+
});
|
|
7014
|
+
if (status === 200 && body.replayed === true) {
|
|
7015
|
+
return {
|
|
7016
|
+
outcome: "replayed",
|
|
7017
|
+
message: `unchanged: ${parsed.name} already imported (${String(body.uid)})`,
|
|
7018
|
+
uid: String(body.uid)
|
|
7019
|
+
};
|
|
7020
|
+
}
|
|
7021
|
+
if (status === 200) {
|
|
7022
|
+
return {
|
|
7023
|
+
outcome: "created",
|
|
7024
|
+
message: `imported ${parsed.name} as a candidate (${String(body.uid)}) \u2014 review and publish in the dashboard`,
|
|
7025
|
+
uid: String(body.uid)
|
|
7026
|
+
};
|
|
7027
|
+
}
|
|
7028
|
+
return {
|
|
7029
|
+
outcome: status === 409 ? "conflict" : "error",
|
|
7030
|
+
message: `import failed (HTTP ${status}): ${String(body.error ?? "")}`,
|
|
7031
|
+
existingUid: body.existing_uid ?? void 0,
|
|
7032
|
+
existingStatus: body.existing_status ?? void 0
|
|
7033
|
+
};
|
|
6022
7034
|
}
|
|
6023
7035
|
|
|
6024
7036
|
// src/hook-core.ts
|
|
6025
|
-
var
|
|
7037
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
6026
7038
|
var import_node_fs3 = __toESM(require("fs"));
|
|
6027
7039
|
var import_node_os3 = __toESM(require("os"));
|
|
6028
7040
|
var import_node_path3 = __toESM(require("path"));
|
|
6029
7041
|
var import_node_child_process2 = require("child_process");
|
|
6030
7042
|
init_codegraph();
|
|
6031
7043
|
init_code_tool();
|
|
7044
|
+
init_error_detail();
|
|
6032
7045
|
init_hook_env();
|
|
6033
7046
|
var NOOP = { kind: "noop" };
|
|
6034
7047
|
function defaultLedger(sessionId, options) {
|
|
@@ -6051,7 +7064,7 @@ function runtimeDir(options) {
|
|
|
6051
7064
|
}
|
|
6052
7065
|
function ledgerPath(sessionId, options) {
|
|
6053
7066
|
const harness = "harness" in options ? options.harness : "claude-code";
|
|
6054
|
-
const key =
|
|
7067
|
+
const key = import_node_crypto4.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
6055
7068
|
return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
|
|
6056
7069
|
}
|
|
6057
7070
|
function waitBriefly(ms) {
|
|
@@ -6093,7 +7106,7 @@ function withLedger(sessionId, options, update) {
|
|
|
6093
7106
|
}
|
|
6094
7107
|
const result = update(ledger);
|
|
6095
7108
|
ledger.updatedAt = Date.now();
|
|
6096
|
-
const temporary = `${file}.${process.pid}.${
|
|
7109
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
6097
7110
|
import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
6098
7111
|
`, { mode: 384 });
|
|
6099
7112
|
import_node_fs3.default.renameSync(temporary, file);
|
|
@@ -6360,6 +7373,51 @@ function isOwnPriorWork(brief, agentId, harness) {
|
|
|
6360
7373
|
const owner = string(object(brief?.lease)?.lease_owner_agent_id);
|
|
6361
7374
|
return owner === agentId || owner === harness;
|
|
6362
7375
|
}
|
|
7376
|
+
function hasMindgraphServer(servers) {
|
|
7377
|
+
if (!servers || typeof servers !== "object") return false;
|
|
7378
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
7379
|
+
if (name === "mindgraph") return true;
|
|
7380
|
+
const cfg = entry && typeof entry === "object" ? entry : void 0;
|
|
7381
|
+
const text = [
|
|
7382
|
+
String(cfg?.command ?? ""),
|
|
7383
|
+
Array.isArray(cfg?.args) ? cfg.args.join(" ") : "",
|
|
7384
|
+
String(cfg?.url ?? "")
|
|
7385
|
+
].join(" ");
|
|
7386
|
+
if (text.includes("mindgraph-mcp")) return true;
|
|
7387
|
+
}
|
|
7388
|
+
return false;
|
|
7389
|
+
}
|
|
7390
|
+
function pathIsWithin(ancestor, descendant) {
|
|
7391
|
+
const base = import_node_path3.default.resolve(ancestor);
|
|
7392
|
+
const target = import_node_path3.default.resolve(descendant);
|
|
7393
|
+
return target === base || target.startsWith(base + import_node_path3.default.sep);
|
|
7394
|
+
}
|
|
7395
|
+
function claudeMcpRegistrationNote(input, options) {
|
|
7396
|
+
if (options.harness !== "claude-code") return void 0;
|
|
7397
|
+
if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
|
|
7398
|
+
try {
|
|
7399
|
+
const configPath = options.claudeConfigPath || import_node_path3.default.join(import_node_os3.default.homedir(), ".claude.json");
|
|
7400
|
+
if (!import_node_fs3.default.existsSync(configPath)) return void 0;
|
|
7401
|
+
const config = JSON.parse(import_node_fs3.default.readFileSync(configPath, "utf8"));
|
|
7402
|
+
if (hasMindgraphServer(config.mcpServers)) return void 0;
|
|
7403
|
+
const cwd = import_node_path3.default.resolve(input.cwd || ".");
|
|
7404
|
+
for (const [projectDir, project] of Object.entries(config.projects || {})) {
|
|
7405
|
+
if (!pathIsWithin(projectDir, cwd)) continue;
|
|
7406
|
+
if (hasMindgraphServer(project?.mcpServers)) return void 0;
|
|
7407
|
+
}
|
|
7408
|
+
for (let dir = cwd; ; dir = import_node_path3.default.dirname(dir)) {
|
|
7409
|
+
const candidate = import_node_path3.default.join(dir, ".mcp.json");
|
|
7410
|
+
if (import_node_fs3.default.existsSync(candidate)) {
|
|
7411
|
+
const parsed = JSON.parse(import_node_fs3.default.readFileSync(candidate, "utf8"));
|
|
7412
|
+
if (hasMindgraphServer(parsed.mcpServers)) return void 0;
|
|
7413
|
+
}
|
|
7414
|
+
if (dir === import_node_path3.default.dirname(dir)) break;
|
|
7415
|
+
}
|
|
7416
|
+
} catch {
|
|
7417
|
+
return void 0;
|
|
7418
|
+
}
|
|
7419
|
+
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.";
|
|
7420
|
+
}
|
|
6363
7421
|
async function sessionStart(input, client2, options) {
|
|
6364
7422
|
const agentId = resolveAgentId(options);
|
|
6365
7423
|
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
@@ -6449,7 +7507,14 @@ async function sessionStart(input, client2, options) {
|
|
|
6449
7507
|
claimFailed: claimFailed && !stillHeld,
|
|
6450
7508
|
scopeNote
|
|
6451
7509
|
});
|
|
6452
|
-
const
|
|
7510
|
+
const registrationNote = claudeMcpRegistrationNote(input, options);
|
|
7511
|
+
if (registrationNote) {
|
|
7512
|
+
console.error(`mindgraph-hook: ${registrationNote}`);
|
|
7513
|
+
}
|
|
7514
|
+
const contextText = registrationNote ? `${registrationNote}
|
|
7515
|
+
|
|
7516
|
+
${briefText}` : briefText;
|
|
7517
|
+
const briefHash = import_node_crypto4.default.createHash("sha256").update(contextText).digest("hex");
|
|
6453
7518
|
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
6454
7519
|
const changed = ledger.lastBriefHash !== briefHash;
|
|
6455
7520
|
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
@@ -6482,7 +7547,7 @@ async function sessionStart(input, client2, options) {
|
|
|
6482
7547
|
ledger.lastBriefHash = briefHash;
|
|
6483
7548
|
return changed || reinjectCompact;
|
|
6484
7549
|
});
|
|
6485
|
-
return shouldInject ? { kind: "context", context:
|
|
7550
|
+
return shouldInject ? { kind: "context", context: contextText } : NOOP;
|
|
6486
7551
|
}
|
|
6487
7552
|
async function preTool(input, options) {
|
|
6488
7553
|
const original = { ...input.tool_input || {} };
|
|
@@ -6548,7 +7613,7 @@ async function postTool(input, client2, options) {
|
|
|
6548
7613
|
ledger.mutatingOperations += 1;
|
|
6549
7614
|
}
|
|
6550
7615
|
if (!payload?.error) {
|
|
6551
|
-
if (name.includes("mindgraph_capture") && ["lesson", "journal"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
|
|
7616
|
+
if (name.includes("mindgraph_capture") && ["lesson", "journal", "skill"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
|
|
6552
7617
|
ledger.memoryWritten = true;
|
|
6553
7618
|
}
|
|
6554
7619
|
}
|
|
@@ -6589,9 +7654,7 @@ async function postTool(input, client2, options) {
|
|
|
6589
7654
|
}
|
|
6590
7655
|
}
|
|
6591
7656
|
} else if (aboutLedgerTask) {
|
|
6592
|
-
ledger
|
|
6593
|
-
ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
|
|
6594
|
-
ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
7657
|
+
adoptConflictFence(ledger, payload);
|
|
6595
7658
|
}
|
|
6596
7659
|
}
|
|
6597
7660
|
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 +7711,35 @@ async function postTool(input, client2, options) {
|
|
|
6648
7711
|
})
|
|
6649
7712
|
);
|
|
6650
7713
|
withLedger(input.session_id, options, (ledger) => {
|
|
6651
|
-
if (payloadIsError(response)) return void 0;
|
|
6652
7714
|
if (ledger.taskUid !== active.taskUid) return void 0;
|
|
7715
|
+
if (payloadIsError(response)) {
|
|
7716
|
+
adoptConflictFence(ledger, response);
|
|
7717
|
+
return void 0;
|
|
7718
|
+
}
|
|
6653
7719
|
ledger.taskVersion = number(response?.task_version) ?? ledger.taskVersion;
|
|
6654
7720
|
ledger.leaseEpoch = number(response?.lease_epoch) ?? ledger.leaseEpoch;
|
|
6655
7721
|
ledger.leaseExpiresAt = number(response?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
6656
7722
|
ledger.lastLeaseRenewalAt = nowSeconds;
|
|
6657
7723
|
return void 0;
|
|
6658
7724
|
});
|
|
6659
|
-
} catch {
|
|
7725
|
+
} catch (cause) {
|
|
7726
|
+
const conflict = conflictState(cause);
|
|
7727
|
+
if (Object.keys(conflict).length > 0) {
|
|
7728
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
7729
|
+
if (ledger.taskUid !== active.taskUid) return void 0;
|
|
7730
|
+
adoptConflictFence(ledger, conflict);
|
|
7731
|
+
return void 0;
|
|
7732
|
+
});
|
|
7733
|
+
}
|
|
6660
7734
|
}
|
|
6661
7735
|
}
|
|
6662
7736
|
return NOOP;
|
|
6663
7737
|
}
|
|
7738
|
+
function adoptConflictFence(ledger, payload) {
|
|
7739
|
+
ledger.taskVersion = number(payload?.current_version) ?? ledger.taskVersion;
|
|
7740
|
+
ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
|
|
7741
|
+
ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
7742
|
+
}
|
|
6664
7743
|
function payloadIsError(payload) {
|
|
6665
7744
|
return Boolean(payload && payload.error);
|
|
6666
7745
|
}
|
|
@@ -6813,195 +7892,19 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
6813
7892
|
}
|
|
6814
7893
|
}
|
|
6815
7894
|
|
|
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
7895
|
// src/cli.ts
|
|
7896
|
+
init_hook_installer();
|
|
6994
7897
|
function recordHookHealth(harness, error) {
|
|
6995
7898
|
try {
|
|
6996
|
-
const dir = process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
6997
|
-
|
|
6998
|
-
const file =
|
|
7899
|
+
const dir = process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime");
|
|
7900
|
+
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
7901
|
+
const file = path7.join(dir, "hook-health.json");
|
|
6999
7902
|
let failures = 0;
|
|
7000
7903
|
try {
|
|
7001
|
-
failures = Number(JSON.parse(
|
|
7904
|
+
failures = Number(JSON.parse(fs6.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
7002
7905
|
} catch {
|
|
7003
7906
|
}
|
|
7004
|
-
|
|
7907
|
+
fs6.writeFileSync(
|
|
7005
7908
|
file,
|
|
7006
7909
|
`${JSON.stringify({
|
|
7007
7910
|
harness,
|
|
@@ -7017,14 +7920,14 @@ function recordHookHealth(harness, error) {
|
|
|
7017
7920
|
}
|
|
7018
7921
|
function clearHookHealth(harness) {
|
|
7019
7922
|
try {
|
|
7020
|
-
const file =
|
|
7021
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
7923
|
+
const file = path7.join(
|
|
7924
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
|
|
7022
7925
|
"hook-health.json"
|
|
7023
7926
|
);
|
|
7024
|
-
if (!
|
|
7025
|
-
const current = JSON.parse(
|
|
7927
|
+
if (!fs6.existsSync(file)) return;
|
|
7928
|
+
const current = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
7026
7929
|
if (!Number(current.consecutiveFailures)) return;
|
|
7027
|
-
|
|
7930
|
+
fs6.writeFileSync(
|
|
7028
7931
|
file,
|
|
7029
7932
|
`${JSON.stringify({
|
|
7030
7933
|
harness,
|
|
@@ -7040,7 +7943,7 @@ function clearHookHealth(harness) {
|
|
|
7040
7943
|
function getClaudeDesktopConfigPath() {
|
|
7041
7944
|
switch (process.platform) {
|
|
7042
7945
|
case "darwin":
|
|
7043
|
-
return
|
|
7946
|
+
return path7.join(
|
|
7044
7947
|
os4.homedir(),
|
|
7045
7948
|
"Library",
|
|
7046
7949
|
"Application Support",
|
|
@@ -7048,13 +7951,13 @@ function getClaudeDesktopConfigPath() {
|
|
|
7048
7951
|
"claude_desktop_config.json"
|
|
7049
7952
|
);
|
|
7050
7953
|
case "win32":
|
|
7051
|
-
return
|
|
7052
|
-
process.env.APPDATA ||
|
|
7954
|
+
return path7.join(
|
|
7955
|
+
process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming"),
|
|
7053
7956
|
"Claude",
|
|
7054
7957
|
"claude_desktop_config.json"
|
|
7055
7958
|
);
|
|
7056
7959
|
case "linux":
|
|
7057
|
-
return
|
|
7960
|
+
return path7.join(
|
|
7058
7961
|
os4.homedir(),
|
|
7059
7962
|
".config",
|
|
7060
7963
|
"Claude",
|
|
@@ -7065,16 +7968,16 @@ function getClaudeDesktopConfigPath() {
|
|
|
7065
7968
|
}
|
|
7066
7969
|
}
|
|
7067
7970
|
function readJsonFile(filePath) {
|
|
7068
|
-
if (!
|
|
7069
|
-
const content =
|
|
7971
|
+
if (!fs6.existsSync(filePath)) return {};
|
|
7972
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
7070
7973
|
return JSON.parse(content);
|
|
7071
7974
|
}
|
|
7072
7975
|
function writeJsonFile(filePath, data) {
|
|
7073
|
-
const dir =
|
|
7074
|
-
if (!
|
|
7075
|
-
|
|
7976
|
+
const dir = path7.dirname(filePath);
|
|
7977
|
+
if (!fs6.existsSync(dir)) {
|
|
7978
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
7076
7979
|
}
|
|
7077
|
-
|
|
7980
|
+
fs6.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
7078
7981
|
}
|
|
7079
7982
|
function getMcpServerEntry(apiKey, baseUrl) {
|
|
7080
7983
|
const env = { MINDGRAPH_API_KEY: apiKey };
|
|
@@ -7161,7 +8064,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
|
|
|
7161
8064
|
}
|
|
7162
8065
|
function uninstallClaudeDesktop() {
|
|
7163
8066
|
const configPath = getClaudeDesktopConfigPath();
|
|
7164
|
-
if (!
|
|
8067
|
+
if (!fs6.existsSync(configPath)) {
|
|
7165
8068
|
console.log("Claude Desktop config not found. Nothing to remove.");
|
|
7166
8069
|
return;
|
|
7167
8070
|
}
|
|
@@ -7188,7 +8091,7 @@ function uninstallClaudeCode() {
|
|
|
7188
8091
|
}
|
|
7189
8092
|
function printStatus() {
|
|
7190
8093
|
const desktopPath = getClaudeDesktopConfigPath();
|
|
7191
|
-
if (
|
|
8094
|
+
if (fs6.existsSync(desktopPath)) {
|
|
7192
8095
|
const config = readJsonFile(desktopPath);
|
|
7193
8096
|
const servers = config.mcpServers;
|
|
7194
8097
|
if (servers && "mindgraph" in servers) {
|
|
@@ -7206,12 +8109,12 @@ function printStatus() {
|
|
|
7206
8109
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
7207
8110
|
);
|
|
7208
8111
|
try {
|
|
7209
|
-
const healthFile =
|
|
7210
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
8112
|
+
const healthFile = path7.join(
|
|
8113
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
|
|
7211
8114
|
"hook-health.json"
|
|
7212
8115
|
);
|
|
7213
|
-
if (
|
|
7214
|
-
const health = JSON.parse(
|
|
8116
|
+
if (fs6.existsSync(healthFile)) {
|
|
8117
|
+
const health = JSON.parse(fs6.readFileSync(healthFile, "utf8"));
|
|
7215
8118
|
const failures = Number(health.consecutiveFailures) || 0;
|
|
7216
8119
|
console.log(
|
|
7217
8120
|
failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
|
|
@@ -7228,7 +8131,7 @@ function withHooksFlag() {
|
|
|
7228
8131
|
}
|
|
7229
8132
|
function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
7230
8133
|
if (withHooks) {
|
|
7231
|
-
const runner = installHookRunner(__filename);
|
|
8134
|
+
const runner = installHookRunner(__filename, void 0, "0.19.0");
|
|
7232
8135
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
7233
8136
|
const result = installClaudeHooks("user");
|
|
7234
8137
|
console.log(
|
|
@@ -7276,6 +8179,20 @@ USAGE:
|
|
|
7276
8179
|
mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
|
|
7277
8180
|
mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
|
|
7278
8181
|
mindgraph-mcp status Show installation status
|
|
8182
|
+
mindgraph-mcp skills pull [--dir .claude/skills]
|
|
8183
|
+
Write your org's published+granted skills as
|
|
8184
|
+
SKILL.md directories (allowlisted frontmatter
|
|
8185
|
+
only). Revoked/archived skills are withdrawn;
|
|
8186
|
+
local edits are never overwritten (.conflict
|
|
8187
|
+
sidecars). NOTE: coding harnesses hot-reload
|
|
8188
|
+
skill files \u2014 running sessions adopt pulled
|
|
8189
|
+
changes immediately; session pinning exists
|
|
8190
|
+
on the managed-agent leg only.
|
|
8191
|
+
mindgraph-mcp skills push <path>
|
|
8192
|
+
Import a SKILL.md directory as a review
|
|
8193
|
+
candidate (nothing auto-publishes). A
|
|
8194
|
+
previously pulled directory routes to the
|
|
8195
|
+
candidate-only update path.
|
|
7279
8196
|
|
|
7280
8197
|
OPTIONS:
|
|
7281
8198
|
--api-key <key> MindGraph API key (or set MINDGRAPH_API_KEY env var)
|
|
@@ -7339,7 +8256,7 @@ async function pollForKey(code) {
|
|
|
7339
8256
|
} catch (e) {
|
|
7340
8257
|
if (e instanceof Error && e.message.includes("Session")) throw e;
|
|
7341
8258
|
}
|
|
7342
|
-
await new Promise((
|
|
8259
|
+
await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
|
|
7343
8260
|
}
|
|
7344
8261
|
throw new Error("Timed out waiting for authorization.");
|
|
7345
8262
|
}
|
|
@@ -7349,7 +8266,7 @@ async function interactiveInit(baseUrl) {
|
|
|
7349
8266
|
input: process.stdin,
|
|
7350
8267
|
output: process.stdout
|
|
7351
8268
|
});
|
|
7352
|
-
const ask = (q) => new Promise((
|
|
8269
|
+
const ask = (q) => new Promise((resolve3) => rl.question(q, resolve3));
|
|
7353
8270
|
console.log("\nMindGraph MCP Server Setup\n");
|
|
7354
8271
|
console.log(" 1. Sign in via browser (recommended)");
|
|
7355
8272
|
console.log(" 2. Enter API key manually");
|
|
@@ -7420,8 +8337,79 @@ async function interactiveInit(baseUrl) {
|
|
|
7420
8337
|
console.log("\nDone! Your knowledge graph is now connected.");
|
|
7421
8338
|
}
|
|
7422
8339
|
async function main2() {
|
|
7423
|
-
const {
|
|
8340
|
+
const {
|
|
8341
|
+
command: command2,
|
|
8342
|
+
apiKey,
|
|
8343
|
+
baseUrl,
|
|
8344
|
+
scope,
|
|
8345
|
+
projectDir,
|
|
8346
|
+
harness,
|
|
8347
|
+
hooks,
|
|
8348
|
+
agentId,
|
|
8349
|
+
positionals,
|
|
8350
|
+
dir
|
|
8351
|
+
} = parseArgs(process.argv);
|
|
7424
8352
|
switch (command2) {
|
|
8353
|
+
case "skills": {
|
|
8354
|
+
if (!apiKey) {
|
|
8355
|
+
console.error(
|
|
8356
|
+
"API key required. Use --api-key or set MINDGRAPH_API_KEY."
|
|
8357
|
+
);
|
|
8358
|
+
process.exit(1);
|
|
8359
|
+
}
|
|
8360
|
+
const sub = positionals[0];
|
|
8361
|
+
const api = httpSkillsApi(
|
|
8362
|
+
baseUrl ?? "https://api.mindgraph.cloud",
|
|
8363
|
+
apiKey,
|
|
8364
|
+
agentId
|
|
8365
|
+
);
|
|
8366
|
+
if (sub === "pull") {
|
|
8367
|
+
if (!agentId) {
|
|
8368
|
+
console.error(
|
|
8369
|
+
"note: no --agent-id/MINDGRAPH_AGENT_ID \u2014 pulling ALL published skills; bind an agent identity to scope the set by its grants."
|
|
8370
|
+
);
|
|
8371
|
+
}
|
|
8372
|
+
const report = await skillsPull(api, dir ?? ".claude/skills", agentId);
|
|
8373
|
+
for (const name of report.written) console.log(` written ${name}`);
|
|
8374
|
+
for (const name of report.unchanged)
|
|
8375
|
+
console.log(` unchanged ${name}`);
|
|
8376
|
+
for (const name of report.locallyEdited)
|
|
8377
|
+
console.log(` local-edit ${name} \u2014 upstream unchanged; left alone`);
|
|
8378
|
+
for (const name of report.conflicts)
|
|
8379
|
+
console.log(
|
|
8380
|
+
` CONFLICT ${name} \u2014 local edits kept; new upstream saved as SKILL.md.conflict`
|
|
8381
|
+
);
|
|
8382
|
+
for (const removal of report.removed)
|
|
8383
|
+
console.log(
|
|
8384
|
+
` withdrawn ${removal.name}${removal.kept ? " (local edits kept as SKILL.md.conflict)" : ""}${removal.statusReason ? ` \u2014 ${removal.statusReason}` : ""}`
|
|
8385
|
+
);
|
|
8386
|
+
for (const refusal of report.refused)
|
|
8387
|
+
console.log(` REFUSED ${refusal.name} \u2014 ${refusal.reason}`);
|
|
8388
|
+
const total = report.written.length + report.unchanged.length + report.locallyEdited.length + report.conflicts.length;
|
|
8389
|
+
console.log(`
|
|
8390
|
+
${total} skill(s) synced.`);
|
|
8391
|
+
} else if (sub === "push") {
|
|
8392
|
+
const target = positionals[1];
|
|
8393
|
+
if (!target) {
|
|
8394
|
+
console.error("Usage: mindgraph-mcp skills push <path>");
|
|
8395
|
+
process.exit(1);
|
|
8396
|
+
}
|
|
8397
|
+
const report = await skillsPush(api, target);
|
|
8398
|
+
console.log(report.message);
|
|
8399
|
+
if (report.existingUid) {
|
|
8400
|
+
console.log(
|
|
8401
|
+
` existing skill: ${report.existingUid}${report.existingStatus ? ` (${report.existingStatus})` : ""}`
|
|
8402
|
+
);
|
|
8403
|
+
}
|
|
8404
|
+
if (report.outcome === "error" || report.outcome === "conflict") {
|
|
8405
|
+
process.exit(1);
|
|
8406
|
+
}
|
|
8407
|
+
} else {
|
|
8408
|
+
console.error("Usage: mindgraph-mcp skills pull|push [...]");
|
|
8409
|
+
process.exit(1);
|
|
8410
|
+
}
|
|
8411
|
+
break;
|
|
8412
|
+
}
|
|
7425
8413
|
case "help":
|
|
7426
8414
|
printUsage();
|
|
7427
8415
|
break;
|
|
@@ -7458,7 +8446,7 @@ async function main2() {
|
|
|
7458
8446
|
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
7459
8447
|
throw new Error("--harness must be claude-code or codex");
|
|
7460
8448
|
}
|
|
7461
|
-
const runner = installHookRunner(__filename);
|
|
8449
|
+
const runner = installHookRunner(__filename, void 0, "0.19.0");
|
|
7462
8450
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
7463
8451
|
const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
|
|
7464
8452
|
console.log(
|