mindgraph-mcp 0.19.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 +23 -3
  2. package/dist/cli.js +1055 -225
  3. package/dist/index.js +1008 -183
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -83,12 +83,12 @@ 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
94
  `${method} ${path8} failed: ${res.status}`,
@@ -104,9 +104,9 @@ var init_dist = __esm({
104
104
  }
105
105
  throw err5;
106
106
  }
107
- const text = await res.text();
108
- if (!text) return void 0;
109
- return JSON.parse(text);
107
+ const text2 = await res.text();
108
+ if (!text2) return void 0;
109
+ return JSON.parse(text2);
110
110
  }
111
111
  throw lastError;
112
112
  }
@@ -232,10 +232,10 @@ var init_dist = __esm({
232
232
  * Resolve text to an existing entity via alias matching.
233
233
  * Returns `{ uid: string | null }`.
234
234
  */
235
- async resolveEntity(text, agentId) {
235
+ async resolveEntity(text2, agentId) {
236
236
  return this.post("/reality/entity", {
237
237
  action: "resolve",
238
- text,
238
+ text: text2,
239
239
  agent_id: agentId
240
240
  });
241
241
  }
@@ -243,10 +243,10 @@ var init_dist = __esm({
243
243
  * Fuzzy-match text against existing entities.
244
244
  * Returns `{ matches: [{ uid, label, score }] }`.
245
245
  */
246
- async fuzzyResolveEntity(text, limit = 5, agentId) {
246
+ async fuzzyResolveEntity(text2, limit = 5, agentId) {
247
247
  return this.post("/reality/entity", {
248
248
  action: "fuzzy_resolve",
249
- text,
249
+ text: text2,
250
250
  limit,
251
251
  agent_id: agentId
252
252
  });
@@ -545,8 +545,8 @@ var init_dist = __esm({
545
545
  async getAliases(uid) {
546
546
  return this.get(`/aliases/${seg(uid)}`);
547
547
  }
548
- async resolveAlias(text) {
549
- return this.get(`/resolve?text=${encodeURIComponent(text)}`);
548
+ async resolveAlias(text2) {
549
+ return this.get(`/resolve?text=${encodeURIComponent(text2)}`);
550
550
  }
551
551
  // ---- Export / Import ----
552
552
  async exportGraph() {
@@ -1163,29 +1163,29 @@ async function runProcess(executable, args, timeoutMs, env, cwd) {
1163
1163
  child.stderr.on("data", collect(stderr));
1164
1164
  child.on("error", (cause) => {
1165
1165
  clearTimeout(timer);
1166
- const error = new Error(cause.message);
1167
- error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
1168
- rejectPromise(error);
1166
+ const error2 = new Error(cause.message);
1167
+ error2.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
1168
+ rejectPromise(error2);
1169
1169
  });
1170
1170
  child.on("close", (code) => {
1171
1171
  clearTimeout(timer);
1172
1172
  if (timedOut) {
1173
- const error = new Error(`command timed out after ${timeoutMs}ms`);
1174
- error.kind = "timeout";
1175
- rejectPromise(error);
1173
+ const error2 = new Error(`command timed out after ${timeoutMs}ms`);
1174
+ error2.kind = "timeout";
1175
+ rejectPromise(error2);
1176
1176
  return;
1177
1177
  }
1178
1178
  if (outputBytes > MAX_OUTPUT_BYTES) {
1179
- const error = new Error("codegraph output exceeded 5 MiB");
1180
- error.kind = "command_failed";
1181
- rejectPromise(error);
1179
+ const error2 = new Error("codegraph output exceeded 5 MiB");
1180
+ error2.kind = "command_failed";
1181
+ rejectPromise(error2);
1182
1182
  return;
1183
1183
  }
1184
1184
  if (code !== 0) {
1185
- const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
1186
- error.kind = "command_failed";
1187
- error.exitCode = code;
1188
- 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);
1189
1189
  return;
1190
1190
  }
1191
1191
  resolvePromise(Buffer.concat(stdout).toString("utf8"));
@@ -1213,9 +1213,7 @@ function codeRefIdentityKey(ref) {
1213
1213
  qualified_name: ref.qualifiedName,
1214
1214
  disambiguator: {
1215
1215
  signature: ref.signature ?? "",
1216
- path: ref.path,
1217
- start_line: ref.startLine,
1218
- end_line: ref.endLine
1216
+ path: ref.path
1219
1217
  }
1220
1218
  };
1221
1219
  }
@@ -2104,7 +2102,7 @@ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
2104
2102
  identity_space_uid: spaceUid,
2105
2103
  agent_id: agentId
2106
2104
  });
2107
- if (response.uid) {
2105
+ if (response.uid && response.created !== false && response.status !== "existing") {
2108
2106
  await client2.entity({
2109
2107
  action: "relate",
2110
2108
  source_uid: response.uid,
@@ -2739,8 +2737,8 @@ async function handleSyncTool(client2, args) {
2739
2737
  }
2740
2738
  }
2741
2739
  return ok2(await remote.memorySync(request));
2742
- } catch (error) {
2743
- return err2("sync_failed", errorDetail(error));
2740
+ } catch (error2) {
2741
+ return err2("sync_failed", errorDetail(error2));
2744
2742
  }
2745
2743
  }
2746
2744
  async function scanRepository(remote, repository, requested, provider, agentId) {
@@ -2874,12 +2872,22 @@ function str(value) {
2874
2872
  function num(value) {
2875
2873
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2876
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
+ }
2877
2885
  function clip(value, max, state) {
2878
- const text = str(value);
2879
- if (!text) return void 0;
2880
- if (text.length <= max) return text;
2886
+ const text2 = str(value);
2887
+ if (!text2) return void 0;
2888
+ if (text2.length <= max) return text2;
2881
2889
  state.bounded = true;
2882
- return `${text.slice(0, max - 1)}\u2026`;
2890
+ return `${text2.slice(0, max - 1)}\u2026`;
2883
2891
  }
2884
2892
  function isNodeShaped(value) {
2885
2893
  const node = obj(value);
@@ -3001,7 +3009,7 @@ function relationshipItems(edges, labelByUid, state, options) {
3001
3009
  for (const raw of edges) {
3002
3010
  const edge = obj(raw);
3003
3011
  if (!edge) continue;
3004
- const type = str(edge.edge_type) || "?";
3012
+ const type = edgeTypeName(edge.edge_type) || "?";
3005
3013
  const from = str(edge.from_uid);
3006
3014
  const to = str(edge.to_uid);
3007
3015
  if (CURATION_EDGE_TYPES.has(type)) continue;
@@ -3014,25 +3022,29 @@ function relationshipItems(edges, labelByUid, state, options) {
3014
3022
  if (lines.length > RELATIONSHIP_MAX) {
3015
3023
  state.bounded = true;
3016
3024
  const extra = lines.length - RELATIONSHIP_MAX;
3025
+ state.omitted += extra;
3017
3026
  return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
3018
3027
  }
3019
3028
  return lines;
3020
3029
  }
3021
- 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);
3022
3033
  const lines = [header];
3023
3034
  let used = header.length;
3024
3035
  for (const section of sections) {
3025
3036
  if (section.items.length === 0 && !section.heading) continue;
3026
3037
  const headingCost = section.heading.length + 2;
3027
- if (used + headingCost > RENDER_CHAR_BUDGET) {
3038
+ if (used + headingCost > packingBudget) {
3028
3039
  state.bounded = true;
3040
+ state.omitted += Math.max(1, section.items.length);
3029
3041
  continue;
3030
3042
  }
3031
3043
  let sectionUsed = 0;
3032
3044
  const kept = [];
3033
3045
  let dropped = 0;
3034
3046
  for (const item of section.items) {
3035
- if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
3047
+ if (used + headingCost + sectionUsed + item.length + 1 > packingBudget) {
3036
3048
  dropped += 1;
3037
3049
  continue;
3038
3050
  }
@@ -3041,13 +3053,17 @@ function assemble(header, sections, state) {
3041
3053
  }
3042
3054
  if (kept.length === 0 && section.items.length > 0) {
3043
3055
  state.bounded = true;
3056
+ state.omitted += dropped;
3044
3057
  continue;
3045
3058
  }
3046
3059
  if (dropped > 0) {
3047
3060
  state.bounded = true;
3061
+ state.omitted += dropped;
3048
3062
  const marker = `(+${dropped} more not shown)`;
3049
- kept.push(marker);
3050
- sectionUsed += marker.length + 1;
3063
+ if (used + headingCost + sectionUsed + marker.length + 1 <= packingBudget) {
3064
+ kept.push(marker);
3065
+ sectionUsed += marker.length + 1;
3066
+ }
3051
3067
  }
3052
3068
  lines.push(
3053
3069
  section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
@@ -3057,10 +3073,10 @@ ${kept.join("\n")}` : kept.join("\n")
3057
3073
  }
3058
3074
  if (state.bounded) {
3059
3075
  lines.push(
3060
- '\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]`
3061
3077
  );
3062
3078
  }
3063
- return lines.join("\n\n");
3079
+ return lines.join("\n\n").slice(0, budget);
3064
3080
  }
3065
3081
  function policySections(payload, state) {
3066
3082
  const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
@@ -3081,7 +3097,7 @@ function hiddenSections(payload) {
3081
3097
  }
3082
3098
  ];
3083
3099
  }
3084
- function renderContext(response) {
3100
+ function renderContext(response, maxChars) {
3085
3101
  const payload = obj(response);
3086
3102
  if (!payload) return void 0;
3087
3103
  const graph = obj(payload.graph);
@@ -3091,7 +3107,7 @@ function renderContext(response) {
3091
3107
  const infraOnly = shaped.length - nodes.length;
3092
3108
  const articles = Array.isArray(payload.articles) ? payload.articles : [];
3093
3109
  const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
3094
- const state = { bounded: false };
3110
+ const state = { bounded: false, omitted: 0 };
3095
3111
  const sections = [];
3096
3112
  if (articles.length > 0) {
3097
3113
  sections.push({
@@ -3134,11 +3150,129 @@ ${clip(article.content, ARTICLE_MAX, state) || ""}`
3134
3150
  return "No results found for this search.";
3135
3151
  }
3136
3152
  if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
3137
- 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
+ );
3138
3198
  }
3139
- 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);
3140
3274
  }
3141
- function renderNodeList(response, title, limit) {
3275
+ function renderNodeList(response, title, limit, maxChars) {
3142
3276
  let candidates = [];
3143
3277
  if (Array.isArray(response)) {
3144
3278
  candidates = response;
@@ -3156,8 +3290,9 @@ function renderNodeList(response, title, limit) {
3156
3290
  let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
3157
3291
  if (nodes.length === 0) return void 0;
3158
3292
  const total = nodes.length;
3159
- const state = { bounded: false };
3293
+ const state = { bounded: false, omitted: 0 };
3160
3294
  if (limit !== void 0 && limit > 0 && nodes.length > limit) {
3295
+ state.omitted += nodes.length - limit;
3161
3296
  nodes = nodes.slice(0, limit);
3162
3297
  state.bounded = true;
3163
3298
  }
@@ -3166,18 +3301,23 @@ function renderNodeList(response, title, limit) {
3166
3301
  return assemble(
3167
3302
  `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
3168
3303
  sections,
3169
- state
3304
+ state,
3305
+ maxChars
3170
3306
  );
3171
3307
  }
3172
- function renderTraversal(response) {
3308
+ function renderTraversal(response, maxChars) {
3309
+ const directSteps = Array.isArray(response) ? response : void 0;
3173
3310
  const payload = obj(response);
3174
- if (!payload) return void 0;
3175
- const container = obj(payload.graph) ?? payload;
3311
+ if (!payload && !directSteps) return void 0;
3312
+ const body = payload ?? {};
3313
+ const container = obj(body.graph) ?? body;
3176
3314
  const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
3177
3315
  const nodes = rawNodes.filter(isNodeShaped);
3178
- const paths = Array.isArray(payload.paths) ? payload.paths : [];
3179
- if (nodes.length === 0 && paths.length === 0) return void 0;
3180
- 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 };
3181
3321
  const sections = [];
3182
3322
  const labelByUid = /* @__PURE__ */ new Map();
3183
3323
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
@@ -3201,6 +3341,42 @@ function renderTraversal(response) {
3201
3341
  });
3202
3342
  sections.push({ heading: "## Paths", items });
3203
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
+ }
3204
3380
  if (nodes.length > 0) {
3205
3381
  sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
3206
3382
  const relationships = relationshipItems(
@@ -3212,7 +3388,13 @@ function renderTraversal(response) {
3212
3388
  sections.push({ heading: "## Relationships", items: relationships });
3213
3389
  }
3214
3390
  }
3215
- 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
+ );
3216
3398
  }
3217
3399
  function neighborLine(value) {
3218
3400
  const node = obj(value);
@@ -3233,7 +3415,7 @@ function renderOntologyAnswer(response) {
3233
3415
  if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
3234
3416
  return hasQuerySignature ? "No matching domain objects." : void 0;
3235
3417
  }
3236
- const state = { bounded: false };
3418
+ const state = { bounded: false, omitted: 0 };
3237
3419
  const sections = [];
3238
3420
  if (objects.length > 0) {
3239
3421
  sections.push(...groupedNodeSections(objects, state));
@@ -3299,7 +3481,7 @@ function renderObjectList(response, title) {
3299
3481
  if (items.length === 0) {
3300
3482
  return rawItems ? `# ${title} (0 results)` : void 0;
3301
3483
  }
3302
- const state = { bounded: false };
3484
+ const state = { bounded: false, omitted: 0 };
3303
3485
  const sections = groupedNodeSections(items, state);
3304
3486
  const total = num(payload.total_count);
3305
3487
  const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
@@ -3314,7 +3496,7 @@ function renderJobs(response) {
3314
3496
  const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
3315
3497
  if (!Array.isArray(jobs)) return void 0;
3316
3498
  if (jobs.length === 0) return "No ingestion jobs.";
3317
- const state = { bounded: false };
3499
+ const state = { bounded: false, omitted: 0 };
3318
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));
3319
3501
  const shown = sorted.slice(0, JOBS_RENDER_MAX);
3320
3502
  if (shown.length < sorted.length) state.bounded = true;
@@ -3340,7 +3522,7 @@ function renderJobs(response) {
3340
3522
  function renderSignals(response) {
3341
3523
  const payload = obj(response);
3342
3524
  if (!payload) return void 0;
3343
- const state = { bounded: false };
3525
+ const state = { bounded: false, omitted: 0 };
3344
3526
  const sections = [];
3345
3527
  for (const [key, value] of Object.entries(payload)) {
3346
3528
  if (!Array.isArray(value) || value.length === 0) continue;
@@ -3364,7 +3546,7 @@ function renderSignals(response) {
3364
3546
  if (sections.length === 0) return void 0;
3365
3547
  return assemble("# Synthesis signals", sections, state);
3366
3548
  }
3367
- 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;
3368
3550
  var init_render = __esm({
3369
3551
  "src/render.ts"() {
3370
3552
  "use strict";
@@ -3375,13 +3557,570 @@ var init_render = __esm({
3375
3557
  CHUNK_MAX = 6e3;
3376
3558
  RELATIONSHIP_MAX = 40;
3377
3559
  RENDER_CHAR_BUDGET = 24e3;
3560
+ MIN_RENDER_CHAR_BUDGET = 512;
3561
+ BOUNDED_MARKER_RESERVE = 220;
3378
3562
  CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
3379
3563
  INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
3380
3564
  JOBS_RENDER_MAX = 20;
3381
3565
  }
3382
3566
  });
3383
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
+
3384
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
+ }
3385
4124
  function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
3386
4125
  if (profile === "coding") return TOOLS;
3387
4126
  return TOOLS.filter(
@@ -3395,8 +4134,8 @@ function ok3(data) {
3395
4134
  }
3396
4135
  function renderedResult(format, raw, render) {
3397
4136
  if (format === "json") return ok3(raw);
3398
- const text = render(raw);
3399
- 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);
3400
4139
  }
3401
4140
  function err3(message) {
3402
4141
  return {
@@ -3404,8 +4143,22 @@ function err3(message) {
3404
4143
  isError: true
3405
4144
  };
3406
4145
  }
3407
- async function handleTool(client2, name, args) {
4146
+ async function handleTool(client2, name, args, memoryConfig = {}) {
3408
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
+ }
3409
4162
  let result;
3410
4163
  switch (name) {
3411
4164
  case "mindgraph_capture":
@@ -3423,6 +4176,9 @@ async function handleTool(client2, name, args) {
3423
4176
  case "mindgraph_retrieve":
3424
4177
  result = await handleRetrieve(client2, args);
3425
4178
  break;
4179
+ case "mindgraph_memory":
4180
+ result = await handleMemoryTool(client2, args, memoryConfig);
4181
+ break;
3426
4182
  case "mindgraph_series_query":
3427
4183
  result = await handleSeriesQuery(client2, args);
3428
4184
  break;
@@ -3504,7 +4260,11 @@ async function handleCapture(client2, args) {
3504
4260
  work_uid,
3505
4261
  execution_uid,
3506
4262
  idempotency_key,
4263
+ reuse_uid,
3507
4264
  supersedes_uid,
4265
+ contradicts_uid,
4266
+ confirm_new,
4267
+ on_near_duplicate,
3508
4268
  summarizes_uids,
3509
4269
  entity_type,
3510
4270
  source_uid,
@@ -3528,7 +4288,11 @@ async function handleCapture(client2, args) {
3528
4288
  work_uid,
3529
4289
  execution_uid,
3530
4290
  idempotency_key,
4291
+ reuse_uid,
3531
4292
  supersedes_uid,
4293
+ contradicts_uid,
4294
+ confirm_new,
4295
+ on_near_duplicate,
3532
4296
  summarizes_uids,
3533
4297
  props,
3534
4298
  agent_id
@@ -3574,27 +4338,32 @@ async function handleCapture(client2, args) {
3574
4338
  const journalProps = { content };
3575
4339
  if (mood) journalProps.mood = mood;
3576
4340
  if (tags) journalProps.tags = tags;
3577
- 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
+ }));
3578
4350
  }
3579
4351
  case "entity": {
3580
4352
  const type = entity_type || "concept";
3581
4353
  const entityProps = { ...props };
3582
4354
  if (summary) entityProps.description = summary;
3583
- switch (type) {
3584
- case "person":
3585
- return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
3586
- case "organization":
3587
- return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
3588
- case "nation":
3589
- return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
3590
- case "event":
3591
- return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
3592
- case "place":
3593
- return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
3594
- case "concept":
3595
- default:
3596
- return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
3597
- }
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
+ }));
3598
4367
  }
3599
4368
  case "observation":
3600
4369
  return ok3(
@@ -3605,7 +4374,13 @@ async function handleCapture(client2, args) {
3605
4374
  confidence,
3606
4375
  salience,
3607
4376
  props,
3608
- agent_id
4377
+ agent_id,
4378
+ idempotency_key,
4379
+ reuse_uid,
4380
+ supersedes_uid,
4381
+ contradicts_uid,
4382
+ confirm_new,
4383
+ on_near_duplicate
3609
4384
  })
3610
4385
  );
3611
4386
  case "source":
@@ -3617,7 +4392,13 @@ async function handleCapture(client2, args) {
3617
4392
  confidence,
3618
4393
  salience,
3619
4394
  props,
3620
- agent_id
4395
+ agent_id,
4396
+ idempotency_key,
4397
+ reuse_uid,
4398
+ supersedes_uid,
4399
+ contradicts_uid,
4400
+ confirm_new,
4401
+ on_near_duplicate
3621
4402
  })
3622
4403
  );
3623
4404
  case "snippet":
@@ -3630,26 +4411,38 @@ async function handleCapture(client2, args) {
3630
4411
  confidence,
3631
4412
  salience,
3632
4413
  props,
3633
- agent_id
4414
+ agent_id,
4415
+ idempotency_key,
4416
+ reuse_uid,
4417
+ supersedes_uid,
4418
+ contradicts_uid,
4419
+ confirm_new,
4420
+ on_near_duplicate
3634
4421
  })
3635
4422
  );
3636
4423
  case "concept":
3637
- return ok3(
3638
- await client2.structure({
3639
- action: "concept",
3640
- label,
3641
- summary,
3642
- confidence,
3643
- salience,
3644
- props,
3645
- agent_id
3646
- })
3647
- );
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
+ }));
3648
4440
  default:
3649
4441
  return err3(`Unknown capture action: ${action}`);
3650
4442
  }
3651
4443
  }
3652
4444
  async function handleReason(client2, args) {
4445
+ const resolution = resolutionFields(args);
3653
4446
  const {
3654
4447
  action,
3655
4448
  label,
@@ -3682,7 +4475,8 @@ async function handleReason(client2, args) {
3682
4475
  ...warrant.content ? { props: { content: warrant.content } } : {}
3683
4476
  } : void 0,
3684
4477
  argument,
3685
- agent_id
4478
+ agent_id,
4479
+ ...resolution
3686
4480
  })
3687
4481
  );
3688
4482
  }
@@ -3695,11 +4489,13 @@ async function handleReason(client2, args) {
3695
4489
  confidence,
3696
4490
  salience,
3697
4491
  props,
3698
- agent_id
4492
+ agent_id,
4493
+ ...resolution
3699
4494
  })
3700
4495
  );
3701
4496
  }
3702
4497
  async function handleCommit(client2, args) {
4498
+ const resolution = resolutionFields(args);
3703
4499
  const {
3704
4500
  action,
3705
4501
  label,
@@ -3730,16 +4526,17 @@ async function handleCommit(client2, args) {
3730
4526
  confidence,
3731
4527
  salience,
3732
4528
  props,
3733
- agent_id
4529
+ agent_id,
4530
+ ...resolution
3734
4531
  })
3735
4532
  );
3736
4533
  case "open_decision":
3737
4534
  if (!label) return err3("label is required for open_decision");
3738
- return ok3(await client2.openDecision(label, { summary, props, agent_id }));
4535
+ return ok3(await client2.openDecision(label, { summary, props, agent_id, ...resolution }));
3739
4536
  case "add_option":
3740
4537
  if (!decision_uid) return err3("decision_uid is required for add_option");
3741
4538
  if (!label) return err3("label is required for add_option");
3742
- 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 }));
3743
4540
  case "add_constraint":
3744
4541
  if (!decision_uid) return err3("decision_uid is required for add_constraint");
3745
4542
  return ok3(
@@ -3749,7 +4546,8 @@ async function handleCommit(client2, args) {
3749
4546
  decision_uid,
3750
4547
  summary,
3751
4548
  props,
3752
- agent_id
4549
+ agent_id,
4550
+ ...resolution
3753
4551
  })
3754
4552
  );
3755
4553
  case "resolve_decision":
@@ -3763,7 +4561,8 @@ async function handleCommit(client2, args) {
3763
4561
  as_of_date,
3764
4562
  session_id,
3765
4563
  retrieval_trace_id,
3766
- agent_id
4564
+ agent_id,
4565
+ ...resolution
3767
4566
  })
3768
4567
  );
3769
4568
  case "get_open_decisions":
@@ -3844,7 +4643,7 @@ async function handlePlan(client2, args) {
3844
4643
  output_snapshot,
3845
4644
  side_effects,
3846
4645
  outcome,
3847
- error,
4646
+ error: error2,
3848
4647
  next_action,
3849
4648
  checkpoint_summary,
3850
4649
  test_summary,
@@ -3907,7 +4706,7 @@ async function handlePlan(client2, args) {
3907
4706
  output_snapshot,
3908
4707
  side_effects,
3909
4708
  outcome,
3910
- error,
4709
+ error: error2,
3911
4710
  next_action,
3912
4711
  checkpoint_summary,
3913
4712
  test_summary,
@@ -3956,7 +4755,7 @@ async function handlePlan(client2, args) {
3956
4755
  output_snapshot,
3957
4756
  side_effects,
3958
4757
  outcome,
3959
- error,
4758
+ error: error2,
3960
4759
  props,
3961
4760
  agent_id
3962
4761
  })
@@ -4042,6 +4841,7 @@ async function handleRetrieve(client2, args) {
4042
4841
  const {
4043
4842
  action,
4044
4843
  format,
4844
+ max_output_chars,
4045
4845
  query,
4046
4846
  start_uid,
4047
4847
  end_uid,
@@ -4096,7 +4896,7 @@ async function handleRetrieve(client2, args) {
4096
4896
  graph_expansion_limit: graph_expansion_limit ?? 3,
4097
4897
  graph_max_depth: graph_max_depth ?? 2
4098
4898
  }),
4099
- renderContext
4899
+ (raw) => renderContext(raw, max_output_chars)
4100
4900
  );
4101
4901
  case "text":
4102
4902
  if (!query) return err3("query is required for text search");
@@ -4110,7 +4910,7 @@ async function handleRetrieve(client2, args) {
4110
4910
  explain,
4111
4911
  agent_id
4112
4912
  }),
4113
- (raw) => renderNodeList(raw, `Text search: ${query}`)
4913
+ (raw) => renderNodeList(raw, `Text search: ${query}`, void 0, max_output_chars)
4114
4914
  );
4115
4915
  case "semantic":
4116
4916
  if (!query) return err3("query is required for semantic search");
@@ -4124,7 +4924,7 @@ async function handleRetrieve(client2, args) {
4124
4924
  explain,
4125
4925
  agent_id
4126
4926
  }),
