memorix 1.9.2 → 1.9.3

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.
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.9.2" : pkg.version;
3235
+ return true ? "1.9.3" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -9515,70 +9515,79 @@ function getDatabase(dataDir) {
9515
9515
  loadBetterSqlite3();
9516
9516
  fs.mkdirSync(dataDir, { recursive: true });
9517
9517
  const dbPath = path3.join(dataDir, "memorix.db");
9518
- const db2 = createDatabase(dbPath);
9519
- db2.pragma("journal_mode = WAL");
9520
- db2.pragma("busy_timeout = 5000");
9521
- db2.pragma("foreign_keys = ON");
9522
- db2.exec(CREATE_OBSERVATIONS_TABLE);
9523
- db2.exec(CREATE_MINI_SKILLS_TABLE);
9524
- db2.exec(CREATE_SESSIONS_TABLE);
9525
- db2.exec(CREATE_META_TABLE);
9526
- db2.exec(CREATE_TEAM_AGENTS_TABLE);
9527
- db2.exec(CREATE_TEAM_TASKS_TABLE);
9528
- db2.exec(CREATE_TEAM_TASK_DEPS_TABLE);
9529
- db2.exec(CREATE_TEAM_MESSAGES_TABLE);
9530
- db2.exec(CREATE_TEAM_LOCKS_TABLE);
9531
- db2.exec(CREATE_TEAM_ROLES_TABLE);
9532
- db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
9533
- db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
9534
- db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
9535
- db2.exec(CREATE_CODE_FILES_TABLE);
9536
- db2.exec(CREATE_CODE_SYMBOLS_TABLE);
9537
- db2.exec(CREATE_CODE_EDGES_TABLE);
9538
- db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
9539
- db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
9540
- db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
9541
- db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
9518
+ let db2;
9542
9519
  try {
9543
- db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
9544
- } catch {
9545
- }
9546
- try {
9547
- db2.exec(`ALTER TABLE mini_skills ADD COLUMN updatedAt TEXT`);
9548
- } catch {
9549
- }
9550
- try {
9551
- db2.exec(`ALTER TABLE observations ADD COLUMN createdByAgentId TEXT`);
9552
- } catch {
9553
- }
9554
- try {
9555
- db2.exec(`ALTER TABLE observations ADD COLUMN writeGeneration INTEGER DEFAULT 0`);
9556
- } catch {
9557
- }
9558
- try {
9559
- db2.exec(`ALTER TABLE team_tasks ADD COLUMN required_role TEXT`);
9560
- } catch {
9561
- }
9562
- try {
9563
- db2.exec(`ALTER TABLE team_tasks ADD COLUMN preferred_role TEXT`);
9564
- } catch {
9565
- }
9566
- try {
9567
- db2.exec(`ALTER TABLE team_messages ADD COLUMN to_role TEXT`);
9568
- } catch {
9569
- }
9570
- try {
9571
- db2.exec(`ALTER TABLE team_messages ADD COLUMN handoff_status TEXT`);
9572
- } catch {
9520
+ db2 = createDatabase(dbPath);
9521
+ db2.pragma("busy_timeout = 5000");
9522
+ db2.pragma("journal_mode = WAL");
9523
+ db2.pragma("foreign_keys = ON");
9524
+ db2.exec(CREATE_OBSERVATIONS_TABLE);
9525
+ db2.exec(CREATE_MINI_SKILLS_TABLE);
9526
+ db2.exec(CREATE_SESSIONS_TABLE);
9527
+ db2.exec(CREATE_META_TABLE);
9528
+ db2.exec(CREATE_TEAM_AGENTS_TABLE);
9529
+ db2.exec(CREATE_TEAM_TASKS_TABLE);
9530
+ db2.exec(CREATE_TEAM_TASK_DEPS_TABLE);
9531
+ db2.exec(CREATE_TEAM_MESSAGES_TABLE);
9532
+ db2.exec(CREATE_TEAM_LOCKS_TABLE);
9533
+ db2.exec(CREATE_TEAM_ROLES_TABLE);
9534
+ db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
9535
+ db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
9536
+ db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
9537
+ db2.exec(CREATE_CODE_FILES_TABLE);
9538
+ db2.exec(CREATE_CODE_SYMBOLS_TABLE);
9539
+ db2.exec(CREATE_CODE_EDGES_TABLE);
9540
+ db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
9541
+ db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
9542
+ db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
9543
+ db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
9544
+ try {
9545
+ db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
9546
+ } catch {
9547
+ }
9548
+ try {
9549
+ db2.exec(`ALTER TABLE mini_skills ADD COLUMN updatedAt TEXT`);
9550
+ } catch {
9551
+ }
9552
+ try {
9553
+ db2.exec(`ALTER TABLE observations ADD COLUMN createdByAgentId TEXT`);
9554
+ } catch {
9555
+ }
9556
+ try {
9557
+ db2.exec(`ALTER TABLE observations ADD COLUMN writeGeneration INTEGER DEFAULT 0`);
9558
+ } catch {
9559
+ }
9560
+ try {
9561
+ db2.exec(`ALTER TABLE team_tasks ADD COLUMN required_role TEXT`);
9562
+ } catch {
9563
+ }
9564
+ try {
9565
+ db2.exec(`ALTER TABLE team_tasks ADD COLUMN preferred_role TEXT`);
9566
+ } catch {
9567
+ }
9568
+ try {
9569
+ db2.exec(`ALTER TABLE team_messages ADD COLUMN to_role TEXT`);
9570
+ } catch {
9571
+ }
9572
+ try {
9573
+ db2.exec(`ALTER TABLE team_messages ADD COLUMN handoff_status TEXT`);
9574
+ } catch {
9575
+ }
9576
+ applySchemaMigrations(db2);
9577
+ db2.exec(CREATE_INDEXES);
9578
+ initializeObservationLexicalIndex(db2);
9579
+ db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
9580
+ db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
9581
+ db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('mini_skills_generation', '0')`).run();
9582
+ _dbCache.set(normalized, db2);
9583
+ return db2;
9584
+ } catch (error40) {
9585
+ try {
9586
+ db2?.close();
9587
+ } catch {
9588
+ }
9589
+ throw error40;
9573
9590
  }
9574
- applySchemaMigrations(db2);
9575
- db2.exec(CREATE_INDEXES);
9576
- initializeObservationLexicalIndex(db2);
9577
- db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
9578
- db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
9579
- db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('mini_skills_generation', '0')`).run();
9580
- _dbCache.set(normalized, db2);
9581
- return db2;
9582
9591
  }
