memorix 1.4.5 → 1.5.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 (75) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/CLAUDE.md +7 -3
  3. package/README.md +5 -2
  4. package/dist/cli/index.js +1470 -1014
  5. package/dist/index.js +1279 -989
  6. package/dist/{maintenance-jobs-B1-W0ssz.d.ts → maintenance-jobs-DVRF-46r.d.ts} +1 -1
  7. package/dist/maintenance-runner.d.ts +1 -1
  8. package/dist/maintenance-runner.js +1769 -830
  9. package/dist/media-audio-runner.js +1774 -836
  10. package/dist/media-video-runner.js +1769 -831
  11. package/dist/memcode-runtime/CHANGELOG.md +78 -0
  12. package/dist/sdk.js +1271 -988
  13. package/dist/types.d.ts +6 -0
  14. package/dist/vector-backfill-runner.d.ts +1 -1
  15. package/dist/vector-backfill-runner.js +1765 -827
  16. package/docs/1.4.5-AGENT-AUTO-MEMORY-RESEARCH.md +95 -0
  17. package/docs/API_REFERENCE.md +23 -4
  18. package/docs/CONFIGURATION.md +29 -3
  19. package/docs/SETUP.md +5 -3
  20. package/package.json +1 -1
  21. package/plugins/antigravity/memorix/mcp_config.json +3 -1
  22. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  23. package/plugins/claude/memorix/.mcp.json +1 -1
  24. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  25. package/plugins/codebuddy/memorix/.mcp.json +1 -1
  26. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/codex/memorix/.mcp.json +1 -1
  28. package/plugins/copilot/memorix/.mcp.json +1 -1
  29. package/plugins/copilot/memorix/plugin.json +1 -1
  30. package/plugins/gemini/memorix/gemini-extension.json +4 -2
  31. package/plugins/omp/memorix/package.json +1 -1
  32. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  33. package/plugins/openclaw/memorix/.mcp.json +3 -1
  34. package/plugins/pi/memorix/package.json +1 -1
  35. package/server.json +2 -2
  36. package/src/cli/commands/dashboard.ts +14 -3
  37. package/src/cli/commands/ingest-commit.ts +47 -3
  38. package/src/cli/commands/memory.ts +17 -4
  39. package/src/cli/commands/purge.ts +120 -0
  40. package/src/cli/commands/serve-http.ts +39 -5
  41. package/src/cli/commands/setup.ts +3 -1
  42. package/src/cli/index.ts +3 -804
  43. package/src/codegraph/auto-context.ts +82 -1
  44. package/src/config/yaml-loader.ts +3 -8
  45. package/src/config.ts +23 -7
  46. package/src/evaluation/session-replay.ts +98 -0
  47. package/src/git/extractor.ts +5 -3
  48. package/src/hooks/installers/index.ts +15 -0
  49. package/src/knowledge/workset.ts +58 -0
  50. package/src/memory/long-term.ts +97 -2
  51. package/src/runtime/control-plane-maintenance.ts +1 -0
  52. package/src/runtime/isolated-maintenance.ts +1 -0
  53. package/src/runtime/lifecycle.ts +19 -0
  54. package/src/runtime/maintenance-jobs.ts +1 -0
  55. package/src/runtime/project-maintenance.ts +8 -0
  56. package/src/search/surfaced-registry.ts +62 -0
  57. package/src/server.ts +74 -14
  58. package/src/store/orama-store.ts +17 -1
  59. package/src/types.ts +6 -0
  60. package/src/cli/commands/audit-list.ts +0 -89
  61. package/src/cli/commands/purge-all-memory.ts +0 -85
  62. package/src/cli/commands/purge-project-memory.ts +0 -83
  63. package/src/cli/commands/uninstall-project-artifacts.ts +0 -86
  64. package/src/cli/tui/Sidebar.tsx +0 -179
  65. package/src/cli/tui/interactive-commands.ts +0 -261
  66. package/src/cli/tui/markdown-render.tsx +0 -371
  67. package/src/cli/tui/use-mouse.ts +0 -157
  68. package/src/cli/tui/useNavigation.ts +0 -56
  69. package/src/orchestrate/context-compact.ts +0 -188
  70. package/src/team/file-locks.ts +0 -173
  71. package/src/team/index.ts +0 -23
  72. package/src/team/messages.ts +0 -203
  73. package/src/team/persistence.ts +0 -96
  74. package/src/team/registry.ts +0 -158
  75. package/src/team/tasks.ts +0 -211