4127
- (raw) => renderNodeList(raw, `Semantic search: ${query}`)
4927
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`, void 0, max_output_chars)
4128
4928
  );
4129
4929
  case "hybrid":
4130
4930
  if (!query) return err3("query is required for hybrid search");
@@ -4139,7 +4939,7 @@ async function handleRetrieve(client2, args) {
4139
4939
  agent_id
4140
4940
  }),
4141
4941
  // With explain, per-leg scoring must survive — raw JSON only.
4142
- explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
4942
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`, void 0, max_output_chars)
4143
4943
  );
4144
4944
  // Structured queries. These go through the generic /retrieve action
4145
4945
  // dispatch rather than the SDK's no-argument convenience methods — the
@@ -4157,7 +4957,7 @@ async function handleRetrieve(client2, args) {
4157
4957
  offset,
4158
4958
  agent_id
4159
4959
  }),
4160
- (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
4960
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit, max_output_chars)
4161
4961
  );
4162
4962
  case "stale_derivations":
4163
4963
  return rendered2(
@@ -4166,7 +4966,7 @@ async function handleRetrieve(client2, args) {
4166
4966
  limit,
4167
4967
  offset
4168
4968
  }),
4169
- (raw) => renderNodeList(raw, "stale derivations", limit)
4969
+ (raw) => renderNodeList(raw, "stale derivations", limit, max_output_chars)
4170
4970
  );
