memorix 1.2.3 → 1.2.5

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 (52) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -3
  3. package/README.zh-CN.md +3 -3
  4. package/dist/cli/index.js +5273 -4730
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/index.js +506 -99
  7. package/dist/index.js.map +1 -1
  8. package/dist/maintenance-runner.js +176 -53
  9. package/dist/maintenance-runner.js.map +1 -1
  10. package/dist/memcode-runtime/CHANGELOG.md +33 -0
  11. package/dist/sdk.d.ts +4 -2
  12. package/dist/sdk.js +507 -99
  13. package/dist/sdk.js.map +1 -1
  14. package/dist/types.d.ts +8 -1
  15. package/dist/types.js.map +1 -1
  16. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +86 -0
  17. package/docs/1.2.5-RETRIEVAL-PERFORMANCE.md +85 -0
  18. package/docs/AGENT_OPERATOR_PLAYBOOK.md +13 -1
  19. package/docs/API_REFERENCE.md +13 -3
  20. package/docs/PERFORMANCE.md +22 -1
  21. package/docs/dev-log/progress.txt +73 -7
  22. package/package.json +2 -1
  23. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  24. package/src/cli/capability-map.ts +1 -1
  25. package/src/cli/command-guide.ts +4 -1
  26. package/src/cli/commands/agent-integrations.ts +5 -1
  27. package/src/cli/commands/codegraph.ts +1 -1
  28. package/src/cli/commands/context.ts +9 -1
  29. package/src/cli/commands/memory.ts +12 -3
  30. package/src/cli/commands/operator-shared.ts +74 -2
  31. package/src/cli/commands/resume.ts +31 -0
  32. package/src/cli/index.ts +5 -1
  33. package/src/codegraph/auto-context.ts +54 -1
  34. package/src/codegraph/task-lens.ts +29 -0
  35. package/src/compact/token-budget.ts +16 -1
  36. package/src/config/resolved-config.ts +29 -4
  37. package/src/config/toml-loader.ts +9 -5
  38. package/src/hooks/handler.ts +127 -66
  39. package/src/hooks/installers/index.ts +5 -4
  40. package/src/hooks/official-skills.ts +6 -4
  41. package/src/hooks/rules/memorix-agent-rules.md +9 -7
  42. package/src/knowledge/context-assembly.ts +4 -1
  43. package/src/knowledge/workset.ts +89 -1
  44. package/src/memory/session.ts +144 -10
  45. package/src/runtime/project-maintenance.ts +1 -14
  46. package/src/sdk.ts +4 -0
  47. package/src/server.ts +144 -25
  48. package/src/store/bun-sqlite-compat.ts +118 -15
  49. package/src/store/orama-store.ts +39 -18
  50. package/src/store/sqlite-db.ts +3 -3
  51. package/src/timeout.ts +23 -0
  52. package/src/types.ts +8 -0