9583
9592
  function closeAllDatabases() {
9584
9593
  for (const [key, db2] of _dbCache) {
@@ -10568,6 +10577,74 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
10568
10577
  }
10569
10578
  });
10570
10579
 
10580
+ // src/store/sqlite-reliability.ts
10581
+ function isTransientSqliteError(error40) {
10582
+ const code = error40?.code;
10583
+ if (typeof code === "string") {
10584
+ return /^SQLITE_BUSY(?:_|$)/i.test(code);
10585
+ }
10586
+ return error40 instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error40.message);
10587
+ }
10588
+ function isSqliteUnavailableError(error40) {
10589
+ return error40 instanceof SqliteUnavailableError || error40 instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error40.message);
10590
+ }
10591
+ function degradedReadError(storeName) {
10592
+ return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
10593
+ }
10594
+ function degradedWriteError(storeName) {
10595
+ return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
10596
+ }
10597
+ function formatSqliteFailure(error40) {
10598
+ return error40 instanceof Error && error40.message ? error40.message : String(error40);
10599
+ }
10600
+ function delay(ms2) {
10601
+ return new Promise((resolve9) => setTimeout(resolve9, ms2));
10602
+ }
10603
+ async function retrySqliteBusy(operation, label) {
10604
+ for (let attempt = 0; ; attempt++) {
10605
+ try {
10606
+ return await operation();
10607
+ } catch (error40) {
10608
+ if (!isTransientSqliteError(error40) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error40;
10609
+ const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
10610
+ console.error(
10611
+ `[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
10612
+ );
10613
+ await delay(waitMs);
10614
+ }
10615
+ }
10616
+ }
10617
+ async function initializeSqliteStore(factory, dataDir, label) {
10618
+ try {
10619
+ loadBetterSqlite3();
10620
+ } catch (error40) {
10621
+ throw new SqliteUnavailableError(error40);
10622
+ }
10623
+ return retrySqliteBusy(async () => {
10624
+ const store2 = factory();
10625
+ await store2.init(dataDir);
10626
+ return store2;
10627
+ }, label);
10628
+ }
10629
+ var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
10630
+ var init_sqlite_reliability = __esm({
10631
+ "src/store/sqlite-reliability.ts"() {
10632
+ "use strict";
10633
+ init_esm_shims();
10634
+ init_sqlite_db();
10635
+ SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
10636
+ SqliteUnavailableError = class extends Error {
10637
+ code = "MEMORIX_SQLITE_UNAVAILABLE";
10638
+ constructor(cause) {
10639
+ super(
10640
+ cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
10641
+ );
10642
+ this.name = "SqliteUnavailableError";
10643
+ }
10644
+ };
10645
+ }
10646
+ });
10647
+
10571
10648
  // src/store/file-lock.ts
10572
10649
  import { promises as fs2 } from "fs";
10573
10650
  import { randomUUID } from "crypto";
@@ -11065,16 +11142,18 @@ async function initMiniSkillStore(dataDir) {
11065
11142
  if (_store && _storeDataDir === dataDir) return _store;
11066
11143
  _store = null;
11067
11144
  _storeDataDir = null;
11145
+ let degradedReason = "SQLite runtime unavailable";
11068
11146
  try {
11069
- const store3 = new MiniSkillSqliteStore();
11070
- await store3.init(dataDir);
11147
+ const store3 = await initializeSqliteStore(() => new MiniSkillSqliteStore(), dataDir, "Mini-skill store");
11071
11148
  _store = store3;
11072
11149
  _storeDataDir = dataDir;
11073
11150
  return store3;
11074
11151
  } catch (err) {
11075
- console.error(`[memorix] MiniSkillSqliteStore unavailable, running in degraded read-only mode: ${err instanceof Error ? err.message : err}`);
11152
+ if (!isSqliteUnavailableError(err)) throw err;
11153
+ degradedReason = err instanceof Error ? err.message : String(err);
11154
+ console.error(`[memorix] MiniSkillSqliteStore unavailable, running in degraded read-only mode: ${degradedReason}`);
11076
11155
  }
11077
- const store2 = new MiniSkillGracefulDegrade();
11156
+ const store2 = new MiniSkillGracefulDegrade(degradedReason);
11078
11157
  await store2.init(dataDir);
11079
11158
  _store = store2;
11080
11159
  _storeDataDir = dataDir;
@@ -11086,6 +11165,7 @@ var init_mini_skill_store = __esm({
11086
11165
  "use strict";
11087
11166
  init_esm_shims();
11088
11167
  init_sqlite_db();
11168
+ init_sqlite_reliability();
11089
11169
  MiniSkillSqliteStore = class {
11090
11170
  db = null;
11091
11171
  dataDir = "";
@@ -11194,7 +11274,7 @@ var init_mini_skill_store = __esm({
11194
11274
  * SQLite serializes write transactions, so concurrent calls are safely sequenced.
11195
11275
  */
11196
11276
  async atomicInsertWithId(skillWithoutId) {
11197
- const result = this.db.transaction(() => {
11277
+ return retrySqliteBusy(async () => this.db.transaction(() => {
11198
11278
  const row = this.stmtGetMeta.get("mini_skills_next_id");
11199
11279
  const nextId2 = row ? parseInt(row.value, 10) : 1;
11200
11280
  const skill2 = { ...skillWithoutId, id: nextId2 };
@@ -11203,8 +11283,7 @@ var init_mini_skill_store = __esm({
11203
11283
  this.stmtBumpGeneration.run();
11204
11284
  this.knownGeneration = this.readGeneration();
11205
11285
  return skill2;
11206
- })();
11207
- return result;
11286
+ })(), "Mini-skill promotion");
11208
11287
  }
11209
11288
  // ── Freshness ────────────────────────────────────────────────────
11210
11289
  async ensureFresh() {
@@ -11223,10 +11302,17 @@ var init_mini_skill_store = __esm({
11223
11302
  }
11224
11303
  };
11225
11304
  MiniSkillGracefulDegrade = class {
11305
+ constructor(reason = "SQLite runtime unavailable") {
11306
+ this.reason = reason;
11307
+ }
11308
+ reason;
11226
11309
  warned = false;
11310
+ unavailableRead() {
11311
+ return degradedReadError("mini-skills");
11312
+ }
11227
11313
  warn() {
11228
11314
  if (!this.warned) {
11229
- console.error("[memorix] MiniSkillStore: SQLite unavailable \u2014 mini-skills are disabled (read-only empty). Install better-sqlite3 for full functionality.");
11315
+ console.error("[memorix] MiniSkillStore: SQLite unavailable \u2014 mini-skills cannot be read or written. Install a supported SQLite runtime.");
11230
11316
  this.warned = true;
11231
11317
  }
11232
11318
  }
@@ -11234,28 +11320,33 @@ var init_mini_skill_store = __esm({
11234
11320
  this.warn();
11235
11321
  }
11236
11322
  async loadAll() {
11237
- return [];
11323
+ throw this.unavailableRead();
11238
11324
  }
11239
11325
  async loadByProject(_projectId) {
11240
- return [];
11326
+ throw this.unavailableRead();
11241
11327
  }
11242
11328
  async loadIdCounter() {
11243
- return 1;
11329
+ throw this.unavailableRead();
11244
11330
  }
11245
11331
  async insert(_skill) {
11246
11332
  this.warn();
11333
+ throw degradedWriteError("mini-skills");
11247
11334
  }
11248
11335
  async update(_skill) {
11249
11336
  this.warn();
11337
+ throw degradedWriteError("mini-skills");
11250
11338
  }
11251
11339
  async remove(_id) {
11252
11340
  this.warn();
11341
+ throw degradedWriteError("mini-skills");
11253
11342
  }
11254
11343
  async saveIdCounter(_nextId) {
11344
+ this.warn();
11345
+ throw degradedWriteError("mini-skills");
11255
11346
  }
11256
11347
  async atomicInsertWithId(skillWithoutId) {
11257
11348
  this.warn();
11258
- return { ...skillWithoutId, id: 0 };
11349
+ throw degradedWriteError("mini-skills");
11259
11350
  }
11260
11351
  async ensureFresh() {
11261
11352
  return false;
@@ -11266,6 +11357,9 @@ var init_mini_skill_store = __esm({
11266
11357
  getBackendName() {
11267
11358
  return "degraded";
11268
11359
  }
11360
+ getBackendReason() {
11361
+ return this.reason;
11362
+ }
11269
11363
  };
11270
11364
  _store = null;
11271
11365
  _storeDataDir = null;
@@ -23207,10 +23301,10 @@ async function fetchWithRetry(url2, apiKey, body, options = {}, attempt = 0, nat
23207
23301
  );
23208
23302
  }
23209
23303
  if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
23210
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
23304
+ const delay3 = BASE_DELAY_MS * Math.pow(2, attempt);
23211
23305
  const retryAfter = response.headers.get("retry-after");
23212
23306
  const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
23213
- const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
23307
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay3;
23214
23308
  console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
23215
23309
  await waitForRetry(waitMs, options.signal);
23216
23310
  return fetchWithRetry(url2, apiKey, body, options, attempt + 1, nativeGemini);
@@ -25242,6 +25336,7 @@ var init_sqlite_store = __esm({
25242
25336
  "use strict";
25243
25337
  init_esm_shims();
25244
25338
  init_sqlite_db();
25339
+ init_sqlite_reliability();
25245
25340
  init_sqlite_fts();
25246
25341
  SqliteBackend = class {
25247
25342
  db = null;
@@ -25519,7 +25614,9 @@ var init_sqlite_store = __esm({
25519
25614
  // ── Compound atomic operation ────────────────────────────────────
25520
25615
  async atomic(fn2) {
25521
25616
  const run = this._atomicQueue.catch(() => void 0).then(async () => {
25522
- this.db.prepare("BEGIN IMMEDIATE").run();
25617
+ await retrySqliteBusy(async () => {
25618
+ this.db.prepare("BEGIN IMMEDIATE").run();
25619
+ }, "Observation atomic write");
25523
25620
  try {
25524
25621
  const tx = {
25525
25622
  loadAll: async () => this.rawLoadAll(),
@@ -25618,15 +25715,15 @@ function resetObservationStore() {
25618
25715
  async function createObservationStore(dataDir) {
25619
25716
  try {
25620
25717
  const { SqliteBackend: SqliteBackend2 } = await Promise.resolve().then(() => (init_sqlite_store(), sqlite_store_exports));
25621
- const store3 = new SqliteBackend2();
25622
- await store3.init(dataDir);
25623
- return store3;
25718
+ return await initializeSqliteStore(() => new SqliteBackend2(), dataDir, "Observation store");
25624
25719
  } catch (err) {
25625
- console.error(`[memorix] SQLite backend unavailable \u2014 degraded mode (read-only): ${err instanceof Error ? err.message : err}`);
25720
+ if (!isSqliteUnavailableError(err)) throw err;
25721
+ const reason = formatSqliteFailure(err);
25722
+ console.error(`[memorix] SQLite module unavailable \u2014 degraded mode (reads/writes disabled): ${reason}`);
25723
+ const degraded = new DegradedBackend(reason);
25724
+ await degraded.init(dataDir);
25725
+ return degraded;
25626
25726
  }
25627
- const store2 = new DegradedBackend();
25628
- await store2.init(dataDir);
25629
- return store2;
25630
25727
  }
25631
25728
  async function initObservationStore(dataDir) {
25632
25729
  if (_store2 && _storeDataDir2 === dataDir) {
@@ -25650,36 +25747,44 @@ var init_obs_store = __esm({
25650
25747
  "src/store/obs-store.ts"() {
25651
25748
  "use strict";
25652
25749
  init_esm_shims();
25750
+ init_sqlite_reliability();
25653
25751
  _store2 = null;
25654
25752
  _storeDataDir2 = null;
25655
25753
  DegradedBackend = class {
25754
+ constructor(reason = "SQLite runtime unavailable") {
25755
+ this.reason = reason;
25756
+ }
25757
+ reason;
25656
25758
  dataDir = "";
25759
+ unavailableRead() {
25760
+ return degradedReadError("observations");
25761
+ }
25657
25762
  async init(dataDir) {
25658
25763
  this.dataDir = dataDir;
25659
25764
  }
25660
25765
  async loadAll() {
25661
- return [];
25766
+ throw this.unavailableRead();
25662
25767
  }
25663
25768
  async loadByProject(_projectId, _options) {
25664
- return [];
25769
+ throw this.unavailableRead();
25665
25770
  }
25666
25771
  async countByProject(_projectId, _options) {
25667
- return 0;
25772
+ throw this.unavailableRead();
25668
25773
  }
25669
25774
  async getById(_id) {
25670
- return void 0;
25775
+ throw this.unavailableRead();
25671
25776
  }
25672
25777
  async loadIdCounter() {
25673
- return 1;
25778
+ throw this.unavailableRead();
25674
25779
  }
25675
25780
  async countAll() {
25676
- return 0;
25781
+ throw this.unavailableRead();
25677
25782
  }
25678
25783
  async listProjectIds() {
25679
- return [];
25784
+ throw this.unavailableRead();
25680
25785
  }
25681
25786
  async searchLexical(_options) {
25682
- return [];
25787
+ throw this.unavailableRead();
25683
25788
  }
25684
25789
  hasLexicalIndex() {
25685
25790
  return false;
@@ -25688,25 +25793,25 @@ var init_obs_store = __esm({
25688
25793
  return false;
25689
25794
  }
25690
25795
  async insert(_obs) {
25691
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25796
+ throw degradedWriteError("observations");
25692
25797
  }
25693
25798
  async update(_obs) {
25694
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25799
+ throw degradedWriteError("observations");
25695
25800
  }
25696
25801
  async remove(_id) {
25697
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25802
+ throw degradedWriteError("observations");
25698
25803
  }
25699
25804
  async bulkReplace(_obs) {
25700
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25805
+ throw degradedWriteError("observations");
25701
25806
  }
25702
25807
  async bulkRemoveByIds(_ids) {
25703
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25808
+ throw degradedWriteError("observations");
25704
25809
  }
25705
25810
  async saveIdCounter(_nextId) {
25706
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25811
+ throw degradedWriteError("observations");
25707
25812
  }
25708
25813
  async atomic(_fn) {
25709
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
25814
+ throw degradedWriteError("observations");
25710
25815
  }
25711
25816
  async ensureFresh() {
25712
25817
  return false;
@@ -25719,6 +25824,9 @@ var init_obs_store = __esm({
25719
25824
  getBackendName() {
25720
25825
  return "degraded";
25721
25826
  }
25827
+ getBackendReason() {
25828
+ return this.reason;
25829
+ }
25722
25830
  };
25723
25831
  }
25724
25832
  });
@@ -73579,10 +73687,10 @@ var require_retry3 = __commonJS({
73579
73687
  if (!await shouldRetryFn(err)) {
73580
73688
  return { shouldRetry: false, config: err.config };
73581
73689
  }
73582
- const delay2 = getNextRetryDelay(config2);
73690
+ const delay3 = getNextRetryDelay(config2);
73583
73691
  err.config.retryConfig.currentRetryAttempt += 1;
73584
- const backoff = config2.retryBackoff ? config2.retryBackoff(err, delay2) : new Promise((resolve9) => {
73585
- setTimeout(resolve9, delay2);
73692
+ const backoff = config2.retryBackoff ? config2.retryBackoff(err, delay3) : new Promise((resolve9) => {
73693
+ setTimeout(resolve9, delay3);
73586
73694
  });
73587
73695
  if (config2.onRetryAttempt) {
73588
73696
  await config2.onRetryAttempt(err);
@@ -127107,7 +127215,7 @@ async function retryBackoff(fn2, strategy) {
127107
127215
  retryInterval = initialInterval * Math.pow(x5, exponent) + Math.random() * 1e3;
127108
127216
  }
127109
127217
  const d4 = Math.min(retryInterval, maxInterval);
127110
- await delay(d4);
127218
+ await delay2(d4);
127111
127219
  x5++;
127112
127220
  }
127113
127221
  }
@@ -127128,8 +127236,8 @@ function retryIntervalFromResponse(res) {
127128
127236
  }
127129
127237
  return 0;
127130
127238
  }
127131
- async function delay(delay2) {
127132
- return new Promise((resolve9) => setTimeout(resolve9, delay2));
127239
+ async function delay2(delay3) {
127240
+ return new Promise((resolve9) => setTimeout(resolve9, delay3));
127133
127241
  }
127134
127242
  var defaultBackoff, PermanentError, TemporaryError, codeRangeRE2;
127135
127243
  var init_retries = __esm({
@@ -191552,16 +191660,18 @@ async function initSessionStore(dataDir) {
191552
191660
  if (_store3 && _storeDataDir3 === dataDir) return _store3;
191553
191661
  _store3 = null;
191554
191662
  _storeDataDir3 = null;
191663
+ let degradedReason = "SQLite runtime unavailable";
191555
191664
  try {
191556
- const store3 = new SessionSqliteStore();
191557
- await store3.init(dataDir);
191665
+ const store3 = await initializeSqliteStore(() => new SessionSqliteStore(), dataDir, "Session store");
191558
191666
  _store3 = store3;
191559
191667
  _storeDataDir3 = dataDir;
191560
191668
  return store3;
191561
191669
  } catch (err) {
191562
- console.error(`[memorix] SessionSqliteStore unavailable, running in degraded read-only mode: ${err instanceof Error ? err.message : err}`);
191670
+ if (!isSqliteUnavailableError(err)) throw err;
191671
+ degradedReason = formatSqliteFailure(err);
191672
+ console.error(`[memorix] SessionSqliteStore unavailable, running in degraded read-only mode: ${degradedReason}`);
191563
191673
  }
191564
- const store2 = new SessionGracefulDegrade();
191674
+ const store2 = new SessionGracefulDegrade(degradedReason);
191565
191675
  await store2.init(dataDir);
191566
191676
  _store3 = store2;
191567
191677
  _storeDataDir3 = dataDir;
@@ -191573,6 +191683,7 @@ var init_session_store = __esm({
191573
191683
  "use strict";
191574
191684
  init_esm_shims();
191575
191685
  init_sqlite_db();
191686
+ init_sqlite_reliability();
191576
191687
  SessionSqliteStore = class {
191577
191688
  db = null;
191578
191689
  dataDir = "";
@@ -191661,7 +191772,7 @@ var init_session_store = __esm({
191661
191772
  }
191662
191773
  const stmtComplete = this._stmtCompleteActive;
191663
191774
  const stmtIns = this.stmtInsert;
191664
- const result = this.db.transaction(() => {
191775
+ return retrySqliteBusy(async () => this.db.transaction(() => {
191665
191776
  let completedCount = 0;
191666
191777
  for (const pid of projectIds) {
191667
191778
  const info2 = stmtComplete.run({ pid, now: now3 });
@@ -191669,18 +191780,24 @@ var init_session_store = __esm({
191669
191780
  }
191670
191781
  stmtIns.run(sessionToRow(newSession));
191671
191782
  return completedCount;
191672
- })();
191673
- return result;
191783
+ })(), "Session rollover");
191674
191784
  }
191675
191785
  getBackendName() {
191676
191786
  return "sqlite";
191677
191787
  }
191678
191788
  };
191679
191789
  SessionGracefulDegrade = class {
191790
+ constructor(reason = "SQLite runtime unavailable") {
191791
+ this.reason = reason;
191792
+ }
191793
+ reason;
191680
191794
  warned = false;
191795
+ unavailableRead() {
191796
+ return degradedReadError("sessions");
191797
+ }
191681
191798
  warn() {
191682
191799
  if (!this.warned) {
191683
- console.error("[memorix] SessionStore: SQLite unavailable \u2014 sessions are disabled (read-only empty). Install better-sqlite3 for full functionality.");
191800
+ console.error("[memorix] SessionStore: SQLite unavailable \u2014 sessions cannot be read or written. Install a supported SQLite runtime.");
191684
191801
  this.warned = true;
191685
191802
  }
191686
191803
  }
@@ -191688,33 +191805,39 @@ var init_session_store = __esm({
191688
191805
  this.warn();
191689
191806
  }
191690
191807
  async loadAll() {
191691
- return [];
191808
+ throw this.unavailableRead();
191692
191809
  }
191693
191810
  async getById(_id) {
191694
- return void 0;
191811
+ throw this.unavailableRead();
191695
191812
  }
191696
191813
  async loadByProject(_projectId) {
191697
- return [];
191814
+ throw this.unavailableRead();
191698
191815
  }
191699
191816
  async loadActive(_projectId) {
191700
- return [];
191817
+ throw this.unavailableRead();
191701
191818
  }
191702
191819
  async insert(_session) {
191703
191820
  this.warn();
191821
+ throw degradedWriteError("sessions");
191704
191822
  }
191705
191823
  async update(_session) {
191706
191824
  this.warn();
191825
+ throw degradedWriteError("sessions");
191707
191826
  }
191708
191827
  async bulkUpdate(_sessions) {
191709
191828
  this.warn();
191829
+ throw degradedWriteError("sessions");
191710
191830
  }
191711
191831
  async atomicRolloverInsert(_newSession, _projectIds, _now) {
191712
191832
  this.warn();
191713
- return 0;
191833
+ throw degradedWriteError("sessions");
191714
191834
  }
191715
191835
  getBackendName() {
191716
191836
  return "degraded";
191717
191837
  }
191838
+ getBackendReason() {
191839
+ return this.reason;
191840
+ }
191718
191841
  };
191719
191842
  _store3 = null;
191720
191843
  _storeDataDir3 = null;
@@ -207835,7 +207958,7 @@ var init_tool_profile = __esm({
207835
207958
  function getMcpServerInfo() {
207836
207959
  return {
207837
207960
  name: "memorix",
207838
- version: true ? "1.9.2" : "1.0.1"
207961
+ version: true ? "1.9.3" : "1.0.1"
207839
207962
  };
207840
207963
  }
207841
207964
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -281651,22 +281774,39 @@ var init_doctor = __esm({
281651
281774
  let activeCount = 0;
281652
281775
  let ranCount = 0;
281653
281776
  let backendName = "unknown";
281777
+ let backendReason;
281778
+ let observationFailure;
281654
281779
  try {
281655
281780
  const { initObservationStore: initObservationStore2, getObservationStore: getObservationStore2 } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
281656
281781
  await initObservationStore2(dataDir);
281657
281782
  const store2 = getObservationStore2();
281658
281783
  backendName = store2.getBackendName();
281784
+ backendReason = store2.getBackendReason?.();
281659
281785
  const { filterReadableObservations: filterReadableObservations2 } = await Promise.resolve().then(() => (init_visibility(), visibility_exports));
281660
281786
  projectObservations = projectId ? filterReadableObservations2(await store2.loadAll(), { projectId }) : [];
281661
281787
  obsCount = projectObservations.length;
281662
281788
  activeCount = projectObservations.filter((o4) => (o4.status ?? "active") === "active").length;
281663
281789
  ranCount = projectObservations.filter((o4) => /^Ran:\s/i.test(o4.title ?? "")).length;
281664
- } catch {
281665
- }
281666
- if (backendName === "degraded") {
281667
- lines.push(warn2("Observations: SQLite unavailable \u2014 degraded (read-only, no data)"));
281668
- issues.push("SQLite backend unavailable \u2014 observations cannot be read or written.");
281790
+ } catch (error40) {
281791
+ observationFailure = error40 instanceof Error ? error40.message : String(error40);
281792
+ }
281793
+ if (observationFailure) {
281794
+ lines.push(fail2(`Observations: storage unavailable \u2014 ${observationFailure}`));
281795
+ issues.push(`Observation store could not be opened: ${observationFailure}`);
281796
+ report.data = {
281797
+ backend: "unavailable",
281798
+ observations: null,
281799
+ active: null,
281800
+ commandLogs: null,
281801
+ error: observationFailure
281802
+ };
281803
+ } else if (backendName === "degraded") {
281804
+ lines.push(fail2(`Backend: degraded (read-only; writes disabled${backendReason ? `; ${backendReason}` : ""})`));
281805
+ lines.push(fail2("Observations: unavailable \u2014 count not reported"));
281806
+ issues.push("SQLite backend unavailable \u2014 observation storage is in degraded read-only mode; observations are not being persisted.");
281807
+ report.data = { backend: "degraded", ...backendReason ? { backendReason } : {}, observations: null, active: null, commandLogs: null };
281669
281808
  } else {
281809
+ lines.push(ok("Backend: sqlite (read-write)"));
281670
281810
  lines.push(ok(`Observations: ${obsCount} total, ${activeCount} active`));
281671
281811
  if (ranCount > 0) {
281672
281812
  const pct = Math.round(ranCount / obsCount * 100);
@@ -281676,29 +281816,63 @@ var init_doctor = __esm({
281676
281816
  }
281677
281817
  }
281678
281818
  }
281679
- report.data = { observations: obsCount, active: activeCount, commandLogs: ranCount };
281819
+ if (!report.data) {
281820
+ report.data = { backend: backendName, observations: obsCount, active: activeCount, commandLogs: ranCount };
281821
+ } else if (!report.data.backend) {
281822
+ report.data.backend = backendName;
281823
+ }
281824
+ let sessionBackend = "unknown";
281825
+ let sessionReason;
281680
281826
  try {
281681
281827
  const { initSessionStore: initSessionStore2, getSessionStore: getSessionStore2 } = await Promise.resolve().then(() => (init_session_store(), session_store_exports));
281682
281828
  await initSessionStore2(dataDir);
281683
281829
  const sessStore = getSessionStore2();
281684
- const sess = await sessStore.loadAll();
281685
- const sessCount = sess.length;
281686
- const activeSess = sess.filter((s4) => s4.status === "active").length;
281687
- lines.push(ok(`Sessions: ${sessCount} total, ${activeSess} active`));
281688
- report.data.sessions = sessCount;
281689
- } catch {
281830
+ sessionBackend = sessStore.getBackendName();
281831
+ sessionReason = sessStore.getBackendReason?.();
281832
+ if (sessionBackend === "degraded") {
281833
+ lines.push(fail2(`Sessions: unavailable \u2014 read-only degraded backend${sessionReason ? `; ${sessionReason}` : ""}`));
281834
+ issues.push("Session storage is in degraded read-only mode.");
281835
+ report.data.sessions = null;
281836
+ if (sessionReason) report.data.sessionBackendReason = sessionReason;
281837
+ } else {
281838
+ const sess = await sessStore.loadAll();
281839
+ const sessCount = sess.length;
281840
+ const activeSess = sess.filter((s4) => s4.status === "active").length;
281841
+ lines.push(ok(`Sessions: ${sessCount} total, ${activeSess} active`));
281842
+ report.data.sessions = sessCount;
281843
+ }
281844
+ } catch (error40) {
281845
+ const message = error40 instanceof Error ? error40.message : String(error40);
281846
+ lines.push(fail2(`Sessions: unavailable (${message})`));
281847
+ issues.push(`Session storage is unavailable: ${message}`);
281848
+ report.data.sessions = null;
281690
281849
  }
281850
+ let skillBackend = "unknown";
281851
+ let skillReason;
281691
281852
  try {
281692
281853
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
281693
281854
  await initMiniSkillStore2(dataDir);
281694
281855
  const skillStore = getMiniSkillStore2();
281695
- const skills = await skillStore.loadAll();
281696
- const skillCount = skills.length;
281697
- if (skillCount > 0) {
281698
- lines.push(ok(`Mini-skills: ${skillCount}`));
281699
- report.data.miniSkills = skillCount;
281856
+ skillBackend = skillStore.getBackendName();
281857
+ skillReason = skillStore.getBackendReason?.();
281858
+ if (skillBackend === "degraded") {
281859
+ lines.push(fail2(`Mini-skills: unavailable \u2014 read-only degraded backend${skillReason ? `; ${skillReason}` : ""}`));
281860
+ issues.push("Mini-skill storage is in degraded read-only mode.");
281861
+ report.data.miniSkills = null;
281862
+ if (skillReason) report.data.miniSkillBackendReason = skillReason;
281863
+ } else {
281864
+ const skills = await skillStore.loadAll();
281865
+ const skillCount = skills.length;
281866
+ if (skillCount > 0) {
281867
+ lines.push(ok(`Mini-skills: ${skillCount}`));
281868
+ report.data.miniSkills = skillCount;
281869
+ }
281700
281870
  }
281701
- } catch {
281871
+ } catch (error40) {
281872
+ const message = error40 instanceof Error ? error40.message : String(error40);
281873
+ lines.push(fail2(`Mini-skills: unavailable (${message})`));
281874
+ issues.push(`Mini-skill storage is unavailable: ${message}`);
281875
+ report.data.miniSkills = null;
281702
281876
  }
281703
281877
  } else {
281704
281878
  lines.push(warn2("No data directory found for current project"));
@@ -281946,6 +282120,9 @@ var init_doctor = __esm({
281946
282120
  } else {
281947
282121
  console.log(lines.join("\n"));
281948
282122
  }
282123
+ if (issues.some((issue2) => /^(Observation store could not be opened|Observation storage |Session storage |Mini-skill storage |SQLite backend unavailable)/.test(issue2))) {
282124
+ process.exitCode = 1;
282125
+ }
281949
282126
  }
281950
282127
  });
281951
282128
  }
@@ -284393,12 +284570,12 @@ function classifyError(ctx, taskId) {
284393
284570
  if (taskId) backoffState.set(taskId, attempt);
284394
284571
  const baseDelay = 2e3;
284395
284572
  const maxDelay = 3e4;
284396
- const delay2 = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
284397
- const jitter = delay2 * 0.25 * (Math.random() * 2 - 1);
284573
+ const delay3 = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
284574
+ const jitter = delay3 * 0.25 * (Math.random() * 2 - 1);
284398
284575
  return {
284399
284576
  category: "transient",
284400
284577
  strategy: "backoff_and_retry",
284401
- delayMs: Math.round(delay2 + jitter),
284578
+ delayMs: Math.round(delay3 + jitter),
284402
284579
  reason: `Transient error detected (attempt ${attempt})`,
284403
284580
  backoffAttempt: attempt
284404
284581
  };