memorix 1.6.1 → 1.6.2

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 (113) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +1574 -1063
  4. package/dist/index.js +588 -107
  5. package/dist/maintenance-runner.js +565 -84
  6. package/dist/media-audio-runner.js +565 -84
  7. package/dist/media-video-runner.js +565 -84
  8. package/dist/memcode-runtime/CHANGELOG.md +22 -0
  9. package/dist/sdk.js +621 -111
  10. package/dist/vector-backfill-runner.js +565 -84
  11. package/docs/DESIGN_DECISIONS.md +357 -357
  12. package/docs/PERFORMANCE.md +15 -0
  13. package/package.json +2 -1
  14. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  15. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  16. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  17. package/plugins/copilot/memorix/plugin.json +1 -1
  18. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  19. package/plugins/omp/memorix/package.json +1 -1
  20. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  21. package/plugins/pi/memorix/package.json +1 -1
  22. package/server.json +2 -2
  23. package/src/audit/index.ts +156 -156
  24. package/src/cli/commands/background.ts +34 -2
  25. package/src/cli/commands/formation.ts +48 -48
  26. package/src/cli/commands/git-hook-install.ts +111 -111
  27. package/src/cli/commands/handoff.ts +54 -54
  28. package/src/cli/commands/hooks-status.ts +63 -63
  29. package/src/cli/commands/ingest.ts +44 -44
  30. package/src/cli/commands/integrate-shared.ts +15 -15
  31. package/src/cli/commands/lock.ts +82 -82
  32. package/src/cli/commands/message.ts +104 -104
  33. package/src/cli/commands/operator-shared.ts +6 -3
  34. package/src/cli/commands/poll.ts +58 -58
  35. package/src/cli/commands/reasoning.ts +118 -118
  36. package/src/cli/commands/serve-http.ts +4 -0
  37. package/src/cli/commands/skills.ts +114 -114
  38. package/src/cli/commands/task.ts +167 -167
  39. package/src/cli/commands/transfer.ts +47 -47
  40. package/src/cli/index.ts +406 -406
  41. package/src/cli/tui/ChatView.tsx +234 -234
  42. package/src/cli/tui/CommandBar.tsx +312 -312
  43. package/src/cli/tui/ContextRail.tsx +118 -118
  44. package/src/cli/tui/HeaderBar.tsx +72 -72
  45. package/src/cli/tui/LogoBanner.tsx +51 -51
  46. package/src/cli/tui/index.ts +41 -41
  47. package/src/cli/version.ts +7 -7
  48. package/src/cli/workbench.ts +1 -1
  49. package/src/dashboard/project-classification.ts +64 -64
  50. package/src/embedding/api-provider.ts +503 -27
  51. package/src/embedding/provider.ts +20 -3
  52. package/src/git/hooks-path.ts +85 -85
  53. package/src/hooks/lifecycle.ts +1 -0
  54. package/src/hooks/pattern-detector.ts +173 -173
  55. package/src/hooks/significance-filter.ts +250 -250
  56. package/src/llm/memory-manager.ts +328 -328
  57. package/src/llm/quality.ts +248 -248
  58. package/src/memory/attribution-guard.ts +249 -249
  59. package/src/memory/disclosure-policy.ts +135 -135
  60. package/src/memory/entity-extractor.ts +197 -197
  61. package/src/memory/formation/evaluate.ts +217 -217
  62. package/src/memory/freshness.ts +122 -122
  63. package/src/memory/graph.ts +197 -197
  64. package/src/memory/observations.ts +68 -18
  65. package/src/memory/refs.ts +94 -94
  66. package/src/memory/secret-filter.ts +79 -79
  67. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  68. package/src/orchestrate/adapters/claude.ts +111 -111
  69. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  70. package/src/orchestrate/adapters/codex.ts +41 -41
  71. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  72. package/src/orchestrate/adapters/gemini.ts +42 -42
  73. package/src/orchestrate/adapters/index.ts +73 -73
  74. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  75. package/src/orchestrate/adapters/opencode.ts +47 -47
  76. package/src/orchestrate/adapters/types.ts +77 -77
  77. package/src/orchestrate/capability-router.ts +284 -284
  78. package/src/orchestrate/cost-tracker.ts +219 -219
  79. package/src/orchestrate/error-recovery.ts +191 -191
  80. package/src/orchestrate/evidence.ts +140 -140
  81. package/src/orchestrate/ledger.ts +110 -110
  82. package/src/orchestrate/memorix-bridge.ts +343 -343
  83. package/src/orchestrate/output-budget.ts +80 -80
  84. package/src/orchestrate/permission.ts +152 -152
  85. package/src/orchestrate/pipeline-trace.ts +131 -131
  86. package/src/orchestrate/prompt-builder.ts +155 -155
  87. package/src/orchestrate/ring-buffer.ts +37 -37
  88. package/src/orchestrate/task-graph.ts +389 -389
  89. package/src/project/aliases.ts +374 -374
  90. package/src/rules/adapters/trae.ts +56 -56
  91. package/src/rules/syncer.ts +235 -235
  92. package/src/sdk.ts +12 -8
  93. package/src/search/query-expansion.ts +52 -52
  94. package/src/server/formation-timeout.ts +27 -27
  95. package/src/skills/mini-skills.ts +386 -386
  96. package/src/store/chat-store.ts +119 -119
  97. package/src/store/graph-store.ts +249 -249
  98. package/src/store/mini-skill-store.ts +349 -349
  99. package/src/store/orama-store.ts +21 -3
  100. package/src/store/persistence-json.ts +212 -212
  101. package/src/store/persistence.ts +291 -291
  102. package/src/store/project-affinity.ts +195 -195
  103. package/src/team/event-bus.ts +76 -76
  104. package/src/team/handoff.ts +161 -161
  105. package/src/team/poll.ts +132 -132
  106. package/src/workspace/mcp-adapters/codex.ts +191 -191
  107. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  108. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  109. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  110. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  111. package/src/workspace/mcp-adapters/trae.ts +134 -134
  112. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  113. package/src/workspace/sanitizer.ts +60 -60