@@ -30,36 +30,111 @@ var init_esm_shims = __esm({
30
30
 
31
31
  // src/store/bun-sqlite-compat.ts
32
32
  import { createRequire } from "module";
33
+ function loadNodeSqlite() {
34
+ const nodeSqlite = requireFromHere("node:sqlite");
35
+ return nodeSqlite.DatabaseSync;
36
+ }
37
+ function isNativeBindingFailure(error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ return /could not locate the bindings file|better_sqlite3\.node|module did not self-register/i.test(message);
40
+ }
41
+ function addCompatibility(db2) {
42
+ if (!db2.pragma) {
43
+ db2.pragma = function(pragma, options) {
44
+ const statement = db2.prepare(`PRAGMA ${pragma}`);
45
+ if (options?.simple) {
46
+ const result = statement.get();
47
+ return result ? Object.values(result)[0] : void 0;
48
+ }
49
+ return statement.all();
50
+ };
51
+ }
52
+ if (!db2.transaction) {
53
+ let depth = 0;
54
+ let sequence = 0;
55
+ db2.transaction = function(fn) {
56
+ return (...args) => {
57
+ const outermost = depth === 0;
58
+ const savepoint = `memorix_tx_${++sequence}`;
59
+ if (outermost) {
60
+ db2.exec("BEGIN IMMEDIATE");
61
+ } else {
62
+ db2.exec(`SAVEPOINT ${savepoint}`);
63
+ }
64
+ depth += 1;
65
+ try {
66
+ const result = fn(...args);
67
+ if (result && typeof result.then === "function") {
68
+ throw new Error("[memorix] SQLite transactions must be synchronous");
69
+ }
70
+ depth -= 1;
71
+ if (outermost) {
72
+ db2.exec("COMMIT");
73
+ } else {
74
+ db2.exec(`RELEASE SAVEPOINT ${savepoint}`);
75
+ }
76
+ return result;
77
+ } catch (error) {
78
+ depth -= 1;
79
+ try {
80
+ if (outermost) {
81
+ db2.exec("ROLLBACK");
82
+ } else {
83
+ db2.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
84
+ db2.exec(`RELEASE SAVEPOINT ${savepoint}`);
85
+ }
86
+ } catch {
87
+ }
88
+ throw error;
89
+ }
90
+ };
91
+ };
92
+ }
93
+ return db2;
94
+ }
95
+ function instantiateDatabase(Sqlite, filePath, options) {
96
+ return driver === "node:sqlite" && options === void 0 ? new Sqlite(filePath) : new Sqlite(filePath, options);
97
+ }
33
98
  function loadSqlite() {
34
99
  if (Database) return Database;
100
+ if (process.env.MEMORIX_SQLITE_DRIVER === "node") {
101
+ Database = loadNodeSqlite();
102
+ driver = "node:sqlite";
103
+ return Database;
104
+ }
35
105
  try {
36
106
  Database = requireFromHere("better-sqlite3");
107
+ driver = "better-sqlite3";
108
+ return Database;
109
+ } catch {
110
+ }
111
+ try {
112
+ Database = loadNodeSqlite();
113
+ driver = "node:sqlite";
37
114
  return Database;
38
115
  } catch {
39
116
  }
40
117
  try {
41
118
  const bunSqlite = requireFromHere("bun:sqlite");
42
119
  Database = bunSqlite.Database;
120
+ driver = "bun:sqlite";
43
121
  return Database;
44
122
  } catch {
45
- throw new Error("[memorix] Neither better-sqlite3 nor bun:sqlite is available");
123
+ throw new Error("[memorix] SQLite is unavailable (better-sqlite3, node:sqlite, and bun:sqlite failed)");
46
124
  }
47
125
  }
48
126
  function createDatabase(path13, options) {
49
127
  const Sqlite = loadSqlite();
50
- const db2 = new Sqlite(path13, options);
51
- if (!db2.pragma) {
52
- db2.pragma = function(pragma, options2) {
53
- if (options2 && options2.simple) {
54
- const result = db2.prepare(`PRAGMA ${pragma}`).get();
55
- return result ? Object.values(result)[0] : void 0;
56
- }
57
- return db2.prepare(`PRAGMA ${pragma}`).all();
58
- };
128
+ try {
129
+ return addCompatibility(instantiateDatabase(Sqlite, path13, options));
130
+ } catch (error) {
131
+ if (driver !== "better-sqlite3" || !isNativeBindingFailure(error)) throw error;
132
+ Database = loadNodeSqlite();
133
+ driver = "node:sqlite";
134
+ return addCompatibility(instantiateDatabase(Database, path13, options));
59
135
  }
60
- return db2;
61
136
  }
62
- var Database, requireFromHere;
137
+ var Database, driver, requireFromHere;
63
138
  var init_bun_sqlite_compat = __esm({
64
139
  "src/store/bun-sqlite-compat.ts"() {
65
140
  "use strict";
@@ -78,7 +153,7 @@ function loadBetterSqlite3() {
78
153
  BetterSqlite3 = loadSqlite();
79
154
  return BetterSqlite3;
80
155
  } catch {
81
- throw new Error("[memorix] SQLite is not available (neither better-sqlite3 nor bun:sqlite)");
156
+ throw new Error("[memorix] SQLite is not available (better-sqlite3, node:sqlite, and bun:sqlite all failed)");
82
157
  }
83
158
  }
84
159
  function hasColumn(db2, table, column) {
@@ -1163,6 +1238,9 @@ function parseTomlValue(raw, filePath, line) {
1163
1238
  if (raw.startsWith('"') && raw.endsWith('"')) {
1164
1239
  return raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1165
1240
  }
1241
+ if (raw.startsWith("'") && raw.endsWith("'")) {
1242
+ return raw.slice(1, -1);
1243
+ }
1166
1244
  if (raw === "true") return true;
1167
1245
  if (raw === "false") return false;
1168
1246
  if (/^-?\d+$/.test(raw)) return Number.parseInt(raw, 10);
@@ -1175,7 +1253,7 @@ function parseTomlValue(raw, filePath, line) {
1175
1253
  throw new Error(`Unsupported TOML value in ${filePath}:${line}`);
1176
1254
  }
1177
1255
  function stripComment(line) {
1178
- let inString = false;
1256
+ let quote = null;
1179
1257
  let escaped = false;
1180
1258
  for (let i = 0; i < line.length; i++) {
1181
1259
  const char = line[i];
@@ -1183,15 +1261,16 @@ function stripComment(line) {
1183
1261
  escaped = false;
1184
1262
  continue;
1185
1263
  }
1186
- if (char === "\\" && inString) {
1264
+ if (char === "\\" && quote === '"') {
1187
1265
  escaped = true;
1188
1266
  continue;
1189
1267
  }
1190
- if (char === '"') {
1191
- inString = !inString;
1268
+ if (char === '"' || char === "'") {
1269
+ if (quote === char) quote = null;
1270
+ else if (quote === null) quote = char;
1192
1271
  continue;
1193
1272
  }
1194
- if (char === "#" && !inString) {
1273
+ if (char === "#" && quote === null) {
1195
1274
  return line.slice(0, i);
1196
1275
  }
1197
1276
  }
@@ -1228,6 +1307,25 @@ function getResolvedConfig(options = {}) {
1228
1307
  const legacy = loadFileConfig();
1229
1308
  const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
1230
1309
  const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
1310
+ const memoryLlmProvider = first(
1311
+ process.env.MEMORIX_LLM_PROVIDER,
1312
+ toml.memory?.llm?.provider,
1313
+ yaml.llm?.provider,
1314
+ legacy.llm?.provider
1315
+ );
1316
+ const memoryLlmModel = first(
1317
+ process.env.MEMORIX_LLM_MODEL,
1318
+ toml.memory?.llm?.model,
1319
+ yaml.llm?.model,
1320
+ legacy.llm?.model
1321
+ );
1322
+ const memoryLlmBaseUrl = first(
1323
+ process.env.MEMORIX_LLM_BASE_URL,
1324
+ toml.memory?.llm?.base_url,
1325
+ yaml.llm?.baseUrl,
1326
+ legacy.llm?.baseUrl
1327
+ );
1328
+ const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
1231
1329
  const resolved = {
1232
1330
  agent: {
1233
1331
  provider: first(
@@ -1265,9 +1363,9 @@ function getResolvedConfig(options = {}) {
1265
1363
  autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml.behavior?.autoCleanup),
1266
1364
  syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml.behavior?.syncAdvisory),
1267
1365
  llm: {
1268
- provider: first(process.env.MEMORIX_LLM_PROVIDER, toml.memory?.llm?.provider, yaml.llm?.provider, legacy.llm?.provider),
1269
- model: first(process.env.MEMORIX_LLM_MODEL, toml.memory?.llm?.model, yaml.llm?.model, legacy.llm?.model),
1270
- baseUrl: first(process.env.MEMORIX_LLM_BASE_URL, toml.memory?.llm?.base_url, yaml.llm?.baseUrl, legacy.llm?.baseUrl),
1366
+ provider: memoryLlmProvider,
1367
+ model: memoryLlmModel,
1368
+ baseUrl: memoryLlmBaseUrl,
1271
1369
  apiKey: first(
1272
1370
  process.env.MEMORIX_LLM_API_KEY,
1273
1371
  process.env.MEMORIX_API_KEY,
@@ -1276,7 +1374,7 @@ function getResolvedConfig(options = {}) {
1276
1374
  legacy.llm?.apiKey,
1277
1375
  process.env.OPENAI_API_KEY,
1278
1376
  process.env.ANTHROPIC_API_KEY,
1279
- process.env.OPENROUTER_API_KEY
1377
+ openRouterMemoryLlmApiKey
1280
1378
  )
1281
1379
  }
1282
1380
  },
@@ -1413,6 +1511,9 @@ function isOpenRouterUrl(value) {
1413
1511
  return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
1414
1512
  }
1415
1513
  }
1514
+ function isOpenRouterMemoryLane(provider2, baseUrl) {
1515
+ return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
1516
+ }
1416
1517
  function normalizeExternalContext(value) {
1417
1518
  const normalized = value?.trim().toLowerCase();
1418
1519
  if (normalized === "auto" || normalized === "off") return normalized;
@@ -4180,6 +4281,29 @@ Rules:
4180
4281
  }
4181
4282
  });
4182
4283
 
4284
+ // src/timeout.ts
4285
+ function withTimeout(promise, ms, label) {
4286
+ return new Promise((resolve, reject) => {
4287
+ const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
4288
+ promise.then(
4289
+ (value) => {
4290
+ clearTimeout(timer);
4291
+ resolve(value);
4292
+ },
4293
+ (error) => {
4294
+ clearTimeout(timer);
4295
+ reject(error);
4296
+ }
4297
+ );
4298
+ });
4299
+ }
4300
+ var init_timeout = __esm({
4301
+ "src/timeout.ts"() {
4302
+ "use strict";
4303
+ init_esm_shims();
4304
+ }
4305
+ });
4306
+
4183
4307
  // src/project/aliases.ts
4184
4308
  var aliases_exports = {};
4185
4309
  __export(aliases_exports, {
@@ -4945,8 +5069,12 @@ async function searchObservations(options) {
4945
5069
  }
4946
5070
  };
4947
5071
  const modeKey = options.projectId ?? SEARCH_MODE_DEFAULT_KEY;
4948
- lastSearchModeByProject.set(modeKey, embeddingEnabled ? "hybrid" : "fulltext");
5072
+ const quality = options.quality ?? "balanced";
4949
5073
  const database = await getDb();
5074
+ lastSearchModeByProject.set(
5075
+ modeKey,
5076
+ quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
5077
+ );
4950
5078
  let projectIds = null;
4951
5079
  if (options.projectId) {
4952
5080
  try {
@@ -4968,11 +5096,15 @@ async function searchObservations(options) {
4968
5096
  }
4969
5097
  const hasQuery = options.query && options.query.trim().length > 0;
4970
5098
  const originalQuery = options.query;
4971
- const tier = hasQuery ? classifyQueryTier(originalQuery) : "fast";
5099
+ const tier = quality === "fast" ? "fast" : hasQuery ? classifyQueryTier(originalQuery) : "fast";
4972
5100
  mark(`tier=${tier}`);
4973
- const expandedEmbeddingQuery = tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
5101
+ if (quality === "thorough" && hasQuery) {
5102
+ const { initLLM: initLLM2, isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
5103
+ if (!isLLMEnabled2()) initLLM2();
5104
+ }
5105
+ const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
4974
5106
  mark("queryExpansion");
4975
- const intentResult = hasQuery ? detectQueryIntent(originalQuery) : null;
5107
+ const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
4976
5108
  const requestLimit = projectIds ? (options.limit ?? 20) * 3 : options.limit ?? 20;
4977
5109
  const defaultBoost = {
4978
5110
  title: 3,
@@ -4986,17 +5118,21 @@ async function searchObservations(options) {
4986
5118
  let searchParams = {
4987
5119
  term: originalQuery,
4988
5120
  limit: requestLimit,
4989
- includeVectors: true,
5121
+ // Fast search never uses vectors, so returning them only adds work and
5122
+ // payload pressure to an explicitly latency-sensitive path.
5123
+ includeVectors: quality !== "fast",
4990
5124
  ...Object.keys(filters).length > 0 ? { where: filters } : {},
4991
5125
  // Search specific fields (not tokens, accessCount, etc.)
4992
5126
  properties: ["title", "entityName", "narrative", "facts", "concepts", "filesModified"],
4993
5127
  // Field boosting: intent-aware or default
4994
5128
  boost: fieldBoost,
4995
5129
  // Fuzzy tolerance: allow 1-char typos for short queries, 2 for longer
4996
- ...hasQuery ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
5130
+ // Fuzzy matching is useful for ordinary recall, but grows sharply with
5131
+ // corpus size. The fast profile deliberately uses exact lexical matching.
5132
+ ...hasQuery && quality !== "fast" ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
4997
5133
  };
4998
5134
  let queryVector = null;
4999
- if (embeddingEnabled && hasQuery && tier !== "fast") {
5135
+ if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
5000
5136
  try {
5001
5137
  const provider2 = await getEmbeddingProvider();
5002
5138
  if (provider2) {
@@ -5011,11 +5147,11 @@ async function searchObservations(options) {
5011
5147
  );
5012
5148
  } else {
5013
5149
  const EMBEDDING_TIMEOUT_MS = 15e3;
5014
- const embedPromise = provider2.embed(expandedEmbeddingQuery);
5015
- const timeoutPromise = new Promise(
5016
- (_, reject) => setTimeout(() => reject(new Error(`Embedding timeout after ${EMBEDDING_TIMEOUT_MS}ms`)), EMBEDDING_TIMEOUT_MS)
5150
+ queryVector = await withTimeout(
5151
+ provider2.embed(expandedEmbeddingQuery),
5152
+ EMBEDDING_TIMEOUT_MS,
5153
+ "Embedding"
5017
5154
  );
5018
- queryVector = await Promise.race([embedPromise, timeoutPromise]);
5019
5155
  mark("embedding");
5020
5156
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
5021
5157
  const isCJKHeavy = cjkRatio > 0.3;
@@ -5256,7 +5392,7 @@ async function searchObservations(options) {
5256
5392
  }
5257
5393
  }
5258
5394
  }
5259
- const shouldRerank = tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
5395
+ const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
5260
5396
  const top = intermediate[0]?.score ?? 0;
5261
5397
  const second = intermediate[1]?.score ?? 0;
5262
5398
  return top > 0 && second / top > 0.7;
@@ -5280,11 +5416,11 @@ async function searchObservations(options) {
5280
5416
  }));
5281
5417
  const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
5282
5418
  const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
5283
- const rerankPromise = rerankResults2(originalQuery, candidates);
5284
- const timeoutPromise = new Promise(
5285
- (_, reject) => setTimeout(() => reject(new Error(`LLM rerank timeout after ${RERANK_TIMEOUT_MS}ms`)), RERANK_TIMEOUT_MS)
5419
+ const { reranked, usedLLM } = await withTimeout(
5420
+ rerankResults2(originalQuery, candidates),
5421
+ RERANK_TIMEOUT_MS,
5422
+ "LLM rerank"
5286
5423
  );
5287
- const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
5288
5424
  mark(`rerank(usedLLM=${usedLLM})`);
5289
5425
  if (usedLLM) {
5290
5426
  lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
@@ -5476,6 +5612,7 @@ var init_orama_store = __esm({
5476
5612
  init_project_affinity();
5477
5613
  init_intent_detector();
5478
5614
  init_query_expansion();
5615
+ init_timeout();
5479
5616
  db = null;
5480
5617
  dbInitPromise = null;
5481
5618
  dbGeneration = 0;
@@ -11720,6 +11857,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
11720
11857
  // src/runtime/project-maintenance.ts
11721
11858
  init_esm_shims();
11722
11859
  init_lifecycle();
11860
+ init_timeout();
11723
11861
  var DEFAULT_VECTOR_BATCH_SIZE = 12;
11724
11862
  var DEFAULT_RETENTION_BATCH_SIZE = 100;
11725
11863
  var DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
@@ -11790,21 +11928,6 @@ async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
11790
11928
  ]);
11791
11929
  return versioned ?? local;
11792
11930
  }
11793
- function withTimeout(promise, ms, label) {
11794
- return new Promise((resolve, reject) => {
11795
- const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
11796
- promise.then(
11797
- (value) => {
11798
- clearTimeout(timer);
11799
- resolve(value);
11800
- },
11801
- (error) => {
11802
- clearTimeout(timer);
11803
- reject(error);
11804
- }
11805
- );
11806
- });
11807
- }
11808
11931
  async function runAutomaticConsolidation(projectId, projectDir2, options) {
11809
11932
  const { isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
11810
11933
  if (!isLLMEnabled2()) {