moflo 4.9.36 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.claude/guidance/shipped/moflo-agent-rules.md +12 -0
  2. package/.claude/guidance/shipped/moflo-memory-protocol.md +34 -0
  3. package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +22 -11
  4. package/.claude/guidance/shipped/moflo-root-cause-discipline.md +47 -0
  5. package/.claude/guidance/shipped/moflo-subagents.md +4 -0
  6. package/.claude/helpers/gate.cjs +3 -3
  7. package/.claude/helpers/statusline.cjs +69 -33
  8. package/.claude/helpers/subagent-bootstrap.json +1 -1
  9. package/.claude/helpers/subagent-start.cjs +1 -1
  10. package/.claude/skills/eldar/SKILL.md +8 -0
  11. package/bin/build-embeddings.mjs +6 -20
  12. package/bin/cli.js +5 -0
  13. package/bin/gate.cjs +3 -3
  14. package/bin/generate-code-map.mjs +4 -24
  15. package/bin/hooks.mjs +3 -12
  16. package/bin/index-all.mjs +3 -13
  17. package/bin/index-guidance.mjs +59 -119
  18. package/bin/index-patterns.mjs +6 -24
  19. package/bin/index-tests.mjs +4 -23
  20. package/bin/lib/db-repair.mjs +4 -25
  21. package/bin/lib/get-backend.mjs +306 -0
  22. package/bin/lib/incremental-write.mjs +27 -7
  23. package/bin/lib/moflo-paths.mjs +64 -4
  24. package/bin/lib/suppress-sqlite-warning.mjs +57 -0
  25. package/bin/migrations/knowledge-purge.mjs +7 -8
  26. package/bin/migrations/knowledge-to-learnings.mjs +7 -9
  27. package/bin/migrations/purge-doc-entries.mjs +52 -0
  28. package/bin/migrations/strip-context-preambles.mjs +95 -0
  29. package/bin/run-migrations.mjs +1 -10
  30. package/bin/semantic-search.mjs +11 -19
  31. package/bin/session-start-launcher.mjs +102 -100
  32. package/bin/simplify-classify.cjs +38 -17
  33. package/dist/src/cli/commands/daemon.js +38 -11
  34. package/dist/src/cli/commands/doctor-checks-coverage-truth.js +136 -0
  35. package/dist/src/cli/commands/doctor-checks-memory-access.js +244 -5
  36. package/dist/src/cli/commands/doctor-checks-memory.js +13 -18
  37. package/dist/src/cli/commands/doctor-checks-version-skew.js +94 -0
  38. package/dist/src/cli/commands/doctor-checks-writers-audit.js +170 -0
  39. package/dist/src/cli/commands/doctor-embedding-hygiene.js +3 -15
  40. package/dist/src/cli/commands/doctor-fixes.js +30 -0
  41. package/dist/src/cli/commands/doctor-registry.js +14 -0
  42. package/dist/src/cli/commands/doctor.js +1 -1
  43. package/dist/src/cli/commands/embeddings.js +17 -22
  44. package/dist/src/cli/commands/memory.js +54 -75
  45. package/dist/src/cli/embeddings/persistent-cache.js +44 -83
  46. package/dist/src/cli/init/claudemd-generator.js +4 -0
  47. package/dist/src/cli/init/moflo-init.js +40 -0
  48. package/dist/src/cli/mcp-tools/memory-tools.js +177 -32
  49. package/dist/src/cli/memory/bridge-core.js +256 -30
  50. package/dist/src/cli/memory/bridge-entries.js +76 -8
  51. package/dist/src/cli/memory/controller-registry.js +7 -2
  52. package/dist/src/cli/memory/controllers/batch-operations.js +5 -1
  53. package/dist/src/cli/memory/controllers/hierarchical-memory.js +7 -2
  54. package/dist/src/cli/memory/controllers/mutation-guard.js +22 -2
  55. package/dist/src/cli/memory/daemon-backend.js +400 -0
  56. package/dist/src/cli/memory/daemon-write-client.js +192 -15
  57. package/dist/src/cli/memory/database-provider.js +57 -40
  58. package/dist/src/cli/memory/hnsw-persistence.js +6 -8
  59. package/dist/src/cli/memory/index.js +0 -1
  60. package/dist/src/cli/memory/memory-bridge.js +40 -8
  61. package/dist/src/cli/memory/memory-initializer.js +286 -220
  62. package/dist/src/cli/memory/rvf-migration.js +25 -11
  63. package/dist/src/cli/memory/sqlite-backend.js +573 -0
  64. package/dist/src/cli/memory/suppress-sqlite-warning.js +49 -0
  65. package/dist/src/cli/services/cherry-pick-learnings.js +32 -21
  66. package/dist/src/cli/services/daemon-dashboard.js +13 -1
  67. package/dist/src/cli/services/daemon-lock.js +58 -1
  68. package/dist/src/cli/services/daemon-memory-rpc.js +245 -10
  69. package/dist/src/cli/services/embeddings-migration.js +9 -12
  70. package/dist/src/cli/services/ephemeral-namespace-purge.js +6 -11
  71. package/dist/src/cli/services/learning-service.js +12 -20
  72. package/dist/src/cli/services/project-root.js +69 -9
  73. package/dist/src/cli/services/soft-delete-purge.js +6 -11
  74. package/dist/src/cli/services/sqljs-migration-store.js +4 -1
  75. package/dist/src/cli/services/subagent-bootstrap.js +1 -1
  76. package/dist/src/cli/shared/events/event-store.js +26 -55
  77. package/dist/src/cli/version.js +1 -1
  78. package/package.json +2 -4
  79. package/dist/src/cli/memory/sqljs-backend.js +0 -643