package/dist/index.js CHANGED
@@ -3373,6 +3373,7 @@ var init_resolved_config = __esm({
3373
3373
  // src/config.ts
3374
3374
  var config_exports = {};
3375
3375
  __export(config_exports, {
3376
+ defaultEmbeddingModelFor: () => defaultEmbeddingModelFor,
3376
3377
  getAgentLLMApiKey: () => getAgentLLMApiKey,
3377
3378
  getAgentLLMBaseUrl: () => getAgentLLMBaseUrl,
3378
3379
  getAgentLLMModel: () => getAgentLLMModel,
@@ -3389,7 +3390,6 @@ __export(config_exports, {
3389
3390
  getLLMProvider: () => getLLMProvider,
3390
3391
  getLoadedEnvFiles: () => getLoadedEnvFiles,
3391
3392
  getServerConfig: () => getServerConfig,
3392
- getTeamConfig: () => getTeamConfig,
3393
3393
  loadDotenv: () => loadDotenv,
3394
3394
  loadFileConfig: () => loadFileConfig,
3395
3395
  loadYamlConfig: () => loadYamlConfig,
@@ -3436,7 +3436,19 @@ function getEmbeddingBaseUrl() {
3436
3436
  return getResolvedEmbeddingLane().baseUrl || "https://api.openai.com/v1";
3437
3437
  }
3438
3438
  function getEmbeddingModel() {
3439
- return getResolvedEmbeddingLane().model || "text-embedding-3-small";
3439
+ const lane = getResolvedEmbeddingLane();
3440
+ return defaultEmbeddingModelFor(lane.baseUrl, lane.model);
3441
+ }
3442
+ function defaultEmbeddingModelFor(baseUrl, explicitModel) {
3443
+ if (explicitModel) return explicitModel;
3444
+ try {
3445
+ const url = baseUrl ? new URL(baseUrl) : null;
3446
+ if (url && (url.hostname === "openrouter.ai" || url.hostname.endsWith(".openrouter.ai"))) {
3447
+ return "qwen/qwen3-embedding-8b";
3448
+ }
3449
+ } catch {
3450
+ }
3451
+ return "text-embedding-3-small";
3440
3452
  }
3441
3453
  function getEmbeddingDimensions() {
3442
3454
  return getResolvedEmbeddingLane().dimensions ?? null;
@@ -3461,14 +3473,10 @@ function getServerConfig() {
3461
3473
  port: server.port
3462
3474
  };
3463
3475
  }
3464
- function getTeamConfig() {
3465
- return loadYamlConfig().team ?? {};
3466
- }
3467
3476
  var init_config = __esm({
3468
3477
  "src/config.ts"() {
3469
3478
  "use strict";
3470
3479
  init_esm_shims();
3471
- init_yaml_loader();
3472
3480
  init_resolved_config();
3473
3481
  init_dotenv_loader();
3474
3482
  init_legacy_loader();
@@ -5753,9 +5761,9 @@ async function saveRegistry(baseDir) {
5753
5761
  await fs7.mkdir(path9.dirname(filePath), { recursive: true });
5754
5762
  await fs7.writeFile(filePath, JSON.stringify(registryCache, null, 2), "utf-8");
5755
5763
  }
5756
- function findMatchingGroup(registry, projectInfo) {
5764
+ function findMatchingGroup(registry2, projectInfo) {
5757
5765
  const normalizedRoot = normalizePath(projectInfo.rootPath);
5758
- for (const group of registry.groups) {
5766
+ for (const group of registry2.groups) {
5759
5767
  if (group.aliases.includes(projectInfo.id)) return group;
5760
5768
  if (group.rootPaths.some((rp) => rp === normalizedRoot)) return group;
5761
5769
  if (projectInfo.gitRemote && group.gitRemote && group.gitRemote === projectInfo.gitRemote) {
@@ -5768,9 +5776,9 @@ function selectCanonical(aliases) {
5768
5776
  return [...aliases].sort((a, b) => idPriority(b) - idPriority(a))[0];
5769
5777
  }
5770
5778
  async function registerAlias(projectInfo, baseDir) {
5771
- const registry = await loadRegistry(baseDir);
5779
+ const registry2 = await loadRegistry(baseDir);
5772
5780
  const normalizedRoot = normalizePath(projectInfo.rootPath);
5773
- const existingGroup = findMatchingGroup(registry, projectInfo);
5781
+ const existingGroup = findMatchingGroup(registry2, projectInfo);
5774
5782
  if (existingGroup) {
5775
5783
  let changed = false;
5776
5784
  if (!existingGroup.aliases.includes(projectInfo.id)) {
@@ -5801,13 +5809,13 @@ async function registerAlias(projectInfo, baseDir) {
5801
5809
  rootPaths: [normalizedRoot],
5802
5810
  ...projectInfo.gitRemote ? { gitRemote: projectInfo.gitRemote } : {}
5803
5811
  };
5804
- registry.groups.push(newGroup);
5812
+ registry2.groups.push(newGroup);
5805
5813
  await saveRegistry(baseDir);
5806
5814
  return newGroup.canonical;
5807
5815
  }
5808
5816
  async function resolveAliases(projectId, baseDir) {
5809
- const registry = await loadRegistry(baseDir);
5810
- for (const group of registry.groups) {
5817
+ const registry2 = await loadRegistry(baseDir);
5818
+ for (const group of registry2.groups) {
5811
5819
  if (group.aliases.includes(projectId) || group.canonical === projectId) {
5812
5820
  return [...group.aliases];
5813
5821
  }
@@ -5815,8 +5823,8 @@ async function resolveAliases(projectId, baseDir) {
5815
5823
  return [projectId];
5816
5824
  }
5817
5825
  async function getCanonicalId(projectId, baseDir) {
5818
- const registry = await loadRegistry(baseDir);
5819
- for (const group of registry.groups) {
5826
+ const registry2 = await loadRegistry(baseDir);
5827
+ for (const group of registry2.groups) {
5820
5828
  if (group.aliases.includes(projectId) || group.canonical === projectId) {
5821
5829
  return group.canonical;
5822
5830
  }
@@ -5824,12 +5832,12 @@ async function getCanonicalId(projectId, baseDir) {
5824
5832
  return projectId;
5825
5833
  }
5826
5834
  async function getAllAliasGroups(baseDir) {
5827
- const registry = await loadRegistry(baseDir);
5828
- return registry.groups;
5835
+ const registry2 = await loadRegistry(baseDir);
5836
+ return registry2.groups;
5829
5837
  }
5830
5838
  async function autoMergeByBaseName(observedIds, baseDir) {
5831
5839
  if (observedIds.length <= 1) return 0;
5832
- const registry = await loadRegistry(baseDir);
5840
+ const registry2 = await loadRegistry(baseDir);
5833
5841
  const byBaseName = /* @__PURE__ */ new Map();
5834
5842
  for (const id of observedIds) {
5835
5843
  const baseName = id.split("/").pop() ?? id;
@@ -5842,7 +5850,7 @@ async function autoMergeByBaseName(observedIds, baseDir) {
5842
5850
  const existingGroups = /* @__PURE__ */ new Set();
5843
5851
  const ungroupedIds = [];
5844
5852
  for (const id of ids) {
5845
- const groupIdx = registry.groups.findIndex(
5853
+ const groupIdx = registry2.groups.findIndex(
5846
5854
  (g) => g.aliases.includes(id) || g.canonical === id
5847
5855
  );
5848
5856
  if (groupIdx >= 0) {
@@ -5856,7 +5864,7 @@ async function autoMergeByBaseName(observedIds, baseDir) {
5856
5864
  const canonical = selectCanonical(allIdsInGroup);
5857
5865
  if (existingGroups.size > 0) {
5858
5866
  const primaryIdx = [...existingGroups][0];
5859
- const primaryGroup = registry.groups[primaryIdx];
5867
+ const primaryGroup = registry2.groups[primaryIdx];
5860
5868
  for (const id of allIdsInGroup) {
5861
5869
  if (!primaryGroup.aliases.includes(id)) {
5862
5870
  primaryGroup.aliases.push(id);
@@ -5864,7 +5872,7 @@ async function autoMergeByBaseName(observedIds, baseDir) {
5864
5872
  }
5865
5873
  const otherIdxs = [...existingGroups].slice(1).sort((a, b) => b - a);
5866
5874
  for (const idx of otherIdxs) {
5867
- const other = registry.groups[idx];
5875
+ const other = registry2.groups[idx];
5868
5876
  for (const alias of other.aliases) {
5869
5877
  if (!primaryGroup.aliases.includes(alias)) {
5870
5878
  primaryGroup.aliases.push(alias);
@@ -5878,12 +5886,12 @@ async function autoMergeByBaseName(observedIds, baseDir) {
5878
5886
  if (other.gitRemote && !primaryGroup.gitRemote) {
5879
5887
  primaryGroup.gitRemote = other.gitRemote;
5880
5888
  }
5881
- registry.groups.splice(idx, 1);
5889
+ registry2.groups.splice(idx, 1);
5882
5890
  }
5883
5891
  primaryGroup.canonical = selectCanonical(primaryGroup.aliases);
5884
5892
  mergeCount++;
5885
5893
  } else {
5886
- registry.groups.push({
5894
+ registry2.groups.push({
5887
5895
  canonical,
5888
5896
  aliases: allIdsInGroup,
5889
5897
  rootPaths: []
@@ -6519,7 +6527,7 @@ async function searchObservations(options) {
6519
6527
  const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
6520
6528
  mark("queryExpansion");
6521
6529
  const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
6522
- const requestLimit = projectIds ? (options.limit ?? 20) * 3 : options.limit ?? 20;
6530
+ const requestLimit = projectIds || options.surfacedIds && options.surfacedIds.length > 0 ? (options.limit ?? 20) * 3 : options.limit ?? 20;
6523
6531
  const defaultBoost = {
6524
6532
  title: 3,
6525
6533
  entityName: 2,
@@ -6751,6 +6759,14 @@ async function searchObservations(options) {
6751
6759
  });
6752
6760
  intermediate.sort((a, b) => b.score - a.score);
6753
6761
  }
6762
+ if (options.surfacedIds && options.surfacedIds.length > 0) {
6763
+ const SURFACED_PENALTY = 0.5;
6764
+ const surfaced = new Set(options.surfacedIds);
6765
+ intermediate = intermediate.map(
6766
+ (entry) => surfaced.has(entry.id) ? { ...entry, score: (entry.score ?? 0) * SURFACED_PENALTY } : entry
6767
+ );
6768
+ intermediate.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
6769
+ }
6754
6770
  if (options.since) {
6755
6771
  const sinceDate = new Date(options.since).getTime();
6756
6772
  intermediate = intermediate.filter((e2) => new Date(e2.rawTime).getTime() >= sinceDate);
@@ -7742,7 +7758,8 @@ var init_maintenance_jobs = __esm({
7742
7758
  "claim-requalification",
7743
7759
  "knowledge-compile",
7744
7760
  "knowledge-lint",
7745
- "workflow-index"
7761
+ "workflow-index",
7762
+ "long-term-maintenance"
7746
7763
  ];
7747
7764
  DEFAULT_MAX_ATTEMPTS = 8;
7748
7765
  DEFAULT_LEASE_MS = 3e4;
@@ -8151,6 +8168,7 @@ __export(lifecycle_exports, {
8151
8168
  enqueueClaimRequalification: () => enqueueClaimRequalification,
8152
8169
  enqueueCodegraphRefresh: () => enqueueCodegraphRefresh,
8153
8170
  enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups,
8171
+ enqueueLongTermMaintenance: () => enqueueLongTermMaintenance,
8154
8172
  enqueueObservationQualification: () => enqueueObservationQualification
8155
8173
  });
8156
8174
  function queueFor(input) {
@@ -8194,6 +8212,14 @@ function enqueueObservationQualification(input) {
8194
8212
  payload: { source: input.source, limit: 100 }
8195
8213
  });
8196
8214
  }
8215
+ function enqueueLongTermMaintenance(input) {
8216
+ queueFor(input).enqueue({
8217
+ projectId: input.projectId,
8218
+ kind: "long-term-maintenance",
8219
+ dedupeKey: "long-term-maintenance",
8220
+ payload: { source: input.source }
8221
+ });
8222
+ }
8197
8223
  function enqueueKnowledgeFollowups(input) {
8198
8224
  const queue = queueFor(input);
8199
8225
  const payload = {
@@ -8373,7 +8399,8 @@ var init_isolated_maintenance = __esm({
8373
8399
  "claim-requalification",
8374
8400
  "knowledge-compile",
8375
8401
  "knowledge-lint",
8376
- "workflow-index"
8402
+ "workflow-index",
8403
+ "long-term-maintenance"
8377
8404
  ];
8378
8405
  DEFAULT_TIMEOUT_MS = 5 * 6e4;
8379
8406
  MAX_CHILD_OUTPUT_BYTES = 1e6;
@@ -144220,6 +144247,889 @@ var init_retention = __esm({
144220
144247
  }
144221
144248
  });
144222
144249
 
144250
+ // src/memory/owner.ts
144251
+ var owner_exports = {};
144252
+ __export(owner_exports, {
144253
+ resolveLocalMemoryOwner: () => resolveLocalMemoryOwner
144254
+ });
144255
+ import { randomUUID as randomUUID6 } from "crypto";
144256
+ import { promises as fs12 } from "fs";
144257
+ import path19 from "path";
144258
+ function ownerPath(dataDir) {
144259
+ const resolved = path19.resolve(dataDir);
144260
+ const home = path19.basename(resolved).toLocaleLowerCase("en-US") === "data" ? path19.dirname(resolved) : resolved;
144261
+ return path19.join(home, "memorix-user.json");
144262
+ }
144263
+ function normalizedOwnerId(value) {
144264
+ const trimmed = value?.trim();
144265
+ return trimmed && ID_PATTERN.test(trimmed) ? trimmed : void 0;
144266
+ }
144267
+ async function readOwnerFile(filePath) {
144268
+ try {
144269
+ const value = JSON.parse(await fs12.readFile(filePath, "utf8"));
144270
+ const id = typeof value.id === "string" ? normalizedOwnerId(value.id) : void 0;
144271
+ return id ? { id, source: "local-file" } : void 0;
144272
+ } catch {
144273
+ return void 0;
144274
+ }
144275
+ }
144276
+ async function resolveLocalMemoryOwner(dataDir, options = {}) {
144277
+ const configured = normalizedOwnerId(process.env.MEMORIX_USER_ID);
144278
+ if (configured) return { id: configured, source: "environment" };
144279
+ const filePath = ownerPath(dataDir);
144280
+ const existing = await readOwnerFile(filePath);
144281
+ if (existing || !options.create) return existing;
144282
+ await fs12.mkdir(path19.dirname(filePath), { recursive: true });
144283
+ const created = {
144284
+ version: 1,
144285
+ id: "local-" + randomUUID6(),
144286
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
144287
+ };
144288
+ try {
144289
+ await fs12.writeFile(filePath, JSON.stringify(created, null, 2) + "\n", { encoding: "utf8", flag: "wx" });
144290
+ return { id: created.id, source: "local-file" };
144291
+ } catch (error) {
144292
+ if (error?.code !== "EEXIST") throw error;
144293
+ const concurrent = await readOwnerFile(filePath);
144294
+ if (concurrent) return concurrent;
144295
+ throw new Error("Memorix could not establish a valid local memory owner identity.");
144296
+ }
144297
+ }
144298
+ var ID_PATTERN;
144299
+ var init_owner = __esm({
144300
+ "src/memory/owner.ts"() {
144301
+ "use strict";
144302
+ init_esm_shims();
144303
+ ID_PATTERN = /^[A-Za-z0-9:_-]{3,160}$/;
144304
+ }
144305
+ });
144306
+
144307
+ // src/memory/long-term-store.ts
144308
+ import { randomUUID as randomUUID7 } from "crypto";
144309
+ function optionalText2(value) {
144310
+ return typeof value === "string" && value.trim() ? value : void 0;
144311
+ }
144312
+ function arrayValue2(value) {
144313
+ if (typeof value !== "string" || !value) return [];
144314
+ try {
144315
+ const parsed = JSON.parse(value);
144316
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
144317
+ } catch {
144318
+ return [];
144319
+ }
144320
+ }
144321
+ function rowToMemory(row) {
144322
+ return {
144323
+ id: row.id,
144324
+ originProjectId: row.originProjectId,
144325
+ ownerId: row.ownerId,
144326
+ scope: row.scope,
144327
+ kind: row.kind,
144328
+ state: row.state,
144329
+ portability: row.portability,
144330
+ title: row.title,
144331
+ content: row.content,
144332
+ facts: arrayValue2(row.factsJson),
144333
+ tags: arrayValue2(row.tagsJson),
144334
+ ...optionalText2(row.applicability) ? { applicability: row.applicability } : {},
144335
+ origin: row.origin,
144336
+ createdAt: row.createdAt,
144337
+ updatedAt: row.updatedAt,
144338
+ ...optionalText2(row.qualifiedAt) ? { qualifiedAt: row.qualifiedAt } : {},
144339
+ ...optionalText2(row.approvedAt) ? { approvedAt: row.approvedAt } : {},
144340
+ ...optionalText2(row.archivedAt) ? { archivedAt: row.archivedAt } : {},
144341
+ ...optionalText2(row.supersededBy) ? { supersededBy: row.supersededBy } : {},
144342
+ ...optionalText2(row.lastValidatedAt) ? { lastValidatedAt: row.lastValidatedAt } : {},
144343
+ accessCount: Number(row.accessCount ?? 0),
144344
+ ...optionalText2(row.lastAccessedAt) ? { lastAccessedAt: row.lastAccessedAt } : {}
144345
+ };
144346
+ }
144347
+ function rowToEvidence(row) {
144348
+ return {
144349
+ id: row.id,
144350
+ memoryId: row.memoryId,
144351
+ kind: row.kind,
144352
+ referenceId: row.referenceId,
144353
+ relation: row.relation,
144354
+ ...optionalText2(row.locator) ? { locator: row.locator } : {},
144355
+ ...optionalText2(row.capturedHash) ? { capturedHash: row.capturedHash } : {},
144356
+ createdAt: row.createdAt
144357
+ };
144358
+ }
144359
+ function rowToEvent(row) {
144360
+ return {
144361
+ id: row.id,
144362
+ memoryId: row.memoryId,
144363
+ kind: row.kind,
144364
+ ...optionalText2(row.fromState) ? { fromState: row.fromState } : {},
144365
+ ...optionalText2(row.toState) ? { toState: row.toState } : {},
144366
+ ...optionalText2(row.detail) ? { detail: row.detail } : {},
144367
+ createdAt: row.createdAt
144368
+ };
144369
+ }
144370
+ var LongTermMemoryStore;
144371
+ var init_long_term_store = __esm({
144372
+ "src/memory/long-term-store.ts"() {
144373
+ "use strict";
144374
+ init_esm_shims();
144375
+ init_sqlite_db();
144376
+ LongTermMemoryStore = class {
144377
+ db = null;
144378
+ async init(dataDir) {
144379
+ this.db = getDatabase(dataDir);
144380
+ }
144381
+ transaction(fn) {
144382
+ return this.db.transaction(fn)();
144383
+ }
144384
+ get(id) {
144385
+ const row = this.db.prepare("SELECT * FROM long_term_memories WHERE id = ?").get(id);
144386
+ return row ? rowToMemory(row) : void 0;
144387
+ }
144388
+ list(options = {}) {
144389
+ const limit2 = Math.max(1, Math.min(1e3, Math.floor(options.limit ?? 500)));
144390
+ const clauses = [];
144391
+ const params = [];
144392
+ if (options.ownerId) {
144393
+ clauses.push("ownerId = ?");
144394
+ params.push(options.ownerId);
144395
+ }
144396
+ if (options.originProjectId) {
144397
+ clauses.push("originProjectId = ?");
144398
+ params.push(options.originProjectId);
144399
+ }
144400
+ const where = clauses.length ? " WHERE " + clauses.join(" AND ") : "";
144401
+ const rows = this.db.prepare(
144402
+ "SELECT * FROM long_term_memories" + where + " ORDER BY updatedAt DESC, id LIMIT ?"
144403
+ ).all(...params, limit2);
144404
+ return rows.map(rowToMemory);
144405
+ }
144406
+ insert(memory) {
144407
+ this.db.prepare(`
144408
+ INSERT INTO long_term_memories (
144409
+ id, originProjectId, ownerId, scope, kind, state, portability,
144410
+ title, content, factsJson, tagsJson, applicability, origin,
144411
+ createdAt, updatedAt, qualifiedAt, approvedAt, archivedAt,
144412
+ supersededBy, lastValidatedAt, accessCount, lastAccessedAt
144413
+ ) VALUES (
144414
+ @id, @originProjectId, @ownerId, @scope, @kind, @state, @portability,
144415
+ @title, @content, @factsJson, @tagsJson, @applicability, @origin,
144416
+ @createdAt, @updatedAt, @qualifiedAt, @approvedAt, @archivedAt,
144417
+ @supersededBy, @lastValidatedAt, @accessCount, @lastAccessedAt
144418
+ )
144419
+ `).run({
144420
+ ...memory,
144421
+ factsJson: JSON.stringify(memory.facts),
144422
+ tagsJson: JSON.stringify(memory.tags),
144423
+ applicability: memory.applicability ?? null,
144424
+ qualifiedAt: memory.qualifiedAt ?? null,
144425
+ approvedAt: memory.approvedAt ?? null,
144426
+ archivedAt: memory.archivedAt ?? null,
144427
+ supersededBy: memory.supersededBy ?? null,
144428
+ lastValidatedAt: memory.lastValidatedAt ?? null,
144429
+ lastAccessedAt: memory.lastAccessedAt ?? null
144430
+ });
144431
+ }
144432
+ update(memory) {
144433
+ this.db.prepare(`
144434
+ UPDATE long_term_memories SET
144435
+ scope = @scope,
144436
+ kind = @kind,
144437
+ state = @state,
144438
+ portability = @portability,
144439
+ title = @title,
144440
+ content = @content,
144441
+ factsJson = @factsJson,
144442
+ tagsJson = @tagsJson,
144443
+ applicability = @applicability,
144444
+ updatedAt = @updatedAt,
144445
+ qualifiedAt = @qualifiedAt,
144446
+ approvedAt = @approvedAt,
144447
+ archivedAt = @archivedAt,
144448
+ supersededBy = @supersededBy,
144449
+ lastValidatedAt = @lastValidatedAt,
144450
+ accessCount = @accessCount,
144451
+ lastAccessedAt = @lastAccessedAt
144452
+ WHERE id = @id
144453
+ `).run({
144454
+ ...memory,
144455
+ factsJson: JSON.stringify(memory.facts),
144456
+ tagsJson: JSON.stringify(memory.tags),
144457
+ applicability: memory.applicability ?? null,
144458
+ qualifiedAt: memory.qualifiedAt ?? null,
144459
+ approvedAt: memory.approvedAt ?? null,
144460
+ archivedAt: memory.archivedAt ?? null,
144461
+ supersededBy: memory.supersededBy ?? null,
144462
+ lastValidatedAt: memory.lastValidatedAt ?? null,
144463
+ lastAccessedAt: memory.lastAccessedAt ?? null
144464
+ });
144465
+ }
144466
+ insertEvidence(input) {
144467
+ const evidence = {
144468
+ id: randomUUID7(),
144469
+ ...input,
144470
+ createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
144471
+ };
144472
+ this.db.prepare(`
144473
+ INSERT INTO long_term_memory_evidence (
144474
+ id, memoryId, kind, referenceId, relation, locator, capturedHash, createdAt
144475
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
144476
+ `).run(
144477
+ evidence.id,
144478
+ evidence.memoryId,
144479
+ evidence.kind,
144480
+ evidence.referenceId,
144481
+ evidence.relation,
144482
+ evidence.locator ?? null,
144483
+ evidence.capturedHash ?? null,
144484
+ evidence.createdAt
144485
+ );
144486
+ return evidence;
144487
+ }
144488
+ listEvidence(memoryId) {
144489
+ return this.db.prepare(`
144490
+ SELECT * FROM long_term_memory_evidence WHERE memoryId = ? ORDER BY createdAt ASC, id
144491
+ `).all(memoryId).map(rowToEvidence);
144492
+ }
144493
+ recordEvent(input) {
144494
+ const event = {
144495
+ id: randomUUID7(),
144496
+ ...input,
144497
+ createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
144498
+ };
144499
+ this.db.prepare(`
144500
+ INSERT INTO long_term_memory_events (id, memoryId, kind, fromState, toState, detail, createdAt)
144501
+ VALUES (?, ?, ?, ?, ?, ?, ?)
144502
+ `).run(
144503
+ event.id,
144504
+ event.memoryId,
144505
+ event.kind,
144506
+ event.fromState ?? null,
144507
+ event.toState ?? null,
144508
+ event.detail ?? null,
144509
+ event.createdAt
144510
+ );
144511
+ return event;
144512
+ }
144513
+ listEvents(memoryId) {
144514
+ return this.db.prepare(`
144515
+ SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
144516
+ `).all(memoryId).map(rowToEvent);
144517
+ }
144518
+ markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
144519
+ const unique2 = [...new Set(ids.filter(Boolean))];
144520
+ if (unique2.length === 0) return;
144521
+ const statement = this.db.prepare(`
144522
+ UPDATE long_term_memories
144523
+ SET accessCount = accessCount + 1, lastAccessedAt = ?
144524
+ WHERE id = ?
144525
+ `);
144526
+ this.db.transaction(() => {
144527
+ for (const id of unique2) statement.run(at, id);
144528
+ })();
144529
+ }
144530
+ };
144531
+ }
144532
+ });
144533
+
144534
+ // src/memory/long-term-types.ts
144535
+ var LONG_TERM_MEMORY_KINDS, LONG_TERM_MEMORY_SCOPES, LONG_TERM_MEMORY_PORTABILITIES, LONG_TERM_MEMORY_EVIDENCE_KINDS, LONG_TERM_MEMORY_EVIDENCE_RELATIONS;
144536
+ var init_long_term_types = __esm({
144537
+ "src/memory/long-term-types.ts"() {
144538
+ "use strict";
144539
+ init_esm_shims();
144540
+ LONG_TERM_MEMORY_KINDS = ["episodic", "semantic", "procedural"];
144541
+ LONG_TERM_MEMORY_SCOPES = ["project", "user", "team"];
144542
+ LONG_TERM_MEMORY_PORTABILITIES = ["project-bound", "portable"];
144543
+ LONG_TERM_MEMORY_EVIDENCE_KINDS = [
144544
+ "observation",
144545
+ "claim",
144546
+ "workflow",
144547
+ "session",
144548
+ "checkpoint",
144549
+ "git",
144550
+ "code",
144551
+ "test",
144552
+ "run",
144553
+ "document",
144554
+ "manual",
144555
+ "user"
144556
+ ];
144557
+ LONG_TERM_MEMORY_EVIDENCE_RELATIONS = [
144558
+ "supports",
144559
+ "verifies",
144560
+ "derives",
144561
+ "user-confirmed"
144562
+ ];
144563
+ }
144564
+ });
144565
+
144566
+ // src/memory/long-term.ts
144567
+ var long_term_exports = {};
144568
+ __export(long_term_exports, {
144569
+ approveLongTermMemory: () => approveLongTermMemory,
144570
+ archiveLongTermMemory: () => archiveLongTermMemory,
144571
+ canReadLongTermMemory: () => canReadLongTermMemory,
144572
+ createLongTermMemoryCandidate: () => createLongTermMemoryCandidate,
144573
+ createManualLongTermMemory: () => createManualLongTermMemory,
144574
+ getLongTermMemoryDetail: () => getLongTermMemoryDetail,
144575
+ isEligibleLongTermMemory: () => isEligibleLongTermMemory,
144576
+ listLongTermMemories: () => listLongTermMemories,
144577
+ maintainLongTermMemories: () => maintainLongTermMemories,
144578
+ maybeAutoQualifyLongTermMemory: () => maybeAutoQualifyLongTermMemory,
144579
+ promoteObservationToLongTermMemory: () => promoteObservationToLongTermMemory,
144580
+ qualifyLongTermMemory: () => qualifyLongTermMemory,
144581
+ renderLongTermMemorySummary: () => renderLongTermMemorySummary,
144582
+ selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
144583
+ supersedeLongTermMemory: () => supersedeLongTermMemory
144584
+ });
144585
+ import { randomUUID as randomUUID8 } from "crypto";
144586
+ function isOneOf(value, values) {
144587
+ return typeof value === "string" && values.includes(value);
144588
+ }
144589
+ function compactText(value, field, max = MAX_TEXT_LENGTH) {
144590
+ if (typeof value !== "string") throw new Error("Long-term memory " + field + " must be text.");
144591
+ const safe = sanitizeCredentials(value).replace(/\s+/g, " ").trim();
144592
+ if (!safe) throw new Error("Long-term memory " + field + " is required.");
144593
+ if (safe.length > max) throw new Error("Long-term memory " + field + " is too long.");
144594
+ return safe;
144595
+ }
144596
+ function compactList(values, field) {
144597
+ if (!values) return [];
144598
+ if (!Array.isArray(values)) throw new Error("Long-term memory " + field + " must be an array.");
144599
+ return [...new Set(values.filter((value) => typeof value === "string").map((value) => compactText(value, field, 500)))].slice(0, MAX_LIST_ITEMS);
144600
+ }
144601
+ function normalizeEvidence(input) {
144602
+ if (!Array.isArray(input) || input.length === 0) {
144603
+ throw new Error("A long-term memory requires at least one source evidence reference.");
144604
+ }
144605
+ return input.map((evidence) => {
144606
+ if (!evidence || !isOneOf(evidence.kind, LONG_TERM_MEMORY_EVIDENCE_KINDS)) {
144607
+ throw new Error("Long-term memory evidence kind is invalid.");
144608
+ }
144609
+ if (!isOneOf(evidence.relation, LONG_TERM_MEMORY_EVIDENCE_RELATIONS)) {
144610
+ throw new Error("Long-term memory evidence relation is invalid.");
144611
+ }
144612
+ return {
144613
+ kind: evidence.kind,
144614
+ referenceId: compactText(evidence.referenceId, "evidence reference", 1e3),
144615
+ relation: evidence.relation,
144616
+ ...evidence.locator ? { locator: compactText(evidence.locator, "evidence locator", 1e3) } : {},
144617
+ ...evidence.capturedHash ? { capturedHash: compactText(evidence.capturedHash, "evidence hash", 1e3) } : {}
144618
+ };
144619
+ });
144620
+ }
144621
+ function assertScope(scope) {
144622
+ if (!isOneOf(scope, LONG_TERM_MEMORY_SCOPES)) throw new Error("Long-term memory scope is invalid.");
144623
+ }
144624
+ function assertKind(kind) {
144625
+ if (!isOneOf(kind, LONG_TERM_MEMORY_KINDS)) throw new Error("Long-term memory kind is invalid.");
144626
+ }
144627
+ function assertPortability(value) {
144628
+ if (!isOneOf(value, LONG_TERM_MEMORY_PORTABILITIES)) {
144629
+ throw new Error("Long-term memory portability is invalid.");
144630
+ }
144631
+ }
144632
+ function assertPortableEvidence(scope, portability, evidence) {
144633
+ if (portability !== "portable") return;
144634
+ if (scope !== "user") {
144635
+ throw new Error("Only user-scoped long-term memories may be portable across projects.");
144636
+ }
144637
+ if (evidence.some((item) => !PORTABLE_EVIDENCE_KINDS.has(item.kind))) {
144638
+ throw new Error("Portable user memory may only use manual or user-confirmed evidence; project code, Git, observation, test, session, claim, and workflow evidence must remain project-bound.");
144639
+ }
144640
+ }
144641
+ function requireTeamReader(scope, reader) {
144642
+ if (scope === "team" && reader?.isTeamMember !== true) {
144643
+ throw new Error("Team long-term memory requires an explicit active team identity for this project.");
144644
+ }
144645
+ }
144646
+ function matchesScope(memory, reader) {
144647
+ if (memory.scope === "project") return memory.originProjectId === reader.projectId;
144648
+ if (memory.scope === "team") {
144649
+ return memory.originProjectId === reader.projectId && reader.isTeamMember === true;
144650
+ }
144651
+ if (!reader.ownerId || memory.ownerId !== reader.ownerId) return false;
144652
+ return memory.portability === "portable" || memory.originProjectId === reader.projectId;
144653
+ }
144654
+ function canReadLongTermMemory(memory, reader) {
144655
+ return matchesScope(memory, reader);
144656
+ }
144657
+ function isEligibleLongTermMemory(memory) {
144658
+ return memory.state === "qualified" || memory.state === "approved";
144659
+ }
144660
+ function taskTerms(task) {
144661
+ const matches = task.toLocaleLowerCase("en-US").match(/[\p{L}\p{N}_./:-]+/gu) ?? [];
144662
+ return [...new Set(matches.filter((term) => term.length > 1 && !STOP_TERMS.has(term)))].slice(0, 16);
144663
+ }
144664
+ function kindFitScore(kind, task) {
144665
+ const normalized = task.toLocaleLowerCase("en-US");
144666
+ const hints = {
144667
+ episodic: ["resume", "continue", "handoff", "previous", "session", "\u63A5\u624B", "\u7EE7\u7EED", "\u4EA4\u63A5", "\u4E0A\u6B21", "\u4F1A\u8BDD"],
144668
+ semantic: ["why", "what", "architecture", "design", "decision", "reason", "\u4E3A\u4EC0\u4E48", "\u662F\u4EC0\u4E48", "\u67B6\u6784", "\u8BBE\u8BA1", "\u51B3\u7B56", "\u539F\u56E0"],
144669
+ procedural: ["how", "steps", "procedure", "run", "release", "publish", "verify", "implement", "\u6D41\u7A0B", "\u6B65\u9AA4", "\u53D1\u5E03", "\u9A8C\u8BC1", "\u4FEE\u590D", "\u5B9E\u73B0"]
144670
+ };
144671
+ return hints[kind].some((hint) => normalized.includes(hint)) ? 1 : 0;
144672
+ }
144673
+ function validationFreshnessScore(memory) {
144674
+ const validatedAt = memory.lastValidatedAt ?? memory.approvedAt ?? memory.qualifiedAt;
144675
+ if (!validatedAt) return 0;
144676
+ const time = Date.parse(validatedAt);
144677
+ if (!Number.isFinite(time)) return 0;
144678
+ const ageDays = Math.max(0, Date.now() - time) / (24 * 60 * 60 * 1e3);
144679
+ if (ageDays <= 30) return 0.35;
144680
+ if (ageDays <= 90) return 0.15;
144681
+ return 0;
144682
+ }
144683
+ function scoreForTask(memory, task) {
144684
+ const terms = taskTerms(task);
144685
+ if (terms.length === 0) return void 0;
144686
+ const title = memory.title.toLocaleLowerCase("en-US");
144687
+ const content = memory.content.toLocaleLowerCase("en-US");
144688
+ const facts = memory.facts.join(" ").toLocaleLowerCase("en-US");
144689
+ const tags = memory.tags.join(" ").toLocaleLowerCase("en-US");
144690
+ const applicability = (memory.applicability ?? "").toLocaleLowerCase("en-US");
144691
+ let score = memory.state === "approved" ? 0.5 : 0;
144692
+ const matched = [];
144693
+ for (const term of terms) {
144694
+ let termScore = 0;
144695
+ if (title.includes(term)) termScore += 5;
144696
+ if (tags.includes(term)) termScore += 4;
144697
+ if (applicability.includes(term)) termScore += 3;
144698
+ if (facts.includes(term)) termScore += 2;
144699
+ if (content.includes(term)) termScore += 1;
144700
+ if (termScore > 0) {
144701
+ score += termScore;
144702
+ matched.push(term);
144703
+ }
144704
+ }
144705
+ if (matched.length === 0) return void 0;
144706
+ score += kindFitScore(memory.kind, task);
144707
+ score += validationFreshnessScore(memory);
144708
+ const kindLabel = memory.kind === "procedural" ? "procedure" : memory.kind === "semantic" ? "fact" : "episode";
144709
+ const freshness = validationFreshnessScore(memory) > 0 ? "; recently validated" : "";
144710
+ return { score, reason: kindLabel + " matches " + matched.slice(0, 3).join(", ") + freshness };
144711
+ }
144712
+ function semanticText(memory) {
144713
+ return sanitizeCredentials([
144714
+ memory.title,
144715
+ memory.applicability,
144716
+ memory.tags.join(" "),
144717
+ memory.facts.join(" "),
144718
+ memory.content
144719
+ ].filter((value) => Boolean(value)).join("\n")).replace(/\s+/g, " ").trim().slice(0, MAX_TEXT_LENGTH);
144720
+ }
144721
+ function cosineSimilarity(left, right) {
144722
+ if (left.length === 0 || left.length !== right.length) return 0;
144723
+ let dot = 0;
144724
+ let leftMagnitude = 0;
144725
+ let rightMagnitude = 0;
144726
+ for (let index = 0; index < left.length; index += 1) {
144727
+ const a = left[index];
144728
+ const b = right[index];
144729
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return 0;
144730
+ dot += a * b;
144731
+ leftMagnitude += a * a;
144732
+ rightMagnitude += b * b;
144733
+ }
144734
+ if (leftMagnitude === 0 || rightMagnitude === 0) return 0;
144735
+ return dot / Math.sqrt(leftMagnitude * rightMagnitude);
144736
+ }
144737
+ function semanticSelection(memory, similarity, task) {
144738
+ if (similarity < MIN_SEMANTIC_SIMILARITY) return void 0;
144739
+ const kindLabel = memory.kind === "procedural" ? "procedure" : memory.kind === "semantic" ? "fact" : "episode";
144740
+ const freshness = validationFreshnessScore(memory) > 0 ? "; recently validated" : "";
144741
+ return {
144742
+ score: similarity * 10 + (memory.state === "approved" ? 0.5 : 0) + kindFitScore(memory.kind, task) + validationFreshnessScore(memory),
144743
+ reason: kindLabel + " semantic match " + similarity.toFixed(2) + freshness
144744
+ };
144745
+ }
144746
+ async function resolveSemanticProvider(explicit) {
144747
+ if (explicit !== void 0) return explicit;
144748
+ try {
144749
+ const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
144750
+ return await withTimeout(
144751
+ getEmbeddingProvider2({ requestTimeoutMs: SEMANTIC_RETRIEVAL_TIMEOUT_MS, retry: false }),
144752
+ SEMANTIC_RETRIEVAL_TIMEOUT_MS,
144753
+ "Long-term semantic provider initialization"
144754
+ );
144755
+ } catch {
144756
+ return null;
144757
+ }
144758
+ }
144759
+ function createStore(dataDir) {
144760
+ const store = new LongTermMemoryStore();
144761
+ return store.init(dataDir).then(() => store);
144762
+ }
144763
+ async function createManualLongTermMemory(input) {
144764
+ const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
144765
+ if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
144766
+ assertScope(input.scope);
144767
+ const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID8(), relation: "supports" }];
144768
+ return createLongTermMemoryWithDataDir({
144769
+ dataDir: input.dataDir,
144770
+ originProjectId: input.projectId,
144771
+ ownerId: owner.id,
144772
+ scope: input.scope,
144773
+ kind: input.kind,
144774
+ portability: input.portability,
144775
+ title: input.title,
144776
+ content: input.content,
144777
+ facts: input.facts,
144778
+ tags: input.tags,
144779
+ applicability: input.applicability,
144780
+ origin: "manual",
144781
+ evidence,
144782
+ reader: input.reader
144783
+ });
144784
+ }
144785
+ async function promoteObservationToLongTermMemory(input) {
144786
+ const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
144787
+ if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
144788
+ return createLongTermMemoryWithDataDir({
144789
+ dataDir: input.dataDir,
144790
+ originProjectId: input.observation.projectId,
144791
+ ownerId: owner.id,
144792
+ scope: input.scope,
144793
+ kind: input.kind,
144794
+ portability: "project-bound",
144795
+ title: input.observation.title,
144796
+ content: input.observation.narrative,
144797
+ facts: input.observation.facts,
144798
+ tags: input.tags ?? input.observation.concepts,
144799
+ applicability: input.applicability,
144800
+ origin: input.observation.source === "git" ? "git" : "agent",
144801
+ evidence: [{
144802
+ kind: "observation",
144803
+ referenceId: "obs:" + input.observation.projectId + ":" + input.observation.id,
144804
+ relation: "derives"
144805
+ }],
144806
+ reader: input.reader
144807
+ });
144808
+ }
144809
+ async function createLongTermMemoryCandidate(input) {
144810
+ return createLongTermMemoryWithDataDir(input);
144811
+ }
144812
+ async function createLongTermMemoryWithDataDir(input) {
144813
+ assertScope(input.scope);
144814
+ assertKind(input.kind);
144815
+ const portability = input.portability ?? "project-bound";
144816
+ assertPortability(portability);
144817
+ requireTeamReader(input.scope, input.reader);
144818
+ const evidenceInputs = normalizeEvidence(input.evidence);
144819
+ assertPortableEvidence(input.scope, portability, evidenceInputs);
144820
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
144821
+ const memory = {
144822
+ id: randomUUID8(),
144823
+ originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
144824
+ ownerId: compactText(input.ownerId, "owner id", 160),
144825
+ scope: input.scope,
144826
+ kind: input.kind,
144827
+ state: "candidate",
144828
+ portability,
144829
+ title: compactText(input.title, "title", 500),
144830
+ content: compactText(input.content, "content"),
144831
+ facts: compactList(input.facts, "facts"),
144832
+ tags: compactList(input.tags, "tags"),
144833
+ ...input.applicability ? { applicability: compactText(input.applicability, "applicability", 1e3) } : {},
144834
+ origin: input.origin ?? "manual",
144835
+ createdAt: now3,
144836
+ updatedAt: now3,
144837
+ accessCount: 0
144838
+ };
144839
+ const store = await createStore(input.dataDir);
144840
+ const evidence = store.transaction(() => {
144841
+ store.insert(memory);
144842
+ const written = evidenceInputs.map((item) => store.insertEvidence({ memoryId: memory.id, ...item, createdAt: now3 }));
144843
+ store.recordEvent({
144844
+ memoryId: memory.id,
144845
+ kind: "created",
144846
+ toState: "candidate",
144847
+ detail: "Created as a source-backed long-term memory candidate.",
144848
+ createdAt: now3
144849
+ });
144850
+ return written;
144851
+ });
144852
+ return { memory, evidence };
144853
+ }
144854
+ function transitionDetail(value) {
144855
+ return compactText(value, "transition reason", 1e3);
144856
+ }
144857
+ async function transitionMemory(input) {
144858
+ const store = await createStore(input.dataDir);
144859
+ return store.transaction(() => {
144860
+ const current = store.get(input.id);
144861
+ if (!current) throw new Error("Long-term memory was not found.");
144862
+ if (current.state !== input.expected) {
144863
+ throw new Error("Long-term memory must be " + input.expected + " before it can be " + input.next + ".");
144864
+ }
144865
+ const evidence = store.listEvidence(current.id);
144866
+ if (evidence.length === 0) throw new Error("Long-term memory cannot advance without source evidence.");
144867
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
144868
+ const next = {
144869
+ ...current,
144870
+ state: input.next,
144871
+ updatedAt: now3,
144872
+ lastValidatedAt: now3,
144873
+ ...input.next === "qualified" ? { qualifiedAt: now3 } : { approvedAt: now3 }
144874
+ };
144875
+ store.update(next);
144876
+ store.recordEvent({
144877
+ memoryId: next.id,
144878
+ kind: input.event,
144879
+ fromState: current.state,
144880
+ toState: next.state,
144881
+ detail: transitionDetail(input.reason),
144882
+ createdAt: now3
144883
+ });
144884
+ return next;
144885
+ });
144886
+ }
144887
+ async function qualifyLongTermMemory(input) {
144888
+ return transitionMemory({ ...input, expected: "candidate", next: "qualified", event: "qualified" });
144889
+ }
144890
+ async function approveLongTermMemory(input) {
144891
+ return transitionMemory({ ...input, expected: "qualified", next: "approved", event: "approved" });
144892
+ }
144893
+ async function archiveLongTermMemory(input) {
144894
+ const store = await createStore(input.dataDir);
144895
+ return store.transaction(() => {
144896
+ const current = store.get(input.id);
144897
+ if (!current) throw new Error("Long-term memory was not found.");
144898
+ if (current.state === "archived" || current.state === "superseded") {
144899
+ throw new Error("Long-term memory is already inactive.");
144900
+ }
144901
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
144902
+ const next = { ...current, state: "archived", archivedAt: now3, updatedAt: now3 };
144903
+ store.update(next);
144904
+ store.recordEvent({
144905
+ memoryId: next.id,
144906
+ kind: "archived",
144907
+ fromState: current.state,
144908
+ toState: "archived",
144909
+ detail: transitionDetail(input.reason),
144910
+ createdAt: now3
144911
+ });
144912
+ return next;
144913
+ });
144914
+ }
144915
+ async function supersedeLongTermMemory(input) {
144916
+ const store = await createStore(input.dataDir);
144917
+ return store.transaction(() => {
144918
+ const current = store.get(input.id);
144919
+ if (!current) throw new Error("Long-term memory was not found.");
144920
+ if (current.state === "archived" || current.state === "superseded") {
144921
+ throw new Error("Long-term memory is already inactive.");
144922
+ }
144923
+ if (current.id === input.supersededBy) {
144924
+ throw new Error("Long-term memory cannot supersede itself.");
144925
+ }
144926
+ const replacement = store.get(input.supersededBy);
144927
+ if (!replacement || !isEligibleLongTermMemory(replacement)) {
144928
+ throw new Error("Replacement long-term memory must be qualified or approved.");
144929
+ }
144930
+ if (replacement.scope !== current.scope) {
144931
+ throw new Error("Replacement long-term memory must use the same scope.");
144932
+ }
144933
+ if (current.scope === "user" && replacement.ownerId !== current.ownerId) {
144934
+ throw new Error("User long-term memory may only be superseded by the same local owner.");
144935
+ }
144936
+ if (current.scope !== "user" && replacement.originProjectId !== current.originProjectId) {
144937
+ throw new Error("Project and team long-term memory may only be superseded within the same project.");
144938
+ }
144939
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
144940
+ const next = {
144941
+ ...current,
144942
+ state: "superseded",
144943
+ supersededBy: replacement.id,
144944
+ updatedAt: now3
144945
+ };
144946
+ store.update(next);
144947
+ store.recordEvent({
144948
+ memoryId: next.id,
144949
+ kind: "superseded",
144950
+ fromState: current.state,
144951
+ toState: "superseded",
144952
+ detail: transitionDetail(input.reason),
144953
+ createdAt: now3
144954
+ });
144955
+ return next;
144956
+ });
144957
+ }
144958
+ async function maybeAutoQualifyLongTermMemory(input) {
144959
+ if (input.source !== "manual" && input.sourceDetail !== "explicit") return null;
144960
+ try {
144961
+ return await qualifyLongTermMemory({
144962
+ dataDir: input.dataDir,
144963
+ id: input.id,
144964
+ reason: "auto-qualified: explicit request carries its own source evidence"
144965
+ });
144966
+ } catch {
144967
+ return null;
144968
+ }
144969
+ }
144970
+ async function maintainLongTermMemories(input) {
144971
+ const now3 = input.now ?? /* @__PURE__ */ new Date();
144972
+ const decayDays = input.decayDays ?? 30;
144973
+ const cutoff = now3.getTime() - decayDays * 24 * 60 * 60 * 1e3;
144974
+ const result = { archived: 0, superseded: 0 };
144975
+ const store = await createStore(input.dataDir);
144976
+ const all = store.list({ originProjectId: input.projectId });
144977
+ for (const memory of all) {
144978
+ if (memory.state !== "qualified" && memory.state !== "approved") continue;
144979
+ const lastActivity = Date.parse(memory.lastAccessedAt ?? memory.updatedAt);
144980
+ if (!Number.isFinite(lastActivity) || lastActivity >= cutoff) continue;
144981
+ try {
144982
+ await archiveLongTermMemory({
144983
+ dataDir: input.dataDir,
144984
+ id: memory.id,
144985
+ reason: "auto-archive: no activity since " + new Date(lastActivity).toISOString().slice(0, 10)
144986
+ });
144987
+ result.archived += 1;
144988
+ } catch {
144989
+ }
144990
+ }
144991
+ const groups = /* @__PURE__ */ new Map();
144992
+ for (const memory of store.list({ originProjectId: input.projectId })) {
144993
+ if (memory.state !== "qualified" && memory.state !== "approved") continue;
144994
+ const key = memory.scope + "\0" + memory.kind + "\0" + memory.title.trim().toLowerCase();
144995
+ const group = groups.get(key) ?? [];
144996
+ group.push(memory);
144997
+ groups.set(key, group);
144998
+ }
144999
+ for (const group of groups.values()) {
145000
+ if (group.length < 2) continue;
145001
+ group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
145002
+ const newest = group[0];
145003
+ for (const older of group.slice(1)) {
145004
+ try {
145005
+ await supersedeLongTermMemory({
145006
+ dataDir: input.dataDir,
145007
+ id: older.id,
145008
+ supersededBy: newest.id,
145009
+ reason: "auto-supersede: a newer record shares the same title"
145010
+ });
145011
+ result.superseded += 1;
145012
+ } catch {
145013
+ }
145014
+ }
145015
+ }
145016
+ return result;
145017
+ }
145018
+ async function listLongTermMemories(input) {
145019
+ const store = await createStore(input.dataDir);
145020
+ return store.list({ limit: input.limit ?? 100 }).filter((memory) => canReadLongTermMemory(memory, input.reader)).filter((memory) => input.includeInactive || memory.state !== "archived" && memory.state !== "superseded").map((memory) => ({ memory, evidence: store.listEvidence(memory.id) }));
145021
+ }
145022
+ async function getLongTermMemoryDetail(input) {
145023
+ const store = await createStore(input.dataDir);
145024
+ const memory = store.get(input.id);
145025
+ if (!memory || !canReadLongTermMemory(memory, input.reader)) {
145026
+ throw new Error("Long-term memory was not found in the active scope.");
145027
+ }
145028
+ store.markAccess([memory.id]);
145029
+ return { memory, evidence: store.listEvidence(memory.id), events: store.listEvents(memory.id) };
145030
+ }
145031
+ async function selectLongTermMemoriesForTask(input) {
145032
+ const task = input.task?.trim();
145033
+ if (!task) return [];
145034
+ const owner = await resolveLocalMemoryOwner(input.dataDir, { create: false });
145035
+ const reader = {
145036
+ projectId: input.projectId,
145037
+ ...owner ? { ownerId: owner.id } : {},
145038
+ ...input.agentId ? { agentId: input.agentId } : {},
145039
+ ...input.isTeamMember ? { isTeamMember: true } : {}
145040
+ };
145041
+ const store = await createStore(input.dataDir);
145042
+ const eligible = store.list({ limit: 1e3 }).filter((memory) => isEligibleLongTermMemory(memory) && canReadLongTermMemory(memory, reader));
145043
+ let selected = eligible.map((memory) => {
145044
+ const ranked = scoreForTask(memory, task);
145045
+ return ranked ? {
145046
+ memory,
145047
+ evidence: store.listEvidence(memory.id),
145048
+ score: ranked.score,
145049
+ reason: ranked.reason
145050
+ } : void 0;
145051
+ }).filter((item) => Boolean(item)).sort((left, right) => right.score - left.score || right.memory.updatedAt.localeCompare(left.memory.updatedAt) || left.memory.id.localeCompare(right.memory.id));
145052
+ if (selected.length === 0 && eligible.length > 0) {
145053
+ const provider2 = await resolveSemanticProvider(input.embeddingProvider);
145054
+ const candidates = eligible.slice(0, MAX_SEMANTIC_CANDIDATES);
145055
+ if (provider2 && candidates.length > 0) {
145056
+ try {
145057
+ const vectors = await withTimeout(
145058
+ provider2.embedBatch([task, ...candidates.map(semanticText)], {
145059
+ timeoutMs: SEMANTIC_RETRIEVAL_TIMEOUT_MS,
145060
+ retry: false
145061
+ }),
145062
+ SEMANTIC_RETRIEVAL_TIMEOUT_MS,
145063
+ "Long-term semantic retrieval"
145064
+ );
145065
+ const query = vectors[0];
145066
+ if (Array.isArray(query)) {
145067
+ selected = candidates.map((memory, index) => {
145068
+ const vector = vectors[index + 1];
145069
+ const ranked = Array.isArray(vector) ? semanticSelection(memory, cosineSimilarity(query, vector), task) : void 0;
145070
+ return ranked ? {
145071
+ memory,
145072
+ evidence: store.listEvidence(memory.id),
145073
+ score: ranked.score,
145074
+ reason: ranked.reason
145075
+ } : void 0;
145076
+ }).filter((item) => Boolean(item)).sort((left, right) => right.score - left.score || right.memory.updatedAt.localeCompare(left.memory.updatedAt) || left.memory.id.localeCompare(right.memory.id));
145077
+ }
145078
+ } catch {
145079
+ }
145080
+ }
145081
+ }
145082
+ selected = selected.slice(0, Math.max(1, Math.min(input.limit ?? 3, 5)));
145083
+ store.markAccess(selected.map((item) => item.memory.id));
145084
+ return selected;
145085
+ }
145086
+ function renderLongTermMemorySummary(memory, maxTokens = 22) {
145087
+ const source = [memory.title, memory.applicability, memory.content].filter(Boolean).join(": ");
145088
+ const safe = sanitizeCredentials(source).replace(/\s+/g, " ").trim();
145089
+ return countTextTokens(safe) <= maxTokens ? safe : truncateToTokenBudget(safe, maxTokens);
145090
+ }
145091
+ var MAX_TEXT_LENGTH, MAX_LIST_ITEMS, MAX_SEMANTIC_CANDIDATES, SEMANTIC_RETRIEVAL_TIMEOUT_MS, MIN_SEMANTIC_SIMILARITY, PORTABLE_EVIDENCE_KINDS, STOP_TERMS;
145092
+ var init_long_term = __esm({
145093
+ "src/memory/long-term.ts"() {
145094
+ "use strict";
145095
+ init_esm_shims();
145096
+ init_token_budget();
145097
+ init_timeout();
145098
+ init_secret_filter();
145099
+ init_owner();
145100
+ init_long_term_store();
145101
+ init_long_term_types();
145102
+ MAX_TEXT_LENGTH = 12e3;
145103
+ MAX_LIST_ITEMS = 40;
145104
+ MAX_SEMANTIC_CANDIDATES = 48;
145105
+ SEMANTIC_RETRIEVAL_TIMEOUT_MS = 1800;
145106
+ MIN_SEMANTIC_SIMILARITY = 0.58;
145107
+ PORTABLE_EVIDENCE_KINDS = /* @__PURE__ */ new Set(["manual", "user"]);
145108
+ STOP_TERMS = /* @__PURE__ */ new Set([
145109
+ "the",
145110
+ "and",
145111
+ "for",
145112
+ "with",
145113
+ "from",
145114
+ "that",
145115
+ "this",
145116
+ "into",
145117
+ "when",
145118
+ "what",
145119
+ "\u5982\u4F55",
145120
+ "\u4EC0\u4E48",
145121
+ "\u8FD9\u4E2A",
145122
+ "\u90A3\u4E2A",
145123
+ "\u6211\u4EEC",
145124
+ "\u9879\u76EE",
145125
+ "\u8FDB\u884C",
145126
+ "\u9700\u8981",
145127
+ "\u7EE7\u7EED",
145128
+ "\u5904\u7406"
145129
+ ]);
145130
+ }
145131
+ });
145132
+
144223
145133
  // src/codegraph/ids.ts
144224
145134
  import { createHash as createHash5 } from "crypto";
144225
145135
  function digest(input) {
@@ -144258,7 +145168,7 @@ var store_exports = {};
144258
145168
  __export(store_exports, {
144259
145169
  CodeGraphStore: () => CodeGraphStore
144260
145170
  });
144261
- import { randomUUID as randomUUID6 } from "crypto";
145171
+ import { randomUUID as randomUUID9 } from "crypto";
144262
145172
  import { posix as pathPosix } from "path";
144263
145173
  function rowToFile(row) {
144264
145174
  return {
@@ -144975,7 +145885,7 @@ var init_store = __esm({
144975
145885
  * so an interrupted scan cannot be advertised as complete.
144976
145886
  */
144977
145887
  recordCodeStateSnapshot(input) {
144978
- const id = randomUUID6();
145888
+ const id = randomUUID9();
144979
145889
  let snapshot;
144980
145890
  const tx = this.db.transaction(() => {
144981
145891
  const previous = this.db.prepare(
@@ -145753,8 +146663,8 @@ var claim_store_exports = {};
145753
146663
  __export(claim_store_exports, {
145754
146664
  ClaimStore: () => ClaimStore
145755
146665
  });
145756
- import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
145757
- function optionalText2(value) {
146666
+ import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
146667
+ function optionalText3(value) {
145758
146668
  return typeof value === "string" && value ? value : void 0;
145759
146669
  }
145760
146670
  function rowToClaim(row) {
@@ -145770,38 +146680,38 @@ function rowToClaim(row) {
145770
146680
  status: row.status,
145771
146681
  confidence: Number(row.confidence),
145772
146682
  observedAt: row.observedAt,
145773
- ...optionalText2(row.validFrom) ? { validFrom: row.validFrom } : {},
145774
- ...optionalText2(row.validTo) ? { validTo: row.validTo } : {},
145775
- ...optionalText2(row.supersededBy) ? { supersededBy: row.supersededBy } : {},
146683
+ ...optionalText3(row.validFrom) ? { validFrom: row.validFrom } : {},
146684
+ ...optionalText3(row.validTo) ? { validTo: row.validTo } : {},
146685
+ ...optionalText3(row.supersededBy) ? { supersededBy: row.supersededBy } : {},
145776
146686
  reviewState: row.reviewState,
145777
146687
  origin: row.origin,
145778
146688
  createdAt: row.createdAt,
145779
146689
  updatedAt: row.updatedAt
145780
146690
  };
145781
146691
  }
145782
- function rowToEvidence(row) {
146692
+ function rowToEvidence2(row) {
145783
146693
  return {
145784
146694
  id: row.id,
145785
146695
  claimId: row.claimId,
145786
146696
  evidenceKind: row.evidenceKind,
145787
146697
  evidenceId: row.evidenceId,
145788
146698
  relation: row.relation,
145789
- ...optionalText2(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
145790
- ...optionalText2(row.locator) ? { locator: row.locator } : {},
145791
- ...optionalText2(row.capturedHash) ? { capturedHash: row.capturedHash } : {},
146699
+ ...optionalText3(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
146700
+ ...optionalText3(row.locator) ? { locator: row.locator } : {},
146701
+ ...optionalText3(row.capturedHash) ? { capturedHash: row.capturedHash } : {},
145792
146702
  createdAt: row.createdAt
145793
146703
  };
145794
146704
  }
145795
- function rowToEvent(row) {
146705
+ function rowToEvent2(row) {
145796
146706
  return {
145797
146707
  id: row.id,
145798
146708
  projectId: row.projectId,
145799
146709
  claimId: row.claimId,
145800
146710
  kind: row.kind,
145801
- ...optionalText2(row.fromStatus) ? { fromStatus: row.fromStatus } : {},
145802
- ...optionalText2(row.toStatus) ? { toStatus: row.toStatus } : {},
145803
- ...optionalText2(row.relatedClaimId) ? { relatedClaimId: row.relatedClaimId } : {},
145804
- ...optionalText2(row.detail) ? { detail: row.detail } : {},
146711
+ ...optionalText3(row.fromStatus) ? { fromStatus: row.fromStatus } : {},
146712
+ ...optionalText3(row.toStatus) ? { toStatus: row.toStatus } : {},
146713
+ ...optionalText3(row.relatedClaimId) ? { relatedClaimId: row.relatedClaimId } : {},
146714
+ ...optionalText3(row.detail) ? { detail: row.detail } : {},
145805
146715
  createdAt: row.createdAt
145806
146716
  };
145807
146717
  }
@@ -145937,7 +146847,7 @@ var init_claim_store = __esm({
145937
146847
  locator, capturedHash, evidenceKey, createdAt
145938
146848
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145939
146849
  `).run(
145940
- randomUUID7(),
146850
+ randomUUID10(),
145941
146851
  input.claimId,
145942
146852
  input.evidenceKind,
145943
146853
  input.evidenceId,
@@ -145955,11 +146865,11 @@ var init_claim_store = __esm({
145955
146865
  SELECT * FROM knowledge_claim_evidence
145956
146866
  WHERE claimId = ?
145957
146867
  ORDER BY createdAt ASC, id
145958
- `).all(claimId).map(rowToEvidence);
146868
+ `).all(claimId).map(rowToEvidence2);
145959
146869
  }
145960
146870
  recordEvent(input) {
145961
146871
  const event = {
145962
- id: randomUUID7(),
146872
+ id: randomUUID10(),
145963
146873
  ...input,
145964
146874
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
145965
146875
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -145987,7 +146897,7 @@ var init_claim_store = __esm({
145987
146897
  SELECT * FROM knowledge_claim_events
145988
146898
  WHERE claimId = ?
145989
146899
  ORDER BY createdAt ASC, id
145990
- `).all(claimId).map(rowToEvent);
146900
+ `).all(claimId).map(rowToEvent2);
145991
146901
  }
145992
146902
  };
145993
146903
  }
@@ -146004,8 +146914,8 @@ __export(claims_exports, {
146004
146914
  supersedeClaim: () => supersedeClaim,
146005
146915
  writeClaim: () => writeClaim
146006
146916
  });
146007
- import { createHash as createHash9, randomUUID as randomUUID8 } from "crypto";
146008
- function compactText(value, field) {
146917
+ import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
146918
+ function compactText2(value, field) {
146009
146919
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
146010
146920
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
146011
146921
  if (!sanitized) throw new Error("Claim " + field + " is required");
@@ -146013,7 +146923,7 @@ function compactText(value, field) {
146013
146923
  return sanitized;
146014
146924
  }
146015
146925
  function normalizedTerm(value) {
146016
- return compactText(value, "identity").toLocaleLowerCase("en-US");
146926
+ return compactText2(value, "identity").toLocaleLowerCase("en-US");
146017
146927
  }
146018
146928
  function hashIdentity(parts) {
146019
146929
  return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
@@ -146042,7 +146952,7 @@ function validateEvidence(input) {
146042
146952
  if (!evidence || typeof evidence !== "object") {
146043
146953
  throw new Error("Claim evidence must be an object");
146044
146954
  }
146045
- const evidenceId = compactText(evidence.evidenceId, "evidence id");
146955
+ const evidenceId = compactText2(evidence.evidenceId, "evidence id");
146046
146956
  if (evidenceId.length > MAX_EVIDENCE_ID_LENGTH) {
146047
146957
  throw new Error("Claim evidence id is too long");
146048
146958
  }
@@ -146056,9 +146966,9 @@ function validateEvidence(input) {
146056
146966
  evidenceKind: evidence.evidenceKind,
146057
146967
  evidenceId,
146058
146968
  relation: evidence.relation,
146059
- ...evidence.snapshotId ? { snapshotId: compactText(evidence.snapshotId, "evidence snapshot id") } : {},
146060
- ...evidence.locator ? { locator: compactText(evidence.locator, "evidence locator") } : {},
146061
- ...evidence.capturedHash ? { capturedHash: compactText(evidence.capturedHash, "evidence hash") } : {}
146969
+ ...evidence.snapshotId ? { snapshotId: compactText2(evidence.snapshotId, "evidence snapshot id") } : {},
146970
+ ...evidence.locator ? { locator: compactText2(evidence.locator, "evidence locator") } : {},
146971
+ ...evidence.capturedHash ? { capturedHash: compactText2(evidence.capturedHash, "evidence hash") } : {}
146062
146972
  };
146063
146973
  });
146064
146974
  }
@@ -146066,10 +146976,10 @@ function normalizeClaimInput(input) {
146066
146976
  if (!["project", "workspace", "team", "workflow", "task"].includes(input.scope)) {
146067
146977
  throw new Error("Claim scope is invalid");
146068
146978
  }
146069
- const projectId = compactText(input.projectId, "project id");
146070
- const subject = compactText(input.subject, "subject");
146071
- const predicate = compactText(input.predicate, "predicate");
146072
- const objectValue = compactText(input.objectValue, "object value");
146979
+ const projectId = compactText2(input.projectId, "project id");
146980
+ const subject = compactText2(input.subject, "subject");
146981
+ const predicate = compactText2(input.predicate, "predicate");
146982
+ const objectValue = compactText2(input.objectValue, "object value");
146073
146983
  const identity = buildClaimIdentity({ subject, predicate, objectValue, scope: input.scope });
146074
146984
  const origin = input.origin ?? "explicit";
146075
146985
  let reviewState = input.reviewState ?? "approved";
@@ -146090,7 +147000,7 @@ function normalizeClaimInput(input) {
146090
147000
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
146091
147001
  return {
146092
147002
  claim: {
146093
- id: randomUUID8(),
147003
+ id: randomUUID11(),
146094
147004
  projectId,
146095
147005
  subject,
146096
147006
  predicate,
@@ -146190,7 +147100,7 @@ function reviewClaim(store, input) {
146190
147100
  if (input.reviewState !== "approved" && input.reviewState !== "rejected") {
146191
147101
  throw new Error("A claim review must approve or reject the claim");
146192
147102
  }
146193
- const detail = compactText(input.detail, "review detail");
147103
+ const detail = compactText2(input.detail, "review detail");
146194
147104
  return store.transaction(() => {
146195
147105
  const claim = store.getClaim(input.claimId);
146196
147106
  if (!claim) throw new Error("Claim was not found");
@@ -146502,8 +147412,8 @@ var init_frontmatter = __esm({
146502
147412
 
146503
147413
  // src/knowledge/markdown.ts
146504
147414
  import { createHash as createHash10 } from "crypto";
146505
- import { promises as fs12 } from "fs";
146506
- import path19 from "path";
147415
+ import { promises as fs13 } from "fs";
147416
+ import path20 from "path";
146507
147417
  function hashContent(content) {
146508
147418
  return createHash10("sha256").update(content).digest("hex");
146509
147419
  }
@@ -146561,8 +147471,8 @@ function extractInternalMarkdownLinks(body) {
146561
147471
  return [...links].sort();
146562
147472
  }
146563
147473
  function resolvePageLink(sourceRelativePath, target) {
146564
- const sourceDir = path19.posix.dirname(sourceRelativePath.replace(/\\/g, "/"));
146565
- const normalized = path19.posix.normalize(path19.posix.join(sourceDir, target.replace(/\\/g, "/")));
147474
+ const sourceDir = path20.posix.dirname(sourceRelativePath.replace(/\\/g, "/"));
147475
+ const normalized = path20.posix.normalize(path20.posix.join(sourceDir, target.replace(/\\/g, "/")));
146566
147476
  if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../")) return void 0;
146567
147477
  return normalized;
146568
147478
  }
@@ -146584,13 +147494,13 @@ function renderKnowledgePage(frontmatter, body) {
146584
147494
  });
146585
147495
  }
146586
147496
  async function readKnowledgePage(absolutePath, workspaceRoot) {
146587
- const raw = await fs12.readFile(absolutePath, "utf8");
147497
+ const raw = await fs13.readFile(absolutePath, "utf8");
146588
147498
  try {
146589
147499
  const parsed = frontmatter_default(raw);
146590
147500
  const frontmatter = validateKnowledgePageFrontmatter(parsed.data);
146591
147501
  return {
146592
147502
  absolutePath,
146593
- relativePath: workspaceRoot ? path19.relative(workspaceRoot, absolutePath).split(path19.sep).join("/") : path19.basename(absolutePath),
147503
+ relativePath: workspaceRoot ? path20.relative(workspaceRoot, absolutePath).split(path20.sep).join("/") : path20.basename(absolutePath),
146594
147504
  frontmatter,
146595
147505
  body: parsed.content.trim(),
146596
147506
  contentHash: hashContent(raw),
@@ -146602,7 +147512,7 @@ async function readKnowledgePage(absolutePath, workspaceRoot) {
146602
147512
  }
146603
147513
  }
146604
147514
  async function writeKnowledgePage(absolutePath, content) {
146605
- await fs12.mkdir(path19.dirname(absolutePath), { recursive: true });
147515
+ await fs13.mkdir(path20.dirname(absolutePath), { recursive: true });
146606
147516
  await atomicWriteFile(absolutePath, content);
146607
147517
  return hashContent(content);
146608
147518
  }
@@ -146632,8 +147542,8 @@ __export(wiki_exports, {
146632
147542
  readKnowledgePage: () => readKnowledgePage
146633
147543
  });
146634
147544
  import { createHash as createHash11 } from "crypto";
146635
- import { promises as fs13 } from "fs";
146636
- import path20 from "path";
147545
+ import { promises as fs14 } from "fs";
147546
+ import path21 from "path";
146637
147547
  function hash2(value) {
146638
147548
  return createHash11("sha256").update(value).digest("hex");
146639
147549
  }
@@ -146765,7 +147675,7 @@ function candidatePage(workspace, title, claims, store) {
146765
147675
  }
146766
147676
  async function fileHashIfPresent(filePath) {
146767
147677
  try {
146768
- return pageContentHash(await fs13.readFile(filePath, "utf8"));
147678
+ return pageContentHash(await fs14.readFile(filePath, "utf8"));
146769
147679
  } catch (error) {
146770
147680
  const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
146771
147681
  if (code === "ENOENT") return void 0;
@@ -146789,8 +147699,8 @@ async function writeIndex(workspace, store) {
146789
147699
  lines.push("No pending proposals.");
146790
147700
  } else {
146791
147701
  for (const proposal of pending) {
146792
- const relative3 = path20.relative(paths.root, proposal.proposalPath).split(path20.sep).join("/");
146793
- lines.push("- [" + path20.basename(proposal.proposalPath, ".md") + "](" + relative3 + ")");
147702
+ const relative3 = path21.relative(paths.root, proposal.proposalPath).split(path21.sep).join("/");
147703
+ lines.push("- [" + path21.basename(proposal.proposalPath, ".md") + "](" + relative3 + ")");
146794
147704
  }
146795
147705
  }
146796
147706
  lines.push("");
@@ -146798,7 +147708,7 @@ async function writeIndex(workspace, store) {
146798
147708
  }
146799
147709
  async function appendLog(workspace, line) {
146800
147710
  const logPath = getKnowledgeWorkspacePaths(workspace).log;
146801
- await fs13.appendFile(logPath, "- " + now() + " " + line + "\n", "utf8");
147711
+ await fs14.appendFile(logPath, "- " + now() + " " + line + "\n", "utf8");
146802
147712
  }
146803
147713
  function toPageRecord(workspace, page, status, contentHash, manualContentHash) {
146804
147714
  return {
@@ -146883,9 +147793,9 @@ async function applyKnowledgeProposal(input) {
146883
147793
  }
146884
147794
  if (proposal.status !== "pending") throw new Error("Knowledge proposal is not pending review");
146885
147795
  const root = getKnowledgeWorkspacePaths(input.workspace).root;
146886
- const targetPath = path20.resolve(proposal.targetPath);
146887
- const proposalPath = path20.resolve(proposal.proposalPath);
146888
- if (!targetPath.startsWith(root + path20.sep) || !proposalPath.startsWith(root + path20.sep)) {
147796
+ const targetPath = path21.resolve(proposal.targetPath);
147797
+ const proposalPath = path21.resolve(proposal.proposalPath);
147798
+ if (!targetPath.startsWith(root + path21.sep) || !proposalPath.startsWith(root + path21.sep)) {
146889
147799
  throw new Error("Knowledge proposal path escapes its workspace");
146890
147800
  }
146891
147801
  await withFileLock(root, async () => {
@@ -146923,12 +147833,12 @@ async function markdownFiles(directory) {
146923
147833
  const files = [];
146924
147834
  let entries;
146925
147835
  try {
146926
- entries = await fs13.readdir(directory, { withFileTypes: true });
147836
+ entries = await fs14.readdir(directory, { withFileTypes: true });
146927
147837
  } catch {
146928
147838
  return files;
146929
147839
  }
146930
147840
  for (const entry of entries) {
146931
- const filePath = path20.join(directory, entry.name);
147841
+ const filePath = path21.join(directory, entry.name);
146932
147842
  if (entry.isDirectory()) {
146933
147843
  files.push(...await markdownFiles(filePath));
146934
147844
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
@@ -146951,13 +147861,13 @@ async function lintKnowledgeWorkspace(input) {
146951
147861
  kind: "malformed-frontmatter",
146952
147862
  severity: "error",
146953
147863
  message: error instanceof Error ? error.message : "Malformed knowledge page.",
146954
- relativePath: path20.relative(paths.root, filePath).split(path20.sep).join("/")
147864
+ relativePath: path21.relative(paths.root, filePath).split(path21.sep).join("/")
146955
147865
  });
146956
147866
  }
146957
147867
  }
146958
147868
  const indexed = /* @__PURE__ */ new Set();
146959
147869
  try {
146960
- const index = await fs13.readFile(paths.index, "utf8");
147870
+ const index = await fs14.readFile(paths.index, "utf8");
146961
147871
  for (const link of extractInternalMarkdownLinks(index)) {
146962
147872
  const resolved = resolvePageLink("index.md", link);
146963
147873
  if (resolved) indexed.add(resolved);
@@ -147046,7 +147956,7 @@ async function lintKnowledgeWorkspace(input) {
147046
147956
  kind: "manual-edit-protected",
147047
147957
  severity: "warning",
147048
147958
  message: "A newer proposal was held because the target page has manual edits.",
147049
- relativePath: path20.relative(paths.root, proposal.targetPath).split(path20.sep).join("/")
147959
+ relativePath: path21.relative(paths.root, proposal.targetPath).split(path21.sep).join("/")
147050
147960
  });
147051
147961
  }
147052
147962
  }
@@ -147179,7 +148089,7 @@ var workflow_store_exports = {};
147179
148089
  __export(workflow_store_exports, {
147180
148090
  WorkflowStore: () => WorkflowStore
147181
148091
  });
147182
- import { randomUUID as randomUUID9 } from "crypto";
148092
+ import { randomUUID as randomUUID12 } from "crypto";
147183
148093
  function parseJson(value, fallback) {
147184
148094
  if (typeof value !== "string" || !value) return fallback;
147185
148095
  try {
@@ -147188,7 +148098,7 @@ function parseJson(value, fallback) {
147188
148098
  return fallback;
147189
148099
  }
147190
148100
  }
147191
- function optionalText3(value) {
148101
+ function optionalText4(value) {
147192
148102
  return typeof value === "string" && value.trim() ? value : void 0;
147193
148103
  }
147194
148104
  function rowToWorkflow(row) {
@@ -147225,14 +148135,14 @@ function rowToRun(row) {
147225
148135
  workflowId: row.workflowId,
147226
148136
  projectId: row.projectId,
147227
148137
  task: row.task,
147228
- ...optionalText3(row.startingSnapshotId) ? { startingSnapshotId: row.startingSnapshotId } : {},
148138
+ ...optionalText4(row.startingSnapshotId) ? { startingSnapshotId: row.startingSnapshotId } : {},
147229
148139
  selectedEvidence: parseJson(row.selectedEvidenceJson, []),
147230
148140
  phaseState: parseJson(row.phaseStateJson, {}),
147231
148141
  outcome: row.outcome,
147232
148142
  verificationVerdict: row.verificationVerdict,
147233
- ...optionalText3(row.failureReason) ? { failureReason: row.failureReason } : {},
148143
+ ...optionalText4(row.failureReason) ? { failureReason: row.failureReason } : {},
147234
148144
  startedAt: row.startedAt,
147235
- ...optionalText3(row.completedAt) ? { completedAt: row.completedAt } : {}
148145
+ ...optionalText4(row.completedAt) ? { completedAt: row.completedAt } : {}
147236
148146
  };
147237
148147
  }
147238
148148
  var WorkflowStore;
@@ -147279,7 +148189,7 @@ var init_workflow_store = __esm({
147279
148189
  }
147280
148190
  recordRun(input) {
147281
148191
  const run = {
147282
- id: randomUUID9(),
148192
+ id: randomUUID12(),
147283
148193
  workflowId: input.workflowId,
147284
148194
  projectId: input.projectId,
147285
148195
  task: input.task.trim(),
@@ -147357,8 +148267,8 @@ var init_outcome_types = __esm({
147357
148267
  });
147358
148268
 
147359
148269
  // src/knowledge/outcome-store.ts
147360
- import { randomUUID as randomUUID10 } from "crypto";
147361
- function optionalText4(value) {
148270
+ import { randomUUID as randomUUID13 } from "crypto";
148271
+ function optionalText5(value) {
147362
148272
  return typeof value === "string" && value.trim() ? value : void 0;
147363
148273
  }
147364
148274
  function text(value, field, max = 1e3) {
@@ -147381,8 +148291,8 @@ function rowToSignal(row) {
147381
148291
  candidateId: row.candidateId,
147382
148292
  kind: row.kind,
147383
148293
  sourceRef: row.sourceRef,
147384
- ...optionalText4(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
147385
- ...optionalText4(row.detail) ? { detail: row.detail } : {},
148294
+ ...optionalText5(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
148295
+ ...optionalText5(row.detail) ? { detail: row.detail } : {},
147386
148296
  observedAt: row.observedAt
147387
148297
  };
147388
148298
  }
@@ -147401,7 +148311,7 @@ var init_outcome_store = __esm({
147401
148311
  }
147402
148312
  record(input) {
147403
148313
  const signal = {
147404
- id: randomUUID10(),
148314
+ id: randomUUID13(),
147405
148315
  projectId: text(input.projectId, "project id"),
147406
148316
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
147407
148317
  candidateId: text(input.candidateId, "candidate id"),
@@ -147828,8 +148738,8 @@ __export(workflows_exports, {
147828
148738
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
147829
148739
  });
147830
148740
  import { createHash as createHash12 } from "crypto";
147831
- import { promises as fs14 } from "fs";
147832
- import path21 from "path";
148741
+ import { promises as fs15 } from "fs";
148742
+ import path22 from "path";
147833
148743
  function hash3(value) {
147834
148744
  return createHash12("sha256").update(value).digest("hex");
147835
148745
  }
@@ -147850,7 +148760,7 @@ function requiredText(data, key) {
147850
148760
  }
147851
148761
  return value.trim();
147852
148762
  }
147853
- function optionalText5(data, key) {
148763
+ function optionalText6(data, key) {
147854
148764
  const value = data[key];
147855
148765
  if (value === void 0 || value === null || value === "") return void 0;
147856
148766
  if (typeof value !== "string") throw new Error("workflow frontmatter field " + key + " must be text");
@@ -147893,11 +148803,11 @@ function phaseFromValue(value, index) {
147893
148803
  }
147894
148804
  const data = value;
147895
148805
  const title = requiredText(data, "title");
147896
- const phaseId = optionalText5(data, "id") ?? slug(title) + "-" + (index + 1);
148806
+ const phaseId = optionalText6(data, "id") ?? slug(title) + "-" + (index + 1);
147897
148807
  return {
147898
148808
  id: phaseId,
147899
148809
  title,
147900
- instructions: optionalText5(data, "instructions") ?? "",
148810
+ instructions: optionalText6(data, "instructions") ?? "",
147901
148811
  branches: optionalStringArray(data, "branches"),
147902
148812
  expectedOutputs: optionalStringArray(data, "expectedOutputs"),
147903
148813
  verificationGates: optionalStringArray(data, "verificationGates")
@@ -147932,7 +148842,7 @@ function phasesFromBody(body) {
147932
148842
  }
147933
148843
  function normalizeSourcePath(sourcePath) {
147934
148844
  const normalized = sourcePath.replace(/\\/g, "/");
147935
- if (!normalized.startsWith("workflows/") || !normalized.toLowerCase().endsWith(".md") || normalized.includes("../") || path21.posix.normalize(normalized) !== normalized) {
148845
+ if (!normalized.startsWith("workflows/") || !normalized.toLowerCase().endsWith(".md") || normalized.includes("../") || path22.posix.normalize(normalized) !== normalized) {
147936
148846
  throw new Error("workflow source path must be a safe workflows/*.md path");
147937
148847
  }
147938
148848
  return normalized;
@@ -148006,7 +148916,7 @@ function parsedWorkflow(data, body, input) {
148006
148916
  id: requiredText(data, "id"),
148007
148917
  workspaceId: input.workspaceId,
148008
148918
  title: requiredText(data, "title"),
148009
- description: optionalText5(data, "description") ?? "",
148919
+ description: optionalText6(data, "description") ?? "",
148010
148920
  status: optionalStatus(data),
148011
148921
  version: optionalNumber(data, "version", 1),
148012
148922
  taskLenses: optionalStringArray(data, "taskLenses"),
@@ -148025,9 +148935,9 @@ function parsedWorkflow(data, body, input) {
148025
148935
  sourcePath: normalizeSourcePath(input.sourcePath),
148026
148936
  sourceHash: "",
148027
148937
  contentHash: input.contentHash,
148028
- createdAt: optionalText5(data, "createdAt") ?? now2(),
148029
- updatedAt: optionalText5(data, "updatedAt") ?? now2(),
148030
- ...optionalText5(data, "importedFrom") ? { importedFrom: optionalText5(data, "importedFrom") } : {}
148938
+ createdAt: optionalText6(data, "createdAt") ?? now2(),
148939
+ updatedAt: optionalText6(data, "updatedAt") ?? now2(),
148940
+ ...optionalText6(data, "importedFrom") ? { importedFrom: optionalText6(data, "importedFrom") } : {}
148031
148941
  };
148032
148942
  if (candidate.phases.length === 0) {
148033
148943
  throw new Error("workflow must contain at least one phase or a non-empty body");
@@ -148081,7 +148991,7 @@ async function writeCanonicalWorkflow(input) {
148081
148991
  const filePath = resolveKnowledgeWorkspaceFile(input.workspace, workflow.sourcePath);
148082
148992
  const content = renderWorkflowMarkdown(workflow);
148083
148993
  await withFileLock(paths.root, async () => {
148084
- await fs14.mkdir(path21.dirname(filePath), { recursive: true });
148994
+ await fs15.mkdir(path22.dirname(filePath), { recursive: true });
148085
148995
  await atomicWriteFile(filePath, content);
148086
148996
  });
148087
148997
  const store = await storeFor2(input.workspace);
@@ -148090,13 +149000,13 @@ async function writeCanonicalWorkflow(input) {
148090
149000
  async function markdownFiles2(directory) {
148091
149001
  let entries;
148092
149002
  try {
148093
- entries = await fs14.readdir(directory, { withFileTypes: true });
149003
+ entries = await fs15.readdir(directory, { withFileTypes: true });
148094
149004
  } catch {
148095
149005
  return [];
148096
149006
  }
148097
149007
  const files = [];
148098
149008
  for (const entry of entries) {
148099
- const absolutePath = path21.join(directory, entry.name);
149009
+ const absolutePath = path22.join(directory, entry.name);
148100
149010
  if (entry.isDirectory()) {
148101
149011
  files.push(...await markdownFiles2(absolutePath));
148102
149012
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
@@ -148111,9 +149021,9 @@ async function syncCanonicalWorkflows(workspace) {
148111
149021
  const workflows = [];
148112
149022
  const errors = [];
148113
149023
  for (const absolutePath of await markdownFiles2(paths.workflows)) {
148114
- const sourcePath = path21.relative(paths.root, absolutePath).split(path21.sep).join("/");
149024
+ const sourcePath = path22.relative(paths.root, absolutePath).split(path22.sep).join("/");
148115
149025
  try {
148116
- const raw = await fs14.readFile(absolutePath, "utf8");
149026
+ const raw = await fs15.readFile(absolutePath, "utf8");
148117
149027
  const workflow = parseWorkflowMarkdown(raw, { workspaceId: workspace.id, sourcePath });
148118
149028
  workflows.push(store.upsertWorkflow(workflow));
148119
149029
  } catch (error) {
@@ -148225,21 +149135,21 @@ function importedWorkflowSpec(input) {
148225
149135
  return materializeWorkflow(spec);
148226
149136
  }
148227
149137
  async function importWindsurfWorkflows(input) {
148228
- const sourceDirectory = path21.join(path21.resolve(input.projectRoot), ".windsurf", "workflows");
149138
+ const sourceDirectory = path22.join(path22.resolve(input.projectRoot), ".windsurf", "workflows");
148229
149139
  const paths = getKnowledgeWorkspacePaths(input.workspace);
148230
149140
  const imported = [];
148231
149141
  const skipped = [];
148232
149142
  let entries;
148233
149143
  try {
148234
- entries = await fs14.readdir(sourceDirectory, { withFileTypes: true });
149144
+ entries = await fs15.readdir(sourceDirectory, { withFileTypes: true });
148235
149145
  } catch {
148236
149146
  return { imported, skipped };
148237
149147
  }
148238
149148
  for (const entry of entries.filter((item) => item.isFile() && item.name.toLowerCase().endsWith(".md"))) {
148239
- const originalPath = path21.join(sourceDirectory, entry.name);
148240
- const sourcePath = path21.relative(path21.resolve(input.projectRoot), originalPath).split(path21.sep).join("/");
149149
+ const originalPath = path22.join(sourceDirectory, entry.name);
149150
+ const sourcePath = path22.relative(path22.resolve(input.projectRoot), originalPath).split(path22.sep).join("/");
148241
149151
  try {
148242
- const raw = await fs14.readFile(originalPath, "utf8");
149152
+ const raw = await fs15.readFile(originalPath, "utf8");
148243
149153
  const workflow = importedWorkflowSpec({
148244
149154
  workspace: input.workspace,
148245
149155
  sourceName: entry.name,
@@ -148248,7 +149158,7 @@ async function importWindsurfWorkflows(input) {
148248
149158
  });
148249
149159
  const targetPath = resolveKnowledgeWorkspaceFile(input.workspace, workflow.sourcePath);
148250
149160
  try {
148251
- await fs14.access(targetPath);
149161
+ await fs15.access(targetPath);
148252
149162
  skipped.push({ sourcePath, reason: "canonical workflow already exists and was preserved" });
148253
149163
  continue;
148254
149164
  } catch {
@@ -148261,12 +149171,12 @@ async function importWindsurfWorkflows(input) {
148261
149171
  });
148262
149172
  }
148263
149173
  }
148264
- await fs14.mkdir(paths.workflows, { recursive: true });
149174
+ await fs15.mkdir(paths.workflows, { recursive: true });
148265
149175
  return { imported, skipped };
148266
149176
  }
148267
149177
  function isWithin2(root, candidate) {
148268
- const relative3 = path21.relative(root, candidate);
148269
- return relative3 !== "" && relative3 !== ".." && !relative3.startsWith(".." + path21.sep) && !path21.isAbsolute(relative3);
149178
+ const relative3 = path22.relative(root, candidate);
149179
+ return relative3 !== "" && relative3 !== ".." && !relative3.startsWith(".." + path22.sep) && !path22.isAbsolute(relative3);
148270
149180
  }
148271
149181
  function adapterRelativePath(workflow, agent) {
148272
149182
  const name = "memorix-workflow-" + slug(workflow.id.replace(/^workflow:/, ""));
@@ -148352,15 +149262,15 @@ async function previewWorkflowAdapter(input) {
148352
149262
  reason: "The canonical workflow does not declare compatibility with this agent."
148353
149263
  };
148354
149264
  }
148355
- const projectRoot = path21.resolve(input.projectRoot);
148356
- const targetPath = path21.resolve(projectRoot, adapterRelativePath(input.workflow, input.agent));
149265
+ const projectRoot = path22.resolve(input.projectRoot);
149266
+ const targetPath = path22.resolve(projectRoot, adapterRelativePath(input.workflow, input.agent));
148357
149267
  if (!isWithin2(projectRoot, targetPath)) {
148358
149268
  throw new Error("Workflow adapter target escapes the project root");
148359
149269
  }
148360
149270
  const content = adapterContent(input.workflow, input.agent);
148361
149271
  let existing;
148362
149272
  try {
148363
- existing = await fs14.readFile(targetPath, "utf8");
149273
+ existing = await fs15.readFile(targetPath, "utf8");
148364
149274
  } catch (error) {
148365
149275
  const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
148366
149276
  if (code !== "ENOENT") throw error;
@@ -148381,11 +149291,11 @@ async function applyWorkflowAdapter(input) {
148381
149291
  if (initial.status === "unsupported" || initial.status === "conflict" || initial.status === "unchanged") {
148382
149292
  return initial;
148383
149293
  }
148384
- const projectRoot = path21.resolve(input.projectRoot);
149294
+ const projectRoot = path22.resolve(input.projectRoot);
148385
149295
  return withFileLock(projectRoot, async () => {
148386
149296
  const current = await previewWorkflowAdapter(input);
148387
149297
  if (current.status !== "create" && current.status !== "update") return current;
148388
- await fs14.mkdir(path21.dirname(current.targetPath), { recursive: true });
149298
+ await fs15.mkdir(path22.dirname(current.targetPath), { recursive: true });
148389
149299
  await atomicWriteFile(current.targetPath, current.content);
148390
149300
  return current;
148391
149301
  });
@@ -148675,6 +149585,11 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
148675
149585
  }
148676
149586
  return { action: "complete" };
148677
149587
  }
149588
+ if (job.kind === "long-term-maintenance") {
149589
+ const { maintainLongTermMemories: maintainLongTermMemories2 } = await Promise.resolve().then(() => (init_long_term(), long_term_exports));
149590
+ await maintainLongTermMemories2({ dataDir: projectDir2, projectId });
149591
+ return { action: "reschedule", delayMs: 24 * 60 * 60 * 1e3, resetAttempts: true };
149592
+ }
148678
149593
  if (job.kind === "consolidation") {
148679
149594
  const limit2 = consolidationBatchSize(job.payload);
148680
149595
  const result2 = await runAutomaticConsolidation(projectId, projectDir2, {
@@ -149016,15 +149931,15 @@ __export(vector_backfill_runner_exports, {
149016
149931
  });
149017
149932
  import { spawn as spawn4 } from "child_process";
149018
149933
  import { existsSync as existsSync10 } from "fs";
149019
- import path22 from "path";
149934
+ import path23 from "path";
149020
149935
  import { fileURLToPath as fileURLToPath5 } from "url";
149021
149936
  function isNonEmptyString(value) {
149022
149937
  return typeof value === "string" && value.trim().length > 0;
149023
149938
  }
149024
149939
  function resolveVectorBackfillRunnerPath(moduleUrl = import.meta.url) {
149025
- const moduleDir = path22.dirname(fileURLToPath5(moduleUrl));
149026
- const distDir = path22.basename(moduleDir) === "cli" ? path22.dirname(moduleDir) : path22.basename(moduleDir) === "runtime" && path22.basename(path22.dirname(moduleDir)) === "src" ? path22.join(path22.dirname(path22.dirname(moduleDir)), "dist") : moduleDir;
149027
- return path22.join(distDir, "vector-backfill-runner.js");
149940
+ const moduleDir = path23.dirname(fileURLToPath5(moduleUrl));
149941
+ const distDir = path23.basename(moduleDir) === "cli" ? path23.dirname(moduleDir) : path23.basename(moduleDir) === "runtime" && path23.basename(path23.dirname(moduleDir)) === "src" ? path23.join(path23.dirname(path23.dirname(moduleDir)), "dist") : moduleDir;
149942
+ return path23.join(distDir, "vector-backfill-runner.js");
149028
149943
  }
149029
149944
  function parseVectorBackfillRequest(raw) {
149030
149945
  let value;
@@ -149033,7 +149948,7 @@ function parseVectorBackfillRequest(raw) {
149033
149948
  } catch {
149034
149949
  throw new Error("Vector backfill runner received invalid JSON input");
149035
149950
  }
149036
- if (!value || typeof value !== "object" || !isNonEmptyString(value.projectId) || !isNonEmptyString(value.projectRoot) || !isNonEmptyString(value.dataDir) || !path22.isAbsolute(value.projectRoot) || !path22.isAbsolute(value.dataDir)) {
149951
+ if (!value || typeof value !== "object" || !isNonEmptyString(value.projectId) || !isNonEmptyString(value.projectRoot) || !isNonEmptyString(value.dataDir) || !path23.isAbsolute(value.projectRoot) || !path23.isAbsolute(value.dataDir)) {
149037
149952
  throw new Error("Vector backfill runner received an invalid request");
149038
149953
  }
149039
149954
  return {
@@ -150115,8 +151030,16 @@ var init_observations = __esm({
150115
151030
  });
150116
151031
 
150117
151032
  // src/store/session-store.ts
150118
- import path23 from "path";
150119
- import fs15 from "fs";
151033
+ var session_store_exports = {};
151034
+ __export(session_store_exports, {
151035
+ SessionGracefulDegrade: () => SessionGracefulDegrade,
151036
+ SessionSqliteStore: () => SessionSqliteStore,
151037
+ getSessionStore: () => getSessionStore,
151038
+ initSessionStore: () => initSessionStore,
151039
+ resetSessionStore: () => resetSessionStore
151040
+ });
151041
+ import path24 from "path";
151042
+ import fs16 from "fs";
150120
151043
  function sessionToRow(session) {
150121
151044
  return {
150122
151045
  id: session.id,
@@ -150145,6 +151068,10 @@ function getSessionStore() {
150145
151068
  }
150146
151069
  return _store3;
150147
151070
  }
151071
+ function resetSessionStore() {
151072
+ _store3 = null;
151073
+ _storeDataDir3 = null;
151074
+ }
150148
151075
  async function initSessionStore(dataDir) {
150149
151076
  if (_store3 && _storeDataDir3 === dataDir) return _store3;
150150
151077
  _store3 = null;
@@ -150198,10 +151125,10 @@ var init_session_store = __esm({
150198
151125
  async migrateFromJsonIfNeeded() {
150199
151126
  const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM sessions`).get();
150200
151127
  if (count2.cnt > 0) return;
150201
- const jsonPath = path23.join(this.dataDir, "sessions.json");
150202
- if (!fs15.existsSync(jsonPath)) return;
151128
+ const jsonPath = path24.join(this.dataDir, "sessions.json");
151129
+ if (!fs16.existsSync(jsonPath)) return;
150203
151130
  try {
150204
- const raw = fs15.readFileSync(jsonPath, "utf-8");
151131
+ const raw = fs16.readFileSync(jsonPath, "utf-8");
150205
151132
  const sessions = JSON.parse(raw);
150206
151133
  if (!Array.isArray(sessions) || sessions.length === 0) return;
150207
151134
  console.error(`[memorix] Migrating ${sessions.length} sessions from JSON to SQLite...`);
@@ -152555,8 +153482,8 @@ var init_trae = __esm({
152555
153482
  });
152556
153483
 
152557
153484
  // src/rules/syncer.ts
152558
- import { promises as fs16 } from "fs";
152559
- import path24 from "path";
153485
+ import { promises as fs17 } from "fs";
153486
+ import path25 from "path";
152560
153487
  var RulesSyncer;
152561
153488
  var init_syncer = __esm({
152562
153489
  "src/rules/syncer.ts"() {
@@ -152601,8 +153528,8 @@ var init_syncer = __esm({
152601
153528
  const found = await this.findFiles(scanPath);
152602
153529
  for (const filePath of found) {
152603
153530
  try {
152604
- const content = await fs16.readFile(filePath, "utf-8");
152605
- const relativePath = path24.relative(this.projectRoot, filePath).replace(/\\/g, "/");
153531
+ const content = await fs17.readFile(filePath, "utf-8");
153532
+ const relativePath = path25.relative(this.projectRoot, filePath).replace(/\\/g, "/");
152606
153533
  const parsed = entry.adapter.parse(relativePath, content);
152607
153534
  rules.push(...parsed);
152608
153535
  } catch {
@@ -152670,7 +153597,7 @@ var init_syncer = __esm({
152670
153597
  const entries = [];
152671
153598
  for (const adapter of this.adapters.values()) {
152672
153599
  const absolutePaths = adapter.filePatterns.map(
152673
- (p) => path24.join(this.projectRoot, p)
153600
+ (p) => path25.join(this.projectRoot, p)
152674
153601
  );
152675
153602
  entries.push({ adapter, paths: absolutePaths });
152676
153603
  }
@@ -152678,13 +153605,13 @@ var init_syncer = __esm({
152678
153605
  }
152679
153606
  /** Find files matching a glob-like path (simple implementation) */
152680
153607
  async findFiles(pattern) {
152681
- const dir = path24.dirname(pattern);
152682
- const fileGlob = path24.basename(pattern);
153608
+ const dir = path25.dirname(pattern);
153609
+ const fileGlob = path25.basename(pattern);
152683
153610
  try {
152684
- const stat4 = await fs16.stat(dir);
153611
+ const stat4 = await fs17.stat(dir);
152685
153612
  if (!stat4.isDirectory()) {
152686
153613
  try {
152687
- await fs16.access(pattern);
153614
+ await fs17.access(pattern);
152688
153615
  return [pattern];
152689
153616
  } catch {
152690
153617
  return [];
@@ -152692,7 +153619,7 @@ var init_syncer = __esm({
152692
153619
  }
152693
153620
  } catch {
152694
153621
  try {
152695
- await fs16.access(pattern);
153622
+ await fs17.access(pattern);
152696
153623
  return [pattern];
152697
153624
  } catch {
152698
153625
  return [];
@@ -152700,16 +153627,16 @@ var init_syncer = __esm({
152700
153627
  }
152701
153628
  if (fileGlob.includes("*")) {
152702
153629
  try {
152703
- const files = await fs16.readdir(dir);
153630
+ const files = await fs17.readdir(dir);
152704
153631
  const ext = fileGlob.replace("*", "");
152705
- return files.filter((f3) => ext ? f3.endsWith(ext) : true).map((f3) => path24.join(dir, f3));
153632
+ return files.filter((f3) => ext ? f3.endsWith(ext) : true).map((f3) => path25.join(dir, f3));
152706
153633
  } catch {
152707
153634
  return [];
152708
153635
  }
152709
153636
  }
152710
153637
  try {
152711
- await fs16.access(path24.join(dir, fileGlob));
152712
- return [path24.join(dir, fileGlob)];
153638
+ await fs17.access(path25.join(dir, fileGlob));
153639
+ return [path25.join(dir, fileGlob)];
152713
153640
  } catch {
152714
153641
  return [];
152715
153642
  }
@@ -155288,12 +156215,52 @@ var init_formation_timeout = __esm({
155288
156215
  }
155289
156216
  });
155290
156217
 
156218
+ // src/search/surfaced-registry.ts
156219
+ function touch(key) {
156220
+ const ids = registry.get(key);
156221
+ if (!ids) return [];
156222
+ registry.delete(key);
156223
+ registry.set(key, ids);
156224
+ return ids;
156225
+ }
156226
+ function getSurfacedIds(key) {
156227
+ return [...touch(key)];
156228
+ }
156229
+ function recordSurfacedIds(key, ids) {
156230
+ if (ids.length === 0) return;
156231
+ let list = registry.get(key) ?? [];
156232
+ const known = new Set(list);
156233
+ for (const id of ids) {
156234
+ if (known.has(id)) continue;
156235
+ known.add(id);
156236
+ list.push(id);
156237
+ }
156238
+ if (list.length > MAX_IDS) {
156239
+ list = list.slice(list.length - MAX_IDS);
156240
+ }
156241
+ registry.set(key, list);
156242
+ if (registry.size > MAX_KEYS) {
156243
+ const oldestKey = registry.keys().next().value;
156244
+ if (oldestKey !== void 0 && oldestKey !== key) registry.delete(oldestKey);
156245
+ }
156246
+ }
156247
+ var MAX_KEYS, MAX_IDS, registry;
156248
+ var init_surfaced_registry = __esm({
156249
+ "src/search/surfaced-registry.ts"() {
156250
+ "use strict";
156251
+ init_esm_shims();
156252
+ MAX_KEYS = 64;
156253
+ MAX_IDS = 200;
156254
+ registry = /* @__PURE__ */ new Map();
156255
+ }
156256
+ });
156257
+
155291
156258
  // src/server/request-context.ts
155292
- import path25 from "path";
156259
+ import path26 from "path";
155293
156260
  function resolveRoot(projectRoot) {
155294
156261
  const value = projectRoot.trim();
155295
156262
  if (!value) throw new Error("Project binding requires a non-empty project root");
155296
- return path25.resolve(value);
156263
+ return path26.resolve(value);
155297
156264
  }
155298
156265
  function createProjectBindingController(projectRoot) {
155299
156266
  return new ProjectBindingController(projectRoot);
@@ -155942,827 +156909,6 @@ var init_session = __esm({
155942
156909
  }
155943
156910
  });
155944
156911
 
155945
- // src/memory/owner.ts
155946
- var owner_exports = {};
155947
- __export(owner_exports, {
155948
- resolveLocalMemoryOwner: () => resolveLocalMemoryOwner
155949
- });
155950
- import { randomUUID as randomUUID11 } from "crypto";
155951
- import { promises as fs17 } from "fs";
155952
- import path26 from "path";
155953
- function ownerPath(dataDir) {
155954
- const resolved = path26.resolve(dataDir);
155955
- const home = path26.basename(resolved).toLocaleLowerCase("en-US") === "data" ? path26.dirname(resolved) : resolved;
155956
- return path26.join(home, "memorix-user.json");
155957
- }
155958
- function normalizedOwnerId(value) {
155959
- const trimmed = value?.trim();
155960
- return trimmed && ID_PATTERN.test(trimmed) ? trimmed : void 0;
155961
- }
155962
- async function readOwnerFile(filePath) {
155963
- try {
155964
- const value = JSON.parse(await fs17.readFile(filePath, "utf8"));
155965
- const id = typeof value.id === "string" ? normalizedOwnerId(value.id) : void 0;
155966
- return id ? { id, source: "local-file" } : void 0;
155967
- } catch {
155968
- return void 0;
155969
- }
155970
- }
155971
- async function resolveLocalMemoryOwner(dataDir, options = {}) {
155972
- const configured = normalizedOwnerId(process.env.MEMORIX_USER_ID);
155973
- if (configured) return { id: configured, source: "environment" };
155974
- const filePath = ownerPath(dataDir);
155975
- const existing = await readOwnerFile(filePath);
155976
- if (existing || !options.create) return existing;
155977
- await fs17.mkdir(path26.dirname(filePath), { recursive: true });
155978
- const created = {
155979
- version: 1,
155980
- id: "local-" + randomUUID11(),
155981
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
155982
- };
155983
- try {
155984
- await fs17.writeFile(filePath, JSON.stringify(created, null, 2) + "\n", { encoding: "utf8", flag: "wx" });
155985
- return { id: created.id, source: "local-file" };
155986
- } catch (error) {
155987
- if (error?.code !== "EEXIST") throw error;
155988
- const concurrent = await readOwnerFile(filePath);
155989
- if (concurrent) return concurrent;
155990
- throw new Error("Memorix could not establish a valid local memory owner identity.");
155991
- }
155992
- }
155993
- var ID_PATTERN;
155994
- var init_owner = __esm({
155995
- "src/memory/owner.ts"() {
155996
- "use strict";
155997
- init_esm_shims();
155998
- ID_PATTERN = /^[A-Za-z0-9:_-]{3,160}$/;
155999
- }
156000
- });
156001
-
156002
- // src/memory/long-term-store.ts
156003
- import { randomUUID as randomUUID12 } from "crypto";
156004
- function optionalText6(value) {
156005
- return typeof value === "string" && value.trim() ? value : void 0;
156006
- }
156007
- function arrayValue2(value) {
156008
- if (typeof value !== "string" || !value) return [];
156009
- try {
156010
- const parsed = JSON.parse(value);
156011
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
156012
- } catch {
156013
- return [];
156014
- }
156015
- }
156016
- function rowToMemory(row) {
156017
- return {
156018
- id: row.id,
156019
- originProjectId: row.originProjectId,
156020
- ownerId: row.ownerId,
156021
- scope: row.scope,
156022
- kind: row.kind,
156023
- state: row.state,
156024
- portability: row.portability,
156025
- title: row.title,
156026
- content: row.content,
156027
- facts: arrayValue2(row.factsJson),
156028
- tags: arrayValue2(row.tagsJson),
156029
- ...optionalText6(row.applicability) ? { applicability: row.applicability } : {},
156030
- origin: row.origin,
156031
- createdAt: row.createdAt,
156032
- updatedAt: row.updatedAt,
156033
- ...optionalText6(row.qualifiedAt) ? { qualifiedAt: row.qualifiedAt } : {},
156034
- ...optionalText6(row.approvedAt) ? { approvedAt: row.approvedAt } : {},
156035
- ...optionalText6(row.archivedAt) ? { archivedAt: row.archivedAt } : {},
156036
- ...optionalText6(row.supersededBy) ? { supersededBy: row.supersededBy } : {},
156037
- ...optionalText6(row.lastValidatedAt) ? { lastValidatedAt: row.lastValidatedAt } : {},
156038
- accessCount: Number(row.accessCount ?? 0),
156039
- ...optionalText6(row.lastAccessedAt) ? { lastAccessedAt: row.lastAccessedAt } : {}
156040
- };
156041
- }
156042
- function rowToEvidence2(row) {
156043
- return {
156044
- id: row.id,
156045
- memoryId: row.memoryId,
156046
- kind: row.kind,
156047
- referenceId: row.referenceId,
156048
- relation: row.relation,
156049
- ...optionalText6(row.locator) ? { locator: row.locator } : {},
156050
- ...optionalText6(row.capturedHash) ? { capturedHash: row.capturedHash } : {},
156051
- createdAt: row.createdAt
156052
- };
156053
- }
156054
- function rowToEvent2(row) {
156055
- return {
156056
- id: row.id,
156057
- memoryId: row.memoryId,
156058
- kind: row.kind,
156059
- ...optionalText6(row.fromState) ? { fromState: row.fromState } : {},
156060
- ...optionalText6(row.toState) ? { toState: row.toState } : {},
156061
- ...optionalText6(row.detail) ? { detail: row.detail } : {},
156062
- createdAt: row.createdAt
156063
- };
156064
- }
156065
- var LongTermMemoryStore;
156066
- var init_long_term_store = __esm({
156067
- "src/memory/long-term-store.ts"() {
156068
- "use strict";
156069
- init_esm_shims();
156070
- init_sqlite_db();
156071
- LongTermMemoryStore = class {
156072
- db = null;
156073
- async init(dataDir) {
156074
- this.db = getDatabase(dataDir);
156075
- }
156076
- transaction(fn) {
156077
- return this.db.transaction(fn)();
156078
- }
156079
- get(id) {
156080
- const row = this.db.prepare("SELECT * FROM long_term_memories WHERE id = ?").get(id);
156081
- return row ? rowToMemory(row) : void 0;
156082
- }
156083
- list(options = {}) {
156084
- const limit2 = Math.max(1, Math.min(1e3, Math.floor(options.limit ?? 500)));
156085
- const clauses = [];
156086
- const params = [];
156087
- if (options.ownerId) {
156088
- clauses.push("ownerId = ?");
156089
- params.push(options.ownerId);
156090
- }
156091
- if (options.originProjectId) {
156092
- clauses.push("originProjectId = ?");
156093
- params.push(options.originProjectId);
156094
- }
156095
- const where = clauses.length ? " WHERE " + clauses.join(" AND ") : "";
156096
- const rows = this.db.prepare(
156097
- "SELECT * FROM long_term_memories" + where + " ORDER BY updatedAt DESC, id LIMIT ?"
156098
- ).all(...params, limit2);
156099
- return rows.map(rowToMemory);
156100
- }
156101
- insert(memory) {
156102
- this.db.prepare(`
156103
- INSERT INTO long_term_memories (
156104
- id, originProjectId, ownerId, scope, kind, state, portability,
156105
- title, content, factsJson, tagsJson, applicability, origin,
156106
- createdAt, updatedAt, qualifiedAt, approvedAt, archivedAt,
156107
- supersededBy, lastValidatedAt, accessCount, lastAccessedAt
156108
- ) VALUES (
156109
- @id, @originProjectId, @ownerId, @scope, @kind, @state, @portability,
156110
- @title, @content, @factsJson, @tagsJson, @applicability, @origin,
156111
- @createdAt, @updatedAt, @qualifiedAt, @approvedAt, @archivedAt,
156112
- @supersededBy, @lastValidatedAt, @accessCount, @lastAccessedAt
156113
- )
156114
- `).run({
156115
- ...memory,
156116
- factsJson: JSON.stringify(memory.facts),
156117
- tagsJson: JSON.stringify(memory.tags),
156118
- applicability: memory.applicability ?? null,
156119
- qualifiedAt: memory.qualifiedAt ?? null,
156120
- approvedAt: memory.approvedAt ?? null,
156121
- archivedAt: memory.archivedAt ?? null,
156122
- supersededBy: memory.supersededBy ?? null,
156123
- lastValidatedAt: memory.lastValidatedAt ?? null,
156124
- lastAccessedAt: memory.lastAccessedAt ?? null
156125
- });
156126
- }
156127
- update(memory) {
156128
- this.db.prepare(`
156129
- UPDATE long_term_memories SET
156130
- scope = @scope,
156131
- kind = @kind,
156132
- state = @state,
156133
- portability = @portability,
156134
- title = @title,
156135
- content = @content,
156136
- factsJson = @factsJson,
156137
- tagsJson = @tagsJson,
156138
- applicability = @applicability,
156139
- updatedAt = @updatedAt,
156140
- qualifiedAt = @qualifiedAt,
156141
- approvedAt = @approvedAt,
156142
- archivedAt = @archivedAt,
156143
- supersededBy = @supersededBy,
156144
- lastValidatedAt = @lastValidatedAt,
156145
- accessCount = @accessCount,
156146
- lastAccessedAt = @lastAccessedAt
156147
- WHERE id = @id
156148
- `).run({
156149
- ...memory,
156150
- factsJson: JSON.stringify(memory.facts),
156151
- tagsJson: JSON.stringify(memory.tags),
156152
- applicability: memory.applicability ?? null,
156153
- qualifiedAt: memory.qualifiedAt ?? null,
156154
- approvedAt: memory.approvedAt ?? null,
156155
- archivedAt: memory.archivedAt ?? null,
156156
- supersededBy: memory.supersededBy ?? null,
156157
- lastValidatedAt: memory.lastValidatedAt ?? null,
156158
- lastAccessedAt: memory.lastAccessedAt ?? null
156159
- });
156160
- }
156161
- insertEvidence(input) {
156162
- const evidence = {
156163
- id: randomUUID12(),
156164
- ...input,
156165
- createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
156166
- };
156167
- this.db.prepare(`
156168
- INSERT INTO long_term_memory_evidence (
156169
- id, memoryId, kind, referenceId, relation, locator, capturedHash, createdAt
156170
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
156171
- `).run(
156172
- evidence.id,
156173
- evidence.memoryId,
156174
- evidence.kind,
156175
- evidence.referenceId,
156176
- evidence.relation,
156177
- evidence.locator ?? null,
156178
- evidence.capturedHash ?? null,
156179
- evidence.createdAt
156180
- );
156181
- return evidence;
156182
- }
156183
- listEvidence(memoryId) {
156184
- return this.db.prepare(`
156185
- SELECT * FROM long_term_memory_evidence WHERE memoryId = ? ORDER BY createdAt ASC, id
156186
- `).all(memoryId).map(rowToEvidence2);
156187
- }
156188
- recordEvent(input) {
156189
- const event = {
156190
- id: randomUUID12(),
156191
- ...input,
156192
- createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
156193
- };
156194
- this.db.prepare(`
156195
- INSERT INTO long_term_memory_events (id, memoryId, kind, fromState, toState, detail, createdAt)
156196
- VALUES (?, ?, ?, ?, ?, ?, ?)
156197
- `).run(
156198
- event.id,
156199
- event.memoryId,
156200
- event.kind,
156201
- event.fromState ?? null,
156202
- event.toState ?? null,
156203
- event.detail ?? null,
156204
- event.createdAt
156205
- );
156206
- return event;
156207
- }
156208
- listEvents(memoryId) {
156209
- return this.db.prepare(`
156210
- SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
156211
- `).all(memoryId).map(rowToEvent2);
156212
- }
156213
- markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
156214
- const unique2 = [...new Set(ids.filter(Boolean))];
156215
- if (unique2.length === 0) return;
156216
- const statement = this.db.prepare(`
156217
- UPDATE long_term_memories
156218
- SET accessCount = accessCount + 1, lastAccessedAt = ?
156219
- WHERE id = ?
156220
- `);
156221
- this.db.transaction(() => {
156222
- for (const id of unique2) statement.run(at, id);
156223
- })();
156224
- }
156225
- };
156226
- }
156227
- });
156228
-
156229
- // src/memory/long-term-types.ts
156230
- var LONG_TERM_MEMORY_KINDS, LONG_TERM_MEMORY_SCOPES, LONG_TERM_MEMORY_PORTABILITIES, LONG_TERM_MEMORY_EVIDENCE_KINDS, LONG_TERM_MEMORY_EVIDENCE_RELATIONS;
156231
- var init_long_term_types = __esm({
156232
- "src/memory/long-term-types.ts"() {
156233
- "use strict";
156234
- init_esm_shims();
156235
- LONG_TERM_MEMORY_KINDS = ["episodic", "semantic", "procedural"];
156236
- LONG_TERM_MEMORY_SCOPES = ["project", "user", "team"];
156237
- LONG_TERM_MEMORY_PORTABILITIES = ["project-bound", "portable"];
156238
- LONG_TERM_MEMORY_EVIDENCE_KINDS = [
156239
- "observation",
156240
- "claim",
156241
- "workflow",
156242
- "session",
156243
- "checkpoint",
156244
- "git",
156245
- "code",
156246
- "test",
156247
- "run",
156248
- "document",
156249
- "manual",
156250
- "user"
156251
- ];
156252
- LONG_TERM_MEMORY_EVIDENCE_RELATIONS = [
156253
- "supports",
156254
- "verifies",
156255
- "derives",
156256
- "user-confirmed"
156257
- ];
156258
- }
156259
- });
156260
-
156261
- // src/memory/long-term.ts
156262
- var long_term_exports = {};
156263
- __export(long_term_exports, {
156264
- approveLongTermMemory: () => approveLongTermMemory,
156265
- archiveLongTermMemory: () => archiveLongTermMemory,
156266
- canReadLongTermMemory: () => canReadLongTermMemory,
156267
- createLongTermMemoryCandidate: () => createLongTermMemoryCandidate,
156268
- createManualLongTermMemory: () => createManualLongTermMemory,
156269
- getLongTermMemoryDetail: () => getLongTermMemoryDetail,
156270
- isEligibleLongTermMemory: () => isEligibleLongTermMemory,
156271
- listLongTermMemories: () => listLongTermMemories,
156272
- promoteObservationToLongTermMemory: () => promoteObservationToLongTermMemory,
156273
- qualifyLongTermMemory: () => qualifyLongTermMemory,
156274
- renderLongTermMemorySummary: () => renderLongTermMemorySummary,
156275
- selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
156276
- supersedeLongTermMemory: () => supersedeLongTermMemory
156277
- });
156278
- import { randomUUID as randomUUID13 } from "crypto";
156279
- function isOneOf(value, values) {
156280
- return typeof value === "string" && values.includes(value);
156281
- }
156282
- function compactText2(value, field, max = MAX_TEXT_LENGTH) {
156283
- if (typeof value !== "string") throw new Error("Long-term memory " + field + " must be text.");
156284
- const safe = sanitizeCredentials(value).replace(/\s+/g, " ").trim();
156285
- if (!safe) throw new Error("Long-term memory " + field + " is required.");
156286
- if (safe.length > max) throw new Error("Long-term memory " + field + " is too long.");
156287
- return safe;
156288
- }
156289
- function compactList(values, field) {
156290
- if (!values) return [];
156291
- if (!Array.isArray(values)) throw new Error("Long-term memory " + field + " must be an array.");
156292
- return [...new Set(values.filter((value) => typeof value === "string").map((value) => compactText2(value, field, 500)))].slice(0, MAX_LIST_ITEMS);
156293
- }
156294
- function normalizeEvidence(input) {
156295
- if (!Array.isArray(input) || input.length === 0) {
156296
- throw new Error("A long-term memory requires at least one source evidence reference.");
156297
- }
156298
- return input.map((evidence) => {
156299
- if (!evidence || !isOneOf(evidence.kind, LONG_TERM_MEMORY_EVIDENCE_KINDS)) {
156300
- throw new Error("Long-term memory evidence kind is invalid.");
156301
- }
156302
- if (!isOneOf(evidence.relation, LONG_TERM_MEMORY_EVIDENCE_RELATIONS)) {
156303
- throw new Error("Long-term memory evidence relation is invalid.");
156304
- }
156305
- return {
156306
- kind: evidence.kind,
156307
- referenceId: compactText2(evidence.referenceId, "evidence reference", 1e3),
156308
- relation: evidence.relation,
156309
- ...evidence.locator ? { locator: compactText2(evidence.locator, "evidence locator", 1e3) } : {},
156310
- ...evidence.capturedHash ? { capturedHash: compactText2(evidence.capturedHash, "evidence hash", 1e3) } : {}
156311
- };
156312
- });
156313
- }
156314
- function assertScope(scope) {
156315
- if (!isOneOf(scope, LONG_TERM_MEMORY_SCOPES)) throw new Error("Long-term memory scope is invalid.");
156316
- }
156317
- function assertKind(kind) {
156318
- if (!isOneOf(kind, LONG_TERM_MEMORY_KINDS)) throw new Error("Long-term memory kind is invalid.");
156319
- }
156320
- function assertPortability(value) {
156321
- if (!isOneOf(value, LONG_TERM_MEMORY_PORTABILITIES)) {
156322
- throw new Error("Long-term memory portability is invalid.");
156323
- }
156324
- }
156325
- function assertPortableEvidence(scope, portability, evidence) {
156326
- if (portability !== "portable") return;
156327
- if (scope !== "user") {
156328
- throw new Error("Only user-scoped long-term memories may be portable across projects.");
156329
- }
156330
- if (evidence.some((item) => !PORTABLE_EVIDENCE_KINDS.has(item.kind))) {
156331
- throw new Error("Portable user memory may only use manual or user-confirmed evidence; project code, Git, observation, test, session, claim, and workflow evidence must remain project-bound.");
156332
- }
156333
- }
156334
- function requireTeamReader(scope, reader) {
156335
- if (scope === "team" && reader?.isTeamMember !== true) {
156336
- throw new Error("Team long-term memory requires an explicit active team identity for this project.");
156337
- }
156338
- }
156339
- function matchesScope(memory, reader) {
156340
- if (memory.scope === "project") return memory.originProjectId === reader.projectId;
156341
- if (memory.scope === "team") {
156342
- return memory.originProjectId === reader.projectId && reader.isTeamMember === true;
156343
- }
156344
- if (!reader.ownerId || memory.ownerId !== reader.ownerId) return false;
156345
- return memory.portability === "portable" || memory.originProjectId === reader.projectId;
156346
- }
156347
- function canReadLongTermMemory(memory, reader) {
156348
- return matchesScope(memory, reader);
156349
- }
156350
- function isEligibleLongTermMemory(memory) {
156351
- return memory.state === "qualified" || memory.state === "approved";
156352
- }
156353
- function taskTerms(task) {
156354
- const matches = task.toLocaleLowerCase("en-US").match(/[\p{L}\p{N}_./:-]+/gu) ?? [];
156355
- return [...new Set(matches.filter((term) => term.length > 1 && !STOP_TERMS.has(term)))].slice(0, 16);
156356
- }
156357
- function kindFitScore(kind, task) {
156358
- const normalized = task.toLocaleLowerCase("en-US");
156359
- const hints = {
156360
- episodic: ["resume", "continue", "handoff", "previous", "session", "\u63A5\u624B", "\u7EE7\u7EED", "\u4EA4\u63A5", "\u4E0A\u6B21", "\u4F1A\u8BDD"],
156361
- semantic: ["why", "what", "architecture", "design", "decision", "reason", "\u4E3A\u4EC0\u4E48", "\u662F\u4EC0\u4E48", "\u67B6\u6784", "\u8BBE\u8BA1", "\u51B3\u7B56", "\u539F\u56E0"],
156362
- procedural: ["how", "steps", "procedure", "run", "release", "publish", "verify", "implement", "\u6D41\u7A0B", "\u6B65\u9AA4", "\u53D1\u5E03", "\u9A8C\u8BC1", "\u4FEE\u590D", "\u5B9E\u73B0"]
156363
- };
156364
- return hints[kind].some((hint) => normalized.includes(hint)) ? 1 : 0;
156365
- }
156366
- function validationFreshnessScore(memory) {
156367
- const validatedAt = memory.lastValidatedAt ?? memory.approvedAt ?? memory.qualifiedAt;
156368
- if (!validatedAt) return 0;
156369
- const time = Date.parse(validatedAt);
156370
- if (!Number.isFinite(time)) return 0;
156371
- const ageDays = Math.max(0, Date.now() - time) / (24 * 60 * 60 * 1e3);
156372
- if (ageDays <= 30) return 0.35;
156373
- if (ageDays <= 90) return 0.15;
156374
- return 0;
156375
- }
156376
- function scoreForTask(memory, task) {
156377
- const terms = taskTerms(task);
156378
- if (terms.length === 0) return void 0;
156379
- const title = memory.title.toLocaleLowerCase("en-US");
156380
- const content = memory.content.toLocaleLowerCase("en-US");
156381
- const facts = memory.facts.join(" ").toLocaleLowerCase("en-US");
156382
- const tags = memory.tags.join(" ").toLocaleLowerCase("en-US");
156383
- const applicability = (memory.applicability ?? "").toLocaleLowerCase("en-US");
156384
- let score = memory.state === "approved" ? 0.5 : 0;
156385
- const matched = [];
156386
- for (const term of terms) {
156387
- let termScore = 0;
156388
- if (title.includes(term)) termScore += 5;
156389
- if (tags.includes(term)) termScore += 4;
156390
- if (applicability.includes(term)) termScore += 3;
156391
- if (facts.includes(term)) termScore += 2;
156392
- if (content.includes(term)) termScore += 1;
156393
- if (termScore > 0) {
156394
- score += termScore;
156395
- matched.push(term);
156396
- }
156397
- }
156398
- if (matched.length === 0) return void 0;
156399
- score += kindFitScore(memory.kind, task);
156400
- score += validationFreshnessScore(memory);
156401
- const kindLabel = memory.kind === "procedural" ? "procedure" : memory.kind === "semantic" ? "fact" : "episode";
156402
- const freshness = validationFreshnessScore(memory) > 0 ? "; recently validated" : "";
156403
- return { score, reason: kindLabel + " matches " + matched.slice(0, 3).join(", ") + freshness };
156404
- }
156405
- function semanticText(memory) {
156406
- return sanitizeCredentials([
156407
- memory.title,
156408
- memory.applicability,
156409
- memory.tags.join(" "),
156410
- memory.facts.join(" "),
156411
- memory.content
156412
- ].filter((value) => Boolean(value)).join("\n")).replace(/\s+/g, " ").trim().slice(0, MAX_TEXT_LENGTH);
156413
- }
156414
- function cosineSimilarity(left, right) {
156415
- if (left.length === 0 || left.length !== right.length) return 0;
156416
- let dot = 0;
156417
- let leftMagnitude = 0;
156418
- let rightMagnitude = 0;
156419
- for (let index = 0; index < left.length; index += 1) {
156420
- const a = left[index];
156421
- const b = right[index];
156422
- if (!Number.isFinite(a) || !Number.isFinite(b)) return 0;
156423
- dot += a * b;
156424
- leftMagnitude += a * a;
156425
- rightMagnitude += b * b;
156426
- }
156427
- if (leftMagnitude === 0 || rightMagnitude === 0) return 0;
156428
- return dot / Math.sqrt(leftMagnitude * rightMagnitude);
156429
- }
156430
- function semanticSelection(memory, similarity, task) {
156431
- if (similarity < MIN_SEMANTIC_SIMILARITY) return void 0;
156432
- const kindLabel = memory.kind === "procedural" ? "procedure" : memory.kind === "semantic" ? "fact" : "episode";
156433
- const freshness = validationFreshnessScore(memory) > 0 ? "; recently validated" : "";
156434
- return {
156435
- score: similarity * 10 + (memory.state === "approved" ? 0.5 : 0) + kindFitScore(memory.kind, task) + validationFreshnessScore(memory),
156436
- reason: kindLabel + " semantic match " + similarity.toFixed(2) + freshness
156437
- };
156438
- }
156439
- async function resolveSemanticProvider(explicit) {
156440
- if (explicit !== void 0) return explicit;
156441
- try {
156442
- const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
156443
- return await withTimeout(
156444
- getEmbeddingProvider2({ requestTimeoutMs: SEMANTIC_RETRIEVAL_TIMEOUT_MS, retry: false }),
156445
- SEMANTIC_RETRIEVAL_TIMEOUT_MS,
156446
- "Long-term semantic provider initialization"
156447
- );
156448
- } catch {
156449
- return null;
156450
- }
156451
- }
156452
- function createStore(dataDir) {
156453
- const store = new LongTermMemoryStore();
156454
- return store.init(dataDir).then(() => store);
156455
- }
156456
- async function createManualLongTermMemory(input) {
156457
- const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
156458
- if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
156459
- assertScope(input.scope);
156460
- const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID13(), relation: "supports" }];
156461
- return createLongTermMemoryWithDataDir({
156462
- dataDir: input.dataDir,
156463
- originProjectId: input.projectId,
156464
- ownerId: owner.id,
156465
- scope: input.scope,
156466
- kind: input.kind,
156467
- portability: input.portability,
156468
- title: input.title,
156469
- content: input.content,
156470
- facts: input.facts,
156471
- tags: input.tags,
156472
- applicability: input.applicability,
156473
- origin: "manual",
156474
- evidence,
156475
- reader: input.reader
156476
- });
156477
- }
156478
- async function promoteObservationToLongTermMemory(input) {
156479
- const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
156480
- if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
156481
- return createLongTermMemoryWithDataDir({
156482
- dataDir: input.dataDir,
156483
- originProjectId: input.observation.projectId,
156484
- ownerId: owner.id,
156485
- scope: input.scope,
156486
- kind: input.kind,
156487
- portability: "project-bound",
156488
- title: input.observation.title,
156489
- content: input.observation.narrative,
156490
- facts: input.observation.facts,
156491
- tags: input.tags ?? input.observation.concepts,
156492
- applicability: input.applicability,
156493
- origin: input.observation.source === "git" ? "git" : "agent",
156494
- evidence: [{
156495
- kind: "observation",
156496
- referenceId: "obs:" + input.observation.projectId + ":" + input.observation.id,
156497
- relation: "derives"
156498
- }],
156499
- reader: input.reader
156500
- });
156501
- }
156502
- async function createLongTermMemoryCandidate(input) {
156503
- return createLongTermMemoryWithDataDir(input);
156504
- }
156505
- async function createLongTermMemoryWithDataDir(input) {
156506
- assertScope(input.scope);
156507
- assertKind(input.kind);
156508
- const portability = input.portability ?? "project-bound";
156509
- assertPortability(portability);
156510
- requireTeamReader(input.scope, input.reader);
156511
- const evidenceInputs = normalizeEvidence(input.evidence);
156512
- assertPortableEvidence(input.scope, portability, evidenceInputs);
156513
- const now3 = (/* @__PURE__ */ new Date()).toISOString();
156514
- const memory = {
156515
- id: randomUUID13(),
156516
- originProjectId: compactText2(input.originProjectId, "origin project id", 1e3),
156517
- ownerId: compactText2(input.ownerId, "owner id", 160),
156518
- scope: input.scope,
156519
- kind: input.kind,
156520
- state: "candidate",
156521
- portability,
156522
- title: compactText2(input.title, "title", 500),
156523
- content: compactText2(input.content, "content"),
156524
- facts: compactList(input.facts, "facts"),
156525
- tags: compactList(input.tags, "tags"),
156526
- ...input.applicability ? { applicability: compactText2(input.applicability, "applicability", 1e3) } : {},
156527
- origin: input.origin ?? "manual",
156528
- createdAt: now3,
156529
- updatedAt: now3,
156530
- accessCount: 0
156531
- };
156532
- const store = await createStore(input.dataDir);
156533
- const evidence = store.transaction(() => {
156534
- store.insert(memory);
156535
- const written = evidenceInputs.map((item) => store.insertEvidence({ memoryId: memory.id, ...item, createdAt: now3 }));
156536
- store.recordEvent({
156537
- memoryId: memory.id,
156538
- kind: "created",
156539
- toState: "candidate",
156540
- detail: "Created as a source-backed long-term memory candidate.",
156541
- createdAt: now3
156542
- });
156543
- return written;
156544
- });
156545
- return { memory, evidence };
156546
- }
156547
- function transitionDetail(value) {
156548
- return compactText2(value, "transition reason", 1e3);
156549
- }
156550
- async function transitionMemory(input) {
156551
- const store = await createStore(input.dataDir);
156552
- return store.transaction(() => {
156553
- const current = store.get(input.id);
156554
- if (!current) throw new Error("Long-term memory was not found.");
156555
- if (current.state !== input.expected) {
156556
- throw new Error("Long-term memory must be " + input.expected + " before it can be " + input.next + ".");
156557
- }
156558
- const evidence = store.listEvidence(current.id);
156559
- if (evidence.length === 0) throw new Error("Long-term memory cannot advance without source evidence.");
156560
- const now3 = (/* @__PURE__ */ new Date()).toISOString();
156561
- const next = {
156562
- ...current,
156563
- state: input.next,
156564
- updatedAt: now3,
156565
- lastValidatedAt: now3,
156566
- ...input.next === "qualified" ? { qualifiedAt: now3 } : { approvedAt: now3 }
156567
- };
156568
- store.update(next);
156569
- store.recordEvent({
156570
- memoryId: next.id,
156571
- kind: input.event,
156572
- fromState: current.state,
156573
- toState: next.state,
156574
- detail: transitionDetail(input.reason),
156575
- createdAt: now3
156576
- });
156577
- return next;
156578
- });
156579
- }
156580
- async function qualifyLongTermMemory(input) {
156581
- return transitionMemory({ ...input, expected: "candidate", next: "qualified", event: "qualified" });
156582
- }
156583
- async function approveLongTermMemory(input) {
156584
- return transitionMemory({ ...input, expected: "qualified", next: "approved", event: "approved" });
156585
- }
156586
- async function archiveLongTermMemory(input) {
156587
- const store = await createStore(input.dataDir);
156588
- return store.transaction(() => {
156589
- const current = store.get(input.id);
156590
- if (!current) throw new Error("Long-term memory was not found.");
156591
- if (current.state === "archived" || current.state === "superseded") {
156592
- throw new Error("Long-term memory is already inactive.");
156593
- }
156594
- const now3 = (/* @__PURE__ */ new Date()).toISOString();
156595
- const next = { ...current, state: "archived", archivedAt: now3, updatedAt: now3 };
156596
- store.update(next);
156597
- store.recordEvent({
156598
- memoryId: next.id,
156599
- kind: "archived",
156600
- fromState: current.state,
156601
- toState: "archived",
156602
- detail: transitionDetail(input.reason),
156603
- createdAt: now3
156604
- });
156605
- return next;
156606
- });
156607
- }
156608
- async function supersedeLongTermMemory(input) {
156609
- const store = await createStore(input.dataDir);
156610
- return store.transaction(() => {
156611
- const current = store.get(input.id);
156612
- if (!current) throw new Error("Long-term memory was not found.");
156613
- if (current.state === "archived" || current.state === "superseded") {
156614
- throw new Error("Long-term memory is already inactive.");
156615
- }
156616
- if (current.id === input.supersededBy) {
156617
- throw new Error("Long-term memory cannot supersede itself.");
156618
- }
156619
- const replacement = store.get(input.supersededBy);
156620
- if (!replacement || !isEligibleLongTermMemory(replacement)) {
156621
- throw new Error("Replacement long-term memory must be qualified or approved.");
156622
- }
156623
- if (replacement.scope !== current.scope) {
156624
- throw new Error("Replacement long-term memory must use the same scope.");
156625
- }
156626
- if (current.scope === "user" && replacement.ownerId !== current.ownerId) {
156627
- throw new Error("User long-term memory may only be superseded by the same local owner.");
156628
- }
156629
- if (current.scope !== "user" && replacement.originProjectId !== current.originProjectId) {
156630
- throw new Error("Project and team long-term memory may only be superseded within the same project.");
156631
- }
156632
- const now3 = (/* @__PURE__ */ new Date()).toISOString();
156633
- const next = {
156634
- ...current,
156635
- state: "superseded",
156636
- supersededBy: replacement.id,
156637
- updatedAt: now3
156638
- };
156639
- store.update(next);
156640
- store.recordEvent({
156641
- memoryId: next.id,
156642
- kind: "superseded",
156643
- fromState: current.state,
156644
- toState: "superseded",
156645
- detail: transitionDetail(input.reason),
156646
- createdAt: now3
156647
- });
156648
- return next;
156649
- });
156650
- }
156651
- async function listLongTermMemories(input) {
156652
- const store = await createStore(input.dataDir);
156653
- return store.list({ limit: input.limit ?? 100 }).filter((memory) => canReadLongTermMemory(memory, input.reader)).filter((memory) => input.includeInactive || memory.state !== "archived" && memory.state !== "superseded").map((memory) => ({ memory, evidence: store.listEvidence(memory.id) }));
156654
- }
156655
- async function getLongTermMemoryDetail(input) {
156656
- const store = await createStore(input.dataDir);
156657
- const memory = store.get(input.id);
156658
- if (!memory || !canReadLongTermMemory(memory, input.reader)) {
156659
- throw new Error("Long-term memory was not found in the active scope.");
156660
- }
156661
- store.markAccess([memory.id]);
156662
- return { memory, evidence: store.listEvidence(memory.id), events: store.listEvents(memory.id) };
156663
- }
156664
- async function selectLongTermMemoriesForTask(input) {
156665
- const task = input.task?.trim();
156666
- if (!task) return [];
156667
- const owner = await resolveLocalMemoryOwner(input.dataDir, { create: false });
156668
- const reader = {
156669
- projectId: input.projectId,
156670
- ...owner ? { ownerId: owner.id } : {},
156671
- ...input.agentId ? { agentId: input.agentId } : {},
156672
- ...input.isTeamMember ? { isTeamMember: true } : {}
156673
- };
156674
- const store = await createStore(input.dataDir);
156675
- const eligible = store.list({ limit: 1e3 }).filter((memory) => isEligibleLongTermMemory(memory) && canReadLongTermMemory(memory, reader));
156676
- let selected = eligible.map((memory) => {
156677
- const ranked = scoreForTask(memory, task);
156678
- return ranked ? {
156679
- memory,
156680
- evidence: store.listEvidence(memory.id),
156681
- score: ranked.score,
156682
- reason: ranked.reason
156683
- } : void 0;
156684
- }).filter((item) => Boolean(item)).sort((left, right) => right.score - left.score || right.memory.updatedAt.localeCompare(left.memory.updatedAt) || left.memory.id.localeCompare(right.memory.id));
156685
- if (selected.length === 0 && eligible.length > 0) {
156686
- const provider2 = await resolveSemanticProvider(input.embeddingProvider);
156687
- const candidates = eligible.slice(0, MAX_SEMANTIC_CANDIDATES);
156688
- if (provider2 && candidates.length > 0) {
156689
- try {
156690
- const vectors = await withTimeout(
156691
- provider2.embedBatch([task, ...candidates.map(semanticText)], {
156692
- timeoutMs: SEMANTIC_RETRIEVAL_TIMEOUT_MS,
156693
- retry: false
156694
- }),
156695
- SEMANTIC_RETRIEVAL_TIMEOUT_MS,
156696
- "Long-term semantic retrieval"
156697
- );
156698
- const query = vectors[0];
156699
- if (Array.isArray(query)) {
156700
- selected = candidates.map((memory, index) => {
156701
- const vector = vectors[index + 1];
156702
- const ranked = Array.isArray(vector) ? semanticSelection(memory, cosineSimilarity(query, vector), task) : void 0;
156703
- return ranked ? {
156704
- memory,
156705
- evidence: store.listEvidence(memory.id),
156706
- score: ranked.score,
156707
- reason: ranked.reason
156708
- } : void 0;
156709
- }).filter((item) => Boolean(item)).sort((left, right) => right.score - left.score || right.memory.updatedAt.localeCompare(left.memory.updatedAt) || left.memory.id.localeCompare(right.memory.id));
156710
- }
156711
- } catch {
156712
- }
156713
- }
156714
- }
156715
- selected = selected.slice(0, Math.max(1, Math.min(input.limit ?? 3, 5)));
156716
- store.markAccess(selected.map((item) => item.memory.id));
156717
- return selected;
156718
- }
156719
- function renderLongTermMemorySummary(memory, maxTokens = 22) {
156720
- const source = [memory.title, memory.applicability, memory.content].filter(Boolean).join(": ");
156721
- const safe = sanitizeCredentials(source).replace(/\s+/g, " ").trim();
156722
- return countTextTokens(safe) <= maxTokens ? safe : truncateToTokenBudget(safe, maxTokens);
156723
- }
156724
- var MAX_TEXT_LENGTH, MAX_LIST_ITEMS, MAX_SEMANTIC_CANDIDATES, SEMANTIC_RETRIEVAL_TIMEOUT_MS, MIN_SEMANTIC_SIMILARITY, PORTABLE_EVIDENCE_KINDS, STOP_TERMS;
156725
- var init_long_term = __esm({
156726
- "src/memory/long-term.ts"() {
156727
- "use strict";
156728
- init_esm_shims();
156729
- init_token_budget();
156730
- init_timeout();
156731
- init_secret_filter();
156732
- init_owner();
156733
- init_long_term_store();
156734
- init_long_term_types();
156735
- MAX_TEXT_LENGTH = 12e3;
156736
- MAX_LIST_ITEMS = 40;
156737
- MAX_SEMANTIC_CANDIDATES = 48;
156738
- SEMANTIC_RETRIEVAL_TIMEOUT_MS = 1800;
156739
- MIN_SEMANTIC_SIMILARITY = 0.58;
156740
- PORTABLE_EVIDENCE_KINDS = /* @__PURE__ */ new Set(["manual", "user"]);
156741
- STOP_TERMS = /* @__PURE__ */ new Set([
156742
- "the",
156743
- "and",
156744
- "for",
156745
- "with",
156746
- "from",
156747
- "that",
156748
- "this",
156749
- "into",
156750
- "when",
156751
- "what",
156752
- "\u5982\u4F55",
156753
- "\u4EC0\u4E48",
156754
- "\u8FD9\u4E2A",
156755
- "\u90A3\u4E2A",
156756
- "\u6211\u4EEC",
156757
- "\u9879\u76EE",
156758
- "\u8FDB\u884C",
156759
- "\u9700\u8981",
156760
- "\u7EE7\u7EED",
156761
- "\u5904\u7406"
156762
- ]);
156763
- }
156764
- });
156765
-
156766
156912
  // src/knowledge/workset.ts
156767
156913
  function unique(values) {
156768
156914
  return [...new Set(values.filter(Boolean))];
@@ -156970,6 +157116,37 @@ function renderTaskWorksetPrompt(input) {
156970
157116
  trust: "source-backed"
156971
157117
  });
156972
157118
  appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
157119
+ if (input.alwaysOn) {
157120
+ const hasAlwaysOn = input.alwaysOn.profile.length > 0 || Boolean(input.alwaysOn.state) || input.alwaysOn.durable.length > 0;
157121
+ if (hasAlwaysOn) {
157122
+ appendLine(lines, "", maxTokens, omitted, "always-on-heading");
157123
+ appendLine(lines, "You and this workspace", maxTokens, omitted, "always-on-heading");
157124
+ for (const profile of input.alwaysOn.profile.slice(0, 2)) {
157125
+ appendLine(lines, "- " + short(profile, 26), maxTokens, omitted, "always-on-profile", selected, {
157126
+ kind: "memory",
157127
+ id: "profile",
157128
+ reason: "user profile memory",
157129
+ trust: "source-backed"
157130
+ });
157131
+ }
157132
+ if (input.alwaysOn.state) {
157133
+ appendLine(lines, "- Recently: " + short(input.alwaysOn.state, 40), maxTokens, omitted, "always-on-state", selected, {
157134
+ kind: "continuation",
157135
+ id: "workspace-state",
157136
+ reason: "latest session summary for this workspace",
157137
+ trust: "historical"
157138
+ });
157139
+ }
157140
+ for (const durable of input.alwaysOn.durable.slice(0, 2)) {
157141
+ appendLine(lines, "- " + short(durable, 28), maxTokens, omitted, "always-on-durable", selected, {
157142
+ kind: "durable-memory",
157143
+ id: "durable-always",
157144
+ reason: "recent durable memory for this workspace",
157145
+ trust: "source-backed"
157146
+ });
157147
+ }
157148
+ }
157149
+ }
156973
157150
  const hasContinuation = Boolean(
156974
157151
  input.continuation?.previousSession || (input.continuation?.memories.length ?? 0) > 0 || input.continuation?.compactCheckpoint
156975
157152
  );
@@ -157440,6 +157617,7 @@ async function buildTaskWorkset(input) {
157440
157617
  version: "1.3",
157441
157618
  task,
157442
157619
  lens: input.lens,
157620
+ ...input.alwaysOn && (input.alwaysOn.profile.length > 0 || input.alwaysOn.state || input.alwaysOn.durable.length > 0) ? { alwaysOn: input.alwaysOn } : {},
157443
157621
  currentFacts: input.currentFacts?.map((fact) => fact.startsWith("Historical note:") ? short(fact, 48) : short(fact, 28)).slice(0, 4) ?? [],
157444
157622
  ...continuation ? { continuation } : {},
157445
157623
  ...input.codeState ? { codeState: short(input.codeState, 28) } : {},
@@ -158592,6 +158770,33 @@ function activeProjectObservations(observations2, projectId) {
158592
158770
  function deliveryEligibleProjectObservations(observations2, projectId) {
158593
158771
  return activeProjectObservations(observations2, projectId).filter((observation) => isEligibleForAutomaticDelivery(observation));
158594
158772
  }
158773
+ async function buildAlwaysOnBlock(input) {
158774
+ const profile = filterReadableObservations(input.eligible, input.reader).filter((observation) => observation.visibility === "personal" && observation.entityName === "user-profile").slice(0, 2).map((observation) => compactContinuationText(observation.title, 22));
158775
+ let state;
158776
+ try {
158777
+ await initSessionStore(input.dataDir);
158778
+ const { getSessionStore: getSessionStore2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
158779
+ const sessions = await getSessionStore2().loadByProject(input.projectId);
158780
+ const latest = sessions.filter((session) => session.endedAt && session.summary?.trim()).sort((a, b) => Date.parse(b.endedAt ?? "") - Date.parse(a.endedAt ?? ""))[0];
158781
+ if (latest?.summary) state = compactContinuationText(latest.summary, 40);
158782
+ } catch {
158783
+ }
158784
+ const durable = [];
158785
+ try {
158786
+ const items = await listLongTermMemories({
158787
+ dataDir: input.dataDir,
158788
+ reader: {
158789
+ projectId: input.projectId,
158790
+ ...input.reader.agentId ? { agentId: input.reader.agentId } : {},
158791
+ ...input.reader.isTeamMember ? { isTeamMember: true } : {}
158792
+ },
158793
+ limit: 10
158794
+ });
158795
+ durable.push(...items.filter((item) => item.memory.state === "approved").sort((a, b) => (b.memory.updatedAt ?? "").localeCompare(a.memory.updatedAt ?? "")).slice(0, 2).map((item) => item.memory.kind + ": " + compactContinuationText(item.memory.title, 22)));
158796
+ } catch {
158797
+ }
158798
+ return { profile, state, durable };
158799
+ }
158595
158800
  function decideRefresh(input) {
158596
158801
  if (input.mode === "never") {
158597
158802
  return { performed: false, reason: "disabled", message: "Automatic project scan disabled." };
@@ -158796,12 +159001,19 @@ async function buildAutoProjectContext(input) {
158796
159001
  };
158797
159002
  }
158798
159003
  }
159004
+ const alwaysOn = await buildAlwaysOnBlock({
159005
+ dataDir: input.dataDir,
159006
+ projectId: input.project.id,
159007
+ reader: input.reader ?? { projectId: input.project.id },
159008
+ eligible: deliveryEligibleProjectObservations(input.observations, input.project.id)
159009
+ });
158799
159010
  const workset = await buildTaskWorkset({
158800
159011
  projectId: input.project.id,
158801
159012
  dataDir: input.dataDir,
158802
159013
  ...task ? { task } : {},
158803
159014
  lens: lens.id,
158804
159015
  startHere,
159016
+ ...alwaysOn.profile.length > 0 || alwaysOn.state || alwaysOn.durable.length > 0 ? { alwaysOn } : {},
158805
159017
  ...externalOutline ? { semanticCode: externalOutline } : {},
158806
159018
  providerQuality: providerQuality2,
158807
159019
  currentFacts: worksetFactLines(currentFacts),
@@ -158984,7 +159196,16 @@ function formatAutoProjectContextSummary(context) {
158984
159196
  lensLine(context),
158985
159197
  "",
158986
159198
  ...formatCurrentFactsLines(context.currentFacts),
158987
- "",
159199
+ ""
159200
+ ].filter(Boolean);
159201
+ if (context.workset.alwaysOn && (context.workset.alwaysOn.profile.length > 0 || context.workset.alwaysOn.state || context.workset.alwaysOn.durable.length > 0)) {
159202
+ lines.push("You and this workspace");
159203
+ for (const profile of context.workset.alwaysOn.profile.slice(0, 2)) lines.push(`- ${profile}`);
159204
+ if (context.workset.alwaysOn.state) lines.push(`- Recently: ${context.workset.alwaysOn.state}`);
159205
+ for (const durable of context.workset.alwaysOn.durable.slice(0, 2)) lines.push(`- ${durable}`);
159206
+ lines.push("");
159207
+ }
159208
+ lines.push(...[
158988
159209
  `- Code memory: ${context.overview.code.files} files / ${context.overview.code.symbols} symbols / ${context.overview.code.refs} memory links`,
158989
159210
  `- Code provider: ${context.providerQuality.selected} (${context.providerQuality.selectedQuality})`,
158990
159211
  `- Languages: ${formatLanguages(context.overview)}`,
@@ -158993,7 +159214,7 @@ function formatAutoProjectContextSummary(context) {
158993
159214
  `- Refresh: ${context.refresh.message}`,
158994
159215
  "",
158995
159216
  "Start here"
158996
- ].filter(Boolean);
159217
+ ].filter(Boolean));
158997
159218
  if (startHere.length > 0) {
158998
159219
  startHere.forEach((path37, index) => lines.push(`${index + 1}. ${path37}`));
158999
159220
  } else {
@@ -159117,6 +159338,8 @@ var init_auto_context = __esm({
159117
159338
  init_store();
159118
159339
  init_admission();
159119
159340
  init_session();
159341
+ init_long_term();
159342
+ init_visibility();
159120
159343
  init_session_store();
159121
159344
  init_task_lens();
159122
159345
  DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
@@ -162067,8 +162290,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
162067
162290
  const { promises: fsP } = await import("fs");
162068
162291
  const registryPath = path30.join(baseDir, ".project-aliases.json");
162069
162292
  const raw = await fsP.readFile(registryPath, "utf-8");
162070
- const registry = JSON.parse(raw);
162071
- aliasGroups = registry.groups || [];
162293
+ const registry2 = JSON.parse(raw);
162294
+ aliasGroups = registry2.groups || [];
162072
162295
  } catch {
162073
162296
  }
162074
162297
  const currentGroup = aliasGroups.find((g) => g.aliases?.includes(effectiveProjectId) || g.canonical === effectiveProjectId);
@@ -164984,13 +165207,28 @@ alwaysApply: true
164984
165207
  '- For "why" decisions, use `memorix_store_reasoning`',
164985
165208
  '- For a stable fact, reusable procedure, or completed episode that merits deliberate long-term review, include `longTerm` in `memorix_store` with the appropriate kind and normally `scope: "project"`. It creates a candidate only: do not use it for routine updates, do not make project-derived evidence portable user memory, and do not assume it enters context until an operator qualifies and approves it through `memorix memory long-term`.',
164986
165209
  "- A `user` + `portable` durable memory delivered in a task brief is intentionally available across projects. When it matches the task, use it as reusable background even if its origin differs; do not treat it as a current-project fact. Expand it only when needed with `memorix_detail` using its `durable:<id>` reference and a specific purpose.",
165210
+ '- Record the user profile: the user\u2019s role, expertise, preferences, and goals. Save these with `entityName: "user-profile"` and `visibility: "personal"` so they stay private and appear in every brief as the "who you are" context.',
164987
165211
  "",
164988
165212
  "**Don't store:** greetings, simple file reads, trivial commands (ls, pwd, git status).",
164989
165213
  "",
165214
+ "**Only store what a future session cannot re-derive.** Code structure, file contents, and Git history are live in the checkout \u2014 do not store facts already visible there. A memory earns its place by capturing the why, the context, or a conclusion the checkout alone cannot show.",
165215
+ "",
165216
+ `**Record what worked, not only what failed.** Store validated approaches and explicit user confirmations alongside corrections. Saving only failures drifts behavior away from what the user already accepted; a clear "yes, that's right" is feedback worth keeping too.`,
165217
+ "",
165218
+ "**Recalled memory is a claim about the past.** A memory naming a specific file, function, or flag describes the past at write time \u2014 check the file exists or grep the symbol before recommending it. If the user says to ignore or not use memory, proceed as if memory were empty: do not apply, cite, compare, or mention stored content.",
165219
+ "",
164990
165220
  "## When to resolve memory",
164991
165221
  "",
164992
165222
  "Use `memorix_resolve` when a task is done or a bug is fixed. This keeps future searches focused on active work instead of surfacing completed items.",
164993
165223
  "",
165224
+ "## End sessions with a summary",
165225
+ "",
165226
+ "When a session finishes, call `memorix_session_end` with a short structured summary so the next agent can resume. Recommended sections:",
165227
+ "- **Goal** \u2014 what this session was working on",
165228
+ "- **Discoveries** \u2014 findings, gotchas, learnings",
165229
+ "- **Accomplished** \u2014 completed items, plus PENDING items for the next session",
165230
+ "- **Relevant Files** \u2014 paths and what changed",
165231
+ "",
164994
165232
  "## Tools quick reference",
164995
165233
  "",
164996
165234
  "| Tool | Use when |",
@@ -165541,6 +165779,12 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
165541
165779
  kind: "consolidation",
165542
165780
  dedupeKey: "startup-consolidation"
165543
165781
  });
165782
+ lifecycle.enqueueLongTermMaintenance({
165783
+ dataDir: projectDir2,
165784
+ projectId: project.id,
165785
+ source: "startup",
165786
+ queue
165787
+ });
165544
165788
  }
165545
165789
  try {
165546
165790
  const { CodeGraphStore: CodeGraphStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
@@ -165590,7 +165834,7 @@ The path should point to a directory containing a .git folder.`
165590
165834
  };
165591
165835
  const server = existingServer ?? new McpServer({
165592
165836
  name: "memorix",
165593
- version: true ? "1.4.5" : "1.0.1"
165837
+ version: true ? "1.5.0" : "1.0.1"
165594
165838
  });
165595
165839
  const originalRegisterTool = server.registerTool.bind(server);
165596
165840
  server.registerTool = ((name, ...args) => {
@@ -166075,7 +166319,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
166075
166319
  let longTermNote = "";
166076
166320
  if (longTerm) {
166077
166321
  try {
166078
- const { promoteObservationToLongTermMemory: promoteObservationToLongTermMemory2 } = await Promise.resolve().then(() => (init_long_term(), long_term_exports));
166322
+ const { promoteObservationToLongTermMemory: promoteObservationToLongTermMemory2, maybeAutoQualifyLongTermMemory: maybeAutoQualifyLongTermMemory2 } = await Promise.resolve().then(() => (init_long_term(), long_term_exports));
166079
166323
  const promoted = await promoteObservationToLongTermMemory2({
166080
166324
  dataDir: projectDir2,
166081
166325
  observation: obs,
@@ -166089,7 +166333,24 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
166089
166333
  ...reader.isTeamMember ? { isTeamMember: true } : {}
166090
166334
  }
166091
166335
  });
166092
- longTermNote = `
166336
+ const autoQualified = await maybeAutoQualifyLongTermMemory2({
166337
+ dataDir: projectDir2,
166338
+ id: promoted.memory.id,
166339
+ sourceDetail: "explicit"
166340
+ });
166341
+ if (autoQualified) {
166342
+ try {
166343
+ const { enqueueLongTermMaintenance: enqueueLongTermMaintenance2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
166344
+ enqueueLongTermMaintenance2({
166345
+ dataDir: projectDir2,
166346
+ projectId: project.id,
166347
+ source: "long-term:" + promoted.memory.id
166348
+ });
166349
+ } catch {
166350
+ }
166351
+ }
166352
+ longTermNote = autoQualified ? `
166353
+ Long-term memory: ${promoted.memory.id} (${promoted.memory.kind}/${promoted.memory.scope}, auto-qualified) and now enters briefs as durable context. Audit with \`memorix memory long-term list\`.` : `
166093
166354
  Long-term candidate: ${promoted.memory.id} (${promoted.memory.kind}/${promoted.memory.scope}). Review it with the CLI before it is delivered automatically.`;
166094
166355
  } catch (longTermError) {
166095
166356
  longTermNote = `
@@ -166285,6 +166546,13 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
166285
166546
  const boundary = requireExplicitAutopilotExpansion("memorix_search", purpose);
166286
166547
  if (boundary) return boundary;
166287
166548
  }
166549
+ let surfacedKey = `project:${project.id}`;
166550
+ try {
166551
+ const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
166552
+ const active = await getActiveSession2(projectDir2, project.id);
166553
+ if (active) surfacedKey = `session:${active.id}`;
166554
+ } catch {
166555
+ }
166288
166556
  return withFreshIndex(async () => {
166289
166557
  const safeLimit2 = limit2 != null ? coerceNumber(limit2, 20) : void 0;
166290
166558
  const safeMaxTokens = maxTokens != null ? coerceNumber(maxTokens, 0) : void 0;
@@ -166302,7 +166570,8 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
166302
166570
  status: status ?? "active",
166303
166571
  source,
166304
166572
  quality,
166305
- reader: getObservationReader(scope === "global" ? "global" : "project")
166573
+ reader: getObservationReader(scope === "global" ? "global" : "project"),
166574
+ surfacedIds: getSurfacedIds(surfacedKey)
166306
166575
  });
166307
166576
  let result;
166308
166577
  try {
@@ -166321,6 +166590,7 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
166321
166590
  }
166322
166591
  throw error;
166323
166592
  }
166593
+ recordSurfacedIds(surfacedKey, result.entries.map((entry) => entry.id));
166324
166594
  const activeBoundary = getActiveAutopilotRetrievalBoundary();
166325
166595
  if (scope !== "global" && activeBoundary && !force && result.entries.length > 0 && result.entries.every((entry) => activeBoundary.coveredObservationIds.has(entry.id))) {
166326
166596
  const duplicate = blockCoveredAutopilotEvidence("memorix_search", result.entries.map((entry) => entry.id), force);
@@ -169283,13 +169553,26 @@ ${lines.join("\n")}` }] };
169283
169553
  if (bytes.length >= 12 && Buffer.from(bytes.subarray(0, 4)).toString("ascii") === "RIFF" && Buffer.from(bytes.subarray(8, 12)).toString("ascii") === "WEBP") return "image/webp";
169284
169554
  return void 0;
169285
169555
  }
169556
+ const mediaGenerationEnabled = process.env.MEMORIX_MCP_MEDIA_GENERATION === "1";
169557
+ const mediaTranscriptionEnabled = process.env.MEMORIX_MCP_MEDIA_TRANSCRIPTION === "1";
169558
+ const mediaGenerationOrTranscription = mediaGenerationEnabled || mediaTranscriptionEnabled;
169286
169559
  server.registerTool(
169287
169560
  "memorix_media",
169288
169561
  {
169289
169562
  title: "Manage Controlled Media",
169290
169563
  description: "Use the controlled local media library for an explicit import, attachment, inspection, or MiniMax generation request. Assets stay outside the Git worktree and enter normal memory only when attach is explicitly true. Use the CLI for destructive removal, quota cleanup, and direct generation. MCP generation is disabled by default and requires MEMORIX_MCP_MEDIA_GENERATION=1 after the operator reviews provider billing.",
169291
169564
  inputSchema: {
169292
- action: z626.enum(["import", "attach", "list", "show", "derive-pdf", "derive-audio", "generate-image", "generate-video", "status", "cancel"]),
169565
+ action: z626.enum([
169566
+ "import",
169567
+ "attach",
169568
+ "list",
169569
+ "show",
169570
+ "derive-pdf",
169571
+ "status",
169572
+ "cancel",
169573
+ ...mediaTranscriptionEnabled ? ["derive-audio"] : [],
169574
+ ...mediaGenerationEnabled ? ["generate-image", "generate-video"] : []
169575
+ ]),
169293
169576
  path: z626.string().optional().describe("Explicit local image/audio/video/PDF path for import."),
169294
169577
  assetId: z626.string().optional().describe("Controlled MediaAsset ID for attach/show."),
169295
169578
  jobId: z626.string().optional().describe("Durable media job ID for status."),
@@ -169297,20 +169580,26 @@ ${lines.join("\n")}` }] };
169297
169580
  limit: z626.number().int().min(1).max(100).optional().describe("Maximum assets to list."),
169298
169581
  title: z626.string().optional().describe("Observation title when attaching generated/imported output."),
169299
169582
  narrative: z626.string().optional().describe("Short retrieval text when attaching an asset."),
169300
- prompt: z626.string().optional().describe("Explicit MiniMax generation prompt."),
169301
- image: z626.string().optional().describe("Base64-encoded reference image for image-to-image generation."),
169302
- model: z626.string().max(160).optional().describe("Provider model for the selected media action."),
169303
- provider: z626.enum(["openai", "groq"]).optional().describe("Explicit audio transcription provider."),
169304
- language: z626.string().max(32).optional().describe("Optional ISO-639-1 hint for audio transcription."),
169305
- region: z626.enum(["global", "cn"]).optional().describe("MiniMax deployment region."),
169306
- n: z626.number().int().min(1).max(4).optional().describe("Image output count."),
169307
- ratio: z626.enum(["adaptive", "1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]).optional().describe("Image or video aspect ratio."),
169308
- width: z626.number().int().min(1).max(8192).optional().describe("Requested image width."),
169309
- height: z626.number().int().min(1).max(8192).optional().describe("Requested image height."),
169310
- duration: z626.union([z626.literal(5), z626.literal(10)]).optional().describe("MiniMax video duration in seconds."),
169311
169583
  maxPages: z626.number().int().min(1).max(100).optional().describe("Bounded PDF extraction page limit."),
169312
169584
  maxChars: z626.number().int().min(1).max(6e4).optional().describe("Bounded PDF extraction character limit."),
169313
- attach: z626.boolean().optional().describe("Attach generated/imported output to normal project memory explicitly.")
169585
+ attach: z626.boolean().optional().describe("Attach generated/imported output to normal project memory explicitly."),
169586
+ ...mediaGenerationOrTranscription ? {
169587
+ model: z626.string().max(160).optional().describe("Provider model for the selected media action."),
169588
+ prompt: z626.string().optional().describe("Explicit prompt for generation or transcription.")
169589
+ } : {},
169590
+ ...mediaTranscriptionEnabled ? {
169591
+ provider: z626.enum(["openai", "groq"]).optional().describe("Explicit audio transcription provider."),
169592
+ language: z626.string().max(32).optional().describe("Optional ISO-639-1 hint for audio transcription.")
169593
+ } : {},
169594
+ ...mediaGenerationEnabled ? {
169595
+ image: z626.string().optional().describe("Base64-encoded reference image for image-to-image generation."),
169596
+ region: z626.enum(["global", "cn"]).optional().describe("MiniMax deployment region."),
169597
+ n: z626.number().int().min(1).max(4).optional().describe("Image output count."),
169598
+ ratio: z626.enum(["adaptive", "1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]).optional().describe("Image or video aspect ratio."),
169599
+ width: z626.number().int().min(1).max(8192).optional().describe("Requested image width."),
169600
+ height: z626.number().int().min(1).max(8192).optional().describe("Requested image height."),
169601
+ duration: z626.union([z626.literal(5), z626.literal(10)]).optional().describe("MiniMax video duration in seconds.")
169602
+ } : {}
169314
169603
  }
169315
169604
  },
169316
169605
  async ({ action, path: assetPath, assetId, jobId, kind, limit: limit2, title, narrative, prompt, image, model, provider: provider2, language, region, n, ratio, width, height, duration, maxPages, maxChars, attach }) => {
@@ -169794,6 +170083,7 @@ var init_server2 = __esm({
169794
170083
  init_formation_timeout();
169795
170084
  init_timeout();
169796
170085
  init_secret_filter();
170086
+ init_surfaced_registry();
169797
170087
  init_request_context();
169798
170088
  FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
169799
170089
  COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
@@ -169923,7 +170213,7 @@ var init_serve_shared = __esm({
169923
170213
 
169924
170214
  // src/cli/version.ts
169925
170215
  function getCliVersion() {
169926
- return true ? "1.4.5" : pkg.version;
170216
+ return true ? "1.5.0" : pkg.version;
169927
170217
  }
169928
170218
  var init_version3 = __esm({
169929
170219
  "src/cli/version.ts"() {