package/dist/index.js CHANGED
@@ -1195,8 +1195,8 @@ async function acquireLock(lockPath) {
1195
1195
  const code = err instanceof Error && "code" in err ? err.code : void 0;
1196
1196
  if (code === "EEXIST" || code === "EPERM") {
1197
1197
  try {
1198
- const stat4 = await fs2.stat(lockPath);
1199
- if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
1198
+ const stat5 = await fs2.stat(lockPath);
1199
+ if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
1200
1200
  await fs2.unlink(lockPath).catch(() => {
1201
1201
  });
1202
1202
  continue;
@@ -3713,24 +3713,131 @@ var init_transformers_provider = __esm({
3713
3713
  // src/embedding/api-provider.ts
3714
3714
  var api_provider_exports = {};
3715
3715
  __export(api_provider_exports, {
3716
- APIEmbeddingProvider: () => APIEmbeddingProvider
3716
+ APIEmbeddingProvider: () => APIEmbeddingProvider,
3717
+ convertArrayCacheToJsonl: () => convertArrayCacheToJsonl,
3718
+ ensureDiskCacheLoaded: () => ensureDiskCacheLoaded,
3719
+ getApiEmbeddingCachePayloadBytesForTests: () => getApiEmbeddingCachePayloadBytesForTests,
3720
+ getApiEmbeddingDiskCacheLoadCountForTests: () => getApiEmbeddingDiskCacheLoadCountForTests,
3721
+ getApiEmbeddingJsonlLineCountCallsForTests: () => getApiEmbeddingJsonlLineCountCallsForTests,
3722
+ getApiEmbeddingMaxEntriesForDimensionsForTests: () => getApiEmbeddingMaxEntriesForDimensionsForTests,
3723
+ migrateJsonArrayCacheToJsonl: () => migrateJsonArrayCacheToJsonl,
3724
+ parseCacheJsonInWorker: () => parseCacheJsonInWorker,
3725
+ resetApiEmbeddingCacheForTests: () => resetApiEmbeddingCacheForTests,
3726
+ saveDiskCacheNow: () => saveDiskCacheNow,
3727
+ seedApiEmbeddingCacheForTests: () => seedApiEmbeddingCacheForTests
3717
3728
  });
3718
3729
  import { createHash as createHash2 } from "crypto";
3719
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3730
+ import { createReadStream, createWriteStream, constants as fsConstants } from "fs";
3731
+ import { access, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rename, stat, unlink } from "fs/promises";
3720
3732
  import { join as join3 } from "path";
3721
3733
  import { homedir as homedir7 } from "os";
3734
+ import { createInterface } from "readline";
3722
3735
  function cacheDir() {
3723
3736
  return process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
3724
3737
  }
3725
3738
  function cacheFile() {
3726
3739
  return join3(cacheDir(), ".embedding-api-cache.json");
3727
3740
  }
3741
+ function cacheJsonlFile() {
3742
+ return join3(cacheDir(), ".embedding-api-cache.jsonl");
3743
+ }
3728
3744
  function dimsCacheFile() {
3729
3745
  return join3(cacheDir(), ".embedding-dims-cache.json");
3730
3746
  }
3731
3747
  function cacheMetaFile() {
3732
3748
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3733
3749
  }
3750
+ function cacheStatFile() {
3751
+ return join3(cacheDir(), ".embedding-api-cache-stat.json");
3752
+ }
3753
+ function payloadBytes(vector) {
3754
+ return vector.length * BYTES_PER_DIM;
3755
+ }
3756
+ function maxCachePayloadBytes() {
3757
+ const raw = process.env.MEMORIX_EMBEDDING_CACHE_MAX_BYTES;
3758
+ if (raw) {
3759
+ const parsed = Number.parseInt(raw, 10);
3760
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
3761
+ }
3762
+ return MAX_CACHE_PAYLOAD_BYTES;
3763
+ }
3764
+ function maxEntriesForDimensions(dimensions) {
3765
+ const dim = Math.max(1, Math.floor(dimensions));
3766
+ const byBytes = Math.floor(maxCachePayloadBytes() / (dim * BYTES_PER_DIM));
3767
+ return Math.max(1, Math.min(MAX_CACHE_ENTRIES, byBytes));
3768
+ }
3769
+ function rememberDimensions(vector) {
3770
+ if (vector.length > 0 && (cacheDimensions === null || vector.length > cacheDimensions)) {
3771
+ cacheDimensions = vector.length;
3772
+ }
3773
+ }
3774
+ function evictOldest() {
3775
+ const firstKey = cache3.keys().next().value;
3776
+ if (firstKey === void 0) return;
3777
+ const old = cache3.get(firstKey);
3778
+ cache3.delete(firstKey);
3779
+ if (old) cachePayloadBytes -= payloadBytes(old);
3780
+ }
3781
+ function evictToFit(incomingBytes, incomingDims) {
3782
+ const limit2 = maxEntriesForDimensions(incomingDims);
3783
+ while (cache3.size > 0 && (cache3.size >= limit2 || cachePayloadBytes + incomingBytes > maxCachePayloadBytes())) {
3784
+ evictOldest();
3785
+ }
3786
+ }
3787
+ function isNumberArray(value) {
3788
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
3789
+ }
3790
+ function parseCacheRow(raw) {
3791
+ if (Array.isArray(raw)) {
3792
+ const hash4 = raw[0];
3793
+ const vector = raw[1];
3794
+ if (typeof hash4 === "string" && isNumberArray(vector)) {
3795
+ return { hash: hash4, vector };
3796
+ }
3797
+ return null;
3798
+ }
3799
+ if (raw && typeof raw === "object") {
3800
+ const row = raw;
3801
+ if (typeof row.h === "string" && isNumberArray(row.v)) {
3802
+ return { hash: row.h, vector: row.v };
3803
+ }
3804
+ }
3805
+ return null;
3806
+ }
3807
+ function cacheInsert(hash4, value, dirty) {
3808
+ rememberDimensions(value);
3809
+ const incoming = payloadBytes(value);
3810
+ const existing = cache3.get(hash4);
3811
+ if (existing) {
3812
+ cachePayloadBytes -= payloadBytes(existing);
3813
+ cache3.delete(hash4);
3814
+ }
3815
+ evictToFit(incoming, value.length || cacheDimensions || DEFAULT_ASSUMED_DIMENSIONS);
3816
+ cache3.set(hash4, value);
3817
+ cachePayloadBytes += incoming;
3818
+ if (dirty) diskCacheDirty2 = true;
3819
+ }
3820
+ async function readCacheStat() {
3821
+ try {
3822
+ const data = JSON.parse(await readFile2(cacheStatFile(), "utf-8"));
3823
+ if (typeof data.entryCount === "number" && Number.isFinite(data.entryCount) && data.entryCount >= 0) {
3824
+ return {
3825
+ version: 1,
3826
+ entryCount: Math.floor(data.entryCount),
3827
+ dimensions: isValidDimension(data.dimensions) ? data.dimensions : null
3828
+ };
3829
+ }
3830
+ } catch {
3831
+ }
3832
+ return null;
3833
+ }
3834
+ async function writeCacheStat(stat5) {
3835
+ try {
3836
+ await mkdir2(cacheDir(), { recursive: true });
3837
+ await writeFile2(cacheStatFile(), JSON.stringify(stat5));
3838
+ } catch {
3839
+ }
3840
+ }
3734
3841
  function normalizeText(text2) {
3735
3842
  return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3736
3843
  }
@@ -3816,29 +3923,300 @@ function toProviderInput(input, baseUrl) {
3816
3923
  }
3817
3924
  return { type: input.modality, url: input.url };
3818
3925
  }
3926
+ function parseCacheJsonInWorker(filePath) {
3927
+ return new Promise((resolve2, reject) => {
3928
+ void import("worker_threads").then(({ Worker }) => {
3929
+ const worker = new Worker(
3930
+ `
3931
+ const { parentPort, workerData } = require('node:worker_threads');
3932
+ const { readFileSync } = require('node:fs');
3933
+ try {
3934
+ const raw = readFileSync(workerData.filePath, 'utf8');
3935
+ parentPort.postMessage({ ok: true, entries: JSON.parse(raw) });
3936
+ } catch (err) {
3937
+ parentPort.postMessage({
3938
+ ok: false,
3939
+ error: err instanceof Error ? err.message : String(err),
3940
+ });
3941
+ }
3942
+ `,
3943
+ { eval: true, workerData: { filePath } }
3944
+ );
3945
+ worker.once("message", (msg) => {
3946
+ void worker.terminate();
3947
+ if (msg?.ok && Array.isArray(msg.entries)) {
3948
+ resolve2(msg.entries);
3949
+ return;
3950
+ }
3951
+ reject(new Error(msg?.error || "embedding cache worker parse failed"));
3952
+ });
3953
+ worker.once("error", reject);
3954
+ }).catch(reject);
3955
+ });
3956
+ }
3957
+ function findJsonValueEnd(source) {
3958
+ let i2 = 0;
3959
+ while (i2 < source.length && /\s/.test(source[i2])) i2 += 1;
3960
+ if (i2 >= source.length) return -1;
3961
+ const start = source[i2];
3962
+ if (start === '"') {
3963
+ i2 += 1;
3964
+ while (i2 < source.length) {
3965
+ if (source[i2] === "\\") {
3966
+ i2 += 2;
3967
+ continue;
3968
+ }
3969
+ if (source[i2] === '"') return i2 + 1;
3970
+ i2 += 1;
3971
+ }
3972
+ return -1;
3973
+ }
3974
+ if (start === "{" || start === "[") {
3975
+ const close = start === "{" ? "}" : "]";
3976
+ let depth = 0;
3977
+ let inString = false;
3978
+ for (; i2 < source.length; i2 += 1) {
3979
+ const ch = source[i2];
3980
+ if (inString) {
3981
+ if (ch === "\\") {
3982
+ i2 += 1;
3983
+ continue;
3984
+ }
3985
+ if (ch === '"') inString = false;
3986
+ continue;
3987
+ }
3988
+ if (ch === '"') {
3989
+ inString = true;
3990
+ continue;
3991
+ }
3992
+ if (ch === start) depth += 1;
3993
+ else if (ch === close) {
3994
+ depth -= 1;
3995
+ if (depth === 0) return i2 + 1;
3996
+ }
3997
+ }
3998
+ return -1;
3999
+ }
4000
+ const rest = source.slice(i2);
4001
+ const match2 = rest.match(/^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/);
4002
+ return match2 ? i2 + match2[0].length : -1;
4003
+ }
4004
+ async function streamJsonArrayToJsonl(filePath, tmpPath) {
4005
+ const input = createReadStream(filePath, { encoding: "utf8" });
4006
+ const out = createWriteStream(tmpPath);
4007
+ let buf = "";
4008
+ let started = false;
4009
+ let count2 = 0;
4010
+ const writeLine = async (line) => {
4011
+ if (!out.write(line)) {
4012
+ await new Promise((resolve2, reject) => {
4013
+ out.once("drain", resolve2);
4014
+ out.once("error", reject);
4015
+ });
4016
+ }
4017
+ };
4018
+ try {
4019
+ for await (const chunk of input) {
4020
+ buf += chunk;
4021
+ if (!started) {
4022
+ const idx = buf.search(/\S/);
4023
+ if (idx < 0) continue;
4024
+ if (buf[idx] !== "[") throw new Error("embedding cache is not a JSON array");
4025
+ buf = buf.slice(idx + 1);
4026
+ started = true;
4027
+ }
4028
+ while (true) {
4029
+ buf = buf.replace(/^\s*,?\s*/, "");
4030
+ if (!buf) break;
4031
+ if (buf[0] === "]") {
4032
+ buf = "";
4033
+ break;
4034
+ }
4035
+ const end = findJsonValueEnd(buf);
4036
+ if (end < 0) break;
4037
+ const value = JSON.parse(buf.slice(0, end));
4038
+ buf = buf.slice(end);
4039
+ const row = parseCacheRow(value);
4040
+ if (row) {
4041
+ await writeLine(`${JSON.stringify({ h: row.hash, v: row.vector })}
4042
+ `);
4043
+ count2 += 1;
4044
+ if (count2 % 50 === 0) {
4045
+ await new Promise((resolve2) => setImmediate(resolve2));
4046
+ }
4047
+ }
4048
+ }
4049
+ if (buf.length > MAX_MIGRATION_VALUE_BYTES) {
4050
+ throw new Error("embedding cache entry too large to migrate safely");
4051
+ }
4052
+ }
4053
+ } finally {
4054
+ await new Promise((resolve2, reject) => {
4055
+ out.end(() => resolve2());
4056
+ out.once("error", reject);
4057
+ });
4058
+ }
4059
+ return count2;
4060
+ }
4061
+ async function migrateJsonArrayCacheToJsonl(filePath, jsonlPath) {
4062
+ const tmpPath = `${jsonlPath}.tmp`;
4063
+ try {
4064
+ const count2 = await streamJsonArrayToJsonl(filePath, tmpPath);
4065
+ await rename(tmpPath, jsonlPath);
4066
+ diskCacheEntryCount = count2;
4067
+ await writeCacheStat({
4068
+ version: 1,
4069
+ entryCount: count2,
4070
+ dimensions: cacheDimensions
4071
+ });
4072
+ return count2;
4073
+ } catch (err) {
4074
+ await unlink(tmpPath).catch(() => void 0);
4075
+ throw err;
4076
+ }
4077
+ }
4078
+ function convertArrayCacheToJsonl(filePath, jsonlPath) {
4079
+ return migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4080
+ }
4081
+ async function loadCacheJsonl(filePath) {
4082
+ const rl = createInterface({
4083
+ input: createReadStream(filePath, { encoding: "utf8" }),
4084
+ crlfDelay: Infinity
4085
+ });
4086
+ let loaded = 0;
4087
+ let onDiskCount = 0;
4088
+ for await (const line of rl) {
4089
+ const trimmed = line.trim();
4090
+ if (!trimmed) continue;
4091
+ try {
4092
+ const row = parseCacheRow(JSON.parse(trimmed));
4093
+ if (!row) continue;
4094
+ onDiskCount += 1;
4095
+ cacheInsert(row.hash, row.vector, false);
4096
+ loaded = cache3.size;
4097
+ } catch {
4098
+ }
4099
+ if (onDiskCount % 250 === 0) {
4100
+ await new Promise((resolve2) => setImmediate(resolve2));
4101
+ }
4102
+ }
4103
+ diskCacheEntryCount = onDiskCount;
4104
+ await writeCacheStat({
4105
+ version: 1,
4106
+ entryCount: onDiskCount,
4107
+ dimensions: cacheDimensions
4108
+ });
4109
+ return loaded;
4110
+ }
4111
+ async function countJsonlLines(filePath) {
4112
+ jsonlLineCountCalls += 1;
4113
+ try {
4114
+ const rl = createInterface({
4115
+ input: createReadStream(filePath, { encoding: "utf8" }),
4116
+ crlfDelay: Infinity
4117
+ });
4118
+ let count2 = 0;
4119
+ for await (const line of rl) {
4120
+ if (line.trim()) count2 += 1;
4121
+ }
4122
+ return count2;
4123
+ } catch {
4124
+ return 0;
4125
+ }
4126
+ }
4127
+ async function resolveOnDiskCount(jsonlPath) {
4128
+ if (diskCacheEntryCount > 0) return diskCacheEntryCount;
4129
+ const sidecar = await readCacheStat();
4130
+ if (sidecar) {
4131
+ diskCacheEntryCount = sidecar.entryCount;
4132
+ if (sidecar.dimensions && cacheDimensions === null) cacheDimensions = sidecar.dimensions;
4133
+ return sidecar.entryCount;
4134
+ }
4135
+ try {
4136
+ await access(jsonlPath, fsConstants.R_OK);
4137
+ } catch {
4138
+ return 0;
4139
+ }
4140
+ const counted = await countJsonlLines(jsonlPath);
4141
+ diskCacheEntryCount = counted;
4142
+ await writeCacheStat({
4143
+ version: 1,
4144
+ entryCount: counted,
4145
+ dimensions: cacheDimensions
4146
+ });
4147
+ return counted;
4148
+ }
3819
4149
  async function loadDiskCache2() {
3820
- if (diskCacheLoaded) return;
4150
+ if (diskCacheLoaded) return cache3.size;
4151
+ let loaded = 0;
3821
4152
  try {
3822
- const raw = await readFile2(cacheFile(), "utf-8");
3823
- const entries = JSON.parse(raw);
3824
- for (const [k, v] of entries) cache3.set(k, v);
3825
- console.error(`[memorix] Loaded ${entries.length} cached API embeddings from disk`);
4153
+ const jsonlPath = cacheJsonlFile();
4154
+ let hasJsonl = false;
4155
+ try {
4156
+ await access(jsonlPath, fsConstants.R_OK);
4157
+ hasJsonl = true;
4158
+ } catch {
4159
+ hasJsonl = false;
4160
+ }
4161
+ if (hasJsonl) {
4162
+ loaded = await loadCacheJsonl(jsonlPath);
4163
+ } else {
4164
+ const filePath = cacheFile();
4165
+ await stat(filePath);
4166
+ await migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4167
+ loaded = await loadCacheJsonl(jsonlPath);
4168
+ }
4169
+ diskCacheLoadCount += 1;
4170
+ console.error(`[memorix] Loaded ${loaded} cached API embeddings from disk`);
3826
4171
  } catch {
3827
4172
  }
3828
4173
  diskCacheLoaded = true;
4174
+ return loaded;
3829
4175
  }
3830
4176
  function startDiskCacheLoad() {
3831
4177
  if (diskCacheLoaded || diskCacheLoadPromise) return;
3832
- diskCacheLoadPromise = loadDiskCache2().catch(() => {
4178
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
3833
4179
  });
3834
4180
  }
3835
4181
  async function ensureDiskCacheLoaded() {
3836
- if (diskCacheLoaded) return;
3837
- if (diskCacheLoadPromise) {
3838
- await diskCacheLoadPromise;
3839
- return;
3840
- }
3841
- await loadDiskCache2();
4182
+ if (diskCacheLoaded) return cache3.size;
4183
+ if (!diskCacheLoadPromise) {
4184
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
4185
+ });
4186
+ }
4187
+ await diskCacheLoadPromise;
4188
+ return cache3.size;
4189
+ }
4190
+ function getApiEmbeddingDiskCacheLoadCountForTests() {
4191
+ return diskCacheLoadCount;
4192
+ }
4193
+ function resetApiEmbeddingCacheForTests() {
4194
+ cache3.clear();
4195
+ cachePayloadBytes = 0;
4196
+ cacheDimensions = null;
4197
+ diskCacheEntryCount = 0;
4198
+ diskCacheLoaded = false;
4199
+ diskCacheLoadPromise = null;
4200
+ diskCacheLoadCount = 0;
4201
+ diskCacheDirty2 = false;
4202
+ jsonlLineCountCalls = 0;
4203
+ }
4204
+ function seedApiEmbeddingCacheForTests(entries) {
4205
+ cache3.clear();
4206
+ cachePayloadBytes = 0;
4207
+ cacheDimensions = null;
4208
+ for (const [key, value] of entries) cacheInsert(key, value, true);
4209
+ diskCacheLoaded = true;
4210
+ diskCacheDirty2 = true;
4211
+ }
4212
+ function getApiEmbeddingCachePayloadBytesForTests() {
4213
+ return cachePayloadBytes;
4214
+ }
4215
+ function getApiEmbeddingJsonlLineCountCallsForTests() {
4216
+ return jsonlLineCountCalls;
4217
+ }
4218
+ function getApiEmbeddingMaxEntriesForDimensionsForTests(dimensions) {
4219
+ return maxEntriesForDimensions(dimensions);
3842
4220
  }
3843
4221
  function dimsCacheKey(config) {
3844
4222
  return [
@@ -3954,8 +4332,46 @@ async function saveDiskCacheNow() {
3954
4332
  if (!diskCacheDirty2) return;
3955
4333
  try {
3956
4334
  await mkdir2(cacheDir(), { recursive: true });
3957
- const entries = Array.from(cache3.entries());
3958
- await writeFile2(cacheFile(), JSON.stringify(entries));
4335
+ const jsonlPath = cacheJsonlFile();
4336
+ const existing = await resolveOnDiskCount(jsonlPath);
4337
+ if (existing > 0 && cache3.size < Math.ceil(existing * 0.5)) {
4338
+ console.error(
4339
+ `[memorix] Refusing to overwrite embedding cache (${existing} on disk vs ${cache3.size} in memory)`
4340
+ );
4341
+ diskCacheDirty2 = false;
4342
+ return;
4343
+ }
4344
+ const tmpPath = `${jsonlPath}.tmp`;
4345
+ await new Promise((resolve2, reject) => {
4346
+ const out = createWriteStream(tmpPath);
4347
+ const entries = cache3.entries();
4348
+ const writeBatch = () => {
4349
+ for (let i2 = 0; i2 < 50; i2++) {
4350
+ const next = entries.next();
4351
+ if (next.done) {
4352
+ out.end();
4353
+ return;
4354
+ }
4355
+ const [hash4, vector] = next.value;
4356
+ if (!out.write(`${JSON.stringify({ h: hash4, v: vector })}
4357
+ `)) {
4358
+ out.once("drain", writeBatch);
4359
+ return;
4360
+ }
4361
+ }
4362
+ setImmediate(writeBatch);
4363
+ };
4364
+ out.on("finish", resolve2);
4365
+ out.on("error", reject);
4366
+ writeBatch();
4367
+ });
4368
+ await rename(tmpPath, jsonlPath);
4369
+ diskCacheEntryCount = cache3.size;
4370
+ await writeCacheStat({
4371
+ version: 1,
4372
+ entryCount: cache3.size,
4373
+ dimensions: cacheDimensions
4374
+ });
3959
4375
  diskCacheDirty2 = false;
3960
4376
  } catch {
3961
4377
  }
@@ -3969,12 +4385,7 @@ function scheduleDiskSave() {
3969
4385
  }, DISK_SAVE_DEBOUNCE_MS);
3970
4386
  }
3971
4387
  function cacheSet(hash4, value) {
3972
- if (cache3.size >= MAX_CACHE_SIZE3) {
3973
- const firstKey = cache3.keys().next().value;
3974
- if (firstKey !== void 0) cache3.delete(firstKey);
3975
- }
3976
- cache3.set(hash4, value);
3977
- diskCacheDirty2 = true;
4388
+ cacheInsert(hash4, value, true);
3978
4389
  }
3979
4390
  function resolveEnvEmbeddingApiKey() {
3980
4391
  return process.env.MEMORIX_EMBEDDING_API_KEY;
@@ -4093,18 +4504,27 @@ async function waitForRetry(waitMs, signal) {
4093
4504
  abortSignal.addEventListener("abort", onAbort, { once: true });
4094
4505
  });
4095
4506
  }
4096
- var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
4507
+ var cache3, MAX_CACHE_ENTRIES, MAX_CACHE_PAYLOAD_BYTES, BYTES_PER_DIM, DEFAULT_ASSUMED_DIMENSIONS, MAX_MIGRATION_VALUE_BYTES, cachePayloadBytes, cacheDimensions, diskCacheEntryCount, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, diskCacheLoadCount, jsonlLineCountCalls, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
4097
4508
  var init_api_provider = __esm({
4098
4509
  "src/embedding/api-provider.ts"() {
4099
4510
  "use strict";
4100
4511
  init_esm_shims();
4101
4512
  init_provider();
4102
4513
  cache3 = /* @__PURE__ */ new Map();
4103
- MAX_CACHE_SIZE3 = 1e4;
4514
+ MAX_CACHE_ENTRIES = 2e5;
4515
+ MAX_CACHE_PAYLOAD_BYTES = 256 * 1024 * 1024;
4516
+ BYTES_PER_DIM = 8;
4517
+ DEFAULT_ASSUMED_DIMENSIONS = 4096;
4518
+ MAX_MIGRATION_VALUE_BYTES = 16 * 1024 * 1024;
4519
+ cachePayloadBytes = 0;
4520
+ cacheDimensions = null;
4521
+ diskCacheEntryCount = 0;
4104
4522
  diskCacheDirty2 = false;
4105
4523
  diskSaveTimer = null;
4106
4524
  diskCacheLoaded = false;
4107
4525
  diskCacheLoadPromise = null;
4526
+ diskCacheLoadCount = 0;
4527
+ jsonlLineCountCalls = 0;
4108
4528
  MAX_INPUT_CHARS = 32e3;
4109
4529
  MAX_CONCURRENCY = 4;
4110
4530
  DISK_SAVE_DEBOUNCE_MS = 5e3;
@@ -4162,7 +4582,9 @@ var init_api_provider = __esm({
4162
4582
  if (config.requestedDimensions) {
4163
4583
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
4164
4584
  }
4165
- startDiskCacheLoad();
4585
+ if (allowNetworkProbe) {
4586
+ startDiskCacheLoad();
4587
+ }
4166
4588
  return new _APIEmbeddingProvider(config, probeDimensions);
4167
4589
  }
4168
4590
  static resolveConfig() {
@@ -4500,7 +4922,12 @@ function warnOnce(message) {
4500
4922
  lastEmbeddingWarningAt = now3;
4501
4923
  console.error(message);
4502
4924
  }
4925
+ function embeddingModeToken() {
4926
+ return process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim() ?? "";
4927
+ }
4503
4928
  function getEmbeddingMode2() {
4929
+ const token = embeddingModeToken();
4930
+ if (cachedEmbeddingMode && cachedEmbeddingModeToken === token) return cachedEmbeddingMode;
4504
4931
  try {
4505
4932
  let cfg;
4506
4933
  try {
@@ -4509,10 +4936,17 @@ function getEmbeddingMode2() {
4509
4936
  cfg = (init_config(), __toCommonJS(config_exports));
4510
4937
  }
4511
4938
  const { getEmbeddingMode: cfgMode } = cfg;
4512
- return cfgMode();
4939
+ cachedEmbeddingMode = cfgMode();
4940
+ cachedEmbeddingModeToken = token;
4941
+ return cachedEmbeddingMode;
4513
4942
  } catch {
4514
- const env2 = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
4515
- if (env2 === "fastembed" || env2 === "transformers" || env2 === "api" || env2 === "auto") return env2;
4943
+ if (token === "fastembed" || token === "transformers" || token === "api" || token === "auto") {
4944
+ cachedEmbeddingMode = token;
4945
+ cachedEmbeddingModeToken = token;
4946
+ return token;
4947
+ }
4948
+ cachedEmbeddingMode = "off";
4949
+ cachedEmbeddingModeToken = token;
4516
4950
  return "off";
4517
4951
  }
4518
4952
  }
@@ -4768,13 +5202,15 @@ function isEmbeddingExplicitlyDisabled() {
4768
5202
  function resetProvider() {
4769
5203
  provider = null;
4770
5204
  initPromise = null;
5205
+ cachedEmbeddingMode = null;
5206
+ cachedEmbeddingModeToken = null;
4771
5207
  lastInitWasTemporaryFailure = false;
4772
5208
  lastFailureTimestamp = 0;
4773
5209
  lastEmbeddingFailure = null;
4774
5210
  lastEmbeddingWarning = "";
4775
5211
  lastEmbeddingWarningAt = 0;
4776
5212
  }
4777
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
5213
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, cachedEmbeddingMode, cachedEmbeddingModeToken, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
4778
5214
  var init_provider = __esm({
4779
5215
  "src/embedding/provider.ts"() {
4780
5216
  "use strict";
@@ -4798,6 +5234,8 @@ var init_provider = __esm({
4798
5234
  MAX_INLINE_EMBEDDING_BYTES = 5 * 1024 * 1024;
4799
5235
  provider = null;
4800
5236
  initPromise = null;
5237
+ cachedEmbeddingMode = null;
5238
+ cachedEmbeddingModeToken = null;
4801
5239
  lastInitWasTemporaryFailure = false;
4802
5240
  lastEmbeddingFailure = null;
4803
5241
  lastEmbeddingWarning = "";
@@ -6555,6 +6993,9 @@ async function attachCachedVectors(database, candidates) {
6555
6993
  await update(database, candidates[index].id, { ...existing, embedding: vector });
6556
6994
  } catch {
6557
6995
  }
6996
+ if (index % 50 === 0) {
6997
+ await new Promise((resolve2) => setImmediate(resolve2));
6998
+ }
6558
6999
  }
6559
7000
  }
6560
7001
  async function hydrateIndex(observations2, options = {}) {
@@ -6605,17 +7046,24 @@ async function hydrateIndex(observations2, options = {}) {
6605
7046
  await insert2(database, doc);
6606
7047
  rememberObservationDoc(doc);
6607
7048
  inserted++;
7049
+ if (inserted % 50 === 0) {
7050
+ await new Promise((resolve2) => setImmediate(resolve2));
7051
+ }
7052
+ if (inserted % 200 === 0) {
7053
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
7054
+ }
6608
7055
  } catch {
6609
7056
  }
6610
7057
  }
6611
- deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
7058
+ deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6612
7059
  }) : null;
6613
7060
  return inserted;
6614
7061
  }
6615
- async function hydrateIndexForStartup(observations2) {
7062
+ async function hydrateIndexForStartup(observations2, options = {}) {
6616
7063
  return hydrateIndex(observations2, {
6617
7064
  allowNetworkProbe: false,
6618
- deferCachedVectors: true
7065
+ deferCachedVectors: true,
7066
+ skipCachedVectors: options.skipCachedVectors
6619
7067
  });
6620
7068
  }
6621
7069
  function getDeferredCachedVectorHydration() {
@@ -9938,7 +10386,7 @@ __export(asset_store_exports, {
9938
10386
  removeMediaAsset: () => removeMediaAsset
9939
10387
  });
9940
10388
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
9941
- import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
10389
+ import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
9942
10390
  import path13 from "path";
9943
10391
  function mediaRoot(dataDir) {
9944
10392
  return path13.resolve(dataDir, "media");
@@ -10028,7 +10476,7 @@ async function persistFile(source, destination) {
10028
10476
  const temporary = `${destination}.${randomUUID5()}.tmp`;
10029
10477
  try {
10030
10478
  await copyFile(source, temporary);
10031
- await rename(temporary, destination);
10479
+ await rename2(temporary, destination);
10032
10480
  return true;
10033
10481
  } catch (error) {
10034
10482
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -10044,7 +10492,7 @@ async function persistBuffer(bytes, destination) {
10044
10492
  const temporary = `${destination}.${randomUUID5()}.tmp`;
10045
10493
  try {
10046
10494
  await writeFile3(temporary, bytes, { flag: "wx" });
10047
- await rename(temporary, destination);
10495
+ await rename2(temporary, destination);
10048
10496
  return true;
10049
10497
  } catch (error) {
10050
10498
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -10084,11 +10532,11 @@ async function importKnownMedia(input) {
10084
10532
  async function importMediaFile(input) {
10085
10533
  const maxBytes = input.maxBytes ?? DEFAULT_MAX_MEDIA_BYTES;
10086
10534
  const source = path13.resolve(input.filePath);
10087
- const stat4 = await lstat(source);
10088
- if (stat4.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
10089
- if (!stat4.isFile()) throw new Error("Media import accepts regular files only");
10090
- if (stat4.size <= 0) throw new Error("Media import rejects empty files");
10091
- if (stat4.size > maxBytes) throw new Error(`Media file is too large (${stat4.size} bytes; limit ${maxBytes})`);
10535
+ const stat5 = await lstat(source);
10536
+ if (stat5.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
10537
+ if (!stat5.isFile()) throw new Error("Media import accepts regular files only");
10538
+ if (stat5.size <= 0) throw new Error("Media import rejects empty files");
10539
+ if (stat5.size > maxBytes) throw new Error(`Media file is too large (${stat5.size} bytes; limit ${maxBytes})`);
10092
10540
  const header = await readHeader(source);
10093
10541
  const detected = detectMedia(header);
10094
10542
  const sha256 = await sha256File(source);
@@ -10096,7 +10544,7 @@ async function importMediaFile(input) {
10096
10544
  dataDir: input.dataDir,
10097
10545
  projectId: input.projectId,
10098
10546
  sha256,
10099
- bytes: stat4.size,
10547
+ bytes: stat5.size,
10100
10548
  detected,
10101
10549
  persist: (destination) => persistFile(source, destination),
10102
10550
  sourceKind: input.sourceKind ?? "import",
@@ -10143,13 +10591,13 @@ async function removeMediaAsset(input) {
10143
10591
  const assetPath = getMediaAssetPath(input.dataDir, asset);
10144
10592
  const stagedPath = deletionStagingPath(mediaRoot(input.dataDir), asset.id);
10145
10593
  await mkdir3(path13.dirname(stagedPath), { recursive: true });
10146
- await rename(assetPath, stagedPath);
10594
+ await rename2(assetPath, stagedPath);
10147
10595
  let detachedLinks;
10148
10596
  try {
10149
10597
  ({ detachedLinks } = store.removeAssetMetadata(input.projectId, asset.id, { force: input.force }));
10150
10598
  } catch (error) {
10151
10599
  try {
10152
- await rename(stagedPath, assetPath);
10600
+ await rename2(stagedPath, assetPath);
10153
10601
  } catch (restoreError) {
10154
10602
  const original = error instanceof Error ? error.message : String(error);
10155
10603
  const restore = restoreError instanceof Error ? restoreError.message : String(restoreError);
@@ -10185,7 +10633,7 @@ async function cleanupMediaTrash(dataDir) {
10185
10633
  } catch (error) {
10186
10634
  if (error?.code === "ENOENT") continue;
10187
10635
  try {
10188
- pendingBytes += (await stat(candidate)).size;
10636
+ pendingBytes += (await stat2(candidate)).size;
10189
10637
  } catch {
10190
10638
  }
10191
10639
  pendingFiles += 1;
@@ -61267,9 +61715,9 @@ var require_node_domexception = __commonJS({
61267
61715
  });
61268
61716
 
61269
61717
  // node_modules/fetch-blob/from.js
61270
- import { statSync as statSync2, createReadStream, promises as fs10 } from "fs";
61718
+ import { statSync as statSync2, createReadStream as createReadStream2, promises as fs10 } from "fs";
61271
61719
  import { basename } from "path";
61272
- var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
61720
+ var import_node_domexception, stat3, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
61273
61721
  var init_from = __esm({
61274
61722
  "node_modules/fetch-blob/from.js"() {
61275
61723
  "use strict";
@@ -61277,23 +61725,23 @@ var init_from = __esm({
61277
61725
  import_node_domexception = __toESM(require_node_domexception(), 1);
61278
61726
  init_file();
61279
61727
  init_fetch_blob();
61280
- ({ stat: stat2 } = fs10);
61728
+ ({ stat: stat3 } = fs10);
61281
61729
  blobFromSync = (path37, type) => fromBlob(statSync2(path37), path37, type);
61282
- blobFrom = (path37, type) => stat2(path37).then((stat4) => fromBlob(stat4, path37, type));
61283
- fileFrom = (path37, type) => stat2(path37).then((stat4) => fromFile(stat4, path37, type));
61730
+ blobFrom = (path37, type) => stat3(path37).then((stat5) => fromBlob(stat5, path37, type));
61731
+ fileFrom = (path37, type) => stat3(path37).then((stat5) => fromFile(stat5, path37, type));
61284
61732
  fileFromSync = (path37, type) => fromFile(statSync2(path37), path37, type);
61285
- fromBlob = (stat4, path37, type = "") => new fetch_blob_default([new BlobDataItem({
61733
+ fromBlob = (stat5, path37, type = "") => new fetch_blob_default([new BlobDataItem({
61286
61734
  path: path37,
61287
- size: stat4.size,
61288
- lastModified: stat4.mtimeMs,
61735
+ size: stat5.size,
61736
+ lastModified: stat5.mtimeMs,
61289
61737
  start: 0
61290
61738
  })], { type });
61291
- fromFile = (stat4, path37, type = "") => new file_default([new BlobDataItem({
61739
+ fromFile = (stat5, path37, type = "") => new file_default([new BlobDataItem({
61292
61740
  path: path37,
61293
- size: stat4.size,
61294
- lastModified: stat4.mtimeMs,
61741
+ size: stat5.size,
61742
+ lastModified: stat5.mtimeMs,
61295
61743
  start: 0
61296
- })], basename(path37), { type, lastModified: stat4.mtimeMs });
61744
+ })], basename(path37), { type, lastModified: stat5.mtimeMs });
61297
61745
  BlobDataItem = class _BlobDataItem {
61298
61746
  #path;
61299
61747
  #start;
@@ -61316,11 +61764,11 @@ var init_from = __esm({
61316
61764
  });
61317
61765
  }
61318
61766
  async *stream() {
61319
- const { mtimeMs } = await stat2(this.#path);
61767
+ const { mtimeMs } = await stat3(this.#path);
61320
61768
  if (mtimeMs > this.lastModified) {
61321
61769
  throw new import_node_domexception.default("The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.", "NotReadableError");
61322
61770
  }
61323
- yield* createReadStream(this.#path, {
61771
+ yield* createReadStream2(this.#path, {
61324
61772
  start: this.#start,
61325
61773
  end: this.#start + this.size - 1
61326
61774
  });
@@ -76950,7 +77398,7 @@ var init_wrapper = __esm({
76950
77398
  });
76951
77399
 
76952
77400
  // node_modules/@google/genai/dist/node/index.mjs
76953
- import { createWriteStream } from "fs";
77401
+ import { createWriteStream as createWriteStream2 } from "fs";
76954
77402
  import * as fs11 from "fs/promises";
76955
77403
  import { writeFile as writeFile4 } from "fs/promises";
76956
77404
  import { Readable } from "stream";
@@ -94230,7 +94678,7 @@ ${underline}`);
94230
94678
  if (params.downloadPath) {
94231
94679
  const response = await downloadFile(params, apiClient);
94232
94680
  if (response instanceof HttpResponse) {
94233
- const writer = createWriteStream(params.downloadPath);
94681
+ const writer = createWriteStream2(params.downloadPath);
94234
94682
  const body = Readable.fromWeb(response.responseInternal.body);
94235
94683
  body.pipe(writer);
94236
94684
  await finished(writer);
@@ -144264,7 +144712,7 @@ function explainRetention(doc, referenceTime) {
144264
144712
  };
144265
144713
  }
144266
144714
  function toRetentionDocument(obs, accessMap) {
144267
- const access2 = accessMap?.get(obs.id);
144715
+ const access3 = accessMap?.get(obs.id);
144268
144716
  return {
144269
144717
  id: `obs-${obs.id}`,
144270
144718
  observationId: obs.id,
@@ -144278,8 +144726,8 @@ function toRetentionDocument(obs, accessMap) {
144278
144726
  tokens: obs.tokens,
144279
144727
  createdAt: obs.createdAt,
144280
144728
  projectId: obs.projectId,
144281
- accessCount: access2?.accessCount ?? 0,
144282
- lastAccessedAt: access2?.lastAccessedAt ?? "",
144729
+ accessCount: access3?.accessCount ?? 0,
144730
+ lastAccessedAt: access3?.lastAccessedAt ?? "",
144283
144731
  status: obs.status ?? "active",
144284
144732
  source: obs.source ?? "agent",
144285
144733
  sourceDetail: obs.sourceDetail ?? "",
@@ -146393,7 +146841,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
146393
146841
  const rel = normalizeCodePath(relative(projectRoot, abs));
146394
146842
  try {
146395
146843
  const text2 = readFileSync6(abs, "utf-8");
146396
- const stat4 = fileStat ?? statSync3(abs);
146844
+ const stat5 = fileStat ?? statSync3(abs);
146397
146845
  const file = {
146398
146846
  id: makeCodeFileId(projectId, rel),
146399
146847
  projectId,
@@ -146402,8 +146850,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
146402
146850
  contentHash: hashText(text2),
146403
146851
  // Preserve filesystem precision. Rounding here made same-size edits in
146404
146852
  // a short Windows timestamp window indistinguishable to refresh.
146405
- mtimeMs: Number(stat4.mtimeMs),
146406
- sizeBytes: Number(stat4.size),
146853
+ mtimeMs: Number(stat5.mtimeMs),
146854
+ sizeBytes: Number(stat5.size),
146407
146855
  indexedAt
146408
146856
  };
146409
146857
  return {
@@ -146430,18 +146878,18 @@ async function indexProjectLite(options) {
146430
146878
  let skippedOversizedFiles = 0;
146431
146879
  let unreadableFiles = 0;
146432
146880
  for (const abs of paths) {
146433
- let stat4;
146881
+ let stat5;
146434
146882
  try {
146435
- stat4 = statSync3(abs);
146883
+ stat5 = statSync3(abs);
146436
146884
  } catch {
146437
146885
  unreadableFiles++;
146438
146886
  continue;
146439
146887
  }
146440
- if (Number(stat4.size) > maxFileBytes) {
146888
+ if (Number(stat5.size) > maxFileBytes) {
146441
146889
  skippedOversizedFiles++;
146442
146890
  continue;
146443
146891
  }
146444
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
146892
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
146445
146893
  if (!attempt.delta) {
146446
146894
  if (attempt.unreadable) unreadableFiles++;
146447
146895
  continue;
@@ -146469,16 +146917,16 @@ async function refreshProjectLite(store, options) {
146469
146917
  for (const abs of paths) {
146470
146918
  const rel = normalizeCodePath(relative(options.projectRoot, abs));
146471
146919
  seenPaths.add(rel);
146472
- let stat4;
146920
+ let stat5;
146473
146921
  try {
146474
- stat4 = statSync3(abs);
146922
+ stat5 = statSync3(abs);
146475
146923
  } catch {
146476
146924
  unreadableFiles++;
146477
146925
  continue;
146478
146926
  }
146479
146927
  const existing = existingByPath.get(rel);
146480
- const mtimeMs = Number(stat4.mtimeMs);
146481
- const sizeBytes = Number(stat4.size);
146928
+ const mtimeMs = Number(stat5.mtimeMs);
146929
+ const sizeBytes = Number(stat5.size);
146482
146930
  if (sizeBytes > maxFileBytes) {
146483
146931
  skippedOversizedFiles++;
146484
146932
  if (existing) oversizedExistingFileIds.add(existing.id);
@@ -146488,7 +146936,7 @@ async function refreshProjectLite(store, options) {
146488
146936
  unchangedFiles++;
146489
146937
  continue;
146490
146938
  }
146491
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
146939
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
146492
146940
  if (!attempt.delta) {
146493
146941
  if (attempt.unreadable) unreadableFiles++;
146494
146942
  continue;
@@ -150236,12 +150684,25 @@ __export(observations_exports, {
150236
150684
  prepareSearchIndex: () => prepareSearchIndex,
150237
150685
  probeSearchIndex: () => probeSearchIndex,
150238
150686
  reindexObservations: () => reindexObservations,
150687
+ resetObservationRuntime: () => resetObservationRuntime,
150239
150688
  resolveObservations: () => resolveObservations,
150240
150689
  storeObservation: () => storeObservation,
150241
150690
  suggestTopicKey: () => suggestTopicKey,
150242
150691
  updateObservationAdmission: () => updateObservationAdmission,
150243
150692
  withFreshObservations: () => withFreshObservations
150244
150693
  });
150694
+ function resetObservationRuntime() {
150695
+ observations = [];
150696
+ nextId = 1;
150697
+ projectDir = null;
150698
+ searchIndexPrepared = false;
150699
+ corpusLoaded = false;
150700
+ loadedObservationStore = null;
150701
+ embeddingWriteMode = "background";
150702
+ embeddingWorkerProjectRoot = void 0;
150703
+ vectorMissingIds.clear();
150704
+ vectorSchemaUpgradePromise = null;
150705
+ }
150245
150706
  function logEmbeddingFailureOnce(key, message) {
150246
150707
  const now3 = Date.now();
150247
150708
  const last = embeddingFailureLogTimestamps.get(key) ?? 0;
@@ -150350,23 +150811,33 @@ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
150350
150811
  async function initObservations(dir, options = {}) {
150351
150812
  embeddingWriteMode = options.embeddingWriteMode ?? "background";
150352
150813
  embeddingWorkerProjectRoot = options.projectRoot;
150353
- if (projectDir === dir) return;
150354
150814
  await initObservationStore(dir);
150355
150815
  const store = getObservationStore();
150356
- observations = await store.loadAll();
150816
+ if (projectDir === dir && loadedObservationStore === store) {
150817
+ if (options.skipCorpusLoad || corpusLoaded) return;
150818
+ }
150357
150819
  nextId = await store.loadIdCounter();
150358
150820
  projectDir = dir;
150359
150821
  searchIndexPrepared = false;
150360
150822
  vectorSchemaUpgradePromise = null;
150823
+ if (options.skipCorpusLoad) {
150824
+ observations = [];
150825
+ corpusLoaded = false;
150826
+ loadedObservationStore = store;
150827
+ return;
150828
+ }
150829
+ observations = await store.loadAll();
150830
+ corpusLoaded = true;
150831
+ loadedObservationStore = store;
150361
150832
  }
150362
150833
  function scheduleObservationEmbedding(input) {
150363
150834
  const { observationId: observationId2, projectId, searchableText, doc } = input;
150364
150835
  vectorMissingIds.add(observationId2);
150836
+ if (isEmbeddingExplicitlyDisabled()) {
150837
+ vectorMissingIds.delete(observationId2);
150838
+ return;
150839
+ }
150365
150840
  if (embeddingWriteMode === "deferred") {
150366
- if (isEmbeddingExplicitlyDisabled()) {
150367
- vectorMissingIds.delete(observationId2);
150368
- return;
150369
- }
150370
150841
  queueVectorBackfill(projectId, {
150371
150842
  detachedWorker: true,
150372
150843
  projectRoot: embeddingWorkerProjectRoot
@@ -150413,7 +150884,7 @@ function scheduleObservationEmbedding(input) {
150413
150884
  });
150414
150885
  }
150415
150886
  async function ensureFreshObservations() {
150416
- if (!projectDir) return false;
150887
+ if (!projectDir || !corpusLoaded) return false;
150417
150888
  try {
150418
150889
  const store = getObservationStore();
150419
150890
  const wasStale = await store.ensureFresh();
@@ -150454,7 +150925,7 @@ async function storeObservation(input) {
150454
150925
  });
150455
150926
  if (safeAttachments) input = { ...input, attachments: safeAttachments };
150456
150927
  await ensureFreshObservations();
150457
- if (input.topicKey) {
150928
+ if (input.topicKey && corpusLoaded) {
150458
150929
  const existing = observations.find(
150459
150930
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
150460
150931
  );
@@ -150551,13 +151022,17 @@ async function storeObservation(input) {
150551
151022
  });
150552
151023
  observation.writeGeneration = store.getGeneration();
150553
151024
  if (upsertedInsideLock || reloadCacheAfterCommit) {
150554
- observations = await store.loadAll();
150555
- nextId = await store.loadIdCounter();
150556
- if (upsertedInsideLock) {
150557
- observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
151025
+ if (corpusLoaded) {
151026
+ observations = await store.loadAll();
151027
+ nextId = await store.loadIdCounter();
151028
+ if (upsertedInsideLock) {
151029
+ observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
151030
+ }
151031
+ } else {
151032
+ nextId = await store.loadIdCounter();
150558
151033
  }
150559
151034
  } else {
150560
- observations.push(observation);
151035
+ if (corpusLoaded) observations.push(observation);
150561
151036
  nextId = observation.id + 1;
150562
151037
  }
150563
151038
  if (upsertedInsideLock) return;
@@ -150625,7 +151100,9 @@ async function storeObservation(input) {
150625
151100
  createdByAgentId: input.createdByAgentId ?? "",
150626
151101
  sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
150627
151102
  };
150628
- await insertObservation(doc);
151103
+ if (corpusLoaded) {
151104
+ await insertObservation(doc);
151105
+ }
150629
151106
  };
150630
151107
  await assignAndPersist();
150631
151108
  if (upsertedInsideLock) {
@@ -150987,9 +151464,11 @@ async function reindexObservations() {
150987
151464
  searchIndexPrepared = true;
150988
151465
  return count2;
150989
151466
  }
150990
- async function prepareSearchIndex() {
151467
+ async function prepareSearchIndex(options = {}) {
150991
151468
  if (searchIndexPrepared) return 0;
150992
- const count2 = await hydrateIndexForStartup(observations);
151469
+ const count2 = await hydrateIndexForStartup(observations, {
151470
+ skipCachedVectors: options.skipCachedVectors
151471
+ });
150993
151472
  if (count2 === 0) {
150994
151473
  searchIndexPrepared = true;
150995
151474
  return 0;
@@ -151181,7 +151660,7 @@ async function backfillVectorEmbeddings(options = {}) {
151181
151660
  ...lastFailure ? { lastError: lastFailure } : {}
151182
151661
  };
151183
151662
  }
151184
- var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
151663
+ var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
151185
151664
  var init_observations = __esm({
151186
151665
  "src/memory/observations.ts"() {
151187
151666
  "use strict";
@@ -151200,6 +151679,8 @@ var init_observations = __esm({
151200
151679
  projectDir = null;
151201
151680
  searchIndexPrepared = false;
151202
151681
  embeddingWriteMode = "background";
151682
+ corpusLoaded = false;
151683
+ loadedObservationStore = null;
151203
151684
  vectorMissingIds = /* @__PURE__ */ new Set();
151204
151685
  vectorBackfillRunning = false;
151205
151686
  vectorSchemaUpgradePromise = null;
@@ -153788,8 +154269,8 @@ var init_syncer = __esm({
153788
154269
  const dir = path25.dirname(pattern);
153789
154270
  const fileGlob = path25.basename(pattern);
153790
154271
  try {
153791
- const stat4 = await fs17.stat(dir);
153792
- if (!stat4.isDirectory()) {
154272
+ const stat5 = await fs17.stat(dir);
154273
+ if (!stat5.isDirectory()) {
153793
154274
  try {
153794
154275
  await fs17.access(pattern);
153795
154276
  return [pattern];
@@ -159380,9 +159861,9 @@ function existingLensCandidates(rootPath, lens) {
159380
159861
  const absolute = path28.join(rootPath, candidate);
159381
159862
  try {
159382
159863
  if (!existsSync16(absolute)) continue;
159383
- const stat4 = statSync4(absolute);
159384
- if (stat4.isFile()) out.push(candidate);
159385
- if (stat4.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
159864
+ const stat5 = statSync4(absolute);
159865
+ if (stat5.isFile()) out.push(candidate);
159866
+ if (stat5.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
159386
159867
  } catch {
159387
159868
  }
159388
159869
  }
@@ -165202,13 +165683,13 @@ async function installHooks(agent, projectRoot, global2 = false) {
165202
165683
  const agentPrev = prevFiles.filter((e2) => e2.agent === agent);
165203
165684
  for (const entry of agentPrev) {
165204
165685
  try {
165205
- const { access: access2, unlink: unlink2 } = await import("fs/promises");
165206
- await access2(entry.path);
165686
+ const { access: access3, unlink: unlink3 } = await import("fs/promises");
165687
+ await access3(entry.path);
165207
165688
  const basename5 = path32.basename(entry.path);
165208
165689
  if (basename5 === "AGENTS.md" || basename5 === "GEMINI.md" || basename5 === "CONTEXT.md") {
165209
165690
  continue;
165210
165691
  }
165211
- await unlink2(entry.path);
165692
+ await unlink3(entry.path);
165212
165693
  await removeFile2(projectRoot, entry.path);
165213
165694
  } catch {
165214
165695
  }
@@ -165728,11 +166209,11 @@ import path33 from "path";
165728
166209
  function resolveGitDir(projectRoot) {
165729
166210
  const dotGit = path33.join(projectRoot, ".git");
165730
166211
  if (!existsSync19(dotGit)) return null;
165731
- const stat4 = statSync5(dotGit);
165732
- if (stat4.isDirectory()) {
166212
+ const stat5 = statSync5(dotGit);
166213
+ if (stat5.isDirectory()) {
165733
166214
  return dotGit;
165734
166215
  }
165735
- if (stat4.isFile()) {
166216
+ if (stat5.isFile()) {
165736
166217
  try {
165737
166218
  const content = readFileSync13(dotGit, "utf-8").trim();
165738
166219
  const match2 = content.match(/^gitdir:\s*(.+)$/);
@@ -166143,7 +166624,7 @@ The path should point to a directory containing a .git folder.`
166143
166624
  };
166144
166625
  const server = existingServer ?? new McpServer({
166145
166626
  name: "memorix",
166146
- version: true ? "1.6.1" : "1.0.1"
166627
+ version: true ? "1.6.2" : "1.0.1"
166147
166628
  });
166148
166629
  const originalRegisterTool = server.registerTool.bind(server);
166149
166630
  server.registerTool = ((name, ...args) => {
@@ -170549,7 +171030,7 @@ var init_serve_shared = __esm({
170549
171030
 
170550
171031
  // src/cli/version.ts
170551
171032
  function getCliVersion() {
170552
- return true ? "1.6.1" : pkg.version;
171033
+ return true ? "1.6.2" : pkg.version;
170553
171034
  }
170554
171035
  var init_version3 = __esm({
170555
171036
  "src/cli/version.ts"() {