4171
4971
  case "preferences":
4172
4972
  return rendered2(
@@ -4176,7 +4976,7 @@ async function handleRetrieve(client2, args) {
4176
4976
  limit,
4177
4977
  agent_id
4178
4978
  }),
4179
- (raw) => renderNodeList(raw, "Preferences", limit)
4979
+ (raw) => renderNodeList(raw, "Preferences", limit, max_output_chars)
4180
4980
  );
4181
4981
  case "layer":
4182
4982
  if (!layer) return err3("layer is required for layer retrieval");
@@ -4188,7 +4988,7 @@ async function handleRetrieve(client2, args) {
4188
4988
  node_types,
4189
4989
  agent_id
4190
4990
  }),
4191
- (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
4991
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit, max_output_chars)
4192
4992
  );
4193
4993
  case "recent":
4194
4994
  return rendered2(
@@ -4202,7 +5002,7 @@ async function handleRetrieve(client2, args) {
4202
5002
  created_before,
4203
5003
  agent_id
4204
5004
  }),
4205
- (raw) => renderNodeList(raw, "Recently ingested", limit)
5005
+ (raw) => renderNodeList(raw, "Recently ingested", limit, max_output_chars)
4206
5006
  );
4207
5007
  // Document inventory
4208
5008
  case "document_index":
