memorix 1.5.0 → 1.6.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 (49) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +3 -3
  3. package/README.zh-CN.md +3 -3
  4. package/dist/cli/index.js +452 -109
  5. package/dist/index.js +376 -87
  6. package/dist/maintenance-runner.js +174 -41
  7. package/dist/media-audio-runner.js +174 -41
  8. package/dist/media-video-runner.js +174 -41
  9. package/dist/memcode-runtime/CHANGELOG.md +37 -0
  10. package/dist/sdk.js +375 -86
  11. package/dist/types.d.ts +2 -0
  12. package/dist/vector-backfill-runner.js +174 -41
  13. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  14. package/docs/API_REFERENCE.md +1 -1
  15. package/docs/DEVELOPMENT.md +5 -3
  16. package/package.json +1 -1
  17. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  18. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  19. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  20. package/plugins/copilot/memorix/plugin.json +1 -1
  21. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  22. package/plugins/omp/memorix/package.json +1 -1
  23. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  24. package/plugins/pi/memorix/package.json +1 -1
  25. package/server.json +2 -2
  26. package/src/cli/commands/codegraph.ts +48 -5
  27. package/src/cli/commands/context.ts +20 -4
  28. package/src/cli/commands/resume.ts +4 -0
  29. package/src/codegraph/auto-context.ts +4 -1
  30. package/src/codegraph/context-pack.ts +3 -1
  31. package/src/codegraph/external-provider.ts +93 -0
  32. package/src/embedding/api-provider.ts +105 -22
  33. package/src/embedding/provider.ts +2 -0
  34. package/src/hooks/installers/index.ts +1 -1
  35. package/src/hooks/official-skills.ts +3 -3
  36. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  37. package/src/knowledge/context-receipt.ts +41 -0
  38. package/src/knowledge/workflows.ts +6 -3
  39. package/src/knowledge/workset.ts +56 -2
  40. package/src/llm/provider.ts +17 -8
  41. package/src/memory/formation/extract.ts +6 -3
  42. package/src/memory/formation/index.ts +2 -1
  43. package/src/memory/formation/resolve.ts +10 -6
  44. package/src/memory/formation/types.ts +3 -1
  45. package/src/memory/long-term.ts +7 -1
  46. package/src/server.ts +29 -13
  47. package/src/store/orama-store.ts +6 -4
  48. package/src/timeout.ts +67 -0
  49. package/src/types.ts +2 -0
package/dist/index.js CHANGED
@@ -3987,6 +3987,8 @@ function getPreferredBatchSize(config) {
3987
3987
  }
