mindgraph-mcp 0.18.0 → 0.20.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.
Files changed (4) hide show
  1. package/README.md +27 -4
  2. package/dist/cli.js +1792 -290
  3. package/dist/index.js +1153 -189
  4. 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, path7, body) {
69
- const url = `${this.baseUrl}${path7}`;
68
+ async request(method, path8, body) {
69
+ const url = `${this.baseUrl}${path8}`;
70
70
  const requestId = createRequestId();
71
71
  const init = {
72
72
  method,
@@ -83,43 +83,44 @@ var init_dist = __esm({
83
83
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
84
84
  const res = await fetch(url, init);
85
85
  if (!res.ok) {
86
- const text2 = await res.text().catch(() => "");
86
+ const text22 = await res.text().catch(() => "");
87
87
  let parsed;
88
88
  try {
89
- parsed = JSON.parse(text2);
89
+ parsed = JSON.parse(text22);
90
90
  } catch {
91
- parsed = text2;
91
+ parsed = text22;
92
92
  }
93
93
  const err5 = new MindGraphError(
94
- `${method} ${path7} failed: ${res.status}`,
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 delay = this.retryBackoffMs * 2 ** attempt;
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
  }
104
105
  throw err5;
105
106
  }
106
- const text = await res.text();
107
- if (!text) return void 0;
108
- return JSON.parse(text);
107
+ const text2 = await res.text();
108
+ if (!text2) return void 0;
109
+ return JSON.parse(text2);
109
110
  }
110
111
  throw lastError;
111
112
  }
112
- get(path7) {
113
- return this.request("GET", path7);
113
+ get(path8) {
114
+ return this.request("GET", path8);
114
115
  }
115
- post(path7, body) {
116
- return this.request("POST", path7, body);
116
+ post(path8, body) {
117
+ return this.request("POST", path8, body);
117
118
  }
118
- patch(path7, body) {
119
- return this.request("PATCH", path7, body);
119
+ patch(path8, body) {
120
+ return this.request("PATCH", path8, body);
120
121
  }
121
- del(path7) {
122
- return this.request("DELETE", path7);
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", {
@@ -185,10 +232,10 @@ var init_dist = __esm({
185
232
  * Resolve text to an existing entity via alias matching.
186
233
  * Returns `{ uid: string | null }`.
187
234
  */
188
- async resolveEntity(text, agentId) {
235
+ async resolveEntity(text2, agentId) {
189
236
  return this.post("/reality/entity", {
190
237
  action: "resolve",
191
- text,
238
+ text: text2,
192
239
  agent_id: agentId
193
240
  });
194
241
  }
@@ -196,10 +243,10 @@ var init_dist = __esm({
196
243
  * Fuzzy-match text against existing entities.
197
244
  * Returns `{ matches: [{ uid, label, score }] }`.
198
245
  */
199
- async fuzzyResolveEntity(text, limit = 5, agentId) {
246
+ async fuzzyResolveEntity(text2, limit = 5, agentId) {
200
247
  return this.post("/reality/entity", {
201
248
  action: "fuzzy_resolve",
202
- text,
249
+ text: text2,
203
250
  limit,
204
251
  agent_id: agentId
205
252
  });
@@ -498,8 +545,8 @@ var init_dist = __esm({
498
545
  async getAliases(uid) {
499
546
  return this.get(`/aliases/${seg(uid)}`);
500
547
  }
501
- async resolveAlias(text) {
502
- return this.get(`/resolve?text=${encodeURIComponent(text)}`);
548
+ async resolveAlias(text2) {
549
+ return this.get(`/resolve?text=${encodeURIComponent(text2)}`);
503
550
  }
504
551
  // ---- Export / Import ----
505
552
  async exportGraph() {
@@ -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
  }
@@ -988,14 +1049,14 @@ function unavailabilityCaveats(reason, message) {
988
1049
  }
989
1050
  return caveats;
990
1051
  }
991
- function normalizePath(path7) {
992
- return path7.replaceAll("\\", "/").replace(/^\.\//, "");
1052
+ function normalizePath(path8) {
1053
+ return path8.replaceAll("\\", "/").replace(/^\.\//, "");
993
1054
  }
994
1055
  function normalizeSignature(signature) {
995
1056
  return signature.replace(/\s+/g, " ").trim();
996
1057
  }
997
- function languageFromPath(path7) {
998
- const extension = (0, import_node_path2.extname)(path7).toLowerCase();
1058
+ function languageFromPath(path8) {
1059
+ const extension = (0, import_node_path2.extname)(path8).toLowerCase();
999
1060
  return {
1000
1061
  ".c": "c",
1001
1062
  ".cc": "cpp",
@@ -1102,29 +1163,29 @@ async function runProcess(executable, args, timeoutMs, env, cwd) {
1102
1163
  child.stderr.on("data", collect(stderr));
1103
1164
  child.on("error", (cause) => {
1104
1165
  clearTimeout(timer);
1105
- const error = new Error(cause.message);
1106
- error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
1107
- rejectPromise(error);
1166
+ const error2 = new Error(cause.message);
1167
+ error2.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
1168
+ rejectPromise(error2);
1108
1169
  });
1109
1170
  child.on("close", (code) => {
1110
1171
  clearTimeout(timer);
1111
1172
  if (timedOut) {
1112
- const error = new Error(`command timed out after ${timeoutMs}ms`);
1113
- error.kind = "timeout";
1114
- rejectPromise(error);
1173
+ const error2 = new Error(`command timed out after ${timeoutMs}ms`);
1174
+ error2.kind = "timeout";
1175
+ rejectPromise(error2);
1115
1176
  return;
1116
1177
  }
1117
1178
  if (outputBytes > MAX_OUTPUT_BYTES) {
1118
- const error = new Error("codegraph output exceeded 5 MiB");
1119
- error.kind = "command_failed";
1120
- rejectPromise(error);
1179
+ const error2 = new Error("codegraph output exceeded 5 MiB");
1180
+ error2.kind = "command_failed";
1181
+ rejectPromise(error2);
1121
1182
  return;
1122
1183
  }
1123
1184
  if (code !== 0) {
1124
- const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
1125
- error.kind = "command_failed";
1126
- error.exitCode = code;
1127
- rejectPromise(error);
1185
+ const error2 = new Error(Buffer.concat(stderr).toString("utf8").trim());
1186
+ error2.kind = "command_failed";
1187
+ error2.exitCode = code;
1188
+ rejectPromise(error2);
1128
1189
  return;
1129
1190
  }
1130
1191
  resolvePromise(Buffer.concat(stdout).toString("utf8"));
@@ -1152,13 +1213,11 @@ function codeRefIdentityKey(ref) {
1152
1213
  qualified_name: ref.qualifiedName,
1153
1214
  disambiguator: {
1154
1215
  signature: ref.signature ?? "",
1155
- path: ref.path,
1156
- start_line: ref.startLine,
1157
- end_line: ref.endLine
1216
+ path: ref.path
1158
1217
  }
1159
1218
  };
1160
1219
  }
1161
- var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
1220
+ 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;
1162
1221
  var init_codegraph = __esm({
1163
1222
  "src/codegraph.ts"() {
1164
1223
  "use strict";
@@ -1167,7 +1226,7 @@ var init_codegraph = __esm({
1167
1226
  import_promises = require("fs/promises");
1168
1227
  import_node_os2 = require("os");
1169
1228
  import_node_path2 = require("path");
1170
- import_node_crypto2 = require("crypto");
1229
+ import_node_crypto3 = require("crypto");
1171
1230
  UnknownRepositoryError = class extends Error {
1172
1231
  constructor(repo) {
1173
1232
  super(
@@ -1296,7 +1355,7 @@ var init_codegraph = __esm({
1296
1355
  } catch {
1297
1356
  }
1298
1357
  (0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
1299
- const generated = (0, import_node_crypto2.randomUUID)();
1358
+ const generated = (0, import_node_crypto3.randomUUID)();
1300
1359
  try {
1301
1360
  (0, import_node_fs2.writeFileSync)(identityFile, `${generated}
1302
1361
  `, {
@@ -2043,7 +2102,7 @@ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
2043
2102
  identity_space_uid: spaceUid,
2044
2103
  agent_id: agentId
2045
2104
  });
2046
- if (response.uid) {
2105
+ if (response.uid && response.created !== false && response.status !== "existing") {
2047
2106
  await client2.entity({
2048
2107
  action: "relate",
2049
2108
  source_uid: response.uid,
@@ -2308,7 +2367,7 @@ function windowsCommand(harness) {
2308
2367
  function installHookRunner(sourceFile, homeDir, version) {
2309
2368
  const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
2310
2369
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
2311
- const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
2370
+ const temporary = `${target}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
2312
2371
  import_node_fs4.default.copyFileSync(sourceFile, temporary);
2313
2372
  import_node_fs4.default.renameSync(temporary, target);
2314
2373
  const versionPath = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
@@ -2478,7 +2537,7 @@ function entryHasOwnedHook(value) {
2478
2537
  }
2479
2538
  function writeSettings(file, settings) {
2480
2539
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
2481
- const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
2540
+ const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
2482
2541
  import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
2483
2542
  `, {
2484
2543
  mode: 384
@@ -2554,14 +2613,14 @@ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2554
2613
  function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2555
2614
  return uninstallHooks("codex", scope, projectDir, codexHomeDir);
2556
2615
  }
2557
- var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto4, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
2616
+ var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto5, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
2558
2617
  var init_hook_installer = __esm({
2559
2618
  "src/hook-installer.ts"() {
2560
2619
  "use strict";
2561
2620
  import_node_fs4 = __toESM(require("fs"));
2562
2621
  import_node_os4 = __toESM(require("os"));
2563
2622
  import_node_path4 = __toESM(require("path"));
2564
- import_node_crypto4 = __toESM(require("crypto"));
2623
+ import_node_crypto5 = __toESM(require("crypto"));
2565
2624
  OWNER_MARKER = "--owner mindgraph";
2566
2625
  RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
2567
2626
  RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
@@ -2603,7 +2662,7 @@ function fileDescriptor(root, absolute) {
2603
2662
  const content = import_node_fs5.default.readFileSync(absolute, "utf8");
2604
2663
  return {
2605
2664
  logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
2606
- content_hash: import_node_crypto5.default.createHash("sha256").update(content).digest("hex"),
2665
+ content_hash: import_node_crypto6.default.createHash("sha256").update(content).digest("hex"),
2607
2666
  bytes: Buffer.byteLength(content),
2608
2667
  content
2609
2668
  };
@@ -2678,8 +2737,8 @@ async function handleSyncTool(client2, args) {
2678
2737
  }
2679
2738
  }
2680
2739
  return ok2(await remote.memorySync(request));
2681
- } catch (error) {
2682
- return err2("sync_failed", errorDetail(error));
2740
+ } catch (error2) {
2741
+ return err2("sync_failed", errorDetail(error2));
2683
2742
  }
2684
2743
  }
2685
2744
  async function scanRepository(remote, repository, requested, provider, agentId) {
@@ -2716,11 +2775,11 @@ async function scanRepository(remote, repository, requested, provider, agentId)
2716
2775
  files: results
2717
2776
  };
2718
2777
  }
2719
- var import_node_crypto5, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
2778
+ var import_node_crypto6, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
2720
2779
  var init_sync_tool = __esm({
2721
2780
  "src/sync-tool.ts"() {
2722
2781
  "use strict";
2723
- import_node_crypto5 = __toESM(require("crypto"));
2782
+ import_node_crypto6 = __toESM(require("crypto"));
2724
2783
  import_node_fs5 = __toESM(require("fs"));
2725
2784
  import_node_path5 = __toESM(require("path"));
2726
2785
  init_codegraph();
@@ -2813,12 +2872,22 @@ function str(value) {
2813
2872
  function num(value) {
2814
2873
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2815
2874
  }
2875
+ function edgeTypeName(value) {
2876
+ return str(value) ?? str(obj(value)?.Custom);
2877
+ }
2878
+ function renderBudget(requested) {
2879
+ if (requested === void 0 || !Number.isFinite(requested)) return RENDER_CHAR_BUDGET;
2880
+ return Math.min(
2881
+ RENDER_CHAR_BUDGET,
2882
+ Math.max(MIN_RENDER_CHAR_BUDGET, Math.floor(requested))
2883
+ );
2884
+ }
2816
2885
  function clip(value, max, state) {
2817
- const text = str(value);
2818
- if (!text) return void 0;
2819
- if (text.length <= max) return text;
2886
+ const text2 = str(value);
2887
+ if (!text2) return void 0;
2888
+ if (text2.length <= max) return text2;
2820
2889
  state.bounded = true;
2821
- return `${text.slice(0, max - 1)}\u2026`;
2890
+ return `${text2.slice(0, max - 1)}\u2026`;
2822
2891
  }
2823
2892
  function isNodeShaped(value) {
2824
2893
  const node = obj(value);
@@ -2940,7 +3009,7 @@ function relationshipItems(edges, labelByUid, state, options) {
2940
3009
  for (const raw of edges) {
2941
3010
  const edge = obj(raw);
2942
3011
  if (!edge) continue;
2943
- const type = str(edge.edge_type) || "?";
3012
+ const type = edgeTypeName(edge.edge_type) || "?";
2944
3013
  const from = str(edge.from_uid);
2945
3014
  const to = str(edge.to_uid);
2946
3015
  if (CURATION_EDGE_TYPES.has(type)) continue;
@@ -2953,25 +3022,29 @@ function relationshipItems(edges, labelByUid, state, options) {
2953
3022
  if (lines.length > RELATIONSHIP_MAX) {
2954
3023
  state.bounded = true;
2955
3024
  const extra = lines.length - RELATIONSHIP_MAX;
3025
+ state.omitted += extra;
2956
3026
  return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
2957
3027
  }
2958
3028
  return lines;
2959
3029
  }
2960
- function assemble(header, sections, state) {
3030
+ function assemble(header, sections, state, maxChars) {
3031
+ const budget = renderBudget(maxChars);
3032
+ const packingBudget = Math.max(header.length, budget - BOUNDED_MARKER_RESERVE);
2961
3033
  const lines = [header];
2962
3034
  let used = header.length;
2963
3035
  for (const section of sections) {
2964
3036
  if (section.items.length === 0 && !section.heading) continue;
2965
3037
  const headingCost = section.heading.length + 2;
2966
- if (used + headingCost > RENDER_CHAR_BUDGET) {
3038
+ if (used + headingCost > packingBudget) {
2967
3039
  state.bounded = true;
3040
+ state.omitted += Math.max(1, section.items.length);
2968
3041
  continue;
2969
3042
  }
2970
3043
  let sectionUsed = 0;
2971
3044
  const kept = [];
2972
3045
  let dropped = 0;
2973
3046
  for (const item of section.items) {
2974
- if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
3047
+ if (used + headingCost + sectionUsed + item.length + 1 > packingBudget) {
2975
3048
  dropped += 1;
2976
3049
  continue;
2977
3050
  }
@@ -2980,13 +3053,17 @@ function assemble(header, sections, state) {
2980
3053
  }
2981
3054
  if (kept.length === 0 && section.items.length > 0) {
2982
3055
  state.bounded = true;
3056
+ state.omitted += dropped;
2983
3057
  continue;
2984
3058
  }
2985
3059
  if (dropped > 0) {
2986
3060
  state.bounded = true;
3061
+ state.omitted += dropped;
2987
3062
  const marker = `(+${dropped} more not shown)`;
2988
- kept.push(marker);
2989
- sectionUsed += marker.length + 1;
3063
+ if (used + headingCost + sectionUsed + marker.length + 1 <= packingBudget) {
3064
+ kept.push(marker);
3065
+ sectionUsed += marker.length + 1;
3066
+ }
2990
3067
  }
2991
3068
  lines.push(
2992
3069
  section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
@@ -2996,10 +3073,10 @@ ${kept.join("\n")}` : kept.join("\n")
2996
3073
  }
2997
3074
  if (state.bounded) {
2998
3075
  lines.push(
2999
- '\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
3076
+ `\u2026[rendered context bounded to ${budget} chars; ${state.omitted} item(s) omitted \u2014 fetch full node content by uid, narrow the query, raise max_output_chars, or pass format: "json" for the raw response]`
3000
3077
  );
3001
3078
  }
3002
- return lines.join("\n\n");
3079
+ return lines.join("\n\n").slice(0, budget);
3003
3080
  }
3004
3081
  function policySections(payload, state) {
3005
3082
  const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
@@ -3020,7 +3097,7 @@ function hiddenSections(payload) {
3020
3097
  }
3021
3098
  ];
3022
3099
  }
3023
- function renderContext(response) {
3100
+ function renderContext(response, maxChars) {
3024
3101
  const payload = obj(response);
3025
3102
  if (!payload) return void 0;
3026
3103
  const graph = obj(payload.graph);
@@ -3030,7 +3107,7 @@ function renderContext(response) {
3030
3107
  const infraOnly = shaped.length - nodes.length;
3031
3108
  const articles = Array.isArray(payload.articles) ? payload.articles : [];
3032
3109
  const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
3033
- const state = { bounded: false };
3110
+ const state = { bounded: false, omitted: 0 };
3034
3111
  const sections = [];
3035
3112
  if (articles.length > 0) {
3036
3113
  sections.push({
@@ -3073,11 +3150,129 @@ ${clip(article.content, ARTICLE_MAX, state) || ""}`
3073
3150
  return "No results found for this search.";
3074
3151
  }
3075
3152
  if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
3076
- return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
3153
+ return assemble(
3154
+ "# Retrieved context\n\nNo graph results matched this search.",
3155
+ sections,
3156
+ state,
3157
+ maxChars
3158
+ );
3159
+ }
3160
+ return assemble("# Retrieved context", sections, state, maxChars);
3161
+ }
3162
+ function resumeEntry(raw, state, fallbackType) {
3163
+ const wrapper = obj(raw);
3164
+ if (!wrapper) return void 0;
3165
+ const value = obj(wrapper.node) ?? wrapper;
3166
+ if (isNodeShaped(value)) return nodeLines(value, state);
3167
+ const uid = str(value.uid);
3168
+ const props = obj(value.props);
3169
+ const label = clip(value.label, LABEL_MAX, state) ?? clip(value.name, LABEL_MAX, state) ?? clip(value.title, LABEL_MAX, state) ?? clip(value.action, LABEL_MAX, state) ?? fallbackType;
3170
+ const status = str(value.status) ?? str(props?.status);
3171
+ const summary = clip(value.summary, SUMMARY_MAX, state) ?? clip(value.description, SUMMARY_MAX, state) ?? clip(value.content, SUMMARY_MAX, state) ?? clip(value.outcome_summary, SUMMARY_MAX, state) ?? clip(props?.summary, SUMMARY_MAX, state);
3172
+ const details = [
3173
+ status ? `status ${status}` : void 0,
3174
+ str(value.path) ? `path ${str(value.path)}` : void 0,
3175
+ str(value.symbol) ? `symbol ${str(value.symbol)}` : void 0
3176
+ ].filter(Boolean);
3177
+ const head = `- **${label}**${uid ? ` [${uid}]` : ""}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
3178
+ return summary && summary !== label ? `${head}: ${summary}` : head;
3179
+ }
3180
+ function resumeSection(heading, values, state, fallbackType) {
3181
+ const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
3182
+ return items.length > 0 ? { heading, items } : void 0;
3183
+ }
3184
+ function renderResumeContext(response, maxChars) {
3185
+ const payload = obj(response);
3186
+ if (!payload) return void 0;
3187
+ const state = { bounded: false, omitted: 0 };
3188
+ const status = str(payload.status);
3189
+ if (!payload.task && status === "no_eligible_work") {
3190
+ const scopedOut = Array.isArray(payload.scoped_out_tasks) ? payload.scoped_out_tasks.length : 0;
3191
+ const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
3192
+ return assemble(
3193
+ "# Resume context",
3194
+ [{ heading: `No eligible work was found in the requested scope.${note}`, items: [] }],
3195
+ state,
3196
+ maxChars
3197
+ );
3077
3198
  }
3078
- return assemble("# Retrieved context", sections, state);
3199
+ const sections = [];
3200
+ const push = (section) => {
3201
+ if (section) sections.push(section);
3202
+ };
3203
+ push(resumeSection("## Current task", [payload.task], state, "Task"));
3204
+ push(
3205
+ resumeSection(
3206
+ "## Goal and project",
3207
+ [payload.goal, payload.project].filter(Boolean),
3208
+ state,
3209
+ "Scope"
3210
+ )
3211
+ );
3212
+ push(resumeSection("## Plan", [payload.plan], state, "Plan"));
3213
+ push(
3214
+ resumeSection(
3215
+ "## Next steps",
3216
+ Array.isArray(payload.next_steps) ? payload.next_steps : [],
3217
+ state,
3218
+ "Next step"
3219
+ )
3220
+ );
3221
+ push(
3222
+ resumeSection(
3223
+ "## Blockers",
3224
+ Array.isArray(payload.blockers) ? payload.blockers : [],
3225
+ state,
3226
+ "Blocker"
3227
+ )
3228
+ );
3229
+ const knowledge = obj(payload.knowledge);
3230
+ push(
3231
+ resumeSection(
3232
+ "## Relevant lessons and risks",
3233
+ [
3234
+ ...Array.isArray(knowledge?.lessons) ? knowledge.lessons : [],
3235
+ ...Array.isArray(knowledge?.risks) ? knowledge.risks : []
3236
+ ],
3237
+ state,
3238
+ "Knowledge"
3239
+ )
3240
+ );
3241
+ push(
3242
+ resumeSection(
3243
+ "## Active execution",
3244
+ [payload.active_execution].filter(Boolean),
3245
+ state,
3246
+ "Execution"
3247
+ )
3248
+ );
3249
+ push(
3250
+ resumeSection(
3251
+ "## Recent executions",
3252
+ Array.isArray(payload.recent_executions) ? payload.recent_executions : [],
3253
+ state,
3254
+ "Execution"
3255
+ )
3256
+ );
3257
+ push(
3258
+ resumeSection(
3259
+ "## Code targets",
3260
+ Array.isArray(payload.code_targets) ? payload.code_targets : [],
3261
+ state,
3262
+ "Code target"
3263
+ )
3264
+ );
3265
+ push(resumeSection("## Lease", [payload.lease].filter(Boolean), state, "Lease"));
3266
+ const warning = clip(payload.warning, SUMMARY_MAX, state);
3267
+ if (warning) sections.push({ heading: "## Warning", items: [`- ${warning}`] });
3268
+ const selectionReason = clip(payload.selection_reason, SUMMARY_MAX, state);
3269
+ if (selectionReason) {
3270
+ sections.push({ heading: "## Selection", items: [`- ${selectionReason}`] });
3271
+ }
3272
+ if (sections.length === 0) return void 0;
3273
+ return assemble("# Resume context", sections, state, maxChars);
3079
3274
  }
3080
- function renderNodeList(response, title, limit) {
3275
+ function renderNodeList(response, title, limit, maxChars) {
3081
3276
  let candidates = [];
3082
3277
  if (Array.isArray(response)) {
3083
3278
  candidates = response;
@@ -3095,8 +3290,9 @@ function renderNodeList(response, title, limit) {
3095
3290
  let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
3096
3291
  if (nodes.length === 0) return void 0;
3097
3292
  const total = nodes.length;
3098
- const state = { bounded: false };
3293
+ const state = { bounded: false, omitted: 0 };
3099
3294
  if (limit !== void 0 && limit > 0 && nodes.length > limit) {
3295
+ state.omitted += nodes.length - limit;
3100
3296
  nodes = nodes.slice(0, limit);
3101
3297
  state.bounded = true;
3102
3298
  }
@@ -3105,31 +3301,36 @@ function renderNodeList(response, title, limit) {
3105
3301
  return assemble(
3106
3302
  `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
3107
3303
  sections,
3108
- state
3304
+ state,
3305
+ maxChars
3109
3306
  );
3110
3307
  }
3111
- function renderTraversal(response) {
3308
+ function renderTraversal(response, maxChars) {
3309
+ const directSteps = Array.isArray(response) ? response : void 0;
3112
3310
  const payload = obj(response);
3113
- if (!payload) return void 0;
3114
- const container = obj(payload.graph) ?? payload;
3311
+ if (!payload && !directSteps) return void 0;
3312
+ const body = payload ?? {};
3313
+ const container = obj(body.graph) ?? body;
3115
3314
  const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
3116
3315
  const nodes = rawNodes.filter(isNodeShaped);
3117
- const paths = Array.isArray(payload.paths) ? payload.paths : [];
3118
- if (nodes.length === 0 && paths.length === 0) return void 0;
3119
- const state = { bounded: false };
3316
+ const paths = Array.isArray(body.paths) ? body.paths : [];
3317
+ const hasSteps = directSteps !== void 0 || Object.hasOwn(body, "steps");
3318
+ const rawSteps = directSteps ?? (Array.isArray(body.steps) ? body.steps : []);
3319
+ if (nodes.length === 0 && paths.length === 0 && !hasSteps) return void 0;
3320
+ const state = { bounded: false, omitted: 0 };
3120
3321
  const sections = [];
3121
3322
  const labelByUid = /* @__PURE__ */ new Map();
3122
3323
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
3123
3324
  if (paths.length > 0) {
3124
- const items = paths.map((raw) => obj(raw)).filter((path7) => Boolean(path7)).map((path7, index) => {
3125
- const uids = Array.isArray(path7.node_uids) ? path7.node_uids.map(String) : [];
3126
- const labels = Array.isArray(path7.labels) ? path7.labels.map(String) : Array.isArray(path7.nodes) ? path7.nodes.map((hop) => {
3325
+ const items = paths.map((raw) => obj(raw)).filter((path8) => Boolean(path8)).map((path8, index) => {
3326
+ const uids = Array.isArray(path8.node_uids) ? path8.node_uids.map(String) : [];
3327
+ const labels = Array.isArray(path8.labels) ? path8.labels.map(String) : Array.isArray(path8.nodes) ? path8.nodes.map((hop) => {
3127
3328
  const hopNode = obj(hop);
3128
3329
  return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
3129
3330
  }) : [];
3130
3331
  const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
3131
- const cost = num(path7.cost) ?? num(path7.path_cost);
3132
- const confidence = num(path7.path_confidence);
3332
+ const cost = num(path8.cost) ?? num(path8.path_cost);
3333
+ const confidence = num(path8.path_confidence);
3133
3334
  const meta = [
3134
3335
  cost !== void 0 ? `cost ${cost}` : void 0,
3135
3336
  confidence !== void 0 ? `confidence ${confidence}` : void 0
@@ -3140,6 +3341,42 @@ function renderTraversal(response) {
3140
3341
  });
3141
3342
  sections.push({ heading: "## Paths", items });
3142
3343
  }
3344
+ if (hasSteps) {
3345
+ const stepItems = rawSteps.map((raw) => obj(raw)).filter((step) => Boolean(step)).map((step) => {
3346
+ const depth = num(step.depth);
3347
+ const uid = str(step.node_uid) ?? str(step.uid) ?? str(step.edge_to_uid) ?? "?";
3348
+ const parent = str(step.parent_uid) ?? str(step.edge_from_uid);
3349
+ const label = str(step.label) || "(unlabeled)";
3350
+ const relation = edgeTypeName(step.edge_type);
3351
+ const role = str(step.traversal_role);
3352
+ const type = str(step.node_type) ?? str(obj(step.node_type)?.Custom);
3353
+ const cost = num(step.path_cost);
3354
+ const confidence = num(step.path_confidence);
3355
+ const prefix = [
3356
+ depth !== void 0 ? `depth ${depth}` : void 0,
3357
+ role,
3358
+ relation ? `\u2014${relation}\u2192` : void 0
3359
+ ].filter(Boolean).join(" ");
3360
+ const meta = [
3361
+ type,
3362
+ parent ? `parent ${parent}` : void 0,
3363
+ cost !== void 0 ? `cost ${cost}` : void 0,
3364
+ confidence !== void 0 ? `confidence ${confidence}` : void 0
3365
+ ].filter(Boolean).join(", ");
3366
+ return `- ${prefix ? `${prefix}: ` : ""}**${label}** [${uid}]${meta ? ` (${meta})` : ""}`;
3367
+ });
3368
+ if (stepItems.length > 0) {
3369
+ sections.push({ heading: "## Traversal steps", items: stepItems });
3370
+ } else {
3371
+ const start = str(body.start_uid) || "?";
3372
+ const end = str(body.end_uid);
3373
+ const mode2 = str(body.mode);
3374
+ sections.push({
3375
+ heading: mode2 === "path" && end ? `No path found from [${start}] to [${end}].` : `No traversal steps returned from [${start}].`,
3376
+ items: []
3377
+ });
3378
+ }
3379
+ }
3143
3380
  if (nodes.length > 0) {
3144
3381
  sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
3145
3382
  const relationships = relationshipItems(
@@ -3151,7 +3388,13 @@ function renderTraversal(response) {
3151
3388
  sections.push({ heading: "## Relationships", items: relationships });
3152
3389
  }
3153
3390
  }
3154
- return assemble("# Graph traversal", sections, state);
3391
+ const mode = str(body.mode);
3392
+ return assemble(
3393
+ `# Graph traversal${mode ? ` (${mode})` : ""}`,
3394
+ sections,
3395
+ state,
3396
+ maxChars
3397
+ );
3155
3398
  }
3156
3399
  function neighborLine(value) {
3157
3400
  const node = obj(value);
@@ -3172,7 +3415,7 @@ function renderOntologyAnswer(response) {
3172
3415
  if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
3173
3416
  return hasQuerySignature ? "No matching domain objects." : void 0;
3174
3417
  }
3175
- const state = { bounded: false };
3418
+ const state = { bounded: false, omitted: 0 };
3176
3419
  const sections = [];
3177
3420
  if (objects.length > 0) {
3178
3421
  sections.push(...groupedNodeSections(objects, state));
@@ -3238,7 +3481,7 @@ function renderObjectList(response, title) {
3238
3481
  if (items.length === 0) {
3239
3482
  return rawItems ? `# ${title} (0 results)` : void 0;
3240
3483
  }
3241
- const state = { bounded: false };
3484
+ const state = { bounded: false, omitted: 0 };
3242
3485
  const sections = groupedNodeSections(items, state);
3243
3486
  const total = num(payload.total_count);
3244
3487
  const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
@@ -3253,7 +3496,7 @@ function renderJobs(response) {
3253
3496
  const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
3254
3497
  if (!Array.isArray(jobs)) return void 0;
3255
3498
  if (jobs.length === 0) return "No ingestion jobs.";
3256
- const state = { bounded: false };
3499
+ const state = { bounded: false, omitted: 0 };
3257
3500
  const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
3258
3501
  const shown = sorted.slice(0, JOBS_RENDER_MAX);
3259
3502
  if (shown.length < sorted.length) state.bounded = true;
@@ -3279,7 +3522,7 @@ function renderJobs(response) {
3279
3522
  function renderSignals(response) {
3280
3523
  const payload = obj(response);
3281
3524
  if (!payload) return void 0;
3282
- const state = { bounded: false };
3525
+ const state = { bounded: false, omitted: 0 };
3283
3526
  const sections = [];
3284
3527
  for (const [key, value] of Object.entries(payload)) {
3285
3528
  if (!Array.isArray(value) || value.length === 0) continue;
@@ -3303,7 +3546,7 @@ function renderSignals(response) {
3303
3546
  if (sections.length === 0) return void 0;
3304
3547
  return assemble("# Synthesis signals", sections, state);
3305
3548
  }
3306
- var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, CURATION_EDGE_TYPES, INFRA_NODE_TYPES, JOBS_RENDER_MAX;
3549
+ var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, MIN_RENDER_CHAR_BUDGET, BOUNDED_MARKER_RESERVE, CURATION_EDGE_TYPES, INFRA_NODE_TYPES, JOBS_RENDER_MAX;
3307
3550
  var init_render = __esm({
3308
3551
  "src/render.ts"() {
3309
3552
  "use strict";
@@ -3314,13 +3557,570 @@ var init_render = __esm({
3314
3557
  CHUNK_MAX = 6e3;
3315
3558
  RELATIONSHIP_MAX = 40;
3316
3559
  RENDER_CHAR_BUDGET = 24e3;
3560
+ MIN_RENDER_CHAR_BUDGET = 512;
3561
+ BOUNDED_MARKER_RESERVE = 220;
3317
3562
  CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
3318
3563
  INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
3319
3564
  JOBS_RENDER_MAX = 20;
3320
3565
  }
3321
3566
  });
3322
3567
 
3568
+ // src/memory-tool.ts
3569
+ function obj2(value) {
3570
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3571
+ }
3572
+ function text(value) {
3573
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3574
+ }
3575
+ function stableJson(value) {
3576
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
3577
+ if (value && typeof value === "object") {
3578
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
3579
+ }
3580
+ return JSON.stringify(value);
3581
+ }
3582
+ function sha2562(value) {
3583
+ return (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
3584
+ }
3585
+ function outputBudget(value) {
3586
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
3587
+ return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
3588
+ }
3589
+ function defined(values) {
3590
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
3591
+ }
3592
+ function memoryStatus(config = {}) {
3593
+ return {
3594
+ schema_version: RESULT_SCHEMA_VERSION,
3595
+ action: "status",
3596
+ profile: "M0",
3597
+ server: defined({
3598
+ name: "mindgraph-mcp",
3599
+ version: config.serverVersion ?? "unknown",
3600
+ base_url: config.baseUrl
3601
+ }),
3602
+ integration: {
3603
+ adapter_state: "model_controlled",
3604
+ harness: config.harness ?? "generic",
3605
+ configured_tool_profile: config.profile ?? "general",
3606
+ automatic_capture: false,
3607
+ automatic_delivery: false,
3608
+ delivery_verification: "unsupported",
3609
+ curator_mode: "disabled"
3610
+ },
3611
+ scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
3612
+ capabilities: {
3613
+ status: true,
3614
+ topic_context: true,
3615
+ authoritative_resume_context: true,
3616
+ bounded_rendering: true,
3617
+ structured_content: true,
3618
+ typed_explicit_writes: true,
3619
+ lifecycle_event_ingest: false,
3620
+ automatic_context_injection: false,
3621
+ curator_mutation: false
3622
+ },
3623
+ degradation: [
3624
+ {
3625
+ code: "automatic_capture_unavailable",
3626
+ message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
3627
+ },
3628
+ {
3629
+ code: "delivery_unverified",
3630
+ message: "A successful MCP read does not prove that the host injected or used the returned context."
3631
+ },
3632
+ {
3633
+ code: "curator_disabled",
3634
+ message: "Curator proposals and autonomous mutation are not enabled in M0."
3635
+ }
3636
+ ]
3637
+ };
3638
+ }
3639
+ function nodeType(node) {
3640
+ const raw = node.node_type;
3641
+ if (typeof raw === "string") return raw;
3642
+ return text(obj2(raw)?.Custom) ?? "Node";
3643
+ }
3644
+ function compactItem(raw, fallbackType) {
3645
+ const wrapper = obj2(raw);
3646
+ if (!wrapper) return void 0;
3647
+ const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
3648
+ const uid = text(value.uid) ?? text(value.chunk_uid);
3649
+ if (!uid) return void 0;
3650
+ return defined({
3651
+ uid,
3652
+ type: fallbackType ?? nodeType(value),
3653
+ label: text(value.label) ?? text(value.document_title),
3654
+ truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
3655
+ superseded: value.superseded === true ? true : void 0,
3656
+ currently_valid: value.currently_valid === false ? false : void 0,
3657
+ valid_at_time: value.valid_at_time === false ? false : void 0,
3658
+ retrieval_origin: text(value.retrieval_origin),
3659
+ retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
3660
+ });
3661
+ }
3662
+ function topicCandidates(raw) {
3663
+ const payload = obj2(raw);
3664
+ if (!payload) return [];
3665
+ const graph = obj2(payload.graph);
3666
+ const items = [];
3667
+ for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
3668
+ const item = compactItem(article, "Article");
3669
+ if (item) items.push(item);
3670
+ }
3671
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
3672
+ const labels = /* @__PURE__ */ new Map();
3673
+ for (const node of rawNodes) {
3674
+ const item = compactItem(node);
3675
+ if (item) {
3676
+ if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
3677
+ items.push(item);
3678
+ const uid = text(item.uid);
3679
+ const label = text(item.label);
3680
+ if (uid && label) labels.set(uid, label);
3681
+ }
3682
+ }
3683
+ for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
3684
+ const edge = obj2(rawEdge);
3685
+ const fromUid = text(edge?.from_uid);
3686
+ const toUid = text(edge?.to_uid);
3687
+ const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
3688
+ if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
3689
+ continue;
3690
+ }
3691
+ items.push(
3692
+ defined({
3693
+ type: "Relationship",
3694
+ from_uid: fromUid,
3695
+ to_uid: toUid,
3696
+ edge_type: edgeType,
3697
+ from_label: labels.get(fromUid),
3698
+ to_label: labels.get(toUid)
3699
+ })
3700
+ );
3701
+ }
3702
+ for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
3703
+ const item = compactItem(chunk, "SourceExcerpt");
3704
+ if (item) items.push(item);
3705
+ }
3706
+ return items;
3707
+ }
3708
+ function resumeCandidates(raw) {
3709
+ const payload = obj2(raw);
3710
+ if (!payload) return [];
3711
+ const items = [];
3712
+ for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
3713
+ const item = compactItem(payload[key], key);
3714
+ if (item) items.push(item);
3715
+ }
3716
+ for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
3717
+ for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
3718
+ const item = compactItem(value, key.replace(/s$/, ""));
3719
+ if (item) items.push(item);
3720
+ }
3721
+ }
3722
+ const knowledge = obj2(payload.knowledge);
3723
+ for (const key of ["lessons", "risks"]) {
3724
+ for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
3725
+ const item = compactItem(value, key.replace(/s$/, ""));
3726
+ if (item) items.push(item);
3727
+ }
3728
+ }
3729
+ return items;
3730
+ }
3731
+ function visibleItems(candidates, rendered2) {
3732
+ const seen = /* @__PURE__ */ new Set();
3733
+ return candidates.filter((item) => {
3734
+ const uid = text(item.uid);
3735
+ if (uid) {
3736
+ if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
3737
+ seen.add(uid);
3738
+ return true;
3739
+ }
3740
+ if (item.type === "Relationship") {
3741
+ const from = text(item.from_label);
3742
+ const to = text(item.to_label);
3743
+ const edgeType = text(item.edge_type);
3744
+ if (!from || !to || !edgeType) return false;
3745
+ const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
3746
+ if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
3747
+ seen.add(key);
3748
+ return true;
3749
+ }
3750
+ return false;
3751
+ });
3752
+ }
3753
+ function latestTimestamp(value) {
3754
+ let latest;
3755
+ const visit = (current) => {
3756
+ if (Array.isArray(current)) {
3757
+ for (const item of current) visit(item);
3758
+ return;
3759
+ }
3760
+ const record = obj2(current);
3761
+ if (!record) return;
3762
+ for (const [key, item] of Object.entries(record)) {
3763
+ if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
3764
+ latest = item;
3765
+ }
3766
+ visit(item);
3767
+ }
3768
+ };
3769
+ visit(value);
3770
+ return latest;
3771
+ }
3772
+ function contextWarnings(raw, rendered2, available, returned) {
3773
+ const warnings = [
3774
+ {
3775
+ code: "delivery_unverified",
3776
+ message: "The MCP server returned this context but cannot verify that the host injected or used it."
3777
+ }
3778
+ ];
3779
+ const serialized = JSON.stringify(raw);
3780
+ if (available > returned || rendered2.includes("item(s) omitted")) {
3781
+ warnings.push({
3782
+ code: "context_bounded",
3783
+ message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
3784
+ });
3785
+ }
3786
+ if (/"superseded":true/.test(serialized)) {
3787
+ warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
3788
+ }
3789
+ if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
3790
+ warnings.push({
3791
+ code: "validity_warning",
3792
+ message: "Some retrieved items are outside their current or requested validity window."
3793
+ });
3794
+ }
3795
+ const payload = obj2(raw);
3796
+ if (payload?.truncated === true || obj2(payload?.truncation)) {
3797
+ const truncation = obj2(payload?.truncation);
3798
+ if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
3799
+ warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
3800
+ }
3801
+ }
3802
+ if (text(payload?.warning)) {
3803
+ warnings.push({ code: "source_warning", message: text(payload?.warning) });
3804
+ }
3805
+ return warnings;
3806
+ }
3807
+ function error(message) {
3808
+ return {
3809
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
3810
+ isError: true
3811
+ };
3812
+ }
3813
+ async function handleMemoryTool(client2, args, config = {}) {
3814
+ const action = text(args.action);
3815
+ if (action === "status") {
3816
+ const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
3817
+ return {
3818
+ content: [{ type: "text", text: JSON.stringify(status) }],
3819
+ structuredContent: status
3820
+ };
3821
+ }
3822
+ if (action !== "context") {
3823
+ return error("action must be 'status' or 'context'");
3824
+ }
3825
+ const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
3826
+ if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
3827
+ return error("action='context' requires mode='topic', 'continuity', or 'resume'");
3828
+ }
3829
+ const budget = outputBudget(args.max_output_chars);
3830
+ const agentId = text(args.agent_id) ?? config.agentId;
3831
+ let raw;
3832
+ let rendered2;
3833
+ let strategy;
3834
+ let candidates;
3835
+ if (mode === "topic") {
3836
+ const query = text(args.query);
3837
+ if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
3838
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
3839
+ const request = defined({
3840
+ query,
3841
+ project_uid: text(args.project_uid),
3842
+ node_limit: limit,
3843
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
3844
+ layer: text(args.layer),
3845
+ chunk_limit: args.include_chunks === true ? limit : 0,
3846
+ article_limit: args.include_documents === true ? limit : 0,
3847
+ valid_at: text(args.valid_at),
3848
+ include_graph: true,
3849
+ graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
3850
+ graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
3851
+ });
3852
+ raw = await client2.retrieveContext(request);
3853
+ rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
3854
+ strategy = "retrieve_context";
3855
+ candidates = topicCandidates(raw);
3856
+ } else {
3857
+ const scopeUids = Array.from(
3858
+ /* @__PURE__ */ new Set([
3859
+ ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
3860
+ ...text(args.project_uid) ? [text(args.project_uid)] : []
3861
+ ])
3862
+ );
3863
+ raw = await client2.plan(
3864
+ defined({
3865
+ action: "resume_work",
3866
+ task_uid: text(args.task_uid),
3867
+ session_uid: text(args.session_uid),
3868
+ scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
3869
+ limit: 1,
3870
+ agent_id: agentId
3871
+ })
3872
+ );
3873
+ rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
3874
+ strategy = "resume_work";
3875
+ candidates = resumeCandidates(raw);
3876
+ }
3877
+ const items = visibleItems(candidates, rendered2);
3878
+ const renderedHash = sha2562(rendered2);
3879
+ const scope = defined({
3880
+ org_id: config.orgId,
3881
+ agent_id: agentId,
3882
+ harness: config.harness ?? "generic",
3883
+ project_uid: text(args.project_uid),
3884
+ task_uid: text(args.task_uid),
3885
+ session_uid: text(args.session_uid),
3886
+ scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
3887
+ });
3888
+ const contextId = `ctx_${sha2562(
3889
+ stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
3890
+ ).slice(0, 24)}`;
3891
+ const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
3892
+ const result = {
3893
+ schema_version: RESULT_SCHEMA_VERSION,
3894
+ action: "context",
3895
+ context_id: contextId,
3896
+ mode,
3897
+ scope,
3898
+ rendered_context: rendered2,
3899
+ rendered_hash: renderedHash,
3900
+ items,
3901
+ retrieval_trace: defined({
3902
+ strategy,
3903
+ query: text(args.query),
3904
+ project_uid: text(args.project_uid),
3905
+ task_uid: text(args.task_uid),
3906
+ session_uid: text(args.session_uid),
3907
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
3908
+ graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
3909
+ graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
3910
+ available_items: candidates.length,
3911
+ returned_items: items.length,
3912
+ omitted_items: Math.max(0, candidates.length - items.length)
3913
+ }),
3914
+ budget: {
3915
+ unit: "characters",
3916
+ requested_chars: budget,
3917
+ used_chars: rendered2.length,
3918
+ available_items: candidates.length,
3919
+ returned_items: items.length,
3920
+ omitted_items: Math.max(0, candidates.length - items.length),
3921
+ bounded
3922
+ },
3923
+ freshness: defined({
3924
+ valid_at: text(args.valid_at),
3925
+ latest_source_timestamp: latestTimestamp(raw)
3926
+ }),
3927
+ warnings: contextWarnings(raw, rendered2, candidates.length, items.length)
3928
+ };
3929
+ return {
3930
+ content: [{ type: "text", text: rendered2 }],
3931
+ structuredContent: result
3932
+ };
3933
+ }
3934
+ var import_node_crypto7, RESULT_SCHEMA_VERSION, DEFAULT_OUTPUT_CHARS, MIN_OUTPUT_CHARS, MAX_OUTPUT_CHARS, HIDDEN_CONTEXT_NODE_TYPES, HIDDEN_CONTEXT_EDGE_TYPES, MEMORY_TOOL;
3935
+ var init_memory_tool = __esm({
3936
+ "src/memory-tool.ts"() {
3937
+ "use strict";
3938
+ import_node_crypto7 = require("crypto");
3939
+ init_render();
3940
+ RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
3941
+ DEFAULT_OUTPUT_CHARS = 8e3;
3942
+ MIN_OUTPUT_CHARS = 512;
3943
+ MAX_OUTPUT_CHARS = 24e3;
3944
+ HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
3945
+ HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
3946
+ MEMORY_TOOL = {
3947
+ name: "mindgraph_memory",
3948
+ description: "Read the portable MindGraph memory surface. Use action='context' with mode='resume' or 'continuity' for authoritative task-first work state, and mode='topic' with a short keyword query for prior knowledge relevant to the user's request. Use action='status' to inspect integration capabilities and limitations. This tool never writes; durable capture and correction use the existing typed MindGraph write tools.",
3949
+ inputSchema: {
3950
+ type: "object",
3951
+ properties: {
3952
+ action: {
3953
+ type: "string",
3954
+ enum: ["status", "context"],
3955
+ description: "Read integration status or bounded memory context"
3956
+ },
3957
+ mode: {
3958
+ type: "string",
3959
+ enum: ["topic", "continuity", "resume"],
3960
+ description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
3961
+ },
3962
+ query: {
3963
+ type: "string",
3964
+ description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
3965
+ },
3966
+ project_uid: { type: "string", description: "Optional project scope" },
3967
+ task_uid: { type: "string", description: "Optional task scope for resume context" },
3968
+ session_uid: { type: "string", description: "Optional durable session scope" },
3969
+ scope_uids: {
3970
+ type: "array",
3971
+ items: { type: "string" },
3972
+ description: "Optional explicit graph scopes for continuity/resume"
3973
+ },
3974
+ node_types: {
3975
+ type: "array",
3976
+ items: { type: "string" },
3977
+ description: "Optional node-type filter for topic context"
3978
+ },
3979
+ layer: {
3980
+ type: "string",
3981
+ enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
3982
+ description: "Optional cognitive-layer filter for topic context"
3983
+ },
3984
+ limit: {
3985
+ type: "integer",
3986
+ minimum: 1,
3987
+ maximum: 50,
3988
+ default: 8,
3989
+ description: "Maximum direct graph items requested before bounded expansion"
3990
+ },
3991
+ include_chunks: {
3992
+ type: "boolean",
3993
+ default: false,
3994
+ description: "Include bounded source excerpts when topic summaries are insufficient"
3995
+ },
3996
+ include_documents: {
3997
+ type: "boolean",
3998
+ default: false,
3999
+ description: "Include bounded knowledge articles for topic context"
4000
+ },
4001
+ valid_at: {
4002
+ type: "string",
4003
+ description: "Optional ISO timestamp for time-aware topic retrieval"
4004
+ },
4005
+ graph_expansion_limit: {
4006
+ type: "integer",
4007
+ minimum: 0,
4008
+ maximum: 20,
4009
+ default: 3,
4010
+ description: "Bounded graph neighbors added after direct topic matches"
4011
+ },
4012
+ graph_max_depth: {
4013
+ type: "integer",
4014
+ minimum: 0,
4015
+ maximum: 5,
4016
+ default: 2,
4017
+ description: "Maximum graph-expansion depth for topic context"
4018
+ },
4019
+ max_output_chars: {
4020
+ type: "integer",
4021
+ minimum: MIN_OUTPUT_CHARS,
4022
+ maximum: MAX_OUTPUT_CHARS,
4023
+ default: DEFAULT_OUTPUT_CHARS,
4024
+ description: "Hard character budget for model-ready context; whole items are retained or omitted"
4025
+ },
4026
+ agent_id: { type: "string", description: "Agent identity" }
4027
+ },
4028
+ required: ["action"]
4029
+ },
4030
+ outputSchema: {
4031
+ type: "object",
4032
+ properties: {
4033
+ schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
4034
+ action: { type: "string", enum: ["status", "context"] },
4035
+ profile: { type: "string" },
4036
+ server: { type: "object" },
4037
+ integration: { type: "object" },
4038
+ capabilities: { type: "object" },
4039
+ degradation: { type: "array", items: { type: "object" } },
4040
+ context_id: { type: "string" },
4041
+ mode: { type: "string", enum: ["topic", "continuity", "resume"] },
4042
+ scope: { type: "object" },
4043
+ rendered_context: { type: "string" },
4044
+ rendered_hash: { type: "string" },
4045
+ items: { type: "array", items: { type: "object" } },
4046
+ retrieval_trace: { type: "object" },
4047
+ budget: { type: "object" },
4048
+ freshness: { type: "object" },
4049
+ warnings: { type: "array", items: { type: "object" } }
4050
+ },
4051
+ required: ["schema_version", "action"],
4052
+ oneOf: [
4053
+ {
4054
+ properties: { action: { const: "status" } },
4055
+ required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
4056
+ },
4057
+ {
4058
+ properties: { action: { const: "context" } },
4059
+ required: [
4060
+ "context_id",
4061
+ "mode",
4062
+ "scope",
4063
+ "rendered_context",
4064
+ "rendered_hash",
4065
+ "items",
4066
+ "retrieval_trace",
4067
+ "budget",
4068
+ "freshness",
4069
+ "warnings"
4070
+ ]
4071
+ }
4072
+ ]
4073
+ },
4074
+ annotations: {
4075
+ readOnlyHint: true,
4076
+ destructiveHint: false,
4077
+ idempotentHint: true,
4078
+ openWorldHint: false
4079
+ }
4080
+ };
4081
+ }
4082
+ });
4083
+
3323
4084
  // src/tools.ts
4085
+ function stableJson2(value) {
4086
+ if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
4087
+ if (value && typeof value === "object") {
4088
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
4089
+ }
4090
+ return JSON.stringify(value);
4091
+ }
4092
+ function defaultWriteIdempotencyKey(tool, args) {
4093
+ if (typeof args.idempotency_key === "string") return args.idempotency_key;
4094
+ const context = args.invocation_context;
4095
+ const sessionId = context?.harnessSessionId;
4096
+ if (typeof sessionId !== "string" || sessionId.length === 0) return void 0;
4097
+ const canonicalArgs = Object.fromEntries(
4098
+ Object.entries(args).filter(
4099
+ ([key]) => key !== "invocation_context" && key !== "idempotency_key"
4100
+ )
4101
+ );
4102
+ return (0, import_node_crypto8.createHash)("sha256").update(
4103
+ stableJson2({
4104
+ harness_session_id: sessionId,
4105
+ harness_turn_id: context?.harnessTurnId ?? null,
4106
+ tool,
4107
+ action: args.action ?? null,
4108
+ args: canonicalArgs
4109
+ })
4110
+ ).digest("hex");
4111
+ }
4112
+ function resolutionFields(args) {
4113
+ return Object.fromEntries(
4114
+ [
4115
+ "idempotency_key",
4116
+ "reuse_uid",
4117
+ "supersedes_uid",
4118
+ "contradicts_uid",
4119
+ "confirm_new",
4120
+ "on_near_duplicate"
4121
+ ].filter((key) => args[key] !== void 0).map((key) => [key, args[key]])
4122
+ );
4123
+ }
3324
4124
  function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
3325
4125
  if (profile === "coding") return TOOLS;
3326
4126
  return TOOLS.filter(
@@ -3334,8 +4134,8 @@ function ok3(data) {
3334
4134
  }
3335
4135
  function renderedResult(format, raw, render) {
3336
4136
  if (format === "json") return ok3(raw);
3337
- const text = render(raw);
3338
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
4137
+ const text2 = render(raw);
4138
+ return text2 !== void 0 ? { content: [{ type: "text", text: text2 }] } : ok3(raw);
3339
4139
  }
3340
4140
  function err3(message) {
3341
4141
  return {
@@ -3343,8 +4143,22 @@ function err3(message) {
3343
4143
  isError: true
3344
4144
  };
3345
4145
  }
3346
- async function handleTool(client2, name, args) {
4146
+ async function handleTool(client2, name, args, memoryConfig = {}) {
3347
4147
  try {
4148
+ if (["mindgraph_capture", "mindgraph_reason", "mindgraph_commit"].includes(name) && args.action !== "get_open_decisions") {
4149
+ const key = defaultWriteIdempotencyKey(name, args);
4150
+ if (key) args = { ...args, idempotency_key: key };
4151
+ const dispositions = [
4152
+ args.reuse_uid,
4153
+ args.supersedes_uid,
4154
+ args.contradicts_uid,
4155
+ args.confirm_new === true ? true : void 0,
4156
+ args.on_near_duplicate
4157
+ ].filter((value) => value !== void 0);
4158
+ if (dispositions.length > 1) {
4159
+ return err3("resolution dispositions are mutually exclusive");
4160
+ }
4161
+ }
3348
4162
  let result;
3349
4163
  switch (name) {
3350
4164
  case "mindgraph_capture":
@@ -3362,6 +4176,12 @@ async function handleTool(client2, name, args) {
3362
4176
  case "mindgraph_retrieve":
3363
4177
  result = await handleRetrieve(client2, args);
3364
4178
  break;
4179
+ case "mindgraph_memory":
4180
+ result = await handleMemoryTool(client2, args, memoryConfig);
4181
+ break;
4182
+ case "mindgraph_series_query":
4183
+ result = await handleSeriesQuery(client2, args);
4184
+ break;
3365
4185
  case "mindgraph_ingest":
3366
4186
  result = await handleIngest(client2, args);
3367
4187
  break;
@@ -3406,17 +4226,45 @@ async function handleTool(client2, name, args) {
3406
4226
  return err3(errorDetail(e));
3407
4227
  }
3408
4228
  }
4229
+ async function handleSeriesQuery(client2, args) {
4230
+ const action = typeof args.action === "string" ? args.action : "";
4231
+ if (!["list_for_entity", "latest", "window", "aggregate"].includes(action)) {
4232
+ return err3(`Unknown series query action: ${action}`);
4233
+ }
4234
+ if (action === "list_for_entity" && typeof args.entity_uid !== "string") {
4235
+ return err3("entity_uid is required for action='list_for_entity'");
4236
+ }
4237
+ if (action !== "list_for_entity" && typeof args.series_uid !== "string") {
4238
+ return err3(`series_uid is required for action='${action}'`);
4239
+ }
4240
+ if ((action === "window" || action === "aggregate") && (typeof args.from !== "number" || typeof args.to !== "number")) {
4241
+ return err3(`from and to are required for action='${action}'`);
4242
+ }
4243
+ if (action === "aggregate" && (typeof args.bucket !== "string" || typeof args.agg !== "string")) {
4244
+ return err3("bucket and agg are required for action='aggregate'");
4245
+ }
4246
+ return ok3(
4247
+ await client2.series(args)
4248
+ );
4249
+ }
3409
4250
  async function handleCapture(client2, args) {
3410
4251
  const {
3411
4252
  action,
3412
4253
  label,
3413
4254
  summary,
3414
4255
  content,
4256
+ name,
4257
+ description,
4258
+ license,
3415
4259
  session_uid,
3416
4260
  work_uid,
3417
4261
  execution_uid,
3418
4262
  idempotency_key,
4263
+ reuse_uid,
3419
4264
  supersedes_uid,
4265
+ contradicts_uid,
4266
+ confirm_new,
4267
+ on_near_duplicate,
3420
4268
  summarizes_uids,
3421
4269
  entity_type,
3422
4270
  source_uid,
@@ -3440,38 +4288,82 @@ async function handleCapture(client2, args) {
3440
4288
  work_uid,
3441
4289
  execution_uid,
3442
4290
  idempotency_key,
4291
+ reuse_uid,
3443
4292
  supersedes_uid,
4293
+ contradicts_uid,
4294
+ confirm_new,
4295
+ on_near_duplicate,
3444
4296
  summarizes_uids,
3445
4297
  props,
3446
4298
  agent_id
3447
4299
  })
3448
4300
  );
4301
+ case "skill": {
4302
+ if (!name) return err3("name is required for action=skill");
4303
+ if (!description) return err3("description is required for action=skill");
4304
+ if (content === void 0) return err3("content is required for action=skill");
4305
+ const hasProvenance = [session_uid, work_uid, execution_uid].some(
4306
+ (uid) => typeof uid === "string" && uid.length > 0
4307
+ ) || Array.isArray(summarizes_uids) && summarizes_uids.some((uid) => typeof uid === "string" && uid.length > 0);
4308
+ if (!hasProvenance) {
4309
+ return err3(
4310
+ "at least one of session_uid, work_uid, execution_uid, or summarizes_uids is required for action=skill"
4311
+ );
4312
+ }
4313
+ return ok3(
4314
+ await client2.distill({
4315
+ label,
4316
+ output_type: "skill",
4317
+ summary,
4318
+ confidence,
4319
+ salience,
4320
+ session_uid,
4321
+ work_uid,
4322
+ execution_uid,
4323
+ idempotency_key,
4324
+ supersedes_uid,
4325
+ summarizes_uids,
4326
+ props: {
4327
+ name,
4328
+ description,
4329
+ content,
4330
+ ...license ? { license } : {}
4331
+ },
4332
+ agent_id
4333
+ })
4334
+ );
4335
+ }
3449
4336
  case "journal": {
3450
4337
  if (!content) return err3("content is required for action=journal");
3451
4338
  const journalProps = { content };
3452
4339
  if (mood) journalProps.mood = mood;
3453
4340
  if (tags) journalProps.tags = tags;
3454
- return ok3(await client2.journal(label, journalProps, { agent_id }));
4341
+ return ok3(await client2.journal(label, journalProps, {
4342
+ agent_id,
4343
+ idempotency_key,
4344
+ reuse_uid,
4345
+ supersedes_uid,
4346
+ contradicts_uid,
4347
+ confirm_new,
4348
+ on_near_duplicate
4349
+ }));
3455
4350
  }
3456
4351
  case "entity": {
3457
4352
  const type = entity_type || "concept";
3458
4353
  const entityProps = { ...props };
3459
4354
  if (summary) entityProps.description = summary;
3460
- switch (type) {
3461
- case "person":
3462
- return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
3463
- case "organization":
3464
- return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
3465
- case "nation":
3466
- return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
3467
- case "event":
3468
- return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
3469
- case "place":
3470
- return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
3471
- case "concept":
3472
- default:
3473
- return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
3474
- }
4355
+ return ok3(await client2.entity({
4356
+ action: "create",
4357
+ label,
4358
+ props: { ...entityProps, entity_type: type },
4359
+ agent_id,
4360
+ idempotency_key,
4361
+ reuse_uid,
4362
+ supersedes_uid,
4363
+ contradicts_uid,
4364
+ confirm_new,
4365
+ on_near_duplicate
4366
+ }));
3475
4367
  }
3476
4368
  case "observation":
3477
4369
  return ok3(
@@ -3482,7 +4374,13 @@ async function handleCapture(client2, args) {
3482
4374
  confidence,
3483
4375
  salience,
3484
4376
  props,
3485
- agent_id
4377
+ agent_id,
4378
+ idempotency_key,
4379
+ reuse_uid,
4380
+ supersedes_uid,
4381
+ contradicts_uid,
4382
+ confirm_new,
4383
+ on_near_duplicate
3486
4384
  })
3487
4385
  );
3488
4386
  case "source":
@@ -3494,7 +4392,13 @@ async function handleCapture(client2, args) {
3494
4392
  confidence,
3495
4393
  salience,
3496
4394
  props,
3497
- agent_id
4395
+ agent_id,
4396
+ idempotency_key,
4397
+ reuse_uid,
4398
+ supersedes_uid,
4399
+ contradicts_uid,
4400
+ confirm_new,
4401
+ on_near_duplicate
3498
4402
  })
3499
4403
  );
3500
4404
  case "snippet":
@@ -3507,26 +4411,38 @@ async function handleCapture(client2, args) {
3507
4411
  confidence,
3508
4412
  salience,
3509
4413
  props,
3510
- agent_id
4414
+ agent_id,
4415
+ idempotency_key,
4416
+ reuse_uid,
4417
+ supersedes_uid,
4418
+ contradicts_uid,
4419
+ confirm_new,
4420
+ on_near_duplicate
3511
4421
  })
3512
4422
  );
3513
4423
  case "concept":
3514
- return ok3(
3515
- await client2.structure({
3516
- action: "concept",
3517
- label,
3518
- summary,
3519
- confidence,
3520
- salience,
3521
- props,
3522
- agent_id
3523
- })
3524
- );
4424
+ return ok3(await client2.entity({
4425
+ action: "create",
4426
+ label,
4427
+ props: {
4428
+ ...props,
4429
+ entity_type: "concept",
4430
+ ...summary ? { description: summary } : {}
4431
+ },
4432
+ agent_id,
4433
+ idempotency_key,
4434
+ reuse_uid,
4435
+ supersedes_uid,
4436
+ contradicts_uid,
4437
+ confirm_new,
4438
+ on_near_duplicate
4439
+ }));
3525
4440
  default:
3526
4441
  return err3(`Unknown capture action: ${action}`);
3527
4442
  }
3528
4443
  }
3529
4444
  async function handleReason(client2, args) {
4445
+ const resolution = resolutionFields(args);
3530
4446
  const {
3531
4447
  action,
3532
4448
  label,
@@ -3559,7 +4475,8 @@ async function handleReason(client2, args) {
3559
4475
  ...warrant.content ? { props: { content: warrant.content } } : {}
3560
4476
  } : void 0,
3561
4477
  argument,
3562
- agent_id
4478
+ agent_id,
4479
+ ...resolution
3563
4480
  })
3564
4481
  );
3565
4482
  }
@@ -3572,11 +4489,13 @@ async function handleReason(client2, args) {
3572
4489
  confidence,
3573
4490
  salience,
3574
4491
  props,
3575
- agent_id
4492
+ agent_id,
4493
+ ...resolution
3576
4494
  })
3577
4495
  );
3578
4496
  }
3579
4497
  async function handleCommit(client2, args) {
4498
+ const resolution = resolutionFields(args);
3580
4499
  const {
3581
4500
  action,
3582
4501
  label,
@@ -3607,16 +4526,17 @@ async function handleCommit(client2, args) {
3607
4526
  confidence,
3608
4527
  salience,
3609
4528
  props,
3610
- agent_id
4529
+ agent_id,
4530
+ ...resolution
3611
4531
  })
3612
4532
  );
3613
4533
  case "open_decision":
3614
4534
  if (!label) return err3("label is required for open_decision");
3615
- return ok3(await client2.openDecision(label, { summary, props, agent_id }));
4535
+ return ok3(await client2.openDecision(label, { summary, props, agent_id, ...resolution }));
3616
4536
  case "add_option":
3617
4537
  if (!decision_uid) return err3("decision_uid is required for add_option");
3618
4538
  if (!label) return err3("label is required for add_option");
3619
- return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
4539
+ return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id, ...resolution }));
3620
4540
  case "add_constraint":
3621
4541
  if (!decision_uid) return err3("decision_uid is required for add_constraint");
3622
4542
  return ok3(
@@ -3626,7 +4546,8 @@ async function handleCommit(client2, args) {
3626
4546
  decision_uid,
3627
4547
  summary,
3628
4548
  props,
3629
- agent_id
4549
+ agent_id,
4550
+ ...resolution
3630
4551
  })
3631
4552
  );
3632
4553
  case "resolve_decision":
@@ -3640,7 +4561,8 @@ async function handleCommit(client2, args) {
3640
4561
  as_of_date,
3641
4562
  session_id,
3642
4563
  retrieval_trace_id,
3643
- agent_id
4564
+ agent_id,
4565
+ ...resolution
3644
4566
  })
3645
4567
  );
3646
4568
  case "get_open_decisions":
@@ -3721,7 +4643,7 @@ async function handlePlan(client2, args) {
3721
4643
  output_snapshot,
3722
4644
  side_effects,
3723
4645
  outcome,
3724
- error,
4646
+ error: error2,
3725
4647
  next_action,
3726
4648
  checkpoint_summary,
3727
4649
  test_summary,
@@ -3784,7 +4706,7 @@ async function handlePlan(client2, args) {
3784
4706
  output_snapshot,
3785
4707
  side_effects,
3786
4708
  outcome,
3787
- error,
4709
+ error: error2,
3788
4710
  next_action,
3789
4711
  checkpoint_summary,
3790
4712
  test_summary,
@@ -3833,7 +4755,7 @@ async function handlePlan(client2, args) {
3833
4755
  output_snapshot,
3834
4756
  side_effects,
3835
4757
  outcome,
3836
- error,
4758
+ error: error2,
3837
4759
  props,
3838
4760
  agent_id
3839
4761
  })
@@ -3919,6 +4841,7 @@ async function handleRetrieve(client2, args) {
3919
4841
  const {
3920
4842
  action,
3921
4843
  format,
4844
+ max_output_chars,
3922
4845
  query,
3923
4846
  start_uid,
3924
4847
  end_uid,
@@ -3973,7 +4896,7 @@ async function handleRetrieve(client2, args) {
3973
4896
  graph_expansion_limit: graph_expansion_limit ?? 3,
3974
4897
  graph_max_depth: graph_max_depth ?? 2
3975
4898
  }),
3976
- renderContext
4899
+ (raw) => renderContext(raw, max_output_chars)
3977
4900
  );
3978
4901
  case "text":
3979
4902
  if (!query) return err3("query is required for text search");
@@ -3987,7 +4910,7 @@ async function handleRetrieve(client2, args) {
3987
4910
  explain,
3988
4911
  agent_id
3989
4912
  }),
3990
- (raw) => renderNodeList(raw, `Text search: ${query}`)
4913
+ (raw) => renderNodeList(raw, `Text search: ${query}`, void 0, max_output_chars)
3991
4914
  );
3992
4915
  case "semantic":
3993
4916
  if (!query) return err3("query is required for semantic search");
@@ -4001,7 +4924,7 @@ async function handleRetrieve(client2, args) {
4001
4924
  explain,
4002
4925
  agent_id
4003
4926
  }),
4004
- (raw) => renderNodeList(raw, `Semantic search: ${query}`)
4927
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`, void 0, max_output_chars)
4005
4928
  );
4006
4929
  case "hybrid":
4007
4930
  if (!query) return err3("query is required for hybrid search");
@@ -4016,7 +4939,7 @@ async function handleRetrieve(client2, args) {
4016
4939
  agent_id
4017
4940
  }),
4018
4941
  // With explain, per-leg scoring must survive — raw JSON only.
4019
- explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
4942
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`, void 0, max_output_chars)
4020
4943
  );
4021
4944
  // Structured queries. These go through the generic /retrieve action
4022
4945
  // dispatch rather than the SDK's no-argument convenience methods — the
@@ -4034,7 +4957,7 @@ async function handleRetrieve(client2, args) {
4034
4957
  offset,
4035
4958
  agent_id
4036
4959
  }),
4037
- (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
4960
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit, max_output_chars)
4038
4961
  );
4039
4962
  case "stale_derivations":
4040
4963
  return rendered2(
@@ -4043,7 +4966,7 @@ async function handleRetrieve(client2, args) {
4043
4966
  limit,
4044
4967
  offset
4045
4968
  }),
4046
- (raw) => renderNodeList(raw, "stale derivations", limit)
4969
+ (raw) => renderNodeList(raw, "stale derivations", limit, max_output_chars)
4047
4970
  );
4048
4971
  case "preferences":
4049
4972
  return rendered2(
@@ -4053,7 +4976,7 @@ async function handleRetrieve(client2, args) {
4053
4976
  limit,
4054
4977
  agent_id
4055
4978
  }),
4056
- (raw) => renderNodeList(raw, "Preferences", limit)
4979
+ (raw) => renderNodeList(raw, "Preferences", limit, max_output_chars)
4057
4980
  );
4058
4981
  case "layer":
4059
4982
  if (!layer) return err3("layer is required for layer retrieval");
@@ -4065,7 +4988,7 @@ async function handleRetrieve(client2, args) {
4065
4988
  node_types,
4066
4989
  agent_id
4067
4990
  }),
4068
- (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
4991
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit, max_output_chars)
4069
4992
  );
4070
4993
  case "recent":
4071
4994
  return rendered2(
@@ -4079,7 +5002,7 @@ async function handleRetrieve(client2, args) {
4079
5002
  created_before,
4080
5003
  agent_id
4081
5004
  }),
4082
- (raw) => renderNodeList(raw, "Recently ingested", limit)
5005
+ (raw) => renderNodeList(raw, "Recently ingested", limit, max_output_chars)
4083
5006
  );
4084
5007
  // Document inventory
4085
5008
  case "document_index":
@@ -4088,7 +5011,7 @@ async function handleRetrieve(client2, args) {
4088
5011
  node_type: "Document",
4089
5012
  limit: limit ?? 100
4090
5013
  }),
4091
- (raw) => renderNodeList(raw, "Ingested documents")
5014
+ (raw) => renderNodeList(raw, "Ingested documents", void 0, max_output_chars)
4092
5015
  );
4093
5016
  // Graph traversal
4094
5017
  case "chain":
@@ -4102,7 +5025,7 @@ async function handleRetrieve(client2, args) {
4102
5025
  include_provenance,
4103
5026
  max_nodes
4104
5027
  }),
4105
- renderTraversal
5028
+ (raw) => renderTraversal(raw, max_output_chars)
4106
5029
  );
4107
5030
  case "neighborhood":
4108
5031
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
@@ -4117,7 +5040,7 @@ async function handleRetrieve(client2, args) {
4117
5040
  include_provenance,
4118
5041
  max_nodes
4119
5042
  }),
4120
- renderTraversal
5043
+ (raw) => renderTraversal(raw, max_output_chars)
4121
5044
  );
4122
5045
  case "path":
4123
5046
  if (!start_uid) return err3("start_uid is required for path traversal");
@@ -4134,7 +5057,7 @@ async function handleRetrieve(client2, args) {
4134
5057
  include_provenance,
4135
5058
  max_nodes
4136
5059
  }),
4137
- renderTraversal
5060
+ (raw) => renderTraversal(raw, max_output_chars)
4138
5061
  );
4139
5062
  case "subgraph":
4140
5063
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
@@ -4149,7 +5072,7 @@ async function handleRetrieve(client2, args) {
4149
5072
  include_provenance,
4150
5073
  max_nodes
4151
5074
  }),
4152
- renderTraversal
5075
+ (raw) => renderTraversal(raw, max_output_chars)
4153
5076
  );
4154
5077
  case "top_k_paths":
4155
5078
  if (!start_uid) return err3("start_uid is required for top_k_paths");
@@ -4165,7 +5088,7 @@ async function handleRetrieve(client2, args) {
4165
5088
  edge_types,
4166
5089
  exclude_edge_types
4167
5090
  }),
4168
- renderTraversal
5091
+ (raw) => renderTraversal(raw, max_output_chars)
4169
5092
  );
4170
5093
  default:
4171
5094
  return err3(`Unknown retrieve action: ${action}`);
@@ -4421,19 +5344,48 @@ async function handleOntology(client2, args) {
4421
5344
  return err3(`Unknown ontology action: ${action}`);
4422
5345
  }
4423
5346
  }
4424
- var TOOLS, INVOCATION_CONTEXT_SCHEMA;
5347
+ var import_node_crypto8, RESOLUTION_PROPERTIES, TOOLS, INVOCATION_CONTEXT_SCHEMA;
4425
5348
  var init_tools = __esm({
4426
5349
  "src/tools.ts"() {
4427
5350
  "use strict";
5351
+ import_node_crypto8 = require("crypto");
4428
5352
  init_error_detail();
4429
5353
  init_code_tool();
4430
5354
  init_codegraph();
4431
5355
  init_sync_tool();
5356
+ init_memory_tool();
4432
5357
  init_render();
5358
+ RESOLUTION_PROPERTIES = {
5359
+ idempotency_key: {
5360
+ type: "string",
5361
+ description: "Stable retry key; a mismatched replay is rejected"
5362
+ },
5363
+ reuse_uid: {
5364
+ type: "string",
5365
+ description: "Reuse this live same-scope node after a duplicate response"
5366
+ },
5367
+ supersedes_uid: {
5368
+ type: "string",
5369
+ description: "Create and mark this same-scope node as superseded"
5370
+ },
5371
+ contradicts_uid: {
5372
+ type: "string",
5373
+ description: "Create and mark this same-scope node as contradicted"
5374
+ },
5375
+ confirm_new: {
5376
+ type: "boolean",
5377
+ description: "Confirm that the new fact is genuinely distinct"
5378
+ },
5379
+ on_near_duplicate: {
5380
+ type: "string",
5381
+ enum: ["create"],
5382
+ description: "Alias for confirm_new on a near-duplicate response"
5383
+ }
5384
+ };
4433
5385
  TOOLS = [
4434
5386
  {
4435
5387
  name: "mindgraph_capture",
4436
- 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 and 'lesson' for a durable learning attributable to a session or source nodes. Prefer 'entity'/'observation' for objective facts, 'journal' for informal notes, and 'lesson' only for reusable knowledge.",
5388
+ 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.",
4437
5389
  inputSchema: {
4438
5390
  type: "object",
4439
5391
  properties: {
@@ -4446,7 +5398,8 @@ var init_tools = __esm({
4446
5398
  "snippet",
4447
5399
  "concept",
4448
5400
  "journal",
4449
- "lesson"
5401
+ "lesson",
5402
+ "skill"
4450
5403
  ],
4451
5404
  description: "Type of knowledge to capture"
4452
5405
  },
@@ -4460,11 +5413,30 @@ var init_tools = __esm({
4460
5413
  },
4461
5414
  content: {
4462
5415
  type: "string",
4463
- description: "Journal content/body text (for action=journal)"
5416
+ maxLength: 65536,
5417
+ description: "Journal body (action=journal) or complete SKILL.md instructions, at most 64 KiB UTF-8 (action=skill)"
5418
+ },
5419
+ name: {
5420
+ type: "string",
5421
+ minLength: 1,
5422
+ maxLength: 64,
5423
+ pattern: "^(?!.*(?:anthropic|claude))(?!-)(?!.*--)[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
5424
+ description: "Stable skill identifier using lowercase a-z, digits, and hyphens (for action=skill)"
5425
+ },
5426
+ description: {
5427
+ type: "string",
5428
+ minLength: 1,
5429
+ maxLength: 1024,
5430
+ description: "When the skill should be used; XML tags are rejected (for action=skill)"
5431
+ },
5432
+ license: {
5433
+ type: "string",
5434
+ description: "Optional skill license (for action=skill)"
4464
5435
  },
4465
5436
  session_uid: {
4466
5437
  type: "string",
4467
- description: "Session UID that produced a lesson (for action=lesson)"
5438
+ minLength: 1,
5439
+ description: "Session UID that produced a lesson or skill"
4468
5440
  },
4469
5441
  work_uid: {
4470
5442
  type: "string",
@@ -4474,18 +5446,12 @@ var init_tools = __esm({
4474
5446
  type: "string",
4475
5447
  description: "Material Execution that produced the lesson"
4476
5448
  },
4477
- idempotency_key: {
4478
- type: "string",
4479
- description: "Stable retry key for an intentional capture"
4480
- },
4481
- supersedes_uid: {
4482
- type: "string",
4483
- description: "Earlier capture corrected or superseded by this one"
4484
- },
5449
+ ...RESOLUTION_PROPERTIES,
4485
5450
  summarizes_uids: {
4486
5451
  type: "array",
4487
5452
  items: { type: "string" },
4488
- description: "Source node UIDs the lesson was learned from (for action=lesson)"
5453
+ minItems: 1,
5454
+ description: "Source node UIDs the lesson or skill was learned from"
4489
5455
  },
4490
5456
  entity_type: {
4491
5457
  type: "string",
@@ -4552,7 +5518,24 @@ var init_tools = __esm({
4552
5518
  description: "Agent identity"
4553
5519
  }
4554
5520
  },
4555
- required: ["action", "label"]
5521
+ required: ["action", "label"],
5522
+ allOf: [
5523
+ {
5524
+ if: {
5525
+ properties: { action: { const: "skill" } },
5526
+ required: ["action"]
5527
+ },
5528
+ then: {
5529
+ required: ["name", "description", "content"],
5530
+ anyOf: [
5531
+ { required: ["session_uid"] },
5532
+ { required: ["work_uid"] },
5533
+ { required: ["execution_uid"] },
5534
+ { required: ["summarizes_uids"] }
5535
+ ]
5536
+ }
5537
+ }
5538
+ ]
4556
5539
  }
4557
5540
  },
4558
5541
  {
@@ -4561,6 +5544,7 @@ var init_tools = __esm({
4561
5544
  inputSchema: {
4562
5545
  type: "object",
4563
5546
  properties: {
5547
+ ...RESOLUTION_PROPERTIES,
4564
5548
  action: {
4565
5549
  type: "string",
4566
5550
  enum: [
@@ -4655,6 +5639,7 @@ var init_tools = __esm({
4655
5639
  inputSchema: {
4656
5640
  type: "object",
4657
5641
  properties: {
5642
+ ...RESOLUTION_PROPERTIES,
4658
5643
  action: {
4659
5644
  type: "string",
4660
5645
  enum: [
@@ -5013,6 +5998,12 @@ var init_tools = __esm({
5013
5998
  enum: ["text", "json"],
5014
5999
  description: "Output format (default 'text'): a compact rendered context block \u2014 labels, uids, summaries, status tags, relationships. Pass 'json' for the raw server response (full props, source-chunk offsets, retrieval metadata)."
5015
6000
  },
6001
+ max_output_chars: {
6002
+ type: "integer",
6003
+ minimum: 512,
6004
+ maximum: 24e3,
6005
+ description: "Hard character budget for rendered text output (512-24000; default 24000). Normal per-field clipping still applies. Whole evidence items are then kept or omitted rather than cut to satisfy this budget; the final marker reports omissions. Ignored when format='json'."
6006
+ },
5016
6007
  query: {
5017
6008
  type: "string",
5018
6009
  description: "Search query \u2014 for 'text'/'context': 1-3 BM25 keywords, drop filler words. For 'semantic'/'hybrid': natural language is OK since it uses vector similarity."
@@ -5138,6 +6129,42 @@ var init_tools = __esm({
5138
6129
  required: ["action"]
5139
6130
  }
5140
6131
  },
6132
+ {
6133
+ name: "mindgraph_series_query",
6134
+ 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.",
6135
+ inputSchema: {
6136
+ type: "object",
6137
+ additionalProperties: false,
6138
+ properties: {
6139
+ action: {
6140
+ type: "string",
6141
+ enum: ["list_for_entity", "latest", "window", "aggregate"]
6142
+ },
6143
+ entity_uid: { type: "string" },
6144
+ series_uid: { type: "string" },
6145
+ from: { type: "number" },
6146
+ to: { type: "number" },
6147
+ cursor: { type: "number" },
6148
+ limit: { type: "number", minimum: 1, maximum: 1e4 },
6149
+ bucket: {
6150
+ type: "string",
6151
+ enum: ["year", "quarter", "month", "week", "day", "hour"]
6152
+ },
6153
+ agg: {
6154
+ type: "string",
6155
+ enum: ["sum", "mean", "min", "max", "count", "std_dev", "last"]
6156
+ },
6157
+ fill: { type: "string", enum: ["none", "null", "prev"] }
6158
+ },
6159
+ required: ["action"]
6160
+ },
6161
+ annotations: {
6162
+ readOnlyHint: true,
6163
+ destructiveHint: false,
6164
+ idempotentHint: true,
6165
+ openWorldHint: false
6166
+ }
6167
+ },
5141
6168
  {
5142
6169
  name: "mindgraph_ingest",
5143
6170
  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.",
@@ -5403,6 +6430,7 @@ var init_tools = __esm({
5403
6430
  required: ["action"]
5404
6431
  }
5405
6432
  },
6433
+ MEMORY_TOOL,
5406
6434
  SYNC_TOOL,
5407
6435
  CODE_TOOL
5408
6436
  ];
@@ -5423,7 +6451,7 @@ var init_tools = __esm({
5423
6451
  }
5424
6452
  };
5425
6453
  for (const tool of TOOLS) {
5426
- if (tool.name === "mindgraph_retrieve") continue;
6454
+ if (tool.name === "mindgraph_retrieve" || tool.name === "mindgraph_memory") continue;
5427
6455
  const schema = tool.inputSchema;
5428
6456
  if (schema.properties && !schema.properties.invocation_context) {
5429
6457
  schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
@@ -5477,8 +6505,8 @@ async function getGeneratedTools(client2) {
5477
6505
  cache = { tools, byName, fetchedAt: Date.now() };
5478
6506
  retryNotBefore = 0;
5479
6507
  return { tools, byName };
5480
- } catch (error) {
5481
- if (error instanceof DuplicateGeneratedToolError) throw error;
6508
+ } catch (error2) {
6509
+ if (error2 instanceof DuplicateGeneratedToolError) throw error2;
5482
6510
  retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
5483
6511
  return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
5484
6512
  }
@@ -5591,8 +6619,8 @@ var init_generated_tools = __esm({
5591
6619
  });
5592
6620
  rendered = (format, raw, render) => {
5593
6621
  if (format === "json") return ok4(raw);
5594
- const text = render(raw);
5595
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok4(raw);
6622
+ const text2 = render(raw);
6623
+ return text2 !== void 0 ? { content: [{ type: "text", text: text2 }] } : ok4(raw);
5596
6624
  };
5597
6625
  err4 = (message) => ({
5598
6626
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
@@ -5624,7 +6652,7 @@ function isTransientStatus(status) {
5624
6652
  return status === 408 || status === 425 || status === 429 || status >= 500;
5625
6653
  }
5626
6654
  function sleep(ms) {
5627
- return new Promise((resolve2) => setTimeout(resolve2, ms));
6655
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
5628
6656
  }
5629
6657
  function headers(apiKey, orgId) {
5630
6658
  const value = {
@@ -5641,7 +6669,9 @@ function unsupportedResponse(status, body) {
5641
6669
  return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
5642
6670
  }
5643
6671
  function toolMutability(toolName, action) {
6672
+ if (toolName === "mindgraph_memory") return "read";
5644
6673
  if (toolName === "mindgraph_retrieve") return "read";
6674
+ if (toolName === "mindgraph_series_query") return "read";
5645
6675
  if (toolName === "mindgraph_plan") {
5646
6676
  return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
5647
6677
  }
@@ -5723,8 +6753,8 @@ async function checkMcpGovernance(toolName, args, config) {
5723
6753
  response = candidate;
5724
6754
  body = candidateBody;
5725
6755
  break;
5726
- } catch (error) {
5727
- lastFailure = error instanceof Error ? error.message : String(error);
6756
+ } catch (error2) {
6757
+ lastFailure = error2 instanceof Error ? error2.message : String(error2);
5728
6758
  if (attempt < retryDelays.length) {
5729
6759
  await sleep(retryDelays[attempt]);
5730
6760
  continue;
@@ -5826,6 +6856,8 @@ var init_governance = __esm({
5826
6856
  UID_FIELDS = [
5827
6857
  "uid",
5828
6858
  "source_uid",
6859
+ "entity_uid",
6860
+ "series_uid",
5829
6861
  "session_uid",
5830
6862
  "work_uid",
5831
6863
  "goal_uid",
@@ -5867,8 +6899,10 @@ function json(uri, data) {
5867
6899
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
5868
6900
  };
5869
6901
  }
5870
- async function readResource(client2, uri) {
6902
+ async function readResource(client2, uri, memoryConfig = {}) {
5871
6903
  switch (uri) {
6904
+ case "mindgraph://memory/status":
6905
+ return json(uri, memoryStatus(memoryConfig));
5872
6906
  case "mindgraph://stats":
5873
6907
  return json(uri, await client2.stats());
5874
6908
  case "mindgraph://goals":
@@ -5900,23 +6934,32 @@ async function readResource(client2, uri) {
5900
6934
  }
5901
6935
  throw new Error(`Unknown resource: ${uri}`);
5902
6936
  }
5903
- async function readGovernedResource(client2, uri, governanceConfig) {
6937
+ async function readGovernedResource(client2, uri, governanceConfig, memoryConfig = {}) {
6938
+ const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
6939
+ const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
5904
6940
  const governance = await checkMcpGovernance(
5905
- RESOURCE_GOVERNANCE_TOOL,
5906
- {},
6941
+ toolName,
6942
+ toolArgs,
5907
6943
  { ...governanceConfig, context: { resource_uri: uri } }
5908
6944
  );
5909
6945
  if (!governance.allowed) {
5910
6946
  throw new Error(governance.message);
5911
6947
  }
5912
- return readResource(client2, uri);
6948
+ return readResource(client2, uri, memoryConfig);
5913
6949
  }
5914
6950
  var RESOURCES, RESOURCE_TEMPLATES, RESOURCE_GOVERNANCE_TOOL;
5915
6951
  var init_resources = __esm({
5916
6952
  "src/resources.ts"() {
5917
6953
  "use strict";
5918
6954
  init_governance();
6955
+ init_memory_tool();
5919
6956
  RESOURCES = [
6957
+ {
6958
+ uri: "mindgraph://memory/status",
6959
+ name: "Memory Integration Status",
6960
+ description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
6961
+ mimeType: "application/json"
6962
+ },
5920
6963
  {
5921
6964
  uri: "mindgraph://stats",
5922
6965
  name: "Graph Statistics",
@@ -5978,10 +7021,10 @@ async function main() {
5978
7021
  const transport = new import_stdio.StdioServerTransport();
5979
7022
  await server.connect(transport);
5980
7023
  console.error(
5981
- `MindGraph MCP server v${"0.18.0"} running on stdio (${BASE_URL})`
7024
+ `MindGraph MCP server v${"0.20.0"} running on stdio (${BASE_URL})`
5982
7025
  );
5983
7026
  }
5984
- var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, PROMPTS;
7027
+ 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, MEMORY_RUNTIME_CONFIG, PROMPTS;
5985
7028
  var init_src = __esm({
5986
7029
  "src/index.ts"() {
5987
7030
  "use strict";
@@ -6019,56 +7062,29 @@ var init_src = __esm({
6019
7062
  });
6020
7063
  INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
6021
7064
 
6022
- ## #1 RULE: Go straight to the user's topic
6023
-
6024
- When the user asks about something, your FIRST and ONLY action should be a SINGLE \`mindgraph_retrieve\` call with action "context" and keyword terms from their message. Nothing else first.
7065
+ ## Default memory workflow: orient \u2192 answer \u2192 preserve
6025
7066
 
6026
- **NEVER do any of these before addressing the user's topic:**
6027
- - Do NOT retrieve active_goals, open_questions, contradictions, or weak_claims
6028
- - Do NOT follow a "session lifecycle" or "warm-up" sequence
6029
- - Do NOT make multiple serial retrieve calls when one "context" call covers it
7067
+ **Orient only when prior durable state may matter.** Self-contained questions do not require a memory call or a warm-up sequence.
6030
7068
 
6031
- There is no session to open. There is no context to pre-load. Just search for what the user asked about.
7069
+ - Resuming work, asking what was in progress, or continuing a task: call \`mindgraph_memory\` once with action \`context\` and mode \`resume\` or \`continuity\`. This uses authoritative, task-first, lease-aware work state.
7070
+ - Asking about a topic that may depend on prior knowledge: call \`mindgraph_memory\` once with action \`context\`, mode \`topic\`, and 1\u20133 discriminating keywords.
7071
+ - Asking about integration capability or automatic behavior: call \`mindgraph_memory\` with action \`status\`.
7072
+ - Use the comprehensive read tools below only when the high-level result is insufficient or the user asks for a specific graph operation.
6032
7073
 
6033
- **Example:** User says "Tell me about Indian philosophy and Taoism"
6034
- \u2713 CORRECT: \`mindgraph_retrieve({action: "context", query: "Indian philosophy Taoism"})\`
6035
- \u2717 WRONG: First retrieve active_goals, then open_questions, then search
6036
- \u2717 WRONG: \`mindgraph_retrieve({action: "context", query: "Tell me about Indian philosophy and Taoism"})\` \u2014 natural language won't match BM25
7074
+ **Answer from the bounded result.** Treat superseded, refuted, or out-of-validity items as warnings, not current facts. Retrieval proves only that the server returned context; it does not prove that a host automatically injected or used it.
6037
7075
 
6038
- ## Two-phase retrieval protocol
7076
+ **Preserve selectively after material durable change.** Use the existing typed write tool for a fact, correction, decision, work-state transition, or reusable lesson. Do not write ephemeral conversation, routine mechanics, unsupported guesses, or information already represented accurately. An explicit no-write is the correct outcome when nothing durable changed.
6039
7077
 
6040
- **Phase 1 \u2014 Orient (default, fast, lightweight):**
6041
- Use \`action: "context"\` with keywords. By default this returns only graph nodes \u2014 structured knowledge with labels, summaries, types, confidence scores, and edges. This is usually sufficient and keeps results compact.
7078
+ ## Topic retrieval details
6042
7079
 
6043
- **Phase 2 \u2014 Fetch source text (only when needed):**
6044
- If you need the original source text behind a node (for verbatim quotes, citations, or when graph summaries lack detail), use \`action: "context"\` with \`include_chunks: true\`. This adds full document chunks (400-800 words each) to the response.
7080
+ Topic context is graph-only by default. Set \`include_chunks: true\` only for source text needed for quotes, citations, or missing detail. Query with a few proper nouns or technical terms rather than a full sentence. If keyword retrieval fails, use \`mindgraph_retrieve\` with \`semantic\` or \`hybrid\`; use \`document_index\` to inspect ingested sources.
6045
7081
 
6046
- **Orientation shortcut:**
6047
- - \`action: "document_index"\` \u2014 list all ingested documents (titles, dates, UIDs) to see what's in the graph
7082
+ ## Comprehensive read tools
6048
7083
 
6049
- ## Search strategy \u2014 keyword first, semantic as fallback
6050
-
6051
- The default search uses BM25 keyword matching. Query with 1\u20133 discriminating terms (proper nouns, technical terms). Drop filler words.
6052
-
6053
- **Keyword examples:**
6054
- - "What is Kissinger's view on NATO?" \u2192 query: \`"Kissinger NATO"\`
6055
- - "Tell me about async in Rust" \u2192 query: \`"Rust async runtime"\`
6056
- - "Implications of carbon tax?" \u2192 query: \`"carbon tax"\`
6057
-
6058
- If keyword search returns no results, escalate:
6059
- 1. Try synonyms (e.g. \`"global warming"\` instead of \`"climate change"\`)
6060
- 2. Use \`action: "semantic"\` \u2014 vector similarity, natural language OK (e.g. \`"ways to manage stress"\` finds "mental health" nodes)
6061
- 3. Use \`action: "hybrid"\` \u2014 combines BM25 + semantic for best coverage
6062
-
6063
- ## When to READ
6064
-
6065
- - User asks about topic X \u2192 \`mindgraph_retrieve\` action "context", query "X keywords"
6066
- - User asks "what documents are in my graph?" \u2192 action "document_index"
6067
- - User explicitly asks "what are my goals?" \u2192 action "active_goals"
6068
- - User explicitly asks about open questions \u2192 action "open_questions"
6069
- - User asks for advice or a recommendation ("suggest a hotel", "what should I read?") \u2192 action "preferences", query "topic keywords" \u2014 tailor the answer to what they like
6070
- - Explore around a known node \u2192 action "neighborhood" with start_uid
6071
- - Find connections between nodes \u2192 action "path" with start_uid + end_uid
7084
+ - Explicit graph inventory or state questions \u2192 the matching \`mindgraph_retrieve\` action such as \`document_index\`, \`active_goals\`, or \`open_questions\`
7085
+ - Advice or recommendations \u2192 \`mindgraph_retrieve\` action \`preferences\`, with topic keywords
7086
+ - Explore around a known node \u2192 \`mindgraph_retrieve\` action \`neighborhood\` with start_uid
7087
+ - Find connections between nodes \u2192 \`mindgraph_retrieve\` action \`path\` with start_uid + end_uid
6072
7088
 
6073
7089
  ## When to WRITE
6074
7090
 
@@ -6103,13 +7119,14 @@ Capture knowledge when the user shares something worth remembering:
6103
7119
 
6104
7120
  ## Coding profile
6105
7121
 
6106
- **Recall order (overrides the #1 rule above for work and code questions):**
6107
- - "What was I working on?" / current work state / resuming anything: call \`mindgraph_plan\` action \`resume_work\` FIRST. Work state is structural; never answer it from keyword search ("recent work" famously retrieves people named Work).
7122
+ **Coding recall order:**
7123
+ - "What was I working on?" / current work state / resuming anything: call \`mindgraph_memory\` action \`context\`, mode \`resume\` FIRST. It delegates to structural \`resume_work\`; never answer work state from keyword search ("recent work" famously retrieves people named Work).
6108
7124
  - Knowledge about specific code (a function, file, or route): \`mindgraph_code\` action \`recall\` \u2014 anchor-based, not keyword-based.
6109
7125
  - Broad knowledge searches: \`mindgraph_retrieve\` as usual, but when searching for work artifacts pass node_types filters (Task, Lesson, RiskAssessment, Claim) to keep personal-graph content out of engineering answers.
6110
7126
 
6111
7127
  - Use mindgraph_sync for explicit, one-way project memory-file imports. It discovers and hashes files, but you deliberately map durable assertions with record; it never writes graph state back to files.
6112
7128
  - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
7129
+ - If a write returns \`near_duplicate\`, choose \`reuse_uid\` or \`supersedes_uid\`; use \`confirm_new\` only for genuinely distinct facts.
6113
7130
  - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
6114
7131
  - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
6115
7132
  } else {
@@ -6119,7 +7136,7 @@ Capture knowledge when the user shares something worth remembering:
6119
7136
  }
6120
7137
  RUNNER_REFRESH = (() => {
6121
7138
  try {
6122
- return refreshHookRunner("0.18.0", import_node_path6.default.join(__dirname, "cli.js"));
7139
+ return refreshHookRunner("0.20.0", import_node_path6.default.join(__dirname, "cli.js"));
6123
7140
  } catch {
6124
7141
  return void 0;
6125
7142
  }
@@ -6133,12 +7150,12 @@ Capture knowledge when the user shares something worth remembering:
6133
7150
  }
6134
7151
  })();
6135
7152
  console.error(
6136
- `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.18.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
7153
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.20.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6137
7154
  );
6138
7155
  }
6139
7156
  HOOK_SKEW_NOTE = (() => {
6140
7157
  try {
6141
- return versionSkewNote("0.18.0");
7158
+ return versionSkewNote("0.20.0");
6142
7159
  } catch {
6143
7160
  return void 0;
6144
7161
  }
@@ -6152,7 +7169,7 @@ ${HOOK_SKEW_NOTE}`;
6152
7169
  server = new import_server.Server(
6153
7170
  {
6154
7171
  name: "mindgraph",
6155
- version: "0.18.0"
7172
+ version: "0.20.0"
6156
7173
  },
6157
7174
  {
6158
7175
  capabilities: {
@@ -6176,6 +7193,14 @@ ${HOOK_SKEW_NOTE}`;
6176
7193
  return process.env.MINDGRAPH_GOVERNANCE === "off";
6177
7194
  }
6178
7195
  };
7196
+ MEMORY_RUNTIME_CONFIG = {
7197
+ serverVersion: "0.20.0",
7198
+ baseUrl: BASE_URL,
7199
+ profile: PROFILE,
7200
+ harness: HARNESS,
7201
+ orgId: ORG_ID,
7202
+ agentId: AGENT_ID
7203
+ };
6179
7204
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
6180
7205
  const { name, arguments: args } = request.params;
6181
7206
  const rawArgs = { ...args || {} };
@@ -6195,7 +7220,7 @@ ${HOOK_SKEW_NOTE}`;
6195
7220
  if (desc) {
6196
7221
  return handleGeneratedTool(client, desc, rawArgs);
6197
7222
  }
6198
- return handleTool(client, name, toolArgs);
7223
+ return handleTool(client, name, toolArgs, MEMORY_RUNTIME_CONFIG);
6199
7224
  });
6200
7225
  PROMPTS = [
6201
7226
  {
@@ -6360,18 +7385,23 @@ ${conversation}`
6360
7385
  return { resourceTemplates: RESOURCE_TEMPLATES };
6361
7386
  });
6362
7387
  server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
6363
- return readGovernedResource(client, request.params.uri, GOVERNANCE_CONFIG);
7388
+ return readGovernedResource(
7389
+ client,
7390
+ request.params.uri,
7391
+ GOVERNANCE_CONFIG,
7392
+ MEMORY_RUNTIME_CONFIG
7393
+ );
6364
7394
  });
6365
- main().catch((error) => {
6366
- console.error("Fatal error:", error);
7395
+ main().catch((error2) => {
7396
+ console.error("Fatal error:", error2);
6367
7397
  process.exit(1);
6368
7398
  });
6369
7399
  }
6370
7400
  });
6371
7401
 
6372
7402
  // src/cli.ts
6373
- var fs5 = __toESM(require("fs"));
6374
- var path6 = __toESM(require("path"));
7403
+ var fs6 = __toESM(require("fs"));
7404
+ var path7 = __toESM(require("path"));
6375
7405
  var os4 = __toESM(require("os"));
6376
7406
  var crypto5 = __toESM(require("crypto"));
6377
7407
  var import_child_process = require("child_process");
@@ -6389,6 +7419,8 @@ function parseArgs(argv) {
6389
7419
  let harness;
6390
7420
  let hooks = false;
6391
7421
  let agentId = process.env.MINDGRAPH_AGENT_ID;
7422
+ const positionals = [];
7423
+ let dir;
6392
7424
  for (let i = 0; i < args.length; i++) {
6393
7425
  const arg = args[i];
6394
7426
  switch (arg) {
@@ -6409,6 +7441,9 @@ function parseArgs(argv) {
6409
7441
  case "--project-dir":
6410
7442
  projectDir = args[++i];
6411
7443
  break;
7444
+ case "--dir":
7445
+ dir = args[++i];
7446
+ break;
6412
7447
  case "--harness":
6413
7448
  harness = args[++i];
6414
7449
  break;
@@ -6427,12 +7462,27 @@ function parseArgs(argv) {
6427
7462
  break;
6428
7463
  default:
6429
7464
  if (!arg.startsWith("-")) {
6430
- command2 = arg;
7465
+ if (command2 === "serve") {
7466
+ command2 = arg;
7467
+ } else {
7468
+ positionals.push(arg);
7469
+ }
6431
7470
  }
6432
7471
  break;
6433
7472
  }
6434
7473
  }
6435
- return { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
7474
+ return {
7475
+ command: command2,
7476
+ apiKey,
7477
+ baseUrl,
7478
+ scope,
7479
+ projectDir,
7480
+ harness,
7481
+ hooks,
7482
+ agentId,
7483
+ positionals,
7484
+ dir
7485
+ };
6436
7486
  }
6437
7487
  function mcpAddFailureOutput(e) {
6438
7488
  const err5 = e;
@@ -6446,8 +7496,375 @@ function classifyMcpAddFailure(e) {
6446
7496
  return "other";
6447
7497
  }
6448
7498
 
7499
+ // src/skills-cli.ts
7500
+ var import_node_crypto2 = require("crypto");
7501
+ var fs2 = __toESM(require("fs"));
7502
+ var path2 = __toESM(require("path"));
7503
+ function httpSkillsApi(baseUrl, apiKey, agentId) {
7504
+ const endpoint = `${baseUrl.replace(/\/$/, "")}/action/skill`;
7505
+ const call = async (body) => {
7506
+ const response = await fetch(endpoint, {
7507
+ method: "POST",
7508
+ headers: {
7509
+ "content-type": "application/json",
7510
+ authorization: `Bearer ${apiKey}`
7511
+ },
7512
+ body: JSON.stringify(
7513
+ agentId ? { agent_id: agentId, ...body } : body
7514
+ )
7515
+ });
7516
+ let parsed = {};
7517
+ try {
7518
+ parsed = await response.json();
7519
+ } catch {
7520
+ parsed = { error: `non-JSON response (HTTP ${response.status})` };
7521
+ }
7522
+ return { status: response.status, body: parsed };
7523
+ };
7524
+ return {
7525
+ async list(grantedTo) {
7526
+ const { status, body } = await call({
7527
+ action: "list",
7528
+ ...grantedTo ? { granted_to: grantedTo } : {}
7529
+ });
7530
+ if (status !== 200) {
7531
+ throw new Error(
7532
+ `skill list failed (HTTP ${status}): ${String(body.error ?? "")}`
7533
+ );
7534
+ }
7535
+ return body.skills ?? [];
7536
+ },
7537
+ async get(uid) {
7538
+ const { status, body } = await call({ action: "get", uid });
7539
+ if (status === 404) return null;
7540
+ if (status !== 200) {
7541
+ throw new Error(
7542
+ `skill get failed (HTTP ${status}): ${String(body.error ?? "")}`
7543
+ );
7544
+ }
7545
+ const props = body.props ?? {};
7546
+ return {
7547
+ uid: String(body.uid ?? uid),
7548
+ name: String(props.name ?? ""),
7549
+ description: String(props.description ?? ""),
7550
+ content: String(props.content ?? ""),
7551
+ license: props.license ?? null,
7552
+ status: String(props.status ?? "candidate"),
7553
+ status_reason: props.status_reason ?? null,
7554
+ content_hash: String(props.content_hash ?? ""),
7555
+ version: Number(body.version ?? 0)
7556
+ };
7557
+ },
7558
+ send: call
7559
+ };
7560
+ }
7561
+ function isValidSkillName(name) {
7562
+ return name.length >= 1 && name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
7563
+ }
7564
+ function renderSkillMd(skill) {
7565
+ const lines = [
7566
+ "---",
7567
+ `name: ${JSON.stringify(skill.name)}`,
7568
+ `description: ${JSON.stringify(skill.description)}`
7569
+ ];
7570
+ if (skill.license) {
7571
+ lines.push(`license: ${JSON.stringify(skill.license)}`);
7572
+ }
7573
+ lines.push(
7574
+ "metadata:",
7575
+ ` mindgraph-uid: ${JSON.stringify(skill.uid)}`,
7576
+ ` mindgraph-version: ${JSON.stringify(String(skill.version))}`,
7577
+ ` mindgraph-content-hash: ${JSON.stringify(skill.content_hash)}`,
7578
+ "---",
7579
+ "",
7580
+ skill.content
7581
+ );
7582
+ return `${lines.join("\n")}
7583
+ `;
7584
+ }
7585
+ function scalar(raw) {
7586
+ const trimmed = raw.trim();
7587
+ try {
7588
+ const parsed = JSON.parse(trimmed);
7589
+ if (typeof parsed === "string") return parsed;
7590
+ } catch {
7591
+ }
7592
+ return trimmed;
7593
+ }
7594
+ function parseSkillMd(text2) {
7595
+ if (!text2.startsWith("---\n")) return null;
7596
+ const end = text2.indexOf("\n---\n", 4);
7597
+ if (end < 0) return null;
7598
+ const frontmatter = text2.slice(4, end + 1);
7599
+ const body = text2.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
7600
+ const entries = [];
7601
+ for (const line of frontmatter.split("\n")) {
7602
+ const top = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
7603
+ if (top) {
7604
+ entries.push({ key: top[1], raw: top[2] });
7605
+ } else if (entries.length > 0) {
7606
+ entries[entries.length - 1].raw += `
7607
+ ${line}`;
7608
+ }
7609
+ }
7610
+ const result = { unknown: {}, body };
7611
+ for (const { key, raw } of entries) {
7612
+ switch (key) {
7613
+ case "name":
7614
+ result.name = scalar(raw);
7615
+ break;
7616
+ case "description":
7617
+ result.description = scalar(raw);
7618
+ break;
7619
+ case "license":
7620
+ result.license = scalar(raw);
7621
+ break;
7622
+ case "metadata": {
7623
+ const uid = /mindgraph-uid:\s*(.+)/.exec(raw);
7624
+ const version = /mindgraph-version:\s*(.+)/.exec(raw);
7625
+ const hash = /mindgraph-content-hash:\s*(.+)/.exec(raw);
7626
+ if (uid) {
7627
+ result.pin = {
7628
+ uid: scalar(uid[1]),
7629
+ version: version ? scalar(version[1]) : void 0,
7630
+ contentHash: hash ? scalar(hash[1]) : void 0
7631
+ };
7632
+ } else if (raw.trim().length > 0) {
7633
+ result.unknown[key] = raw;
7634
+ }
7635
+ break;
7636
+ }
7637
+ default:
7638
+ result.unknown[key] = raw;
7639
+ break;
7640
+ }
7641
+ }
7642
+ return result;
7643
+ }
7644
+ var STATE_FILE = ".mindgraph-skills-state.json";
7645
+ function sha256(text2) {
7646
+ return (0, import_node_crypto2.createHash)("sha256").update(text2).digest("hex");
7647
+ }
7648
+ function loadState(dir) {
7649
+ try {
7650
+ return JSON.parse(
7651
+ fs2.readFileSync(path2.join(dir, STATE_FILE), "utf8")
7652
+ );
7653
+ } catch {
7654
+ return {};
7655
+ }
7656
+ }
7657
+ function saveState(dir, state) {
7658
+ fs2.writeFileSync(
7659
+ path2.join(dir, STATE_FILE),
7660
+ `${JSON.stringify(state, null, 2)}
7661
+ `
7662
+ );
7663
+ }
7664
+ function isSymlink(target) {
7665
+ try {
7666
+ return fs2.lstatSync(target).isSymbolicLink();
7667
+ } catch {
7668
+ return false;
7669
+ }
7670
+ }
7671
+ function safeSkillPaths(dir, name) {
7672
+ if (!isValidSkillName(name)) {
7673
+ return `invalid skill name ${JSON.stringify(name)}`;
7674
+ }
7675
+ const root = path2.resolve(dir);
7676
+ const skillDir = path2.resolve(root, name);
7677
+ if (skillDir !== path2.join(root, name) || !skillDir.startsWith(root + path2.sep)) {
7678
+ return "resolved path escapes the target directory";
7679
+ }
7680
+ if (isSymlink(skillDir)) {
7681
+ return "target directory is a symlink";
7682
+ }
7683
+ const skillFile = path2.join(skillDir, "SKILL.md");
7684
+ if (isSymlink(skillFile)) {
7685
+ return "target SKILL.md is a symlink";
7686
+ }
7687
+ return { skillDir, skillFile };
7688
+ }
7689
+ async function skillsPull(api, dir, grantedTo) {
7690
+ const report = {
7691
+ written: [],
7692
+ unchanged: [],
7693
+ locallyEdited: [],
7694
+ conflicts: [],
7695
+ removed: [],
7696
+ refused: []
7697
+ };
7698
+ fs2.mkdirSync(dir, { recursive: true });
7699
+ const state = loadState(dir);
7700
+ const summaries = await api.list(grantedTo);
7701
+ const fetched = /* @__PURE__ */ new Set();
7702
+ for (const summary of summaries) {
7703
+ const detail = await api.get(summary.uid);
7704
+ if (!detail || detail.status !== "published") continue;
7705
+ fetched.add(detail.name);
7706
+ const paths = safeSkillPaths(dir, detail.name);
7707
+ if (typeof paths === "string") {
7708
+ report.refused.push({ name: detail.name, reason: paths });
7709
+ continue;
7710
+ }
7711
+ const { skillDir, skillFile } = paths;
7712
+ const rendered2 = renderSkillMd(detail);
7713
+ let existing = null;
7714
+ try {
7715
+ existing = fs2.readFileSync(skillFile, "utf8");
7716
+ } catch {
7717
+ existing = null;
7718
+ }
7719
+ if (existing === rendered2) {
7720
+ state[detail.name] = {
7721
+ uid: detail.uid,
7722
+ content_hash: detail.content_hash,
7723
+ file_sha256: sha256(rendered2)
7724
+ };
7725
+ report.unchanged.push(detail.name);
7726
+ continue;
7727
+ }
7728
+ const recorded = state[detail.name];
7729
+ const locallyModified = existing !== null && (!recorded || sha256(existing) !== recorded.file_sha256);
7730
+ if (locallyModified) {
7731
+ if (recorded && recorded.content_hash === detail.content_hash) {
7732
+ report.locallyEdited.push(detail.name);
7733
+ continue;
7734
+ }
7735
+ fs2.writeFileSync(`${skillFile}.conflict`, rendered2);
7736
+ report.conflicts.push(detail.name);
7737
+ continue;
7738
+ }
7739
+ fs2.mkdirSync(skillDir, { recursive: true });
7740
+ fs2.writeFileSync(skillFile, rendered2);
7741
+ state[detail.name] = {
7742
+ uid: detail.uid,
7743
+ content_hash: detail.content_hash,
7744
+ file_sha256: sha256(rendered2)
7745
+ };
7746
+ report.written.push(detail.name);
7747
+ }
7748
+ for (const [name, recorded] of Object.entries(state)) {
7749
+ if (fetched.has(name)) continue;
7750
+ const paths = safeSkillPaths(dir, name);
7751
+ if (typeof paths === "string") {
7752
+ delete state[name];
7753
+ continue;
7754
+ }
7755
+ const { skillDir, skillFile } = paths;
7756
+ let statusReason = null;
7757
+ try {
7758
+ const gone = await api.get(recorded.uid);
7759
+ statusReason = gone?.status_reason ?? (gone ? `status: ${gone.status}` : null);
7760
+ } catch {
7761
+ statusReason = null;
7762
+ }
7763
+ let existing = null;
7764
+ try {
7765
+ existing = fs2.readFileSync(skillFile, "utf8");
7766
+ } catch {
7767
+ existing = null;
7768
+ }
7769
+ if (existing === null) {
7770
+ delete state[name];
7771
+ continue;
7772
+ }
7773
+ const locallyModified = sha256(existing) !== recorded.file_sha256;
7774
+ if (locallyModified) {
7775
+ fs2.renameSync(skillFile, `${skillFile}.conflict`);
7776
+ report.removed.push({ name, statusReason, kept: true });
7777
+ } else {
7778
+ fs2.rmSync(skillDir, { recursive: true, force: true });
7779
+ report.removed.push({ name, statusReason, kept: false });
7780
+ }
7781
+ delete state[name];
7782
+ }
7783
+ saveState(dir, state);
7784
+ return report;
7785
+ }
7786
+ async function skillsPush(api, skillPath) {
7787
+ const resolved = path2.resolve(skillPath);
7788
+ const file = resolved.endsWith("SKILL.md") ? resolved : path2.join(resolved, "SKILL.md");
7789
+ if (isSymlink(file)) {
7790
+ return { outcome: "error", message: `${file} is a symlink; refusing` };
7791
+ }
7792
+ let text2;
7793
+ try {
7794
+ text2 = fs2.readFileSync(file, "utf8");
7795
+ } catch {
7796
+ return { outcome: "error", message: `could not read ${file}` };
7797
+ }
7798
+ const parsed = parseSkillMd(text2);
7799
+ if (!parsed) {
7800
+ return {
7801
+ outcome: "error",
7802
+ message: "SKILL.md must start with a --- frontmatter block"
7803
+ };
7804
+ }
7805
+ if (!parsed.name || !parsed.description) {
7806
+ return {
7807
+ outcome: "error",
7808
+ message: "SKILL.md frontmatter requires name and description"
7809
+ };
7810
+ }
7811
+ if (parsed.pin?.uid) {
7812
+ const { status: status2, body: body2 } = await api.send({
7813
+ action: "update",
7814
+ uid: parsed.pin.uid,
7815
+ name: parsed.name,
7816
+ description: parsed.description,
7817
+ content: parsed.body,
7818
+ ...parsed.license ? { license: parsed.license } : {}
7819
+ });
7820
+ if (status2 === 200) {
7821
+ return {
7822
+ outcome: "updated",
7823
+ message: `updated candidate ${parsed.name} (${parsed.pin.uid})`,
7824
+ uid: parsed.pin.uid
7825
+ };
7826
+ }
7827
+ return {
7828
+ outcome: status2 === 409 ? "conflict" : "error",
7829
+ message: `update failed (HTTP ${status2}): ${String(body2.error ?? "")}`,
7830
+ existingUid: body2.existing_uid ?? void 0,
7831
+ existingStatus: body2.existing_status ?? void 0
7832
+ };
7833
+ }
7834
+ const { status, body } = await api.send({
7835
+ action: "create",
7836
+ skill_source: "imported",
7837
+ name: parsed.name,
7838
+ description: parsed.description,
7839
+ content: parsed.body,
7840
+ ...parsed.license ? { license: parsed.license } : {},
7841
+ ...Object.keys(parsed.unknown).length > 0 ? { imported_frontmatter: parsed.unknown } : {},
7842
+ import_origin: `skills-push:${path2.basename(resolved)}`
7843
+ });
7844
+ if (status === 200 && body.replayed === true) {
7845
+ return {
7846
+ outcome: "replayed",
7847
+ message: `unchanged: ${parsed.name} already imported (${String(body.uid)})`,
7848
+ uid: String(body.uid)
7849
+ };
7850
+ }
7851
+ if (status === 200) {
7852
+ return {
7853
+ outcome: "created",
7854
+ message: `imported ${parsed.name} as a candidate (${String(body.uid)}) \u2014 review and publish in the dashboard`,
7855
+ uid: String(body.uid)
7856
+ };
7857
+ }
7858
+ return {
7859
+ outcome: status === 409 ? "conflict" : "error",
7860
+ message: `import failed (HTTP ${status}): ${String(body.error ?? "")}`,
7861
+ existingUid: body.existing_uid ?? void 0,
7862
+ existingStatus: body.existing_status ?? void 0
7863
+ };
7864
+ }
7865
+
6449
7866
  // src/hook-core.ts
6450
- var import_node_crypto3 = __toESM(require("crypto"));
7867
+ var import_node_crypto4 = __toESM(require("crypto"));
6451
7868
  var import_node_fs3 = __toESM(require("fs"));
6452
7869
  var import_node_os3 = __toESM(require("os"));
6453
7870
  var import_node_path3 = __toESM(require("path"));
@@ -6477,7 +7894,7 @@ function runtimeDir(options) {
6477
7894
  }
6478
7895
  function ledgerPath(sessionId, options) {
6479
7896
  const harness = "harness" in options ? options.harness : "claude-code";
6480
- const key = import_node_crypto3.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
7897
+ const key = import_node_crypto4.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
6481
7898
  return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
6482
7899
  }
6483
7900
  function waitBriefly(ms) {
@@ -6496,8 +7913,8 @@ function withLedger(sessionId, options, update) {
6496
7913
  try {
6497
7914
  lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
6498
7915
  break;
6499
- } catch (error) {
6500
- if (error.code !== "EEXIST") throw error;
7916
+ } catch (error2) {
7917
+ if (error2.code !== "EEXIST") throw error2;
6501
7918
  try {
6502
7919
  if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 3e3) import_node_fs3.default.unlinkSync(lock);
6503
7920
  } catch {
@@ -6519,7 +7936,7 @@ function withLedger(sessionId, options, update) {
6519
7936
  }
6520
7937
  const result = update(ledger);
6521
7938
  ledger.updatedAt = Date.now();
6522
- const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
7939
+ const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
6523
7940
  import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
6524
7941
  `, { mode: 384 });
6525
7942
  import_node_fs3.default.renameSync(temporary, file);
@@ -6602,11 +8019,11 @@ var BRIEF_CHAR_BUDGET = 9e3;
6602
8019
  function renderBrief(brief, meta) {
6603
8020
  let bounded = false;
6604
8021
  const clipTracked = (value, max) => {
6605
- const text = string(value);
6606
- if (!text) return void 0;
6607
- if (text.length <= max) return text;
8022
+ const text2 = string(value);
8023
+ if (!text2) return void 0;
8024
+ if (text2.length <= max) return text2;
6608
8025
  bounded = true;
6609
- return `${text.slice(0, max - 1)}\u2026`;
8026
+ return `${text2.slice(0, max - 1)}\u2026`;
6610
8027
  };
6611
8028
  const nodeLine = (value, summaryMax) => {
6612
8029
  const node = object(value);
@@ -6765,9 +8182,9 @@ async function repositoryScopeUids(input, client2, options, agentId) {
6765
8182
  `${repository.repoId}: ${string(payload?.status) || "unresolved"}`
6766
8183
  );
6767
8184
  return void 0;
6768
- } catch (error) {
8185
+ } catch (error2) {
6769
8186
  failures.push(
6770
- `${repository.repoId}: ${error?.message || "resolve failed"}`
8187
+ `${repository.repoId}: ${error2?.message || "resolve failed"}`
6771
8188
  );
6772
8189
  return void 0;
6773
8190
  }
@@ -6791,12 +8208,12 @@ function hasMindgraphServer(servers) {
6791
8208
  for (const [name, entry] of Object.entries(servers)) {
6792
8209
  if (name === "mindgraph") return true;
6793
8210
  const cfg = entry && typeof entry === "object" ? entry : void 0;
6794
- const text = [
8211
+ const text2 = [
6795
8212
  String(cfg?.command ?? ""),
6796
8213
  Array.isArray(cfg?.args) ? cfg.args.join(" ") : "",
6797
8214
  String(cfg?.url ?? "")
6798
8215
  ].join(" ");
6799
- if (text.includes("mindgraph-mcp")) return true;
8216
+ if (text2.includes("mindgraph-mcp")) return true;
6800
8217
  }
6801
8218
  return false;
6802
8219
  }
@@ -6927,7 +8344,7 @@ async function sessionStart(input, client2, options) {
6927
8344
  const contextText = registrationNote ? `${registrationNote}
6928
8345
 
6929
8346
  ${briefText}` : briefText;
6930
- const briefHash = import_node_crypto3.default.createHash("sha256").update(contextText).digest("hex");
8347
+ const briefHash = import_node_crypto4.default.createHash("sha256").update(contextText).digest("hex");
6931
8348
  const shouldInject = withLedger(input.session_id, options, (ledger) => {
6932
8349
  const changed = ledger.lastBriefHash !== briefHash;
6933
8350
  const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
@@ -7026,7 +8443,7 @@ async function postTool(input, client2, options) {
7026
8443
  ledger.mutatingOperations += 1;
7027
8444
  }
7028
8445
  if (!payload?.error) {
7029
- if (name.includes("mindgraph_capture") && ["lesson", "journal"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
8446
+ if (name.includes("mindgraph_capture") && (["lesson", "journal", "skill"].includes(action || "") || ["observation", "entity"].includes(action || "") && (Array.isArray(args.code_refs) || typeof args.supersedes_uid === "string")) || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
7030
8447
  ledger.memoryWritten = true;
7031
8448
  }
7032
8449
  }
@@ -7307,21 +8724,21 @@ async function runCodexHook(input, client2, options = {}) {
7307
8724
 
7308
8725
  // src/cli.ts
7309
8726
  init_hook_installer();
7310
- function recordHookHealth(harness, error) {
8727
+ function recordHookHealth(harness, error2) {
7311
8728
  try {
7312
- const dir = process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime");
7313
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
7314
- const file = path6.join(dir, "hook-health.json");
8729
+ const dir = process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime");
8730
+ fs6.mkdirSync(dir, { recursive: true, mode: 448 });
8731
+ const file = path7.join(dir, "hook-health.json");
7315
8732
  let failures = 0;
7316
8733
  try {
7317
- failures = Number(JSON.parse(fs5.readFileSync(file, "utf8")).consecutiveFailures) || 0;
8734
+ failures = Number(JSON.parse(fs6.readFileSync(file, "utf8")).consecutiveFailures) || 0;
7318
8735
  } catch {
7319
8736
  }
7320
- fs5.writeFileSync(
8737
+ fs6.writeFileSync(
7321
8738
  file,
7322
8739
  `${JSON.stringify({
7323
8740
  harness,
7324
- lastError: error.slice(0, 500),
8741
+ lastError: error2.slice(0, 500),
7325
8742
  lastFailureAt: (/* @__PURE__ */ new Date()).toISOString(),
7326
8743
  consecutiveFailures: failures + 1
7327
8744
  })}
@@ -7333,14 +8750,14 @@ function recordHookHealth(harness, error) {
7333
8750
  }
7334
8751
  function clearHookHealth(harness) {
7335
8752
  try {
7336
- const file = path6.join(
7337
- process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
8753
+ const file = path7.join(
8754
+ process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
7338
8755
  "hook-health.json"
7339
8756
  );
7340
- if (!fs5.existsSync(file)) return;
7341
- const current = JSON.parse(fs5.readFileSync(file, "utf8"));
8757
+ if (!fs6.existsSync(file)) return;
8758
+ const current = JSON.parse(fs6.readFileSync(file, "utf8"));
7342
8759
  if (!Number(current.consecutiveFailures)) return;
7343
- fs5.writeFileSync(
8760
+ fs6.writeFileSync(
7344
8761
  file,
7345
8762
  `${JSON.stringify({
7346
8763
  harness,
@@ -7356,7 +8773,7 @@ function clearHookHealth(harness) {
7356
8773
  function getClaudeDesktopConfigPath() {
7357
8774
  switch (process.platform) {
7358
8775
  case "darwin":
7359
- return path6.join(
8776
+ return path7.join(
7360
8777
  os4.homedir(),
7361
8778
  "Library",
7362
8779
  "Application Support",
@@ -7364,13 +8781,13 @@ function getClaudeDesktopConfigPath() {
7364
8781
  "claude_desktop_config.json"
7365
8782
  );
7366
8783
  case "win32":
7367
- return path6.join(
7368
- process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming"),
8784
+ return path7.join(
8785
+ process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming"),
7369
8786
  "Claude",
7370
8787
  "claude_desktop_config.json"
7371
8788
  );
7372
8789
  case "linux":
7373
- return path6.join(
8790
+ return path7.join(
7374
8791
  os4.homedir(),
7375
8792
  ".config",
7376
8793
  "Claude",
@@ -7381,16 +8798,16 @@ function getClaudeDesktopConfigPath() {
7381
8798
  }
7382
8799
  }
7383
8800
  function readJsonFile(filePath) {
7384
- if (!fs5.existsSync(filePath)) return {};
7385
- const content = fs5.readFileSync(filePath, "utf-8");
8801
+ if (!fs6.existsSync(filePath)) return {};
8802
+ const content = fs6.readFileSync(filePath, "utf-8");
7386
8803
  return JSON.parse(content);
7387
8804
  }
7388
8805
  function writeJsonFile(filePath, data) {
7389
- const dir = path6.dirname(filePath);
7390
- if (!fs5.existsSync(dir)) {
7391
- fs5.mkdirSync(dir, { recursive: true });
8806
+ const dir = path7.dirname(filePath);
8807
+ if (!fs6.existsSync(dir)) {
8808
+ fs6.mkdirSync(dir, { recursive: true });
7392
8809
  }
7393
- fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
8810
+ fs6.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7394
8811
  }
7395
8812
  function getMcpServerEntry(apiKey, baseUrl) {
7396
8813
  const env = { MINDGRAPH_API_KEY: apiKey };
@@ -7477,7 +8894,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
7477
8894
  }
7478
8895
  function uninstallClaudeDesktop() {
7479
8896
  const configPath = getClaudeDesktopConfigPath();
7480
- if (!fs5.existsSync(configPath)) {
8897
+ if (!fs6.existsSync(configPath)) {
7481
8898
  console.log("Claude Desktop config not found. Nothing to remove.");
7482
8899
  return;
7483
8900
  }
@@ -7504,7 +8921,7 @@ function uninstallClaudeCode() {
7504
8921
  }
7505
8922
  function printStatus() {
7506
8923
  const desktopPath = getClaudeDesktopConfigPath();
7507
- if (fs5.existsSync(desktopPath)) {
8924
+ if (fs6.existsSync(desktopPath)) {
7508
8925
  const config = readJsonFile(desktopPath);
7509
8926
  const servers = config.mcpServers;
7510
8927
  if (servers && "mindgraph" in servers) {
@@ -7522,12 +8939,12 @@ function printStatus() {
7522
8939
  `MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
7523
8940
  );
7524
8941
  try {
7525
- const healthFile = path6.join(
7526
- process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
8942
+ const healthFile = path7.join(
8943
+ process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
7527
8944
  "hook-health.json"
7528
8945
  );
7529
- if (fs5.existsSync(healthFile)) {
7530
- const health = JSON.parse(fs5.readFileSync(healthFile, "utf8"));
8946
+ if (fs6.existsSync(healthFile)) {
8947
+ const health = JSON.parse(fs6.readFileSync(healthFile, "utf8"));
7531
8948
  const failures = Number(health.consecutiveFailures) || 0;
7532
8949
  console.log(
7533
8950
  failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
@@ -7544,7 +8961,7 @@ function withHooksFlag() {
7544
8961
  }
7545
8962
  function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
7546
8963
  if (withHooks) {
7547
- const runner = installHookRunner(__filename, void 0, "0.18.0");
8964
+ const runner = installHookRunner(__filename, void 0, "0.20.0");
7548
8965
  console.log(`Installed pinned hook runner at ${runner}`);
7549
8966
  const result = installClaudeHooks("user");
7550
8967
  console.log(
@@ -7592,6 +9009,20 @@ USAGE:
7592
9009
  mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
7593
9010
  mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
7594
9011
  mindgraph-mcp status Show installation status
9012
+ mindgraph-mcp skills pull [--dir .claude/skills]
9013
+ Write your org's published+granted skills as
9014
+ SKILL.md directories (allowlisted frontmatter
9015
+ only). Revoked/archived skills are withdrawn;
9016
+ local edits are never overwritten (.conflict
9017
+ sidecars). NOTE: coding harnesses hot-reload
9018
+ skill files \u2014 running sessions adopt pulled
9019
+ changes immediately; session pinning exists
9020
+ on the managed-agent leg only.
9021
+ mindgraph-mcp skills push <path>
9022
+ Import a SKILL.md directory as a review
9023
+ candidate (nothing auto-publishes). A
9024
+ previously pulled directory routes to the
9025
+ candidate-only update path.
7595
9026
 
7596
9027
  OPTIONS:
7597
9028
  --api-key <key> MindGraph API key (or set MINDGRAPH_API_KEY env var)
@@ -7655,7 +9086,7 @@ async function pollForKey(code) {
7655
9086
  } catch (e) {
7656
9087
  if (e instanceof Error && e.message.includes("Session")) throw e;
7657
9088
  }
7658
- await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
9089
+ await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
7659
9090
  }
7660
9091
  throw new Error("Timed out waiting for authorization.");
7661
9092
  }
@@ -7665,7 +9096,7 @@ async function interactiveInit(baseUrl) {
7665
9096
  input: process.stdin,
7666
9097
  output: process.stdout
7667
9098
  });
7668
- const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
9099
+ const ask = (q) => new Promise((resolve3) => rl.question(q, resolve3));
7669
9100
  console.log("\nMindGraph MCP Server Setup\n");
7670
9101
  console.log(" 1. Sign in via browser (recommended)");
7671
9102
  console.log(" 2. Enter API key manually");
@@ -7736,8 +9167,79 @@ async function interactiveInit(baseUrl) {
7736
9167
  console.log("\nDone! Your knowledge graph is now connected.");
7737
9168
  }
7738
9169
  async function main2() {
7739
- const { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
9170
+ const {
9171
+ command: command2,
9172
+ apiKey,
9173
+ baseUrl,
9174
+ scope,
9175
+ projectDir,
9176
+ harness,
9177
+ hooks,
9178
+ agentId,
9179
+ positionals,
9180
+ dir
9181
+ } = parseArgs(process.argv);
7740
9182
  switch (command2) {
9183
+ case "skills": {
9184
+ if (!apiKey) {
9185
+ console.error(
9186
+ "API key required. Use --api-key or set MINDGRAPH_API_KEY."
9187
+ );
9188
+ process.exit(1);
9189
+ }
9190
+ const sub = positionals[0];
9191
+ const api = httpSkillsApi(
9192
+ baseUrl ?? "https://api.mindgraph.cloud",
9193
+ apiKey,
9194
+ agentId
9195
+ );
9196
+ if (sub === "pull") {
9197
+ if (!agentId) {
9198
+ console.error(
9199
+ "note: no --agent-id/MINDGRAPH_AGENT_ID \u2014 pulling ALL published skills; bind an agent identity to scope the set by its grants."
9200
+ );
9201
+ }
9202
+ const report = await skillsPull(api, dir ?? ".claude/skills", agentId);
9203
+ for (const name of report.written) console.log(` written ${name}`);
9204
+ for (const name of report.unchanged)
9205
+ console.log(` unchanged ${name}`);
9206
+ for (const name of report.locallyEdited)
9207
+ console.log(` local-edit ${name} \u2014 upstream unchanged; left alone`);
9208
+ for (const name of report.conflicts)
9209
+ console.log(
9210
+ ` CONFLICT ${name} \u2014 local edits kept; new upstream saved as SKILL.md.conflict`
9211
+ );
9212
+ for (const removal of report.removed)
9213
+ console.log(
9214
+ ` withdrawn ${removal.name}${removal.kept ? " (local edits kept as SKILL.md.conflict)" : ""}${removal.statusReason ? ` \u2014 ${removal.statusReason}` : ""}`
9215
+ );
9216
+ for (const refusal of report.refused)
9217
+ console.log(` REFUSED ${refusal.name} \u2014 ${refusal.reason}`);
9218
+ const total = report.written.length + report.unchanged.length + report.locallyEdited.length + report.conflicts.length;
9219
+ console.log(`
9220
+ ${total} skill(s) synced.`);
9221
+ } else if (sub === "push") {
9222
+ const target = positionals[1];
9223
+ if (!target) {
9224
+ console.error("Usage: mindgraph-mcp skills push <path>");
9225
+ process.exit(1);
9226
+ }
9227
+ const report = await skillsPush(api, target);
9228
+ console.log(report.message);
9229
+ if (report.existingUid) {
9230
+ console.log(
9231
+ ` existing skill: ${report.existingUid}${report.existingStatus ? ` (${report.existingStatus})` : ""}`
9232
+ );
9233
+ }
9234
+ if (report.outcome === "error" || report.outcome === "conflict") {
9235
+ process.exit(1);
9236
+ }
9237
+ } else {
9238
+ console.error("Usage: mindgraph-mcp skills pull|push [...]");
9239
+ process.exit(1);
9240
+ }
9241
+ break;
9242
+ }
7741
9243
  case "help":
7742
9244
  printUsage();
7743
9245
  break;
@@ -7774,7 +9276,7 @@ async function main2() {
7774
9276
  if (hookHarness !== "claude-code" && hookHarness !== "codex") {
7775
9277
  throw new Error("--harness must be claude-code or codex");
7776
9278
  }
7777
- const runner = installHookRunner(__filename, void 0, "0.18.0");
9279
+ const runner = installHookRunner(__filename, void 0, "0.20.0");
7778
9280
  console.log(`Installed pinned hook runner at ${runner}`);
7779
9281
  const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
7780
9282
  console.log(
@@ -7855,13 +9357,13 @@ async function main2() {
7855
9357
  process.stdout.write(`${JSON.stringify(output)}
7856
9358
  `);
7857
9359
  clearHookHealth(hookHarness);
7858
- } catch (error) {
9360
+ } catch (error2) {
7859
9361
  recordHookHealth(
7860
9362
  hookHarness,
7861
- error?.message || "hook failed"
9363
+ error2?.message || "hook failed"
7862
9364
  );
7863
9365
  console.error(
7864
- `mindgraph-hook: failing open (${error?.message || "unknown error"})`
9366
+ `mindgraph-hook: failing open (${error2?.message || "unknown error"})`
7865
9367
  );
7866
9368
  process.stdout.write("{}\n");
7867
9369
  }
@@ -7880,7 +9382,7 @@ async function main2() {
7880
9382
  process.exit(1);
7881
9383
  }
7882
9384
  }
7883
- main2().catch((error) => {
7884
- console.error("Error:", error.message || error);
9385
+ main2().catch((error2) => {
9386
+ console.error("Error:", error2.message || error2);
7885
9387
  process.exit(1);
7886
9388
  });