@@ -4211,7 +5011,7 @@ async function handleRetrieve(client2, args) {
4211
5011
  node_type: "Document",
4212
5012
  limit: limit ?? 100
4213
5013
  }),
4214
- (raw) => renderNodeList(raw, "Ingested documents")
5014
+ (raw) => renderNodeList(raw, "Ingested documents", void 0, max_output_chars)
4215
5015
  );
4216
5016
  // Graph traversal
4217
5017
  case "chain":
@@ -4225,7 +5025,7 @@ async function handleRetrieve(client2, args) {
4225
5025
  include_provenance,
4226
5026
  max_nodes
4227
5027
  }),
4228
- renderTraversal
5028
+ (raw) => renderTraversal(raw, max_output_chars)
4229
5029
  );
4230
5030
  case "neighborhood":
4231
5031
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
@@ -4240,7 +5040,7 @@ async function handleRetrieve(client2, args) {
4240
5040
  include_provenance,
4241
5041
  max_nodes
4242
5042
  }),
4243
- renderTraversal
5043
+ (raw) => renderTraversal(raw, max_output_chars)
4244
5044
  );
4245
5045
  case "path":
4246
5046
  if (!start_uid) return err3("start_uid is required for path traversal");
@@ -4257,7 +5057,7 @@ async function handleRetrieve(client2, args) {
4257
5057
  include_provenance,
4258
5058
  max_nodes
4259
5059
  }),