3988
3988
  function parseBatchLimit(error) {
3989
3989
  if (!(error instanceof Error)) return null;
3990
+ const status = error instanceof EmbeddingAPIError ? error.status : Number(error.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
3991
+ if (status > 0 && status !== 400) return null;
3990
3992
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
3991
3993
  if (explicit) return parseInt(explicit[1], 10);
3992
3994
  if (/batch size/i.test(error.message)) {
@@ -3999,37 +4001,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
3999
4001
  const controller = new AbortController();
4000
4002
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
4001
4003
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
4004
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
4002
4005
  let response;
4003
4006
  try {
4004
4007
  response = await fetch(url, {
4005
4008
  method: "POST",
4006
4009
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
4007
4010
  body: JSON.stringify(body),
4008
- signal: controller.signal
4011
+ signal
4009
4012
  });
4010
4013
  } catch (err) {
4011
4014
  clearTimeout(timeout);
4012
- if (err instanceof Error && err.name === "AbortError") {
4015
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4013
4016
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4014
4017
  }
4015
4018
  throw err;
4016
4019
  }
4017
- clearTimeout(timeout);
4018
- if (response.ok) {
4019
- return response.json();
4020
+ try {
4021
+ if (response.ok) {
4022
+ return await raceWithAbort(
4023
+ response.json(),
4024
+ signal
4025
+ );
4026
+ }
4027
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4028
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4029
+ const retryAfter = response.headers.get("retry-after");
4030
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
4031
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
4032
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
4033
+ await waitForRetry(waitMs, options.signal);
4034
+ return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4035
+ }
4036
+ const errorText2 = await raceWithAbort(
4037
+ response.text().catch(() => "unknown error"),
4038
+ signal
4039
+ );
4040
+ throw new EmbeddingAPIError(response.status, errorText2);
4041
+ } catch (err) {
4042
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4043
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4044
+ }
4045
+ throw err;
4046
+ } finally {
4047
+ clearTimeout(timeout);
4020
4048
  }
4021
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4022
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4023
- const retryAfter = response.headers.get("retry-after");
4024
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
4025
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
4049
+ }
4050
+ function abortError() {
4051
+ const error = new Error("Embedding API request aborted");
4052
+ error.name = "AbortError";
4053
+ return error;
4054
+ }
4055
+ function raceWithAbort(promise, signal) {
4056
+ if (signal.aborted) return Promise.reject(abortError());
4057
+ return new Promise((resolve2, reject) => {
4058
+ const onAbort = () => {
4059
+ signal.removeEventListener("abort", onAbort);
4060
+ reject(abortError());
4061
+ };
4062
+ signal.addEventListener("abort", onAbort, { once: true });
4063
+ promise.then(
4064
+ (value) => {
4065
+ signal.removeEventListener("abort", onAbort);
4066
+ resolve2(value);
4067
+ },
4068
+ (error) => {
4069
+ signal.removeEventListener("abort", onAbort);
4070
+ reject(error);
4071
+ }
4072
+ );
4073
+ });
4074
+ }
4075
+ async function waitForRetry(waitMs, signal) {
4076
+ if (!signal) {
4026
4077
  await new Promise((resolve2) => setTimeout(resolve2, waitMs));
4027
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4078
+ return;
4028
4079
  }
4029
- const errorText2 = await response.text().catch(() => "unknown error");
4030
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
4080
+ const abortSignal = signal;
4081
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
4082
+ await new Promise((resolve2, reject) => {
4083
+ const timer = setTimeout(done, waitMs);
4084
+ const onAbort = () => {
4085
+ clearTimeout(timer);
4086
+ abortSignal.removeEventListener("abort", onAbort);
4087
+ reject(new Error("Embedding API retry aborted"));
4088
+ };
4089
+ function done() {
4090
+ abortSignal.removeEventListener("abort", onAbort);
4091
+ resolve2();
4092
+ }
4093
+ abortSignal.addEventListener("abort", onAbort, { once: true });
4094
+ });
4031
4095
  }
4032
- var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, APIEmbeddingProvider;
4096
+ var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
4033
4097
  var init_api_provider = __esm({
4034
4098
  "src/embedding/api-provider.ts"() {
4035
4099
  "use strict";
@@ -4048,6 +4112,14 @@ var init_api_provider = __esm({
4048
4112
  DASHSCOPE_MAX_BATCH_SIZE = 10;
4049
4113
  MAX_RETRIES2 = 3;
4050
4114
  BASE_DELAY_MS = 500;
4115
+ EmbeddingAPIError = class extends Error {
4116
+ status;
4117
+ constructor(status, detail) {
4118
+ super(`Embedding API error (${status}): ${detail}`);
4119
+ this.name = "EmbeddingAPIError";
4120
+ this.status = status;
4121
+ }
4122
+ };
4051
4123
  APIEmbeddingProvider = class _APIEmbeddingProvider {
4052
4124
  name;
4053
4125
  dimensions;
@@ -4259,15 +4331,14 @@ var init_api_provider = __esm({
4259
4331
  }
4260
4332
  } catch (error) {
4261
4333
  const providerLimit = parseBatchLimit(error);
4262
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
4263
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
4334
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
4264
4335
  console.error(
4265
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
4336
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
4266
4337
  );
4267
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
4338
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
4268
4339
  await processChunk(
4269
- chunkTexts.slice(start, start + fallbackSize),
4270
- chunkIndices.slice(start, start + fallbackSize)
4340
+ chunkTexts.slice(start, start + providerLimit),
4341
+ chunkIndices.slice(start, start + providerLimit)
4271
4342
  );
4272
4343
  }
4273
4344
  return;
@@ -5131,22 +5202,23 @@ function getLLMConfig() {
5131
5202
  function setLLMConfig(config) {
5132
5203
  currentConfig = config;
5133
5204
  }
5134
- async function callLLM(systemPrompt, userMessage) {
5205
+ async function callLLM(systemPrompt, userMessage, signal) {
5135
5206
  if (!currentConfig) {
5136
5207
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
5137
5208
  }
5138
5209
  if (currentConfig.provider === "anthropic") {
5139
- return callAnthropic(systemPrompt, userMessage);
5210
+ return callAnthropic(systemPrompt, userMessage, signal);
5140
5211
  }
5141
- return callOpenAICompatible(systemPrompt, userMessage);
5212
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
5142
5213
  }
5143
- async function callOpenAICompatible(systemPrompt, userMessage) {
5214
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5144
5215
  const config = currentConfig;
5145
5216
  let base = config.baseUrl.replace(/\/+$/, "");
5146
5217
  if (!base.endsWith("/v1")) base += "/v1";
5147
5218
  const url = `${base}/chat/completions`;
5219
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5148
5220
  const response = await fetch(url, {
5149
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5221
+ signal: fetchSignal,
5150
5222
  method: "POST",
5151
5223
  headers: {
5152
5224
  "Content-Type": "application/json",
@@ -5163,10 +5235,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5163
5235
  })
5164
5236
  });
5165
5237
  if (!response.ok) {
5166
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5238
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5167
5239
  throw new Error(`LLM API error (${response.status}): ${error}`);
5168
5240
  }
5169
- const data = await readResponseJson(response);
5241
+ const data = await readResponseJson(response, signal);
5170
5242
  return {
5171
5243
  content: data.choices[0]?.message?.content ?? "",
5172
5244
  usage: data.usage ? {
@@ -5175,11 +5247,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5175
5247
  } : void 0
5176
5248
  };
5177
5249
  }
5178
- async function callAnthropic(systemPrompt, userMessage) {
5250
+ async function callAnthropic(systemPrompt, userMessage, signal) {
5179
5251
  const config = currentConfig;
5180
5252
  const url = `${config.baseUrl}/messages`;
5253
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5181
5254
  const response = await fetch(url, {
5182
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5255
+ signal: fetchSignal,
5183
5256
  method: "POST",
5184
5257
  headers: {
5185
5258
  "Content-Type": "application/json",
@@ -5197,10 +5270,10 @@ async function callAnthropic(systemPrompt, userMessage) {
5197
5270
  })
5198
5271
  });
5199
5272
  if (!response.ok) {
5200
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5273
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5201
5274
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
5202
5275
  }
5203
- const data = await readResponseJson(response);
5276
+ const data = await readResponseJson(response, signal);
5204
5277
  return {
5205
5278
  content: data.content[0]?.text ?? "",
5206
5279
  usage: data.usage ? {
@@ -5705,6 +5778,57 @@ function withTimeout(promise, ms, label) {
5705
5778
  );
5706
5779
  });
5707
5780
  }
5781
+ function withTimeoutSignal(operation, ms, label, parentSignal) {
5782
+ const controller = new AbortController();
5783
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
5784
+ return new Promise((resolve2, reject) => {
5785
+ let settled = false;
5786
+ let timedOut = false;
5787
+ let timer;
5788
+ const cleanup = () => {
5789
+ if (timer !== void 0) clearTimeout(timer);
5790
+ parentSignal?.removeEventListener("abort", onParentAbort);
5791
+ };
5792
+ const onParentAbort = () => {
5793
+ if (settled || timedOut) return;
5794
+ settled = true;
5795
+ cleanup();
5796
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
5797
+ };
5798
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
5799
+ timer = setTimeout(() => {
5800
+ if (settled) return;
5801
+ timedOut = true;
5802
+ controller.abort(new Error(`${label} timed out after ${ms}ms`));
5803
+ settled = true;
5804
+ cleanup();
5805
+ reject(new Error(`${label} timed out after ${ms}ms`));
5806
+ }, ms);
5807
+ let pending;
5808
+ try {
5809
+ pending = operation(signal);
5810
+ } catch (error) {
5811
+ settled = true;
5812
+ cleanup();
5813
+ reject(error);
5814
+ return;
5815
+ }
5816
+ pending.then(
5817
+ (value) => {
5818
+ if (settled) return;
5819
+ settled = true;
5820
+ cleanup();
5821
+ resolve2(value);
5822
+ },
5823
+ (error) => {
5824
+ if (settled) return;
5825
+ settled = true;
5826
+ cleanup();
5827
+ reject(timedOut ? new Error(`${label} timed out after ${ms}ms`) : error);
5828
+ }
5829
+ );
5830
+ });
5831
+ }
5708
5832
  var init_timeout = __esm({
5709
5833
  "src/timeout.ts"() {
5710
5834
  "use strict";
@@ -6569,16 +6693,18 @@ async function searchObservations(options) {
6569
6693
  );
6570
6694
  } else {
6571
6695
  const EMBEDDING_TIMEOUT_MS = 15e3;
6572
- queryVector = await withTimeout(
6573
- provider2.embedInput ? provider2.embedInput(
6696
+ queryVector = await withTimeoutSignal(
6697
+ (signal) => provider2.embedInput ? provider2.embedInput(
6574
6698
  { modality: "text", text: expandedEmbeddingQuery },
6575
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
6699
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
6576
6700
  ) : provider2.embed(expandedEmbeddingQuery, {
6577
6701
  timeoutMs: EMBEDDING_TIMEOUT_MS,
6578
- retry: false
6702
+ retry: false,
6703
+ signal
6579
6704
  }),
6580
6705
  EMBEDDING_TIMEOUT_MS,
6581
- "Embedding"
6706
+ "Embedding",
6707
+ options.signal
6582
6708
  );
6583
6709
  mark("embedding");
6584
6710
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -144998,7 +145124,13 @@ async function maintainLongTermMemories(input) {
144998
145124
  }
144999
145125
  for (const group of groups.values()) {
145000
145126
  if (group.length < 2) continue;
145001
- group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
145127
+ group.sort((a, b) => {
145128
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
145129
+ if (byCreatedAt !== 0) return byCreatedAt;
145130
+ const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
145131
+ if (byUpdatedAt !== 0) return byUpdatedAt;
145132
+ return b.id.localeCompare(a.id);
145133
+ });
145002
145134
  const newest = group[0];
145003
145135
  for (const older of group.slice(1)) {
145004
145136
  try {
@@ -148726,6 +148858,7 @@ var init_task_lens = __esm({
148726
148858
  var workflows_exports = {};
148727
148859
  __export(workflows_exports, {
148728
148860
  WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
148861
+ WORKFLOW_AGENT_TARGETS: () => WORKFLOW_AGENT_TARGETS,
148729
148862
  applyWorkflowAdapter: () => applyWorkflowAdapter,
148730
148863
  importWindsurfWorkflows: () => importWindsurfWorkflows,
148731
148864
  parseWorkflowMarkdown: () => parseWorkflowMarkdown,
@@ -148791,7 +148924,7 @@ function optionalStatus(data) {
148791
148924
  return value;
148792
148925
  }
148793
148926
  function normalizeAgents(values) {
148794
- const unknown3 = values.filter((value) => !KNOWN_AGENTS.includes(value));
148927
+ const unknown3 = values.filter((value) => !WORKFLOW_AGENT_TARGETS.includes(value));
148795
148928
  if (unknown3.length > 0) {
148796
148929
  throw new Error("workflow frontmatter has unsupported agent: " + unknown3[0]);
148797
148930
  }
@@ -149067,7 +149200,7 @@ function taskScore(workflow, task) {
149067
149200
  return { score, reasons: [...new Set(reasons)] };
149068
149201
  }
149069
149202
  function selectWorkflows(input) {
149070
- const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
149203
+ const selected = input.workflows.filter((workflow) => workflow.status === "active" && (!input.agent || workflow.compatibleAgents.includes(input.agent))).map((workflow) => {
149071
149204
  const match2 = taskScore(workflow, input.task);
149072
149205
  const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
149073
149206
  return {
@@ -149347,7 +149480,7 @@ async function selectWorkspaceWorkflows(input) {
149347
149480
  errors: synced.errors
149348
149481
  };
149349
149482
  }
149350
- var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
149483
+ var WORKFLOW_AGENT_TARGETS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
149351
149484
  var init_workflows2 = __esm({
149352
149485
  "src/knowledge/workflows.ts"() {
149353
149486
  "use strict";
@@ -149359,7 +149492,7 @@ var init_workflows2 = __esm({
149359
149492
  init_workflow_store();
149360
149493
  init_outcome_store();
149361
149494
  init_task_lens();
149362
- KNOWN_AGENTS = [
149495
+ WORKFLOW_AGENT_TARGETS = [
149363
149496
  "windsurf",
149364
149497
  "cursor",
149365
149498
  "claude-code",
@@ -155333,13 +155466,13 @@ function verifyType(declaredType, narrative, title) {
155333
155466
  }
155334
155467
  return { type: declaredType, corrected: false };
155335
155468
  }
155336
- async function extractFactsWithLLM(narrative, title, existingFacts) {
155469
+ async function extractFactsWithLLM(narrative, title, existingFacts, signal) {
155337
155470
  try {
155338
155471
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
155339
155472
  const input = `Title: ${title}
155340
155473
  Content: ${narrative}${existingFacts.length > 0 ? `
155341
155474
  Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
155342
- const response = await callLLM2(LLM_EXTRACT_PROMPT, input);
155475
+ const response = await callLLM2(LLM_EXTRACT_PROMPT, input, signal);
155343
155476
  const text2 = response.content.trim();
155344
155477
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
155345
155478
  if (!jsonMatch) return [];
@@ -155348,15 +155481,16 @@ Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
155348
155481
  if (!Array.isArray(facts)) return [];
155349
155482
  const existingLower = new Set(existingFacts.map((f3) => f3.toLowerCase().trim()));
155350
155483
  return facts.filter((f3) => typeof f3 === "string" && f3.length >= 5).filter((f3) => !existingLower.has(f3.toLowerCase().trim())).slice(0, 10);
155351
- } catch {
155484
+ } catch (error) {
155485
+ if (signal?.aborted) throw error;
155352
155486
  return [];
155353
155487
  }
155354
155488
  }
155355
- async function runExtract(input, existingEntities, useLLM = false) {
155489
+ async function runExtract(input, existingEntities, useLLM = false, signal) {
155356
155490
  const callerFacts = input.facts ?? [];
155357
155491
  let extractedFacts;
155358
155492
  if (useLLM) {
155359
- extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts);
155493
+ extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts, signal);
155360
155494
  if (extractedFacts.length === 0) {
155361
155495
  extractedFacts = extractFacts2(input.narrative, callerFacts);
155362
155496
  }
@@ -155576,7 +155710,7 @@ function mergeFacts2(oldFacts, newFacts) {
155576
155710
  }
155577
155711
  return merged;
155578
155712
  }
155579
- async function resolveWithLLM(extracted, hits, getObservation2) {
155713
+ async function resolveWithLLM(extracted, hits, getObservation2, signal) {
155580
155714
  try {
155581
155715
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
155582
155716
  const existingMemories = hits.slice(0, 5).map((h2, i2) => ({
@@ -155593,7 +155727,7 @@ Facts: ${extracted.facts.join("; ")}
155593
155727
 
155594
155728
  EXISTING MEMORIES:
155595
155729
  ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Facts: ${m2.facts}`).join("\n")}`;
155596
- const response = await callLLM2(LLM_RESOLVE_PROMPT, input);
155730
+ const response = await callLLM2(LLM_RESOLVE_PROMPT, input, signal);
155597
155731
  const text2 = response.content.trim();
155598
155732
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
155599
155733
  if (!jsonMatch) return null;
@@ -155630,7 +155764,8 @@ ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Fact
155630
155764
  };
155631
155765
  }
155632
155766
  return null;
155633
- } catch {
155767
+ } catch (error) {
155768
+ if (signal?.aborted) throw error;
155634
155769
  return null;
155635
155770
  }
155636
155771
  }
@@ -155648,19 +155783,20 @@ function scoreCandidate(extracted, candidate) {
155648
155783
  const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
155649
155784
  return { score, searchSimilarity, entityMatch, richer, contradiction };
155650
155785
  }
155651
- async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
155786
+ async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false, signal) {
155652
155787
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
155653
155788
  let hits;
155654
155789
  try {
155655
- hits = await searchMemories(query, 5, projectId);
155656
- } catch {
155790
+ hits = await searchMemories(query, 5, projectId, signal);
155791
+ } catch (error) {
155792
+ if (signal?.aborted) throw error;
155657
155793
  return { action: "new", reason: "Search unavailable, defaulting to new" };
155658
155794
  }
155659
155795
  if (hits.length === 0) {
155660
155796
  return { action: "new", reason: "No similar existing memories found" };
155661
155797
  }
155662
155798
  if (useLLM) {
155663
- const llmResult = await resolveWithLLM(extracted, hits, getObservation2);
155799
+ const llmResult = await resolveWithLLM(extracted, hits, getObservation2, signal);
155664
155800
  if (llmResult) return llmResult;
155665
155801
  }
155666
155802
  const scored = hits.map((hit) => ({
@@ -156082,7 +156218,7 @@ async function runFormation(input, config) {
156082
156218
  const existingEntities = config.getEntityNames();
156083
156219
  const extractStartTime = Date.now();
156084
156220
  emitStageEvent("extract", "start");
156085
- const extraction = await runExtract(input, existingEntities, config.useLLM);
156221
+ const extraction = await runExtract(input, existingEntities, config.useLLM, config.signal);
156086
156222
  stageDurationsMs.extract = Date.now() - extractStartTime;
156087
156223
  emitStageEvent("extract", "success", stageDurationsMs.extract);
156088
156224
  stagesCompleted = 1;
@@ -156102,7 +156238,8 @@ async function runFormation(input, config) {
156102
156238
  input.projectId,
156103
156239
  config.searchMemories,
156104
156240
  config.getObservation,
156105
- config.useLLM
156241
+ config.useLLM,
156242
+ config.signal
156106
156243
  );
156107
156244
  stageDurationsMs.resolve = Date.now() - resolveStartTime;
156108
156245
  emitStageEvent("resolve", "success", stageDurationsMs.resolve);
@@ -156991,6 +157128,15 @@ function workflowOutput(selection) {
156991
157128
  cautions: selection.cautions.map((caution) => short(caution, 22))
156992
157129
  };
156993
157130
  }
157131
+ function workflowLoadout(agent, selections) {
157132
+ if (!agent) return void 0;
157133
+ return {
157134
+ agent,
157135
+ workflowIds: selections.map((selection) => selection.workflow.id),
157136
+ requiredContext: unique(selections.flatMap((selection) => selection.workflow.requiredContext)).map((item) => short(item, 18)).slice(0, 4),
157137
+ allowedTools: unique(selections.flatMap((selection) => selection.workflow.allowedTools)).map((item) => short(item, 12)).slice(0, 6)
157138
+ };
157139
+ }
156994
157140
  function appendLine(lines, candidate, maxTokens, omitted, omittedKind, selected, receiptSelection) {
156995
157141
  const next = lines.length ? lines.join("\n") + "\n" + candidate : candidate;
156996
157142
  if (countTextTokens(next) <= maxTokens) {
@@ -157451,6 +157597,22 @@ function renderTaskWorksetPrompt(input) {
157451
157597
  );
157452
157598
  }
157453
157599
  }
157600
+ if (input.agentLoadout) {
157601
+ appendLine(lines, "", maxTokens, omitted, "agent-loadout-heading");
157602
+ appendLine(lines, "Agent loadout (" + input.agentLoadout.agent + ")", maxTokens, omitted, "agent-loadout-heading");
157603
+ for (const required of input.agentLoadout.requiredContext.slice(0, 2)) {
157604
+ appendLine(lines, "- Need: " + required, maxTokens, omitted, "agent-loadout-context");
157605
+ }
157606
+ if (input.agentLoadout.allowedTools.length > 0) {
157607
+ appendLine(
157608
+ lines,
157609
+ "- Workflow tools: " + input.agentLoadout.allowedTools.join(", "),
157610
+ maxTokens,
157611
+ omitted,
157612
+ "agent-loadout-tools"
157613
+ );
157614
+ }
157615
+ }
157454
157616
  if (input.verification.length > 0) {
157455
157617
  appendLine(lines, "", maxTokens, omitted, "verification-heading");
157456
157618
  appendLine(lines, "Verify", maxTokens, omitted, "verification-heading");
@@ -157542,6 +157704,7 @@ async function buildTaskWorkset(input) {
157542
157704
  let workspace;
157543
157705
  let pages = [];
157544
157706
  let workflows = [];
157707
+ let workflowSelections = [];
157545
157708
  try {
157546
157709
  workspace = await preferredWorkspace(input.projectId, input.dataDir);
157547
157710
  if (workspace) {
@@ -157558,14 +157721,15 @@ async function buildTaskWorkset(input) {
157558
157721
  if (task) {
157559
157722
  const workflowStore = new WorkflowStore();
157560
157723
  await workflowStore.init(input.dataDir);
157561
- const selected = selectWorkflows({
157724
+ workflowSelections = selectWorkflows({
157562
157725
  workflows: workflowStore.listWorkflows(workspace.id, "active"),
157563
157726
  task,
157564
157727
  projectId: input.projectId,
157565
157728
  store: workflowStore,
157729
+ ...input.agent ? { agent: input.agent } : {},
157566
157730
  limit: 2
157567
157731
  });
157568
- workflows = selected.map(workflowOutput);
157732
+ workflows = workflowSelections.map(workflowOutput);
157569
157733
  for (const workflow of workflows) {
157570
157734
  for (const caution of workflow.cautions) {
157571
157735
  cautions.push({ kind: "workflow-failed-verification", message: caution });
@@ -157587,6 +157751,10 @@ async function buildTaskWorkset(input) {
157587
157751
  workflows
157588
157752
  });
157589
157753
  const governedDurableIds = new Set(governed.durableMemory.map((memory) => memory.id));
157754
+ const agentLoadout = workflowLoadout(
157755
+ input.agent,
157756
+ workflowSelections.filter((selection2) => governed.workflows.some((workflow) => workflow.id === selection2.workflow.id))
157757
+ );
157590
157758
  const evidenceIds = unique(governed.claims.flatMap((claim) => evidenceIdsForClaim(
157591
157759
  claim,
157592
157760
  evidenceByClaim.get(claim.id) ?? []
@@ -157631,6 +157799,7 @@ async function buildTaskWorkset(input) {
157631
157799
  pages: pages.filter((page) => page.claimIds.some((claimId) => governed.claims.some((claim) => claim.id === claimId))),
157632
157800
  durableMemory: governed.durableMemory,
157633
157801
  workflows: governed.workflows,
157802
+ ...agentLoadout ? { agentLoadout } : {},
157634
157803
  cautions: normalizedCautions,
157635
157804
  verification,
157636
157805
  evidenceIds,
@@ -157849,10 +158018,12 @@ var init_current_facts = __esm({
157849
158018
  var external_provider_exports = {};
157850
158019
  __export(external_provider_exports, {
157851
158020
  DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS: () => DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS,
158021
+ EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS: () => EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS,
157852
158022
  MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES: () => MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES,
157853
158023
  MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS: () => MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS,
157854
158024
  getExternalCodeGraphContext: () => getExternalCodeGraphContext,
157855
- inspectExternalCodeGraph: () => inspectExternalCodeGraph
158025
+ inspectExternalCodeGraph: () => inspectExternalCodeGraph,
158026
+ runExternalCodeGraphLifecycle: () => runExternalCodeGraphLifecycle
157856
158027
  });
157857
158028
  import { spawn as spawn5 } from "child_process";
157858
158029
  import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
@@ -158051,6 +158222,66 @@ async function inspectExternalCodeGraph(input) {
158051
158222
  const health = healthFromStatus(status);
158052
158223
  return { health, quality: providerQuality({ mode, health }) };
158053
158224
  }
158225
+ async function runExternalCodeGraphLifecycle(input) {
158226
+ const mode = input.mode ?? "auto";
158227
+ if (mode === "off") {
158228
+ const health = { state: "disabled", reason: "External CodeGraph is disabled by configuration." };
158229
+ return {
158230
+ action: input.action,
158231
+ performed: false,
158232
+ message: "External CodeGraph is disabled by configuration.",
158233
+ health,
158234
+ quality: providerQuality({ mode, health })
158235
+ };
158236
+ }
158237
+ const indexPath = join20(input.projectRoot, ".codegraph");
158238
+ if (input.action === "sync" && !existsSync15(indexPath)) {
158239
+ const health = { state: "not-detected", reason: "No local .codegraph index is present for this project." };
158240
+ return {
158241
+ action: input.action,
158242
+ performed: false,
158243
+ message: 'Initialize CodeGraph first with "memorix codegraph init".',
158244
+ health,
158245
+ quality: providerQuality({ mode, health })
158246
+ };
158247
+ }
158248
+ if (input.action === "init" && existsSync15(indexPath)) {
158249
+ const inspected2 = await inspectExternalCodeGraph(input);
158250
+ return {
158251
+ action: input.action,
158252
+ performed: false,
158253
+ message: "A local CodeGraph directory already exists; no initialization was performed.",
158254
+ ...inspected2
158255
+ };
158256
+ }
158257
+ const result = await runSafely(input.runner ?? defaultRunner, {
158258
+ command: input.command,
158259
+ args: input.action === "init" ? ["init", input.projectRoot] : ["sync", input.projectRoot, "--quiet"],
158260
+ cwd: input.projectRoot,
158261
+ // A context read must stay fast; an explicit user-requested index sync is
158262
+ // a different operation and gets its own bounded maintenance window.
158263
+ timeoutMs: EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS,
158264
+ maxOutputBytes: MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES
158265
+ });
158266
+ if (!result.ok) {
158267
+ const health = unavailableHealth(result);
158268
+ return {
158269
+ action: input.action,
158270
+ performed: false,
158271
+ message: "CodeGraph " + input.action + " did not complete: " + (health.reason ?? "unknown local error"),
158272
+ health,
158273
+ quality: providerQuality({ mode, health })
158274
+ };
158275
+ }
158276
+ const inspected = await inspectExternalCodeGraph(input);
158277
+ const remainsStale = inspected.health.state === "stale";
158278
+ return {
158279
+ action: input.action,
158280
+ performed: true,
158281
+ message: input.action === "init" ? 'Local CodeGraph initialization completed. Run "memorix codegraph sync" when source changes.' : remainsStale ? "Local CodeGraph synchronization completed, but the graph remains stale against the current worktree; this task will use Lite evidence." : "Local CodeGraph synchronization completed and is ready for task-scoped semantic context.",
158282
+ ...inspected
158283
+ };
158284
+ }
158054
158285
  function safeRelativePath(projectRoot, value, exclude) {
158055
158286
  const path37 = safeText(value, 1024);
158056
158287
  if (!path37 || isAbsolute(path37)) return void 0;
@@ -158176,7 +158407,7 @@ async function getExternalCodeGraphContext(input) {
158176
158407
  quality: providerQuality({ mode, health: inspected.health, ...contributes ? { selected: "external" } : {} })
158177
158408
  };
158178
158409
  }
158179
- var DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS, MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS, MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES, MAX_EXTERNAL_NODE_COUNT, MAX_EXTERNAL_EDGE_COUNT, MAX_EXTERNAL_FILE_COUNT, MAX_EXTERNAL_TEXT_LENGTH, LITE_SUPPORTED_LANGUAGES, defaultRunner;
158410
+ var DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS, MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS, EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS, MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES, MAX_EXTERNAL_NODE_COUNT, MAX_EXTERNAL_EDGE_COUNT, MAX_EXTERNAL_FILE_COUNT, MAX_EXTERNAL_TEXT_LENGTH, LITE_SUPPORTED_LANGUAGES, defaultRunner;
158180
158411
  var init_external_provider = __esm({
158181
158412
  "src/codegraph/external-provider.ts"() {
158182
158413
  "use strict";
@@ -158186,6 +158417,7 @@ var init_external_provider = __esm({
158186
158417
  init_ids();
158187
158418
  DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS = 1200;
158188
158419
  MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS = 5e3;
158420
+ EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS = 3e4;
158189
158421
  MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES = 256 * 1024;
158190
158422
  MAX_EXTERNAL_NODE_COUNT = 16;
158191
158423
  MAX_EXTERNAL_EDGE_COUNT = 24;
@@ -159011,6 +159243,7 @@ async function buildAutoProjectContext(input) {
159011
159243
  projectId: input.project.id,
159012
159244
  dataDir: input.dataDir,
159013
159245
  ...task ? { task } : {},
159246
+ ...input.agent ? { agent: input.agent } : {},
159014
159247
  lens: lens.id,
159015
159248
  startHere,
159016
159249
  ...alwaysOn.profile.length > 0 || alwaysOn.state || alwaysOn.durable.length > 0 ? { alwaysOn } : {},
@@ -159347,6 +159580,34 @@ var init_auto_context = __esm({
159347
159580
  }
159348
159581
  });
159349
159582
 
159583
+ // src/knowledge/context-receipt.ts
159584
+ var context_receipt_exports = {};
159585
+ __export(context_receipt_exports, {
159586
+ buildBoundedContextReceipt: () => buildBoundedContextReceipt
159587
+ });
159588
+ function buildBoundedContextReceipt(input) {
159589
+ const provider2 = input.providerQuality ?? input.workset.provenance.codeProvider;
159590
+ return {
159591
+ schemaVersion: "1",
159592
+ task: input.workset.task,
159593
+ brief: input.workset.prompt,
159594
+ receipt: input.workset.receipt,
159595
+ code: {
159596
+ selected: provider2?.selected ?? "lite",
159597
+ quality: provider2?.selectedQuality ?? "heuristic",
159598
+ externalState: provider2?.external.state ?? "not-detected",
159599
+ ...input.workset.provenance.snapshotId ? { snapshotId: input.workset.provenance.snapshotId } : {}
159600
+ },
159601
+ ...input.workset.agentLoadout ? { loadout: input.workset.agentLoadout } : {}
159602
+ };
159603
+ }
159604
+ var init_context_receipt = __esm({
159605
+ "src/knowledge/context-receipt.ts"() {
159606
+ "use strict";
159607
+ init_esm_shims();
159608
+ }
159609
+ });
159610
+
159350
159611
  // src/codegraph/context-pack.ts
159351
159612
  var context_pack_exports = {};
159352
159613
  __export(context_pack_exports, {
@@ -159505,6 +159766,7 @@ async function attachTaskWorkset(input) {
159505
159766
  projectId: input.projectId,
159506
159767
  dataDir: input.dataDir,
159507
159768
  task: input.pack.task,
159769
+ ...input.agent ? { agent: input.agent } : {},
159508
159770
  lens: input.lens,
159509
159771
  currentFacts: input.currentFacts ?? [],
159510
159772
  ...input.codeState ? { codeState: input.codeState } : {},
@@ -163897,8 +164159,8 @@ var init_official_skills = __esm({
163897
164159
  "",
163898
164160
  "| Situation | Prefer | CLI fallback |",
163899
164161
  "|---|---|---|",
163900
- '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --json` |',
163901
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --json` |',
164162
+ '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --brief-json` |',
164163
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
163902
164164
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
163903
164165
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
163904
164166
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -163911,7 +164173,7 @@ var init_official_skills = __esm({
163911
164173
  "",
163912
164174
  "- Default first step for non-trivial coding work: call `memorix_project_context` before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Pass the user's actual task so Memorix can choose a bugfix, feature, release, onboarding, refactor, docs, test, or general brief. Start by reading its suggested files.",
163913
164175
  "- In Claude Code headless/print-mode, an initial MCP `pending` status is not a failure by itself. Prefer MCP first: use the client's tool discovery/dynamic loading to find `memorix_project_context` when it is not in the first visible tool list.",
163914
- '- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --json` instead. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
164176
+ '- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --brief-json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --brief-json` instead. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
163915
164177
  "- After a successful `memorix_project_context`, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use Context Pack, search, or detail only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.",
163916
164178
  "- If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.",
163917
164179
  "- Use `memorix_search` for a specific decision, bug, file, or prior change that the project context did not answer. Fetch detail only for a result you still need after the brief.",
@@ -165170,7 +165432,7 @@ alwaysApply: true
165170
165432
  "",
165171
165433
  `Default first step for non-trivial coding work: call \`memorix_project_context\` with the user's actual task before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Memorix will choose a task-lensed brief (bugfix, feature, release, onboarding, refactor, docs, test, or general). When the task is continuing prior work, the same brief also includes a bounded prior-work projection. Treat its "Start here" files as the first ${contextNoun} files to inspect.`,
165172
165434
  "",
165173
- 'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: `memorix resume "<task>" --json`. For a new task, use `memorix context "<task>" --json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
165435
+ 'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: `memorix resume "<task>" --brief-json`. For a new task, use `memorix context "<task>" --brief-json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
165174
165436
  "",
165175
165437
  "After a successful `memorix_project_context` result, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use `memorix_context_pack`, `memorix_search`, or `memorix_detail` only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.",
165176
165438
  "If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.",
@@ -165834,7 +166096,7 @@ The path should point to a directory containing a .git folder.`
165834
166096
  };
165835
166097
  const server = existingServer ?? new McpServer({
165836
166098
  name: "memorix",
165837
- version: true ? "1.5.0" : "1.0.1"
166099
+ version: true ? "1.6.0" : "1.0.1"
165838
166100
  });
165839
166101
  const originalRegisterTool = server.registerTool.bind(server);
165840
166102
  server.registerTool = ((name, ...args) => {
@@ -166023,8 +166285,8 @@ The path should point to a directory containing a .git folder.`
166023
166285
  mode: "active",
166024
166286
  useLLM: isLLMEnabled(),
166025
166287
  minValueScore: 0.3,
166026
- searchMemories: async (q, limit2, pid) => {
166027
- const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader });
166288
+ searchMemories: async (q, limit2, pid, signal) => {
166289
+ const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader, signal });
166028
166290
  if (result.entries.length === 0) return [];
166029
166291
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
166030
166292
  return details.documents.map((d, i2) => ({
@@ -166054,16 +166316,19 @@ The path should point to a directory containing a .git folder.`
166054
166316
  getEntityNames: () => graphManager.getEntityNames(),
166055
166317
  onStageEvent: onFormationStageEvent
166056
166318
  };
166057
- formationResult = await withTimeout(
166058
- runFormation({
166059
- entityName,
166060
- type,
166061
- title,
166062
- narrative,
166063
- facts: safeFacts,
166064
- projectId: project.id,
166065
- source: "explicit"
166066
- }, formationConfig),
166319
+ formationResult = await withTimeoutSignal(
166320
+ async (signal) => {
166321
+ formationConfig.signal = signal;
166322
+ return runFormation({
166323
+ entityName,
166324
+ type,
166325
+ title,
166326
+ narrative,
166327
+ facts: safeFacts,
166328
+ projectId: project.id,
166329
+ source: "explicit"
166330
+ }, formationConfig);
166331
+ },
166067
166332
  FORMATION_TIMEOUT_MS,
166068
166333
  "Formation pipeline"
166069
166334
  );
@@ -166413,8 +166678,8 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166413
166678
  mode: formationMode,
166414
166679
  useLLM: isLLMEnabled(),
166415
166680
  minValueScore: 0.3,
166416
- searchMemories: async (q, limit2, pid) => {
166417
- const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader });
166681
+ searchMemories: async (q, limit2, pid, signal) => {
166682
+ const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader, signal });
166418
166683
  if (result.entries.length === 0) return [];
166419
166684
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
166420
166685
  return details.documents.map((d, i2) => ({
@@ -166435,8 +166700,11 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166435
166700
  },
166436
166701
  getEntityNames: () => graphManager.getEntityNames()
166437
166702
  };
166438
- const formed = await withTimeout(
166439
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
166703
+ const formed = await withTimeoutSignal(
166704
+ async (signal) => {
166705
+ formationConfig.signal = signal;
166706
+ return runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig);
166707
+ },
166440
166708
  FORMATION_TIMEOUT_MS,
166441
166709
  "Shadow formation"
166442
166710
  );
@@ -166662,13 +166930,28 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
166662
166930
  refresh: z626.enum(["auto", "always", "never"]).optional().default("auto").describe(
166663
166931
  "Code Memory refresh policy. auto refreshes only when missing or stale."
166664
166932
  ),
166665
- format: z626.enum(["prompt", "summary", "json"]).optional().default("prompt").describe(
166666
- 'Output format. "prompt" is agent-ready; "summary" is human-readable; "json" is structured.'
166933
+ format: z626.enum(["prompt", "summary", "json", "receipt"]).optional().default("prompt").describe(
166934
+ 'Output format. "prompt" is agent-ready; "summary" is human-readable; "receipt" is bounded JSON; "json" is detailed diagnostics.'
166667
166935
  ),
166936
+ agent: z626.enum([
166937
+ "windsurf",
166938
+ "cursor",
166939
+ "claude-code",
166940
+ "codex",
166941
+ "copilot",
166942
+ "antigravity",
166943
+ "gemini-cli",
166944
+ "openclaw",
166945
+ "hermes",
166946
+ "omp",
166947
+ "kiro",
166948
+ "opencode",
166949
+ "trae"
166950
+ ]).optional().describe("Optional target agent for compatible workflow selection."),
166668
166951
  limit: z626.number().optional().describe("Reserved for future source limits; current prompt stays compact by default.")
166669
166952
  }
166670
166953
  },
166671
- async ({ task, refresh, format }) => {
166954
+ async ({ task, refresh, format, agent }) => {
166672
166955
  const unresolved = requireResolvedProject("build project context for the current project");
166673
166956
  if (unresolved) return unresolved;
166674
166957
  const [
@@ -166680,12 +166963,14 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
166680
166963
  },
166681
166964
  { getObservationStore: getObservationStore2 },
166682
166965
  { MaintenanceJobStore: MaintenanceJobStore2 },
166683
- { enqueueCodegraphRefresh: enqueueCodegraphRefresh2 }
166966
+ { enqueueCodegraphRefresh: enqueueCodegraphRefresh2 },
166967
+ { buildBoundedContextReceipt: buildBoundedContextReceipt2 }
166684
166968
  ] = await Promise.all([
166685
166969
  Promise.resolve().then(() => (init_auto_context(), auto_context_exports)),
166686
166970
  Promise.resolve().then(() => (init_obs_store(), obs_store_exports)),
166687
166971
  Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)),
166688
- Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports))
166972
+ Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports)),
166973
+ Promise.resolve().then(() => (init_context_receipt(), context_receipt_exports))
166689
166974
  ]);
166690
166975
  const observations2 = filterReadableObservations(
166691
166976
  await getObservationStore2().loadByProject(project.id, { status: "active" }),
@@ -166696,6 +166981,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
166696
166981
  dataDir: projectDir2,
166697
166982
  observations: observations2,
166698
166983
  task,
166984
+ agent,
166699
166985
  refresh: refresh ?? "auto",
166700
166986
  reader: getObservationReader(),
166701
166987
  enqueueRefresh: () => {
@@ -166708,7 +166994,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
166708
166994
  });
166709
166995
  }
166710
166996
  });
166711
- const text2 = format === "json" ? JSON.stringify({ ...context, brief: buildAutoProjectBrief2(context) }, null, 2) : format === "summary" ? formatAutoProjectContextSummary2(context) : formatAutoProjectContextPrompt2(context);
166997
+ const text2 = format === "json" ? JSON.stringify({ ...context, brief: buildAutoProjectBrief2(context) }, null, 2) : format === "receipt" ? JSON.stringify(buildBoundedContextReceipt2({
166998
+ workset: context.workset,
166999
+ providerQuality: context.providerQuality
167000
+ }), null, 2) : format === "summary" ? formatAutoProjectContextSummary2(context) : formatAutoProjectContextPrompt2(context);
166712
167001
  autopilotRetrievalBoundary = {
166713
167002
  projectId: project.id,
166714
167003
  issuedAt: Date.now(),
@@ -170213,7 +170502,7 @@ var init_serve_shared = __esm({
170213
170502
 
170214
170503
  // src/cli/version.ts
170215
170504
  function getCliVersion() {
170216
- return true ? "1.5.0" : pkg.version;
170505
+ return true ? "1.6.0" : pkg.version;
170217
170506
  }
170218
170507
  var init_version3 = __esm({
170219
170508
  "src/cli/version.ts"() {