@@ -25,25 +25,15 @@
25
25
  import { existsSync, readdirSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'fs';
26
26
  import { resolve, relative, dirname, basename, extname } from 'path';
27
27
  import { fileURLToPath } from 'url';
28
- import { mofloResolveURL } from './lib/moflo-resolve.mjs';
29
- import { memoryDbPath } from './lib/moflo-paths.mjs';
28
+ import { memoryDbPath, findProjectRoot } from './lib/moflo-paths.mjs';
29
+ import { openBackend } from './lib/get-backend.mjs';
30
+ import { applyIncrementalChunks } from './lib/incremental-write.mjs';
30
31
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
31
32
  import { createProcessManager } from './lib/process-manager.mjs';
32
- const initSqlJs = (await import(mofloResolveURL('sql.js'))).default;
33
33
 
34
34
 
35
35
  const __dirname = dirname(fileURLToPath(import.meta.url));
36
36
 
37
- function findProjectRoot() {
38
- let dir = process.cwd();
39
- const root = resolve(dir, '/');
40
- while (dir !== root) {
41
- if (existsSync(resolve(dir, 'package.json'))) return dir;
42
- dir = dirname(dir);
43
- }
44
- return process.cwd();
45
- }
46
-
47
37
  const projectRoot = findProjectRoot();
48
38
 
49
39
  // Locate the moflo package root (for bundled guidance that ships with moflo)
@@ -181,14 +171,7 @@ function ensureDbDir() {
181
171
 
182
172
  async function getDb() {
183
173
  ensureDbDir();
184
- const SQL = await initSqlJs();
185
- let db;
186
- if (existsSync(DB_PATH)) {
187
- const buffer = readFileSync(DB_PATH);
188
- db = new SQL.Database(buffer);
189
- } else {
190
- db = new SQL.Database();
191
- }
174
+ const db = await openBackend(projectRoot, { create: true });
192
175
 
193
176
  // Ensure table exists with unique constraint
194
177
  db.run(`
@@ -221,12 +204,7 @@ async function getDb() {
221
204
  }
222
205
 
223
206
  function saveDb(db) {
224
- const data = db.export();
225
- writeFileSync(DB_PATH, Buffer.from(data));
226
- }
227
-
228
- function generateId() {
229
- return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
207
+ db.save();
230
208
  }
231
209
 
232
210
  function hashContent(content) {
@@ -239,34 +217,17 @@ function hashContent(content) {
239
217
  return hash.toString(16);
240
218
  }
241
219
 
242
- function storeEntry(db, key, content, metadata = {}, tags = []) {
243
- const now = Date.now();
244
- const id = generateId();
245
- const metaJson = JSON.stringify(metadata);
246
- const tagsJson = JSON.stringify(tags);
247
-
248
- db.run(`
249
- INSERT OR REPLACE INTO memory_entries
250
- (id, key, namespace, content, metadata, tags, created_at, updated_at, status)
251
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active')
252
- `, [id, key, NAMESPACE, content, metaJson, tagsJson, now, now]);
253
-
254
- return true;
255
- }
256
-
257
- function deleteByPrefix(db, prefix) {
258
- db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key LIKE ?`, [NAMESPACE, `${prefix}%`]);
259
- }
260
-
261
- function getEntryHash(db, key) {
220
+ // #1053 S4: doc-* entries retired. Doc-level skip check now reads
221
+ // docContentHash off chunk-0 (every chunk carries it).
222
+ function getDocHashFromChunkZero(db, chunkPrefix) {
262
223
  const stmt = db.prepare('SELECT metadata FROM memory_entries WHERE key = ? AND namespace = ?');
263
- stmt.bind([key, NAMESPACE]);
224
+ stmt.bind([`${chunkPrefix}-0`, NAMESPACE]);
264
225
  const entry = stmt.step() ? stmt.getAsObject() : null;
265
226
  stmt.free();
266
227
  if (entry?.metadata) {
267
228
  try {
268
229
  const meta = JSON.parse(entry.metadata);
269
- return meta.contentHash;
230
+ return meta.docContentHash;
270
231
  } catch { /* ignore */ }
271
232
  }
272
233
  return null;
@@ -536,9 +497,10 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
536
497
  const content = readFileSync(filePath, 'utf-8');
537
498
  const contentHash = hashContent(content);
538
499
 
539
- // Check if content changed (skip if same hash unless --force)
500
+ // Check if content changed (skip if same hash unless --force).
501
+ // #1053 S4: doc-* retired — read docContentHash off chunk-0 instead.
540
502
  if (!force) {
541
- const existingHash = getEntryHash(db, docKey);
503
+ const existingHash = getDocHashFromChunkZero(db, chunkPrefix);
542
504
  if (existingHash === contentHash) {
543
505
  return { docKey, status: 'unchanged', chunks: 0 };
544
506
  }
@@ -547,28 +509,23 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
547
509
  const stats = statSync(filePath);
548
510
  const relativePath = '/' + relative(projectRoot, filePath).replace(/\\/g, '/');
549
511
 
550
- // Delete old chunks for this file before re-indexing
551
- deleteByPrefix(db, chunkPrefix);
552
-
553
- // 1. Store full document
554
- const docMetadata = {
555
- ...extraMetadata,
556
- type: 'document',
557
- filePath: relativePath,
558
- fileSize: stats.size,
559
- lastModified: stats.mtime.toISOString(),
560
- contentHash,
561
- indexedAt: new Date().toISOString(),
562
- ragVersion: '2.0', // Mark as full RAG indexed
563
- };
512
+ // #1053 S4: drop any legacy doc-* row left over from a pre-S4 install.
513
+ // The chunker stopped emitting these and the audit found zero production
514
+ // readers; safe one-time cleanup as part of the per-doc re-index.
515
+ db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key = ?`, [NAMESPACE, docKey]);
564
516
 
565
- storeEntry(db, docKey, content, docMetadata, [keyPrefix, 'document', ...extraTags]);
566
- debug(`Stored document: ${docKey}`);
517
+ // #1053 S4: Chunker no longer writes doc-* entries. Audit found zero
518
+ // production readers — they duplicated chunk semantic territory and
519
+ // ate ~13% of search slots without unique signal. parentDoc on chunks
520
+ // remains as an identifier/grouping label; callers needing the source
521
+ // file Read parentPath, per shipped/moflo-memory-protocol.md.
567
522
 
568
- // 2. Chunk and store semantic pieces with full RAG linking
523
+ // Chunk and store semantic pieces with full RAG linking
569
524
  const chunks = chunkMarkdown(content, fileName);
570
525
 
571
526
  if (chunks.length === 0) {
527
+ // No chunks generated → sweep any stragglers from a prior run and exit.
528
+ db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key LIKE ?`, [NAMESPACE, `${chunkPrefix}-%`]);
572
529
  return { docKey, status: 'indexed', chunks: 0 };
573
530
  }
574
531
 
@@ -576,31 +533,17 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
576
533
  const hierarchy = buildHierarchy(chunks, chunkPrefix);
577
534
  const siblings = chunks.map((_, i) => `${chunkPrefix}-${i}`);
578
535
 
579
- // Update document with children references
580
- const docChildrenMeta = {
581
- ...docMetadata,
582
- children: siblings,
583
- chunkCount: chunks.length,
584
- };
585
- storeEntry(db, docKey, content, docChildrenMeta, [keyPrefix, 'document', ...extraTags]);
586
-
587
- for (let i = 0; i < chunks.length; i++) {
588
- const chunk = chunks[i];
536
+ // #1057: route chunk writes through applyIncrementalChunks so an
537
+ // unchanged chunk (~95% of the time when only one section of a doc
538
+ // edited) keeps its embedding column. Pre-#1057 this loop used raw
539
+ // INSERT OR REPLACE after a blanket deleteByPrefix — every re-index
540
+ // wiped every chunk's embedding and forced build-embeddings to
541
+ // re-vectorise the whole doc on every save. See
542
+ // feedback_indexer_preserve_embeddings.md.
543
+ const chunkRows = chunks.map((chunk, i) => {
589
544
  const chunkKey = `${chunkPrefix}-${i}`;
590
-
591
- // Build prev/next links
592
545
  const prevChunk = i > 0 ? `${chunkPrefix}-${i - 1}` : null;
593
546
  const nextChunk = i < chunks.length - 1 ? `${chunkPrefix}-${i + 1}` : null;
594
-
595
- // Extract overlapping context from adjacent chunks
596
- const contextBefore = i > 0
597
- ? extractOverlapContext(chunks[i - 1].content, overlapPercent, 'end')
598
- : null;
599
- const contextAfter = i < chunks.length - 1
600
- ? extractOverlapContext(chunks[i + 1].content, overlapPercent, 'start')
601
- : null;
602
-
603
- // Get hierarchical relationships
604
547
  const hierInfo = hierarchy[chunkKey];
605
548
 
606
549
  const chunkMetadata = {
@@ -608,9 +551,13 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
608
551
  type: 'chunk',
609
552
  ragVersion: '2.0',
610
553
 
611
- // Document relationship
554
+ // Document relationship — parentDoc is an identifier/grouping label
555
+ // only after #1053 S4; the actual source doc is at parentPath.
556
+ // docContentHash is the file-level hash, used by the skip-if-unchanged
557
+ // check (the chunker reads it off chunk-0 to decide whether to re-index).
612
558
  parentDoc: docKey,
613
559
  parentPath: relativePath,
560
+ docContentHash: contentHash,
614
561
 
615
562
  // Sequential navigation (forward/backward links)
616
563
  chunkIndex: i,
@@ -632,41 +579,34 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
632
579
  isPart: chunk.isPart || false,
633
580
  partNum: chunk.partNum || null,
634
581
 
635
- // Overlapping context for continuity
636
- contextOverlapPercent: overlapPercent,
637
- hasContextBefore: !!contextBefore,
638
- hasContextAfter: !!contextAfter,
639
-
640
582
  // Content metadata
641
583
  contentLength: chunk.content.length,
642
584
  contentHash: hashContent(chunk.content),
643
585
  indexedAt: new Date().toISOString(),
644
586
  };
645
587
 
646
- // Build searchable content with title context
647
- // Include overlap context for better retrieval
648
- let searchableContent = `# ${chunk.title}\n\n`;
649
-
650
- if (contextBefore) {
651
- searchableContent += `[Context from previous section:]\n${contextBefore}\n\n---\n\n`;
652
- }
653
-
654
- searchableContent += chunk.content;
655
-
656
- if (contextAfter) {
657
- searchableContent += `\n\n---\n\n[Context from next section:]\n${contextAfter}`;
658
- }
659
-
660
- // Store chunk with full metadata
661
- storeEntry(
662
- db,
663
- chunkKey,
664
- searchableContent,
665
- chunkMetadata,
666
- [keyPrefix, 'chunk', `level-${chunk.level}`, chunk.title.toLowerCase().replace(/[^a-z0-9]+/g, '-'), ...extraTags]
667
- );
588
+ // #1053 S5: title heading + chunk body. No prev/next preamble.
589
+ const searchableContent = `# ${chunk.title}\n\n${chunk.content}`;
590
+
591
+ return {
592
+ key: chunkKey,
593
+ content: searchableContent,
594
+ metadata: chunkMetadata,
595
+ tags: [
596
+ keyPrefix,
597
+ 'chunk',
598
+ `level-${chunk.level}`,
599
+ chunk.title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
600
+ ...extraTags,
601
+ ],
602
+ };
603
+ });
668
604
 
669
- debug(` Stored chunk ${i}: ${chunk.title} (${chunk.content.length} chars, prev=${!!prevChunk}, next=${!!nextChunk})`);
605
+ const counts = applyIncrementalChunks(db, NAMESPACE, chunkRows, {
606
+ keyPrefix: `${chunkPrefix}-`,
607
+ });
608
+ if (verbose) {
609
+ debug(` Doc ${docKey}: inserted=${counts.inserted} updated=${counts.updated} unchanged=${counts.unchanged} removed=${counts.removed}`);
670
610
  }
671
611
 
672
612
  return { docKey, status: 'indexed', chunks: chunks.length };
@@ -28,23 +28,13 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from
28
28
  import { resolve, dirname, relative, basename, extname } from 'path';
29
29
  import { fileURLToPath } from 'url';
30
30
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
31
- import { mofloResolveURL } from './lib/moflo-resolve.mjs';
32
- import { memoryDbPath, MOFLO_DIR } from './lib/moflo-paths.mjs';
31
+ import { memoryDbPath, MOFLO_DIR, findProjectRoot } from './lib/moflo-paths.mjs';
32
+ import { openBackend } from './lib/get-backend.mjs';
33
33
  import { applyIncrementalChunks, computeContentListHash } from './lib/incremental-write.mjs';
34
34
  import { createProcessManager } from './lib/process-manager.mjs';
35
35
 
36
36
  const __dirname = dirname(fileURLToPath(import.meta.url));
37
37
 
38
- function findProjectRoot() {
39
- let dir = process.cwd();
40
- const root = resolve(dir, '/');
41
- while (dir !== root) {
42
- if (existsSync(resolve(dir, 'package.json'))) return dir;
43
- dir = dirname(dir);
44
- }
45
- return process.cwd();
46
- }
47
-
48
38
  const projectRoot = findProjectRoot();
49
39
  const NAMESPACE = 'patterns';
50
40
  const DB_PATH = memoryDbPath(projectRoot);
@@ -76,16 +66,9 @@ function ensureDbDir() {
76
66
  async function getDb() {
77
67
  ensureDbDir();
78
68
  // Lazy: hash-cache-match and no-source-files early-exits in main() never
79
- // reach this, and the sql.js wasm cold-load is ~400ms otherwise wasted.
80
- const initSqlJs = (await import(mofloResolveURL('sql.js'))).default;
81
- const SQL = await initSqlJs();
82
- let db;
83
- if (existsSync(DB_PATH)) {
84
- const buffer = readFileSync(DB_PATH);
85
- db = new SQL.Database(buffer);
86
- } else {
87
- db = new SQL.Database();
88
- }
69
+ // reach this, and the backend cold-load (sql.js wasm ~400ms, node:sqlite
70
+ // WAL init ~20ms) is otherwise wasted on the no-op path.
71
+ const db = await openBackend(projectRoot, { create: true });
89
72
  db.run(`
90
73
  CREATE TABLE IF NOT EXISTS memory_entries (
91
74
  id TEXT PRIMARY KEY,
@@ -114,8 +97,7 @@ async function getDb() {
114
97
  }
115
98
 
116
99
  function saveDb(db) {
117
- const data = db.export();
118
- writeFileSync(DB_PATH, Buffer.from(data));
100
+ db.save();
119
101
  }
120
102
 
121
103
  function countNamespace(db) {
@@ -26,24 +26,13 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from
26
26
  import { resolve, dirname, relative, basename, extname, join } from 'path';
27
27
  import { fileURLToPath } from 'url';
28
28
  import { execSync, execFileSync, spawn } from 'child_process';
29
- import { mofloResolveURL } from './lib/moflo-resolve.mjs';
30
- import { memoryDbPath, MOFLO_DIR } from './lib/moflo-paths.mjs';
29
+ import { memoryDbPath, MOFLO_DIR, findProjectRoot } from './lib/moflo-paths.mjs';
30
+ import { openBackend } from './lib/get-backend.mjs';
31
31
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
32
32
  import { applyIncrementalChunks, computeContentListHash } from './lib/incremental-write.mjs';
33
- const initSqlJs = (await import(mofloResolveURL('sql.js'))).default;
34
33
 
35
34
  const __dirname = dirname(fileURLToPath(import.meta.url));
36
35
 
37
- function findProjectRoot() {
38
- let dir = process.cwd();
39
- const root = resolve(dir, '/');
40
- while (dir !== root) {
41
- if (existsSync(resolve(dir, 'package.json'))) return dir;
42
- dir = dirname(dir);
43
- }
44
- return process.cwd();
45
- }
46
-
47
36
  const projectRoot = findProjectRoot();
48
37
  const NAMESPACE = 'tests';
49
38
  const DB_PATH = memoryDbPath(projectRoot);
@@ -87,14 +76,7 @@ function ensureDbDir() {
87
76
 
88
77
  async function getDb() {
89
78
  ensureDbDir();
90
- const SQL = await initSqlJs();
91
- let db;
92
- if (existsSync(DB_PATH)) {
93
- const buffer = readFileSync(DB_PATH);
94
- db = new SQL.Database(buffer);
95
- } else {
96
- db = new SQL.Database();
97
- }
79
+ const db = await openBackend(projectRoot, { create: true });
98
80
 
99
81
  db.run(`
100
82
  CREATE TABLE IF NOT EXISTS memory_entries (
@@ -124,8 +106,7 @@ async function getDb() {
124
106
  }
125
107
 
126
108
  function saveDb(db) {
127
- const data = db.export();
128
- writeFileSync(DB_PATH, Buffer.from(data));
109
+ db.save();
129
110
  }
130
111
 
131
112
  function countNamespace(db) {
@@ -24,20 +24,9 @@
24
24
  * the DB and BEFORE the embeddings migration / soft-delete purge / ephemeral
25
25
  * purge — those all swallow corruption errors and silently no-op.
26
26
  */
27
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
27
+ import { existsSync } from 'node:fs';
28
28
  import { memoryDbPath } from './moflo-paths.mjs';
29
-
30
- let _initSqlJs = null;
31
-
32
- async function loadSqlJs() {
33
- if (_initSqlJs) return _initSqlJs;
34
- // sql.js is a hard dependency of moflo (see top-level package.json);
35
- // resolving it from the consumer's node_modules works because the launcher
36
- // runs from the consumer cwd.
37
- const mod = await import('sql.js');
38
- _initSqlJs = mod.default || mod;
39
- return _initSqlJs;
40
- }
29
+ import { openBackend } from './get-backend.mjs';
41
30
 
42
31
  function isOk(execResult) {
43
32
  const rows = execResult?.[0]?.values ?? [];
@@ -63,18 +52,9 @@ export async function repairMemoryDbIfCorrupt(projectRoot) {
63
52
  const dbPath = memoryDbPath(projectRoot);
64
53
  if (!existsSync(dbPath)) return { repaired: false, errors: 0 };
65
54
 
66
- let initSql;
67
- try {
68
- initSql = await loadSqlJs();
69
- } catch {
70
- return { repaired: false, errors: 0 };
71
- }
72
-
73
55
  let db = null;
74
56
  try {
75
- const SQL = await initSql();
76
- const data = readFileSync(dbPath);
77
- db = new SQL.Database(data);
57
+ db = await openBackend(projectRoot, { create: false });
78
58
 
79
59
  const before = db.exec('PRAGMA integrity_check');
80
60
  if (isOk(before)) {
@@ -89,8 +69,7 @@ export async function repairMemoryDbIfCorrupt(projectRoot) {
89
69
  return { repaired: false, errors, persistent: true };
90
70
  }
91
71
 
92
- const out = Buffer.from(db.export());
93
- writeFileSync(dbPath, out);
72
+ db.save();
94
73
  return { repaired: true, errors };
95
74
  } catch {
96
75
  return { repaired: false, errors: 0 };