4260
- renderTraversal
5060
+ (raw) => renderTraversal(raw, max_output_chars)
4261
5061
  );
4262
5062
  case "subgraph":
4263
5063
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
@@ -4272,7 +5072,7 @@ async function handleRetrieve(client2, args) {
4272
5072
  include_provenance,
4273
5073
  max_nodes
4274
5074
  }),
4275
- renderTraversal
5075
+ (raw) => renderTraversal(raw, max_output_chars)
4276
5076
  );
4277
5077
  case "top_k_paths":
4278
5078
  if (!start_uid) return err3("start_uid is required for top_k_paths");
@@ -4288,7 +5088,7 @@ async function handleRetrieve(client2, args) {
4288
5088
  edge_types,
4289
5089
  exclude_edge_types
4290
5090
  }),
4291
- renderTraversal
5091
+ (raw) => renderTraversal(raw, max_output_chars)
4292
5092
  );
4293
5093
  default:
4294
5094
  return err3(`Unknown retrieve action: ${action}`);
@@ -4544,15 +5344,44 @@ async function handleOntology(client2, args) {
4544
5344
  return err3(`Unknown ontology action: ${action}`);
4545
5345
  }
4546
5346
  }
4547
- var TOOLS, INVOCATION_CONTEXT_SCHEMA;
5347
+ var import_node_crypto8, RESOLUTION_PROPERTIES, TOOLS, INVOCATION_CONTEXT_SCHEMA;
4548
5348
  var init_tools = __esm({
4549
5349
  "src/tools.ts"() {
4550
5350
  "use strict";
5351
+ import_node_crypto8 = require("crypto");
4551
5352
  init_error_detail();
4552
5353
  init_code_tool();
4553
5354
  init_codegraph();
4554
5355
  init_sync_tool();
5356
+ init_memory_tool();
4555
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
+ };
4556
5385
  TOOLS = [
4557
5386
  {
4558
5387
  name: "mindgraph_capture",
@@ -4617,14 +5446,7 @@ var init_tools = __esm({
4617
5446
  type: "string",
4618
5447
  description: "Material Execution that produced the lesson"
4619
5448
  },
4620
- idempotency_key: {
4621
- type: "string",
4622
- description: "Stable retry key for an intentional capture"
4623
- },
4624
- supersedes_uid: {
4625
- type: "string",
4626
- description: "Earlier capture corrected or superseded by this one"
4627
- },
5449
+ ...RESOLUTION_PROPERTIES,
4628
5450
  summarizes_uids: {
4629
5451
  type: "array",
4630
5452
  items: { type: "string" },
@@ -4722,6 +5544,7 @@ var init_tools = __esm({
4722
5544
  inputSchema: {
4723
5545
  type: "object",
4724
5546
  properties: {
5547
+ ...RESOLUTION_PROPERTIES,
4725
5548
  action: {
4726
5549
  type: "string",
4727
5550
  enum: [
@@ -4816,6 +5639,7 @@ var init_tools = __esm({
4816
5639
  inputSchema: {
4817
5640
  type: "object",
4818
5641
  properties: {
5642
+ ...RESOLUTION_PROPERTIES,
4819
5643
  action: {
4820
5644
  type: "string",
4821
5645
  enum: [
@@ -5174,6 +5998,12 @@ var init_tools = __esm({
5174
5998
  enum: ["text", "json"],
5175
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)."
5176
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
+ },
5177
6007
  query: {
5178
6008
  type: "string",
5179
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."
@@ -5600,6 +6430,7 @@ var init_tools = __esm({
5600
6430
  required: ["action"]
5601
6431
  }
5602
6432
  },
6433
+ MEMORY_TOOL,
5603
6434
  SYNC_TOOL,
5604
6435
  CODE_TOOL
5605
6436
  ];
@@ -5620,7 +6451,7 @@ var init_tools = __esm({
5620
6451
  }
5621
6452
  };
5622
6453
  for (const tool of TOOLS) {
5623
- if (tool.name === "mindgraph_retrieve") continue;
6454
+ if (tool.name === "mindgraph_retrieve" || tool.name === "mindgraph_memory") continue;
5624
6455
  const schema = tool.inputSchema;
5625
6456
  if (schema.properties && !schema.properties.invocation_context) {
5626
6457
  schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
@@ -5674,8 +6505,8 @@ async function getGeneratedTools(client2) {
5674
6505
  cache = { tools, byName, fetchedAt: Date.now() };
5675
6506
  retryNotBefore = 0;
5676
6507
  return { tools, byName };
5677
- } catch (error) {
5678
- if (error instanceof DuplicateGeneratedToolError) throw error;
6508
+ } catch (error2) {
6509
+ if (error2 instanceof DuplicateGeneratedToolError) throw error2;
5679
6510
  retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
5680
6511
  return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
5681
6512
  }
@@ -5788,8 +6619,8 @@ var init_generated_tools = __esm({
5788
6619
  });
5789
6620
  rendered = (format, raw, render) => {
5790
6621
  if (format === "json") return ok4(raw);
5791
- const text = render(raw);
5792
- 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);
5793
6624
  };
5794
6625
  err4 = (message) => ({
5795
6626
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
@@ -5838,6 +6669,7 @@ function unsupportedResponse(status, body) {
5838
6669
  return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
5839
6670
  }
5840
6671
  function toolMutability(toolName, action) {
6672
+ if (toolName === "mindgraph_memory") return "read";
5841
6673
  if (toolName === "mindgraph_retrieve") return "read";
5842
6674
  if (toolName === "mindgraph_series_query") return "read";
5843
6675
  if (toolName === "mindgraph_plan") {
@@ -5921,8 +6753,8 @@ async function checkMcpGovernance(toolName, args, config) {
5921
6753
  response = candidate;
5922
6754
  body = candidateBody;
5923
6755
  break;
5924
- } catch (error) {
5925
- lastFailure = error instanceof Error ? error.message : String(error);
6756
+ } catch (error2) {
6757
+ lastFailure = error2 instanceof Error ? error2.message : String(error2);
5926
6758
  if (attempt < retryDelays.length) {
5927
6759
  await sleep(retryDelays[attempt]);
5928
6760
  continue;
@@ -6067,8 +6899,10 @@ function json(uri, data) {
6067
6899
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
6068
6900
  };
6069
6901
  }
6070
- async function readResource(client2, uri) {
6902
+ async function readResource(client2, uri, memoryConfig = {}) {
6071
6903
  switch (uri) {
6904
+ case "mindgraph://memory/status":
6905
+ return json(uri, memoryStatus(memoryConfig));
6072
6906
  case "mindgraph://stats":
6073
6907
  return json(uri, await client2.stats());
6074
6908
  case "mindgraph://goals":
@@ -6100,23 +6934,32 @@ async function readResource(client2, uri) {
6100
6934
  }
6101
6935
  throw new Error(`Unknown resource: ${uri}`);
6102
6936
  }
6103
- 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" } : {};
6104
6940
  const governance = await checkMcpGovernance(
6105
- RESOURCE_GOVERNANCE_TOOL,
6106
- {},
6941
+ toolName,
6942
+ toolArgs,
6107
6943
  { ...governanceConfig, context: { resource_uri: uri } }
6108
6944
  );
6109
6945
  if (!governance.allowed) {
6110
6946
  throw new Error(governance.message);
6111
6947
  }
6112
- return readResource(client2, uri);
6948
+ return readResource(client2, uri, memoryConfig);
6113
6949
  }
6114
6950
  var RESOURCES, RESOURCE_TEMPLATES, RESOURCE_GOVERNANCE_TOOL;
6115
6951
  var init_resources = __esm({
6116
6952
  "src/resources.ts"() {
6117
6953
  "use strict";
6118
6954
  init_governance();
6955
+ init_memory_tool();
6119
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
+ },
6120
6963
  {
6121
6964
  uri: "mindgraph://stats",
6122
6965
  name: "Graph Statistics",
@@ -6178,10 +7021,10 @@ async function main() {
6178
7021
  const transport = new import_stdio.StdioServerTransport();
6179
7022
  await server.connect(transport);
6180
7023
  console.error(
6181
- `MindGraph MCP server v${"0.19.0"} running on stdio (${BASE_URL})`
7024
+ `MindGraph MCP server v${"0.20.0"} running on stdio (${BASE_URL})`
6182
7025
  );
6183
7026
  }
6184
- var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, PROMPTS;
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;
6185
7028
  var init_src = __esm({
6186
7029
  "src/index.ts"() {
6187
7030
  "use strict";
@@ -6219,56 +7062,29 @@ var init_src = __esm({
6219
7062
  });
6220
7063
  INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
6221
7064
 
6222
- ## #1 RULE: Go straight to the user's topic
6223
-
6224
- 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.
6225
-
6226
- **NEVER do any of these before addressing the user's topic:**
6227
- - Do NOT retrieve active_goals, open_questions, contradictions, or weak_claims
6228
- - Do NOT follow a "session lifecycle" or "warm-up" sequence
6229
- - Do NOT make multiple serial retrieve calls when one "context" call covers it
6230
-
6231
- There is no session to open. There is no context to pre-load. Just search for what the user asked about.
7065
+ ## Default memory workflow: orient \u2192 answer \u2192 preserve
6232
7066
 
6233
- **Example:** User says "Tell me about Indian philosophy and Taoism"
6234
- \u2713 CORRECT: \`mindgraph_retrieve({action: "context", query: "Indian philosophy Taoism"})\`
6235
- \u2717 WRONG: First retrieve active_goals, then open_questions, then search
6236
- \u2717 WRONG: \`mindgraph_retrieve({action: "context", query: "Tell me about Indian philosophy and Taoism"})\` \u2014 natural language won't match BM25
7067
+ **Orient only when prior durable state may matter.** Self-contained questions do not require a memory call or a warm-up sequence.
6237
7068
 
6238
- ## Two-phase retrieval protocol
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.
6239
7073
 
6240
- **Phase 1 \u2014 Orient (default, fast, lightweight):**
6241
- 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.
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.
6242
7075
 
6243
- **Phase 2 \u2014 Fetch source text (only when needed):**
6244
- 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.
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.
6245
7077
 
6246
- **Orientation shortcut:**
6247
- - \`action: "document_index"\` \u2014 list all ingested documents (titles, dates, UIDs) to see what's in the graph
7078
+ ## Topic retrieval details
6248
7079
 
6249
- ## Search strategy \u2014 keyword first, semantic as fallback
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.
6250
7081
 
6251
- The default search uses BM25 keyword matching. Query with 1\u20133 discriminating terms (proper nouns, technical terms). Drop filler words.
7082
+ ## Comprehensive read tools
6252
7083
 
6253
- **Keyword examples:**
6254
- - "What is Kissinger's view on NATO?" \u2192 query: \`"Kissinger NATO"\`
6255
- - "Tell me about async in Rust" \u2192 query: \`"Rust async runtime"\`
6256
- - "Implications of carbon tax?" \u2192 query: \`"carbon tax"\`
6257
-
6258
- If keyword search returns no results, escalate:
6259
- 1. Try synonyms (e.g. \`"global warming"\` instead of \`"climate change"\`)
6260
- 2. Use \`action: "semantic"\` \u2014 vector similarity, natural language OK (e.g. \`"ways to manage stress"\` finds "mental health" nodes)
6261
- 3. Use \`action: "hybrid"\` \u2014 combines BM25 + semantic for best coverage
6262
-
6263
- ## When to READ
6264
-
6265
- - User asks about topic X \u2192 \`mindgraph_retrieve\` action "context", query "X keywords"
6266
- - User asks "what documents are in my graph?" \u2192 action "document_index"
6267
- - User explicitly asks "what are my goals?" \u2192 action "active_goals"
6268
- - User explicitly asks about open questions \u2192 action "open_questions"
6269
- - 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
6270
- - Explore around a known node \u2192 action "neighborhood" with start_uid
6271
- - 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
6272
7088
 
6273
7089
  ## When to WRITE
6274
7090
 
@@ -6303,13 +7119,14 @@ Capture knowledge when the user shares something worth remembering:
6303
7119
 
6304
7120
  ## Coding profile
6305
7121
 
6306
- **Recall order (overrides the #1 rule above for work and code questions):**
6307
- - "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).
6308
7124
  - Knowledge about specific code (a function, file, or route): \`mindgraph_code\` action \`recall\` \u2014 anchor-based, not keyword-based.
6309
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.
6310
7126
 
6311
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.
6312
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.
6313
7130
  - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
6314
7131
  - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
6315
7132
  } else {
@@ -6319,7 +7136,7 @@ Capture knowledge when the user shares something worth remembering:
6319
7136
  }
6320
7137
  RUNNER_REFRESH = (() => {
6321
7138
  try {
6322
- return refreshHookRunner("0.19.0", import_node_path6.default.join(__dirname, "cli.js"));
7139
+ return refreshHookRunner("0.20.0", import_node_path6.default.join(__dirname, "cli.js"));
6323
7140
  } catch {
6324
7141
  return void 0;
6325
7142
  }
@@ -6333,12 +7150,12 @@ Capture knowledge when the user shares something worth remembering:
6333
7150
  }
6334
7151
  })();
6335
7152
  console.error(
6336
- `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.19.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
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(", ")}` : "")
6337
7154
  );
6338
7155
  }
6339
7156
  HOOK_SKEW_NOTE = (() => {
6340
7157
  try {
6341
- return versionSkewNote("0.19.0");
7158
+ return versionSkewNote("0.20.0");
6342
7159
  } catch {
6343
7160
  return void 0;
6344
7161
  }
@@ -6352,7 +7169,7 @@ ${HOOK_SKEW_NOTE}`;
6352
7169
  server = new import_server.Server(
6353
7170
  {
6354
7171
  name: "mindgraph",
6355
- version: "0.19.0"
7172
+ version: "0.20.0"
6356
7173
  },
6357
7174
  {
6358
7175
  capabilities: {
@@ -6376,6 +7193,14 @@ ${HOOK_SKEW_NOTE}`;
6376
7193
  return process.env.MINDGRAPH_GOVERNANCE === "off";
6377
7194
  }
6378
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
+ };
6379
7204
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
6380
7205
  const { name, arguments: args } = request.params;
6381
7206
  const rawArgs = { ...args || {} };
@@ -6395,7 +7220,7 @@ ${HOOK_SKEW_NOTE}`;
6395
7220
  if (desc) {
6396
7221
  return handleGeneratedTool(client, desc, rawArgs);
6397
7222
  }
6398
- return handleTool(client, name, toolArgs);
7223
+ return handleTool(client, name, toolArgs, MEMORY_RUNTIME_CONFIG);
6399
7224
  });
6400
7225
  PROMPTS = [
6401
7226
  {
@@ -6560,10 +7385,15 @@ ${conversation}`
6560
7385
  return { resourceTemplates: RESOURCE_TEMPLATES };
6561
7386
  });
6562
7387
  server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
6563
- 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
+ );
6564
7394
  });
6565
- main().catch((error) => {
6566
- console.error("Fatal error:", error);
7395
+ main().catch((error2) => {
7396
+ console.error("Fatal error:", error2);
6567
7397
  process.exit(1);
6568
7398
  });
6569
7399
  }
@@ -6761,12 +7591,12 @@ function scalar(raw) {
6761
7591
  }
6762
7592
  return trimmed;
6763
7593
  }
6764
- function parseSkillMd(text) {
6765
- if (!text.startsWith("---\n")) return null;
6766
- const end = text.indexOf("\n---\n", 4);
7594
+ function parseSkillMd(text2) {
7595
+ if (!text2.startsWith("---\n")) return null;
7596
+ const end = text2.indexOf("\n---\n", 4);
6767
7597
  if (end < 0) return null;
6768
- const frontmatter = text.slice(4, end + 1);
6769
- const body = text.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
7598
+ const frontmatter = text2.slice(4, end + 1);
7599
+ const body = text2.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
6770
7600
  const entries = [];
6771
7601
  for (const line of frontmatter.split("\n")) {
6772
7602
  const top = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
@@ -6812,8 +7642,8 @@ ${line}`;
6812
7642
  return result;
6813
7643
  }
6814
7644
  var STATE_FILE = ".mindgraph-skills-state.json";
6815
- function sha256(text) {
6816
- return (0, import_node_crypto2.createHash)("sha256").update(text).digest("hex");
7645
+ function sha256(text2) {
7646
+ return (0, import_node_crypto2.createHash)("sha256").update(text2).digest("hex");
6817
7647
  }
6818
7648
  function loadState(dir) {
6819
7649
  try {
@@ -6959,13 +7789,13 @@ async function skillsPush(api, skillPath) {
6959
7789
  if (isSymlink(file)) {
6960
7790
  return { outcome: "error", message: `${file} is a symlink; refusing` };
6961
7791
  }
6962
- let text;
7792
+ let text2;
6963
7793
  try {
6964
- text = fs2.readFileSync(file, "utf8");
7794
+ text2 = fs2.readFileSync(file, "utf8");
6965
7795
  } catch {
6966
7796
  return { outcome: "error", message: `could not read ${file}` };
6967
7797
  }
6968
- const parsed = parseSkillMd(text);
7798
+ const parsed = parseSkillMd(text2);
6969
7799
  if (!parsed) {
6970
7800
  return {
6971
7801
  outcome: "error",
@@ -7083,8 +7913,8 @@ function withLedger(sessionId, options, update) {
7083
7913
  try {
7084
7914
  lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
7085
7915
  break;
7086
- } catch (error) {
7087
- if (error.code !== "EEXIST") throw error;
7916
+ } catch (error2) {
7917
+ if (error2.code !== "EEXIST") throw error2;
7088
7918
  try {
7089
7919
  if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 3e3) import_node_fs3.default.unlinkSync(lock);
7090
7920
  } catch {
@@ -7189,11 +8019,11 @@ var BRIEF_CHAR_BUDGET = 9e3;
7189
8019
  function renderBrief(brief, meta) {
7190
8020
  let bounded = false;
7191
8021
  const clipTracked = (value, max) => {
7192
- const text = string(value);
7193
- if (!text) return void 0;
7194
- if (text.length <= max) return text;
8022
+ const text2 = string(value);
8023
+ if (!text2) return void 0;
8024
+ if (text2.length <= max) return text2;
7195
8025
  bounded = true;
7196
- return `${text.slice(0, max - 1)}\u2026`;
8026
+ return `${text2.slice(0, max - 1)}\u2026`;
7197
8027
  };
7198
8028
  const nodeLine = (value, summaryMax) => {
7199
8029
  const node = object(value);
@@ -7352,9 +8182,9 @@ async function repositoryScopeUids(input, client2, options, agentId) {
7352
8182
  `${repository.repoId}: ${string(payload?.status) || "unresolved"}`
7353
8183
  );
7354
8184
  return void 0;
7355
- } catch (error) {
8185
+ } catch (error2) {
7356
8186
  failures.push(
7357
- `${repository.repoId}: ${error?.message || "resolve failed"}`
8187
+ `${repository.repoId}: ${error2?.message || "resolve failed"}`
7358
8188
  );
7359
8189
  return void 0;
7360
8190
  }
@@ -7378,12 +8208,12 @@ function hasMindgraphServer(servers) {
7378
8208
  for (const [name, entry] of Object.entries(servers)) {
7379
8209
  if (name === "mindgraph") return true;
7380
8210
  const cfg = entry && typeof entry === "object" ? entry : void 0;
7381
- const text = [
8211
+ const text2 = [
7382
8212
  String(cfg?.command ?? ""),
7383
8213
  Array.isArray(cfg?.args) ? cfg.args.join(" ") : "",
7384
8214
  String(cfg?.url ?? "")
7385
8215
  ].join(" ");
7386
- if (text.includes("mindgraph-mcp")) return true;
8216
+ if (text2.includes("mindgraph-mcp")) return true;
7387
8217
  }
7388
8218
  return false;
7389
8219
  }
@@ -7613,7 +8443,7 @@ async function postTool(input, client2, options) {
7613
8443
  ledger.mutatingOperations += 1;
7614
8444
  }
7615
8445
  if (!payload?.error) {
7616
- if (name.includes("mindgraph_capture") && ["lesson", "journal", "skill"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
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") {
7617
8447
  ledger.memoryWritten = true;
7618
8448
  }
7619
8449
  }
@@ -7894,7 +8724,7 @@ async function runCodexHook(input, client2, options = {}) {
7894
8724
 
7895
8725
  // src/cli.ts
7896
8726
  init_hook_installer();
7897
- function recordHookHealth(harness, error) {
8727
+ function recordHookHealth(harness, error2) {
7898
8728
  try {
7899
8729
  const dir = process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime");
7900
8730
  fs6.mkdirSync(dir, { recursive: true, mode: 448 });
@@ -7908,7 +8738,7 @@ function recordHookHealth(harness, error) {
7908
8738
  file,
7909
8739
  `${JSON.stringify({
7910
8740
  harness,
7911
- lastError: error.slice(0, 500),
8741
+ lastError: error2.slice(0, 500),
7912
8742
  lastFailureAt: (/* @__PURE__ */ new Date()).toISOString(),
7913
8743
  consecutiveFailures: failures + 1
7914
8744
  })}
@@ -8131,7 +8961,7 @@ function withHooksFlag() {
8131
8961
  }
8132
8962
  function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
8133
8963
  if (withHooks) {
8134
- const runner = installHookRunner(__filename, void 0, "0.19.0");
8964
+ const runner = installHookRunner(__filename, void 0, "0.20.0");
8135
8965
  console.log(`Installed pinned hook runner at ${runner}`);
8136
8966
  const result = installClaudeHooks("user");
8137
8967
  console.log(
@@ -8446,7 +9276,7 @@ ${total} skill(s) synced.`);
8446
9276
  if (hookHarness !== "claude-code" && hookHarness !== "codex") {
8447
9277
  throw new Error("--harness must be claude-code or codex");
8448
9278
  }
8449
- const runner = installHookRunner(__filename, void 0, "0.19.0");
9279
+ const runner = installHookRunner(__filename, void 0, "0.20.0");
8450
9280
  console.log(`Installed pinned hook runner at ${runner}`);
8451
9281
  const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
8452
9282
  console.log(
@@ -8527,13 +9357,13 @@ ${total} skill(s) synced.`);
8527
9357
  process.stdout.write(`${JSON.stringify(output)}
8528
9358
  `);
8529
9359
  clearHookHealth(hookHarness);
8530
- } catch (error) {
9360
+ } catch (error2) {
8531
9361
  recordHookHealth(
8532
9362
  hookHarness,
8533
- error?.message || "hook failed"
9363
+ error2?.message || "hook failed"
8534
9364
  );
8535
9365
  console.error(
8536
- `mindgraph-hook: failing open (${error?.message || "unknown error"})`
9366
+ `mindgraph-hook: failing open (${error2?.message || "unknown error"})`
8537
9367
  );
8538
9368
  process.stdout.write("{}\n");
8539
9369
  }
@@ -8552,7 +9382,7 @@ ${total} skill(s) synced.`);
8552
9382
  process.exit(1);
8553
9383
  }
8554
9384
  }
8555
- main2().catch((error) => {
8556
- console.error("Error:", error.message || error);
9385
+ main2().catch((error2) => {
9386
+ console.error("Error:", error2.message || error2);
8557
9387
  process.exit(1);
8558
9388
  });