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/cli/index.js CHANGED
@@ -3221,7 +3221,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3221
3221
 
3222
3222
  // src/cli/version.ts
3223
3223
  function getCliVersion() {
3224
- return true ? "1.5.0" : pkg.version;
3224
+ return true ? "1.6.0" : pkg.version;
3225
3225
  }
3226
3226
  var init_version = __esm({
3227
3227
  "src/cli/version.ts"() {
@@ -22327,6 +22327,8 @@ function getPreferredBatchSize(config2) {
22327
22327
  }
22328
22328
  function parseBatchLimit(error40) {
22329
22329
  if (!(error40 instanceof Error)) return null;
22330
+ const status = error40 instanceof EmbeddingAPIError ? error40.status : Number(error40.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
22331
+ if (status > 0 && status !== 400) return null;
22330
22332
  const explicit = error40.message.match(/should not be larger than\s+(\d+)/i);
22331
22333
  if (explicit) return parseInt(explicit[1], 10);
22332
22334
  if (/batch size/i.test(error40.message)) {
@@ -22339,37 +22341,99 @@ async function fetchWithRetry(url2, apiKey, body, options = {}, attempt = 0, nat
22339
22341
  const controller = new AbortController();
22340
22342
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
22341
22343
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
22344
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
22342
22345
  let response;
22343
22346
  try {
22344
22347
  response = await fetch(url2, {
22345
22348
  method: "POST",
22346
22349
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
22347
22350
  body: JSON.stringify(body),
22348
- signal: controller.signal
22351
+ signal
22349
22352
  });
22350
22353
  } catch (err) {
22351
22354
  clearTimeout(timeout);
22352
- if (err instanceof Error && err.name === "AbortError") {
22355
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
22353
22356
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url2}`);
22354
22357
  }
22355
22358
  throw err;
22356
22359
  }
22357
- clearTimeout(timeout);
22358
- if (response.ok) {
22359
- return response.json();
22360
+ try {
22361
+ if (response.ok) {
22362
+ return await raceWithAbort(
22363
+ response.json(),
22364
+ signal
22365
+ );
22366
+ }
22367
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
22368
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
22369
+ const retryAfter = response.headers.get("retry-after");
22370
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
22371
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
22372
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
22373
+ await waitForRetry(waitMs, options.signal);
22374
+ return fetchWithRetry(url2, apiKey, body, options, attempt + 1, nativeGemini);
22375
+ }
22376
+ const errorText2 = await raceWithAbort(
22377
+ response.text().catch(() => "unknown error"),
22378
+ signal
22379
+ );
22380
+ throw new EmbeddingAPIError(response.status, errorText2);
22381
+ } catch (err) {
22382
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
22383
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url2}`);
22384
+ }
22385
+ throw err;
22386
+ } finally {
22387
+ clearTimeout(timeout);
22360
22388
  }
22361
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
22362
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
22363
- const retryAfter = response.headers.get("retry-after");
22364
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
22365
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
22389
+ }
22390
+ function abortError() {
22391
+ const error40 = new Error("Embedding API request aborted");
22392
+ error40.name = "AbortError";
22393
+ return error40;
22394
+ }
22395
+ function raceWithAbort(promise2, signal) {
22396
+ if (signal.aborted) return Promise.reject(abortError());
22397
+ return new Promise((resolve6, reject) => {
22398
+ const onAbort = () => {
22399
+ signal.removeEventListener("abort", onAbort);
22400
+ reject(abortError());
22401
+ };
22402
+ signal.addEventListener("abort", onAbort, { once: true });
22403
+ promise2.then(
22404
+ (value) => {
22405
+ signal.removeEventListener("abort", onAbort);
22406
+ resolve6(value);
22407
+ },
22408
+ (error40) => {
22409
+ signal.removeEventListener("abort", onAbort);
22410
+ reject(error40);
22411
+ }
22412
+ );
22413
+ });
22414
+ }
22415
+ async function waitForRetry(waitMs, signal) {
22416
+ if (!signal) {
22366
22417
  await new Promise((resolve6) => setTimeout(resolve6, waitMs));
22367
- return fetchWithRetry(url2, apiKey, body, options, attempt + 1, nativeGemini);
22418
+ return;
22368
22419
  }
22369
- const errorText2 = await response.text().catch(() => "unknown error");
22370
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
22420
+ const abortSignal = signal;
22421
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
22422
+ await new Promise((resolve6, reject) => {
22423
+ const timer = setTimeout(done, waitMs);
22424
+ const onAbort = () => {
22425
+ clearTimeout(timer);
22426
+ abortSignal.removeEventListener("abort", onAbort);
22427
+ reject(new Error("Embedding API retry aborted"));
22428
+ };
22429
+ function done() {
22430
+ abortSignal.removeEventListener("abort", onAbort);
22431
+ resolve6();
22432
+ }
22433
+ abortSignal.addEventListener("abort", onAbort, { once: true });
22434
+ });
22371
22435
  }
22372
- 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;
22436
+ 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;
22373
22437
  var init_api_provider = __esm({
22374
22438
  "src/embedding/api-provider.ts"() {
22375
22439
  "use strict";
@@ -22388,6 +22452,14 @@ var init_api_provider = __esm({
22388
22452
  DASHSCOPE_MAX_BATCH_SIZE = 10;
22389
22453
  MAX_RETRIES2 = 3;
22390
22454
  BASE_DELAY_MS = 500;
22455
+ EmbeddingAPIError = class extends Error {
22456
+ status;
22457
+ constructor(status, detail) {
22458
+ super(`Embedding API error (${status}): ${detail}`);
22459
+ this.name = "EmbeddingAPIError";
22460
+ this.status = status;
22461
+ }
22462
+ };
22391
22463
  APIEmbeddingProvider = class _APIEmbeddingProvider {
22392
22464
  name;
22393
22465
  dimensions;
@@ -22599,15 +22671,14 @@ var init_api_provider = __esm({
22599
22671
  }
22600
22672
  } catch (error40) {
22601
22673
  const providerLimit = parseBatchLimit(error40);
22602
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
22603
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
22674
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
22604
22675
  console.error(
22605
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
22676
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
22606
22677
  );
22607
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
22678
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
22608
22679
  await processChunk(
22609
- chunkTexts.slice(start, start + fallbackSize),
22610
- chunkIndices.slice(start, start + fallbackSize)
22680
+ chunkTexts.slice(start, start + providerLimit),
22681
+ chunkIndices.slice(start, start + providerLimit)
22611
22682
  );
22612
22683
  }
22613
22684
  return;
@@ -23471,22 +23542,23 @@ function getLLMConfig() {
23471
23542
  function setLLMConfig(config2) {
23472
23543
  currentConfig = config2;
23473
23544
  }
23474
- async function callLLM(systemPrompt, userMessage) {
23545
+ async function callLLM(systemPrompt, userMessage, signal) {
23475
23546
  if (!currentConfig) {
23476
23547
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
23477
23548
  }
23478
23549
  if (currentConfig.provider === "anthropic") {
23479
- return callAnthropic(systemPrompt, userMessage);
23550
+ return callAnthropic(systemPrompt, userMessage, signal);
23480
23551
  }
23481
- return callOpenAICompatible(systemPrompt, userMessage);
23552
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
23482
23553
  }
23483
- async function callOpenAICompatible(systemPrompt, userMessage) {
23554
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
23484
23555
  const config2 = currentConfig;
23485
23556
  let base = config2.baseUrl.replace(/\/+$/, "");
23486
23557
  if (!base.endsWith("/v1")) base += "/v1";
23487
23558
  const url2 = `${base}/chat/completions`;
23559
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
23488
23560
  const response = await fetch(url2, {
23489
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
23561
+ signal: fetchSignal,
23490
23562
  method: "POST",
23491
23563
  headers: {
23492
23564
  "Content-Type": "application/json",
@@ -23503,10 +23575,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
23503
23575
  })
23504
23576
  });
23505
23577
  if (!response.ok) {
23506
- const error40 = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
23578
+ const error40 = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
23507
23579
  throw new Error(`LLM API error (${response.status}): ${error40}`);
23508
23580
  }
23509
- const data = await readResponseJson(response);
23581
+ const data = await readResponseJson(response, signal);
23510
23582
  return {
23511
23583
  content: data.choices[0]?.message?.content ?? "",
23512
23584
  usage: data.usage ? {
@@ -23515,11 +23587,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
23515
23587
  } : void 0
23516
23588
  };
23517
23589
  }
23518
- async function callAnthropic(systemPrompt, userMessage) {
23590
+ async function callAnthropic(systemPrompt, userMessage, signal) {
23519
23591
  const config2 = currentConfig;
23520
23592
  const url2 = `${config2.baseUrl}/messages`;
23593
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
23521
23594
  const response = await fetch(url2, {
23522
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
23595
+ signal: fetchSignal,
23523
23596
  method: "POST",
23524
23597
  headers: {
23525
23598
  "Content-Type": "application/json",
@@ -23537,10 +23610,10 @@ async function callAnthropic(systemPrompt, userMessage) {
23537
23610
  })
23538
23611
  });
23539
23612
  if (!response.ok) {
23540
- const error40 = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
23613
+ const error40 = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
23541
23614
  throw new Error(`Anthropic API error (${response.status}): ${error40}`);
23542
23615
  }
23543
- const data = await readResponseJson(response);
23616
+ const data = await readResponseJson(response, signal);
23544
23617
  return {
23545
23618
  content: data.content[0]?.text ?? "",
23546
23619
  usage: data.usage ? {
@@ -24045,6 +24118,57 @@ function withTimeout(promise2, ms2, label) {
24045
24118
  );
24046
24119
  });
24047
24120
  }
24121
+ function withTimeoutSignal(operation, ms2, label, parentSignal) {
24122
+ const controller = new AbortController();
24123
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
24124
+ return new Promise((resolve6, reject) => {
24125
+ let settled = false;
24126
+ let timedOut = false;
24127
+ let timer;
24128
+ const cleanup = () => {
24129
+ if (timer !== void 0) clearTimeout(timer);
24130
+ parentSignal?.removeEventListener("abort", onParentAbort);
24131
+ };
24132
+ const onParentAbort = () => {
24133
+ if (settled || timedOut) return;
24134
+ settled = true;
24135
+ cleanup();
24136
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
24137
+ };
24138
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
24139
+ timer = setTimeout(() => {
24140
+ if (settled) return;
24141
+ timedOut = true;
24142
+ controller.abort(new Error(`${label} timed out after ${ms2}ms`));
24143
+ settled = true;
24144
+ cleanup();
24145
+ reject(new Error(`${label} timed out after ${ms2}ms`));
24146
+ }, ms2);
24147
+ let pending;
24148
+ try {
24149
+ pending = operation(signal);
24150
+ } catch (error40) {
24151
+ settled = true;
24152
+ cleanup();
24153
+ reject(error40);
24154
+ return;
24155
+ }
24156
+ pending.then(
24157
+ (value) => {
24158
+ if (settled) return;
24159
+ settled = true;
24160
+ cleanup();
24161
+ resolve6(value);
24162
+ },
24163
+ (error40) => {
24164
+ if (settled) return;
24165
+ settled = true;
24166
+ cleanup();
24167
+ reject(timedOut ? new Error(`${label} timed out after ${ms2}ms`) : error40);
24168
+ }
24169
+ );
24170
+ });
24171
+ }
24048
24172
  var init_timeout = __esm({
24049
24173
  "src/timeout.ts"() {
24050
24174
  "use strict";
@@ -178220,7 +178344,13 @@ async function maintainLongTermMemories(input) {
178220
178344
  }
178221
178345
  for (const group of groups.values()) {
178222
178346
  if (group.length < 2) continue;
178223
- group.sort((a4, b4) => Date.parse(b4.createdAt) - Date.parse(a4.createdAt));
178347
+ group.sort((a4, b4) => {
178348
+ const byCreatedAt = Date.parse(b4.createdAt) - Date.parse(a4.createdAt);
178349
+ if (byCreatedAt !== 0) return byCreatedAt;
178350
+ const byUpdatedAt = Date.parse(b4.updatedAt) - Date.parse(a4.updatedAt);
178351
+ if (byUpdatedAt !== 0) return byUpdatedAt;
178352
+ return b4.id.localeCompare(a4.id);
178353
+ });
178224
178354
  const newest = group[0];
178225
178355
  for (const older of group.slice(1)) {
178226
178356
  try {
@@ -181948,6 +182078,7 @@ var init_task_lens = __esm({
181948
182078
  var workflows_exports = {};
181949
182079
  __export(workflows_exports, {
181950
182080
  WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
182081
+ WORKFLOW_AGENT_TARGETS: () => WORKFLOW_AGENT_TARGETS,
181951
182082
  applyWorkflowAdapter: () => applyWorkflowAdapter,
181952
182083
  importWindsurfWorkflows: () => importWindsurfWorkflows,
181953
182084
  parseWorkflowMarkdown: () => parseWorkflowMarkdown,
@@ -182013,7 +182144,7 @@ function optionalStatus(data) {
182013
182144
  return value;
182014
182145
  }
182015
182146
  function normalizeAgents(values) {
182016
- const unknown2 = values.filter((value) => !KNOWN_AGENTS.includes(value));
182147
+ const unknown2 = values.filter((value) => !WORKFLOW_AGENT_TARGETS.includes(value));
182017
182148
  if (unknown2.length > 0) {
182018
182149
  throw new Error("workflow frontmatter has unsupported agent: " + unknown2[0]);
182019
182150
  }
@@ -182289,7 +182420,7 @@ function taskScore(workflow, task) {
182289
182420
  return { score, reasons: [...new Set(reasons)] };
182290
182421
  }
182291
182422
  function selectWorkflows(input) {
182292
- const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
182423
+ const selected = input.workflows.filter((workflow) => workflow.status === "active" && (!input.agent || workflow.compatibleAgents.includes(input.agent))).map((workflow) => {
182293
182424
  const match2 = taskScore(workflow, input.task);
182294
182425
  const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
182295
182426
  return {
@@ -182569,7 +182700,7 @@ async function selectWorkspaceWorkflows(input) {
182569
182700
  errors: synced.errors
182570
182701
  };
182571
182702
  }
182572
- var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
182703
+ var WORKFLOW_AGENT_TARGETS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
182573
182704
  var init_workflows2 = __esm({
182574
182705
  "src/knowledge/workflows.ts"() {
182575
182706
  "use strict";
@@ -182581,7 +182712,7 @@ var init_workflows2 = __esm({
182581
182712
  init_workflow_store();
182582
182713
  init_outcome_store();
182583
182714
  init_task_lens();
182584
- KNOWN_AGENTS = [
182715
+ WORKFLOW_AGENT_TARGETS = [
182585
182716
  "windsurf",
182586
182717
  "cursor",
182587
182718
  "claude-code",
@@ -184736,16 +184867,18 @@ async function searchObservations(options) {
184736
184867
  );
184737
184868
  } else {
184738
184869
  const EMBEDDING_TIMEOUT_MS = 15e3;
184739
- queryVector = await withTimeout(
184740
- provider2.embedInput ? provider2.embedInput(
184870
+ queryVector = await withTimeoutSignal(
184871
+ (signal) => provider2.embedInput ? provider2.embedInput(
184741
184872
  { modality: "text", text: expandedEmbeddingQuery },
184742
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
184873
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
184743
184874
  ) : provider2.embed(expandedEmbeddingQuery, {
184744
184875
  timeoutMs: EMBEDDING_TIMEOUT_MS,
184745
- retry: false
184876
+ retry: false,
184877
+ signal
184746
184878
  }),
184747
184879
  EMBEDDING_TIMEOUT_MS,
184748
- "Embedding"
184880
+ "Embedding",
184881
+ options.signal
184749
184882
  );
184750
184883
  mark("embedding");
184751
184884
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -190999,8 +191132,8 @@ var init_official_skills = __esm({
190999
191132
  "",
191000
191133
  "| Situation | Prefer | CLI fallback |",
191001
191134
  "|---|---|---|",
191002
- '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --json` |',
191003
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --json` |',
191135
+ '| 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` |',
191136
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
191004
191137
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
191005
191138
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
191006
191139
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -191013,7 +191146,7 @@ var init_official_skills = __esm({
191013
191146
  "",
191014
191147
  "- 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.",
191015
191148
  "- 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.",
191016
- '- 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.',
191149
+ '- 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.',
191017
191150
  "- 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.",
191018
191151
  "- 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.",
191019
191152
  "- 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.",
@@ -192272,7 +192405,7 @@ alwaysApply: true
192272
192405
  "",
192273
192406
  `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.`,
192274
192407
  "",
192275
- '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.',
192408
+ '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.',
192276
192409
  "",
192277
192410
  "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.",
192278
192411
  "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.",
@@ -194314,6 +194447,15 @@ function workflowOutput(selection) {
194314
194447
  cautions: selection.cautions.map((caution) => short(caution, 22))
194315
194448
  };
194316
194449
  }
194450
+ function workflowLoadout(agent, selections) {
194451
+ if (!agent) return void 0;
194452
+ return {
194453
+ agent,
194454
+ workflowIds: selections.map((selection) => selection.workflow.id),
194455
+ requiredContext: unique(selections.flatMap((selection) => selection.workflow.requiredContext)).map((item) => short(item, 18)).slice(0, 4),
194456
+ allowedTools: unique(selections.flatMap((selection) => selection.workflow.allowedTools)).map((item) => short(item, 12)).slice(0, 6)
194457
+ };
194458
+ }
194317
194459
  function appendLine(lines, candidate2, maxTokens, omitted, omittedKind, selected, receiptSelection) {
194318
194460
  const next = lines.length ? lines.join("\n") + "\n" + candidate2 : candidate2;
194319
194461
  if (countTextTokens(next) <= maxTokens) {
@@ -194774,6 +194916,22 @@ function renderTaskWorksetPrompt(input) {
194774
194916
  );
194775
194917
  }
194776
194918
  }
194919
+ if (input.agentLoadout) {
194920
+ appendLine(lines, "", maxTokens, omitted, "agent-loadout-heading");
194921
+ appendLine(lines, "Agent loadout (" + input.agentLoadout.agent + ")", maxTokens, omitted, "agent-loadout-heading");
194922
+ for (const required2 of input.agentLoadout.requiredContext.slice(0, 2)) {
194923
+ appendLine(lines, "- Need: " + required2, maxTokens, omitted, "agent-loadout-context");
194924
+ }
194925
+ if (input.agentLoadout.allowedTools.length > 0) {
194926
+ appendLine(
194927
+ lines,
194928
+ "- Workflow tools: " + input.agentLoadout.allowedTools.join(", "),
194929
+ maxTokens,
194930
+ omitted,
194931
+ "agent-loadout-tools"
194932
+ );
194933
+ }
194934
+ }
194777
194935
  if (input.verification.length > 0) {
194778
194936
  appendLine(lines, "", maxTokens, omitted, "verification-heading");
194779
194937
  appendLine(lines, "Verify", maxTokens, omitted, "verification-heading");
@@ -194865,6 +195023,7 @@ async function buildTaskWorkset(input) {
194865
195023
  let workspace;
194866
195024
  let pages = [];
194867
195025
  let workflows = [];
195026
+ let workflowSelections = [];
194868
195027
  try {
194869
195028
  workspace = await preferredWorkspace(input.projectId, input.dataDir);
194870
195029
  if (workspace) {
@@ -194881,14 +195040,15 @@ async function buildTaskWorkset(input) {
194881
195040
  if (task) {
194882
195041
  const workflowStore = new WorkflowStore();
194883
195042
  await workflowStore.init(input.dataDir);
194884
- const selected = selectWorkflows({
195043
+ workflowSelections = selectWorkflows({
194885
195044
  workflows: workflowStore.listWorkflows(workspace.id, "active"),
194886
195045
  task,
194887
195046
  projectId: input.projectId,
194888
195047
  store: workflowStore,
195048
+ ...input.agent ? { agent: input.agent } : {},
194889
195049
  limit: 2
194890
195050
  });
194891
- workflows = selected.map(workflowOutput);
195051
+ workflows = workflowSelections.map(workflowOutput);
194892
195052
  for (const workflow of workflows) {
194893
195053
  for (const caution of workflow.cautions) {
194894
195054
  cautions.push({ kind: "workflow-failed-verification", message: caution });
@@ -194910,6 +195070,10 @@ async function buildTaskWorkset(input) {
194910
195070
  workflows
194911
195071
  });
194912
195072
  const governedDurableIds = new Set(governed.durableMemory.map((memory) => memory.id));
195073
+ const agentLoadout = workflowLoadout(
195074
+ input.agent,
195075
+ workflowSelections.filter((selection2) => governed.workflows.some((workflow) => workflow.id === selection2.workflow.id))
195076
+ );
194913
195077
  const evidenceIds = unique(governed.claims.flatMap((claim) => evidenceIdsForClaim(
194914
195078
  claim,
194915
195079
  evidenceByClaim.get(claim.id) ?? []
@@ -194954,6 +195118,7 @@ async function buildTaskWorkset(input) {
194954
195118
  pages: pages.filter((page) => page.claimIds.some((claimId) => governed.claims.some((claim) => claim.id === claimId))),
194955
195119
  durableMemory: governed.durableMemory,
194956
195120
  workflows: governed.workflows,
195121
+ ...agentLoadout ? { agentLoadout } : {},
194957
195122
  cautions: normalizedCautions,
194958
195123
  verification,
194959
195124
  evidenceIds,
@@ -195172,10 +195337,12 @@ var init_current_facts = __esm({
195172
195337
  var external_provider_exports = {};
195173
195338
  __export(external_provider_exports, {
195174
195339
  DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS: () => DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS,
195340
+ EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS: () => EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS,
195175
195341
  MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES: () => MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES,
195176
195342
  MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS: () => MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS,
195177
195343
  getExternalCodeGraphContext: () => getExternalCodeGraphContext,
195178
- inspectExternalCodeGraph: () => inspectExternalCodeGraph
195344
+ inspectExternalCodeGraph: () => inspectExternalCodeGraph,
195345
+ runExternalCodeGraphLifecycle: () => runExternalCodeGraphLifecycle
195179
195346
  });
195180
195347
  import { spawn as spawn5 } from "child_process";
195181
195348
  import { existsSync as existsSync16, readFileSync as readFileSync9 } from "fs";
@@ -195374,6 +195541,66 @@ async function inspectExternalCodeGraph(input) {
195374
195541
  const health = healthFromStatus(status);
195375
195542
  return { health, quality: providerQuality({ mode, health }) };
195376
195543
  }
195544
+ async function runExternalCodeGraphLifecycle(input) {
195545
+ const mode = input.mode ?? "auto";
195546
+ if (mode === "off") {
195547
+ const health = { state: "disabled", reason: "External CodeGraph is disabled by configuration." };
195548
+ return {
195549
+ action: input.action,
195550
+ performed: false,
195551
+ message: "External CodeGraph is disabled by configuration.",
195552
+ health,
195553
+ quality: providerQuality({ mode, health })
195554
+ };
195555
+ }
195556
+ const indexPath = join23(input.projectRoot, ".codegraph");
195557
+ if (input.action === "sync" && !existsSync16(indexPath)) {
195558
+ const health = { state: "not-detected", reason: "No local .codegraph index is present for this project." };
195559
+ return {
195560
+ action: input.action,
195561
+ performed: false,
195562
+ message: 'Initialize CodeGraph first with "memorix codegraph init".',
195563
+ health,
195564
+ quality: providerQuality({ mode, health })
195565
+ };
195566
+ }
195567
+ if (input.action === "init" && existsSync16(indexPath)) {
195568
+ const inspected2 = await inspectExternalCodeGraph(input);
195569
+ return {
195570
+ action: input.action,
195571
+ performed: false,
195572
+ message: "A local CodeGraph directory already exists; no initialization was performed.",
195573
+ ...inspected2
195574
+ };
195575
+ }
195576
+ const result = await runSafely(input.runner ?? defaultRunner, {
195577
+ command: input.command,
195578
+ args: input.action === "init" ? ["init", input.projectRoot] : ["sync", input.projectRoot, "--quiet"],
195579
+ cwd: input.projectRoot,
195580
+ // A context read must stay fast; an explicit user-requested index sync is
195581
+ // a different operation and gets its own bounded maintenance window.
195582
+ timeoutMs: EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS,
195583
+ maxOutputBytes: MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES
195584
+ });
195585
+ if (!result.ok) {
195586
+ const health = unavailableHealth(result);
195587
+ return {
195588
+ action: input.action,
195589
+ performed: false,
195590
+ message: "CodeGraph " + input.action + " did not complete: " + (health.reason ?? "unknown local error"),
195591
+ health,
195592
+ quality: providerQuality({ mode, health })
195593
+ };
195594
+ }
195595
+ const inspected = await inspectExternalCodeGraph(input);
195596
+ const remainsStale = inspected.health.state === "stale";
195597
+ return {
195598
+ action: input.action,
195599
+ performed: true,
195600
+ 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.",
195601
+ ...inspected
195602
+ };
195603
+ }
195377
195604
  function safeRelativePath(projectRoot, value, exclude) {
195378
195605
  const path47 = safeText(value, 1024);
195379
195606
  if (!path47 || isAbsolute2(path47)) return void 0;
@@ -195499,7 +195726,7 @@ async function getExternalCodeGraphContext(input) {
195499
195726
  quality: providerQuality({ mode, health: inspected.health, ...contributes ? { selected: "external" } : {} })
195500
195727
  };
195501
195728
  }
195502
- 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;
195729
+ 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;
195503
195730
  var init_external_provider = __esm({
195504
195731
  "src/codegraph/external-provider.ts"() {
195505
195732
  "use strict";
@@ -195509,6 +195736,7 @@ var init_external_provider = __esm({
195509
195736
  init_ids();
195510
195737
  DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS = 1200;
195511
195738
  MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS = 5e3;
195739
+ EXTERNAL_CODEGRAPH_LIFECYCLE_TIMEOUT_MS = 3e4;
195512
195740
  MAX_EXTERNAL_CODEGRAPH_OUTPUT_BYTES = 256 * 1024;
195513
195741
  MAX_EXTERNAL_NODE_COUNT = 16;
195514
195742
  MAX_EXTERNAL_EDGE_COUNT = 24;
@@ -197231,6 +197459,7 @@ async function buildAutoProjectContext(input) {
197231
197459
  projectId: input.project.id,
197232
197460
  dataDir: input.dataDir,
197233
197461
  ...task ? { task } : {},
197462
+ ...input.agent ? { agent: input.agent } : {},
197234
197463
  lens: lens.id,
197235
197464
  startHere,
197236
197465
  ...alwaysOn.profile.length > 0 || alwaysOn.state || alwaysOn.durable.length > 0 ? { alwaysOn } : {},
@@ -197567,6 +197796,34 @@ var init_auto_context = __esm({
197567
197796
  }
197568
197797
  });
197569
197798
 
197799
+ // src/knowledge/context-receipt.ts
197800
+ var context_receipt_exports = {};
197801
+ __export(context_receipt_exports, {
197802
+ buildBoundedContextReceipt: () => buildBoundedContextReceipt
197803
+ });
197804
+ function buildBoundedContextReceipt(input) {
197805
+ const provider2 = input.providerQuality ?? input.workset.provenance.codeProvider;
197806
+ return {
197807
+ schemaVersion: "1",
197808
+ task: input.workset.task,
197809
+ brief: input.workset.prompt,
197810
+ receipt: input.workset.receipt,
197811
+ code: {
197812
+ selected: provider2?.selected ?? "lite",
197813
+ quality: provider2?.selectedQuality ?? "heuristic",
197814
+ externalState: provider2?.external.state ?? "not-detected",
197815
+ ...input.workset.provenance.snapshotId ? { snapshotId: input.workset.provenance.snapshotId } : {}
197816
+ },
197817
+ ...input.workset.agentLoadout ? { loadout: input.workset.agentLoadout } : {}
197818
+ };
197819
+ }
197820
+ var init_context_receipt = __esm({
197821
+ "src/knowledge/context-receipt.ts"() {
197822
+ "use strict";
197823
+ init_esm_shims();
197824
+ }
197825
+ });
197826
+
197570
197827
  // src/cli/commands/context.ts
197571
197828
  var context_exports = {};
197572
197829
  __export(context_exports, {
@@ -197577,6 +197834,12 @@ function coerceRefreshMode(input) {
197577
197834
  if (value === "always" || value === "never" || value === "auto") return value;
197578
197835
  throw new Error("refresh must be one of: auto, always, never");
197579
197836
  }
197837
+ function coerceAgentTarget(input) {
197838
+ const value = input?.trim().toLowerCase();
197839
+ if (!value) return void 0;
197840
+ if (WORKFLOW_AGENT_TARGETS.includes(value)) return value;
197841
+ throw new Error("agent must be one of: " + WORKFLOW_AGENT_TARGETS.join(", "));
197842
+ }
197580
197843
  var context_default;
197581
197844
  var init_context2 = __esm({
197582
197845
  "src/cli/commands/context.ts"() {
@@ -197586,6 +197849,8 @@ var init_context2 = __esm({
197586
197849
  init_auto_context();
197587
197850
  init_observations();
197588
197851
  init_visibility();
197852
+ init_context_receipt();
197853
+ init_workflows2();
197589
197854
  init_operator_shared();
197590
197855
  context_default = defineCommand({
197591
197856
  meta: {
@@ -197601,10 +197866,12 @@ var init_context2 = __esm({
197601
197866
  },
197602
197867
  resume: { type: "boolean", description: "Always include the bounded prior-work projection" },
197603
197868
  refresh: { type: "string", description: "Project scan policy: auto, always, or never" },
197604
- json: { type: "boolean", description: "Emit machine-readable JSON output" }
197869
+ agent: { type: "string", description: "Optional target agent for compatible workflow selection" },
197870
+ json: { type: "boolean", description: "Emit machine-readable JSON output" },
197871
+ briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON" }
197605
197872
  },
197606
197873
  run: async ({ args }) => {
197607
- const asJson = !!args.json;
197874
+ const asJson = !!args.json || !!args.briefJson;
197608
197875
  const task = args.task?.trim() || args.input?.trim() || void 0;
197609
197876
  try {
197610
197877
  const { project, dataDir, reader } = await getCliProjectContext();
@@ -197613,23 +197880,25 @@ var init_context2 = __esm({
197613
197880
  dataDir,
197614
197881
  observations: filterReadableObservations(getAllObservations(), reader),
197615
197882
  task,
197883
+ agent: coerceAgentTarget(args.agent),
197616
197884
  refresh: coerceRefreshMode(args.refresh),
197617
197885
  reader,
197618
197886
  ...args.resume ? { continuation: "always" } : {}
197619
197887
  });
197888
+ const detailed = {
197889
+ project,
197890
+ lens: context.lens,
197891
+ brief: buildAutoProjectBrief(context),
197892
+ currentFacts: context.currentFacts,
197893
+ overview: context.overview,
197894
+ refresh: context.refresh,
197895
+ providerQuality: context.providerQuality,
197896
+ workset: context.workset,
197897
+ ...context.task ? { task: context.task } : {},
197898
+ ...context.continuation ? { continuation: context.continuation } : {}
197899
+ };
197620
197900
  emitResult(
197621
- {
197622
- project,
197623
- lens: context.lens,
197624
- brief: buildAutoProjectBrief(context),
197625
- currentFacts: context.currentFacts,
197626
- overview: context.overview,
197627
- refresh: context.refresh,
197628
- providerQuality: context.providerQuality,
197629
- workset: context.workset,
197630
- ...context.task ? { task: context.task } : {},
197631
- ...context.continuation ? { continuation: context.continuation } : {}
197632
- },
197901
+ args.briefJson ? buildBoundedContextReceipt({ workset: context.workset, providerQuality: context.providerQuality }) : detailed,
197633
197902
  formatAutoProjectContextPrompt(context),
197634
197903
  asJson
197635
197904
  );
@@ -197661,7 +197930,9 @@ var init_resume = __esm({
197661
197930
  args: {
197662
197931
  task: { type: "positional", description: "Current continuation task", required: false },
197663
197932
  refresh: { type: "string", description: "Project scan policy: auto, always, or never" },
197664
- json: { type: "boolean", description: "Emit machine-readable JSON output" }
197933
+ agent: { type: "string", description: "Optional target agent for compatible workflow selection" },
197934
+ json: { type: "boolean", description: "Emit machine-readable JSON output" },
197935
+ briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON" }
197665
197936
  },
197666
197937
  async run({ args }) {
197667
197938
  await context_default.run?.({
@@ -197669,7 +197940,9 @@ var init_resume = __esm({
197669
197940
  _: [],
197670
197941
  input: args.task,
197671
197942
  refresh: args.refresh,
197943
+ agent: args.agent,
197672
197944
  json: args.json,
197945
+ briefJson: args.briefJson,
197673
197946
  resume: true
197674
197947
  },
197675
197948
  rawArgs: [],
@@ -197949,6 +198222,7 @@ async function attachTaskWorkset(input) {
197949
198222
  projectId: input.projectId,
197950
198223
  dataDir: input.dataDir,
197951
198224
  task: input.pack.task,
198225
+ ...input.agent ? { agent: input.agent } : {},
197952
198226
  lens: input.lens,
197953
198227
  currentFacts: input.currentFacts ?? [],
197954
198228
  ...input.codeState ? { codeState: input.codeState } : {},
@@ -198057,7 +198331,8 @@ function formatStatus(status, quality) {
198057
198331
  status.indexedAt ? `- Indexed at: ${status.indexedAt}` : "- Indexed at: never",
198058
198332
  ...quality ? [
198059
198333
  `- Persistent provider: ${status.provider} (heuristic local index)`,
198060
- `- External semantic CodeGraph: ${quality.external.state}` + (quality.external.reason ? ` (${quality.external.reason})` : "")
198334
+ `- External semantic CodeGraph: ${quality.external.state}` + (quality.external.reason ? ` (${quality.external.reason})` : ""),
198335
+ `- Task-scoped provider: ${quality.selected} (${quality.selectedQuality})`
198061
198336
  ] : []
198062
198337
  ].join("\n");
198063
198338
  }
@@ -198065,6 +198340,8 @@ function formatUsageHint() {
198065
198340
  return [
198066
198341
  "Usage:",
198067
198342
  " memorix codegraph refresh",
198343
+ " memorix codegraph init # explicit local CodeGraph initialization",
198344
+ " memorix codegraph sync # explicit local CodeGraph synchronization",
198068
198345
  " memorix codegraph status --json",
198069
198346
  " memorix codegraph diff [--from <snapshot>] [--to <snapshot>]",
198070
198347
  " memorix codegraph impact --path <relative-source-path>",
@@ -198119,6 +198396,12 @@ function compactFacts(project) {
198119
198396
  facts.push(formatGitFact(current.git));
198120
198397
  return { facts, dirty: current.git.dirty };
198121
198398
  }
198399
+ function coerceAgentTarget2(input) {
198400
+ const value = input?.trim().toLowerCase();
198401
+ if (!value) return void 0;
198402
+ if (WORKFLOW_AGENT_TARGETS.includes(value)) return value;
198403
+ throw new Error("agent must be one of: " + WORKFLOW_AGENT_TARGETS.join(", "));
198404
+ }
198122
198405
  var codegraph_default;
198123
198406
  var init_codegraph = __esm({
198124
198407
  "src/cli/commands/codegraph.ts"() {
@@ -198133,6 +198416,8 @@ var init_codegraph = __esm({
198133
198416
  init_task_lens();
198134
198417
  init_external_provider();
198135
198418
  init_resolved_config();
198419
+ init_context_receipt();
198420
+ init_workflows2();
198136
198421
  init_observations();
198137
198422
  init_visibility();
198138
198423
  init_operator_shared();
@@ -198142,19 +198427,21 @@ var init_codegraph = __esm({
198142
198427
  description: "Inspect and refresh CodeGraph Memory for the current project"
198143
198428
  },
198144
198429
  args: {
198145
- action: { type: "string", description: "Action: status, refresh, diff, impact, or context-pack" },
198430
+ action: { type: "string", description: "Action: status, refresh, diff, impact, init, sync, or context-pack" },
198146
198431
  task: { type: "string", description: "Task text for context-pack" },
198147
198432
  path: { type: "string", description: "Indexed relative source path for impact" },
198148
198433
  limit: { type: "string", description: "Max active memories to inspect for context-pack" },
198149
198434
  from: { type: "string", description: "Earlier CodeGraph snapshot id for diff" },
198150
198435
  to: { type: "string", description: "Later CodeGraph snapshot id for diff" },
198151
198436
  impactLimit: { type: "string", description: "Maximum graph relations to inspect for diff impact" },
198152
- json: { type: "boolean", description: "Emit machine-readable JSON output" }
198437
+ agent: { type: "string", description: "Optional target agent for compatible workflow selection" },
198438
+ json: { type: "boolean", description: "Emit machine-readable JSON output" },
198439
+ briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON for context-pack" }
198153
198440
  },
198154
198441
  run: async ({ args }) => {
198155
198442
  const positional = args._ ?? [];
198156
198443
  const action = (positional[0] || args.action || "status").toLowerCase();
198157
- const asJson = !!args.json;
198444
+ const asJson = !!args.json || !!args.briefJson;
198158
198445
  try {
198159
198446
  const { project, dataDir, reader } = await getCliProjectContext();
198160
198447
  const store2 = new CodeGraphStore();
@@ -198221,6 +198508,26 @@ ${formatUsageHint()}`;
198221
198508
  );
198222
198509
  return;
198223
198510
  }
198511
+ case "init":
198512
+ case "sync": {
198513
+ const lifecycle = await runExternalCodeGraphLifecycle({
198514
+ action,
198515
+ projectRoot: project.rootPath,
198516
+ mode: codegraphConfig.externalContext,
198517
+ command: codegraphConfig.externalCommand,
198518
+ timeoutMs: codegraphConfig.externalTimeoutMs
198519
+ });
198520
+ emitResult(
198521
+ { project, lifecycle, providerQuality: lifecycle.quality },
198522
+ [
198523
+ lifecycle.message,
198524
+ `- External semantic CodeGraph: ${lifecycle.health.state}`,
198525
+ `- Task-scoped provider: ${lifecycle.quality.selected} (${lifecycle.quality.selectedQuality})`
198526
+ ].join("\n"),
198527
+ asJson
198528
+ );
198529
+ return;
198530
+ }
198224
198531
  case "diff": {
198225
198532
  const snapshots = store2.listSnapshots(project.id, 2);
198226
198533
  const fromSnapshotId = args.from?.trim() || snapshots[1]?.id;
@@ -198305,13 +198612,18 @@ ${formatUsageHint()}`;
198305
198612
  ...external.outline ? { semanticCode: external.outline } : {},
198306
198613
  providerQuality: external.quality,
198307
198614
  ...external.caution ? { runtimeCautions: [{ kind: "external-codegraph-fallback", message: external.caution }] } : {},
198308
- reader
198615
+ reader,
198616
+ agent: coerceAgentTarget2(args.agent)
198309
198617
  });
198310
- emitResult({ project, pack, providerQuality: external.quality }, buildContextPackPrompt(pack), asJson);
198618
+ emitResult(
198619
+ args.briefJson && pack.workset ? buildBoundedContextReceipt({ workset: pack.workset, providerQuality: external.quality }) : { project, pack, providerQuality: external.quality },
198620
+ buildContextPackPrompt(pack),
198621
+ asJson
198622
+ );
198311
198623
  return;
198312
198624
  }
198313
198625
  default:
198314
- emitError(`unknown codegraph action "${action}". Use "status", "refresh", "diff", "impact", or "context-pack".`, asJson);
198626
+ emitError(`unknown codegraph action "${action}". Use "status", "refresh", "init", "sync", "diff", "impact", or "context-pack".`, asJson);
198315
198627
  }
198316
198628
  } catch (error40) {
198317
198629
  emitError(error40 instanceof Error ? error40.message : String(error40), asJson);
@@ -199205,13 +199517,13 @@ function verifyType(declaredType, narrative, title) {
199205
199517
  }
199206
199518
  return { type: declaredType, corrected: false };
199207
199519
  }
199208
- async function extractFactsWithLLM(narrative, title, existingFacts) {
199520
+ async function extractFactsWithLLM(narrative, title, existingFacts, signal) {
199209
199521
  try {
199210
199522
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
199211
199523
  const input = `Title: ${title}
199212
199524
  Content: ${narrative}${existingFacts.length > 0 ? `
199213
199525
  Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
199214
- const response = await callLLM2(LLM_EXTRACT_PROMPT, input);
199526
+ const response = await callLLM2(LLM_EXTRACT_PROMPT, input, signal);
199215
199527
  const text2 = response.content.trim();
199216
199528
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
199217
199529
  if (!jsonMatch) return [];
@@ -199220,15 +199532,16 @@ Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
199220
199532
  if (!Array.isArray(facts)) return [];
199221
199533
  const existingLower = new Set(existingFacts.map((f7) => f7.toLowerCase().trim()));
199222
199534
  return facts.filter((f7) => typeof f7 === "string" && f7.length >= 5).filter((f7) => !existingLower.has(f7.toLowerCase().trim())).slice(0, 10);
199223
- } catch {
199535
+ } catch (error40) {
199536
+ if (signal?.aborted) throw error40;
199224
199537
  return [];
199225
199538
  }
199226
199539
  }
199227
- async function runExtract(input, existingEntities, useLLM = false) {
199540
+ async function runExtract(input, existingEntities, useLLM = false, signal) {
199228
199541
  const callerFacts = input.facts ?? [];
199229
199542
  let extractedFacts;
199230
199543
  if (useLLM) {
199231
- extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts);
199544
+ extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts, signal);
199232
199545
  if (extractedFacts.length === 0) {
199233
199546
  extractedFacts = extractFacts2(input.narrative, callerFacts);
199234
199547
  }
@@ -199448,7 +199761,7 @@ function mergeFacts2(oldFacts, newFacts) {
199448
199761
  }
199449
199762
  return merged;
199450
199763
  }
199451
- async function resolveWithLLM(extracted, hits, getObservation2) {
199764
+ async function resolveWithLLM(extracted, hits, getObservation2, signal) {
199452
199765
  try {
199453
199766
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
199454
199767
  const existingMemories = hits.slice(0, 5).map((h5, i4) => ({
@@ -199465,7 +199778,7 @@ Facts: ${extracted.facts.join("; ")}
199465
199778
 
199466
199779
  EXISTING MEMORIES:
199467
199780
  ${existingMemories.map((m6) => `[ID:${m6.id}] ${m6.title} | ${m6.content} | Facts: ${m6.facts}`).join("\n")}`;
199468
- const response = await callLLM2(LLM_RESOLVE_PROMPT, input);
199781
+ const response = await callLLM2(LLM_RESOLVE_PROMPT, input, signal);
199469
199782
  const text2 = response.content.trim();
199470
199783
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
199471
199784
  if (!jsonMatch) return null;
@@ -199502,7 +199815,8 @@ ${existingMemories.map((m6) => `[ID:${m6.id}] ${m6.title} | ${m6.content} | Fact
199502
199815
  };
199503
199816
  }
199504
199817
  return null;
199505
- } catch {
199818
+ } catch (error40) {
199819
+ if (signal?.aborted) throw error40;
199506
199820
  return null;
199507
199821
  }
199508
199822
  }
@@ -199520,19 +199834,20 @@ function scoreCandidate(extracted, candidate2) {
199520
199834
  const contradiction = hasContradiction(candidate2.narrative, extracted.narrative);
199521
199835
  return { score, searchSimilarity, entityMatch, richer, contradiction };
199522
199836
  }
199523
- async function runResolve(extracted, projectId, searchMemories3, getObservation2, useLLM = false) {
199837
+ async function runResolve(extracted, projectId, searchMemories3, getObservation2, useLLM = false, signal) {
199524
199838
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
199525
199839
  let hits;
199526
199840
  try {
199527
- hits = await searchMemories3(query, 5, projectId);
199528
- } catch {
199841
+ hits = await searchMemories3(query, 5, projectId, signal);
199842
+ } catch (error40) {
199843
+ if (signal?.aborted) throw error40;
199529
199844
  return { action: "new", reason: "Search unavailable, defaulting to new" };
199530
199845
  }
199531
199846
  if (hits.length === 0) {
199532
199847
  return { action: "new", reason: "No similar existing memories found" };
199533
199848
  }
199534
199849
  if (useLLM) {
199535
- const llmResult = await resolveWithLLM(extracted, hits, getObservation2);
199850
+ const llmResult = await resolveWithLLM(extracted, hits, getObservation2, signal);
199536
199851
  if (llmResult) return llmResult;
199537
199852
  }
199538
199853
  const scored = hits.map((hit) => ({
@@ -199954,7 +200269,7 @@ async function runFormation(input, config2) {
199954
200269
  const existingEntities = config2.getEntityNames();
199955
200270
  const extractStartTime = Date.now();
199956
200271
  emitStageEvent("extract", "start");
199957
- const extraction = await runExtract(input, existingEntities, config2.useLLM);
200272
+ const extraction = await runExtract(input, existingEntities, config2.useLLM, config2.signal);
199958
200273
  stageDurationsMs.extract = Date.now() - extractStartTime;
199959
200274
  emitStageEvent("extract", "success", stageDurationsMs.extract);
199960
200275
  stagesCompleted = 1;
@@ -199974,7 +200289,8 @@ async function runFormation(input, config2) {
199974
200289
  input.projectId,
199975
200290
  config2.searchMemories,
199976
200291
  config2.getObservation,
199977
- config2.useLLM
200292
+ config2.useLLM,
200293
+ config2.signal
199978
200294
  );
199979
200295
  stageDurationsMs.resolve = Date.now() - resolveStartTime;
199980
200296
  emitStageEvent("resolve", "success", stageDurationsMs.resolve);
@@ -258708,7 +259024,7 @@ The path should point to a directory containing a .git folder.`
258708
259024
  };
258709
259025
  const server = existingServer ?? new McpServer({
258710
259026
  name: "memorix",
258711
- version: true ? "1.5.0" : "1.0.1"
259027
+ version: true ? "1.6.0" : "1.0.1"
258712
259028
  });
258713
259029
  const originalRegisterTool = server.registerTool.bind(server);
258714
259030
  server.registerTool = ((name, ...args) => {
@@ -258897,8 +259213,8 @@ The path should point to a directory containing a .git folder.`
258897
259213
  mode: "active",
258898
259214
  useLLM: isLLMEnabled(),
258899
259215
  minValueScore: 0.3,
258900
- searchMemories: async (q4, limit2, pid) => {
258901
- const result = await compactSearch({ query: q4, limit: limit2, projectId: pid, status: "active", reader });
259216
+ searchMemories: async (q4, limit2, pid, signal) => {
259217
+ const result = await compactSearch({ query: q4, limit: limit2, projectId: pid, status: "active", reader, signal });
258902
259218
  if (result.entries.length === 0) return [];
258903
259219
  const details = await compactDetail(result.entries.map((e5) => e5.id), { reader });
258904
259220
  return details.documents.map((d4, i4) => ({
@@ -258928,16 +259244,19 @@ The path should point to a directory containing a .git folder.`
258928
259244
  getEntityNames: () => graphManager.getEntityNames(),
258929
259245
  onStageEvent: onFormationStageEvent
258930
259246
  };
258931
- formationResult = await withTimeout(
258932
- runFormation({
258933
- entityName,
258934
- type,
258935
- title,
258936
- narrative,
258937
- facts: safeFacts,
258938
- projectId: project.id,
258939
- source: "explicit"
258940
- }, formationConfig),
259247
+ formationResult = await withTimeoutSignal(
259248
+ async (signal) => {
259249
+ formationConfig.signal = signal;
259250
+ return runFormation({
259251
+ entityName,
259252
+ type,
259253
+ title,
259254
+ narrative,
259255
+ facts: safeFacts,
259256
+ projectId: project.id,
259257
+ source: "explicit"
259258
+ }, formationConfig);
259259
+ },
258941
259260
  FORMATION_TIMEOUT_MS,
258942
259261
  "Formation pipeline"
258943
259262
  );
@@ -259287,8 +259606,8 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
259287
259606
  mode: formationMode,
259288
259607
  useLLM: isLLMEnabled(),
259289
259608
  minValueScore: 0.3,
259290
- searchMemories: async (q4, limit2, pid) => {
259291
- const result = await compactSearch({ query: q4, limit: limit2, projectId: pid, status: "active", reader });
259609
+ searchMemories: async (q4, limit2, pid, signal) => {
259610
+ const result = await compactSearch({ query: q4, limit: limit2, projectId: pid, status: "active", reader, signal });
259292
259611
  if (result.entries.length === 0) return [];
259293
259612
  const details = await compactDetail(result.entries.map((e5) => e5.id), { reader });
259294
259613
  return details.documents.map((d4, i4) => ({
@@ -259309,8 +259628,11 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
259309
259628
  },
259310
259629
  getEntityNames: () => graphManager.getEntityNames()
259311
259630
  };
259312
- const formed = await withTimeout(
259313
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
259631
+ const formed = await withTimeoutSignal(
259632
+ async (signal) => {
259633
+ formationConfig.signal = signal;
259634
+ return runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig);
259635
+ },
259314
259636
  FORMATION_TIMEOUT_MS,
259315
259637
  "Shadow formation"
259316
259638
  );
@@ -259536,13 +259858,28 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
259536
259858
  refresh: external_exports2.enum(["auto", "always", "never"]).optional().default("auto").describe(
259537
259859
  "Code Memory refresh policy. auto refreshes only when missing or stale."
259538
259860
  ),
259539
- format: external_exports2.enum(["prompt", "summary", "json"]).optional().default("prompt").describe(
259540
- 'Output format. "prompt" is agent-ready; "summary" is human-readable; "json" is structured.'
259861
+ format: external_exports2.enum(["prompt", "summary", "json", "receipt"]).optional().default("prompt").describe(
259862
+ 'Output format. "prompt" is agent-ready; "summary" is human-readable; "receipt" is bounded JSON; "json" is detailed diagnostics.'
259541
259863
  ),
259864
+ agent: external_exports2.enum([
259865
+ "windsurf",
259866
+ "cursor",
259867
+ "claude-code",
259868
+ "codex",
259869
+ "copilot",
259870
+ "antigravity",
259871
+ "gemini-cli",
259872
+ "openclaw",
259873
+ "hermes",
259874
+ "omp",
259875
+ "kiro",
259876
+ "opencode",
259877
+ "trae"
259878
+ ]).optional().describe("Optional target agent for compatible workflow selection."),
259542
259879
  limit: external_exports2.number().optional().describe("Reserved for future source limits; current prompt stays compact by default.")
259543
259880
  }
259544
259881
  },
259545
- async ({ task, refresh, format }) => {
259882
+ async ({ task, refresh, format, agent }) => {
259546
259883
  const unresolved = requireResolvedProject("build project context for the current project");
259547
259884
  if (unresolved) return unresolved;
259548
259885
  const [
@@ -259554,12 +259891,14 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
259554
259891
  },
259555
259892
  { getObservationStore: getObservationStore2 },
259556
259893
  { MaintenanceJobStore: MaintenanceJobStore2 },
259557
- { enqueueCodegraphRefresh: enqueueCodegraphRefresh2 }
259894
+ { enqueueCodegraphRefresh: enqueueCodegraphRefresh2 },
259895
+ { buildBoundedContextReceipt: buildBoundedContextReceipt2 }
259558
259896
  ] = await Promise.all([
259559
259897
  Promise.resolve().then(() => (init_auto_context(), auto_context_exports)),
259560
259898
  Promise.resolve().then(() => (init_obs_store(), obs_store_exports)),
259561
259899
  Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)),
259562
- Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports))
259900
+ Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports)),
259901
+ Promise.resolve().then(() => (init_context_receipt(), context_receipt_exports))
259563
259902
  ]);
259564
259903
  const observations2 = filterReadableObservations(
259565
259904
  await getObservationStore2().loadByProject(project.id, { status: "active" }),
@@ -259570,6 +259909,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
259570
259909
  dataDir: projectDir2,
259571
259910
  observations: observations2,
259572
259911
  task,
259912
+ agent,
259573
259913
  refresh: refresh ?? "auto",
259574
259914
  reader: getObservationReader(),
259575
259915
  enqueueRefresh: () => {
@@ -259582,7 +259922,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
259582
259922
  });
259583
259923
  }
259584
259924
  });
259585
- const text2 = format === "json" ? JSON.stringify({ ...context, brief: buildAutoProjectBrief2(context) }, null, 2) : format === "summary" ? formatAutoProjectContextSummary2(context) : formatAutoProjectContextPrompt2(context);
259925
+ const text2 = format === "json" ? JSON.stringify({ ...context, brief: buildAutoProjectBrief2(context) }, null, 2) : format === "receipt" ? JSON.stringify(buildBoundedContextReceipt2({
259926
+ workset: context.workset,
259927
+ providerQuality: context.providerQuality
259928
+ }), null, 2) : format === "summary" ? formatAutoProjectContextSummary2(context) : formatAutoProjectContextPrompt2(context);
259586
259929
  autopilotRetrievalBoundary = {
259587
259930
  projectId: project.id,
259588
259931
  issuedAt: Date.now(),