moflo 4.9.37 → 4.10.1
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/.claude/guidance/shipped/moflo-memory-protocol.md +5 -1
- package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +22 -11
- package/.claude/guidance/shipped/moflo-root-cause-discipline.md +47 -0
- package/.claude/helpers/statusline.cjs +69 -33
- package/.claude/helpers/subagent-bootstrap.json +1 -1
- package/.claude/helpers/subagent-start.cjs +1 -1
- package/bin/build-embeddings.mjs +6 -20
- package/bin/cli.js +5 -0
- package/bin/generate-code-map.mjs +4 -24
- package/bin/hooks.mjs +3 -12
- package/bin/index-all.mjs +3 -13
- package/bin/index-guidance.mjs +36 -85
- package/bin/index-patterns.mjs +6 -24
- package/bin/index-tests.mjs +4 -23
- package/bin/lib/db-repair.mjs +358 -62
- package/bin/lib/get-backend.mjs +306 -0
- package/bin/lib/incremental-write.mjs +27 -7
- package/bin/lib/moflo-paths.mjs +64 -4
- package/bin/lib/suppress-sqlite-warning.mjs +57 -0
- package/bin/migrations/knowledge-purge.mjs +7 -8
- package/bin/migrations/knowledge-to-learnings.mjs +7 -9
- package/bin/migrations/purge-doc-entries.mjs +7 -8
- package/bin/migrations/strip-context-preambles.mjs +4 -6
- package/bin/run-migrations.mjs +1 -10
- package/bin/semantic-search.mjs +7 -18
- package/bin/session-start-launcher.mjs +144 -108
- package/bin/simplify-classify.cjs +38 -17
- package/dist/src/cli/commands/daemon.js +38 -11
- package/dist/src/cli/commands/doctor-checks-config.js +60 -0
- package/dist/src/cli/commands/doctor-checks-coverage-truth.js +136 -0
- package/dist/src/cli/commands/doctor-checks-memory-access.js +146 -86
- package/dist/src/cli/commands/doctor-checks-memory.js +13 -18
- package/dist/src/cli/commands/doctor-checks-version-skew.js +94 -0
- package/dist/src/cli/commands/doctor-checks-writers-audit.js +170 -0
- package/dist/src/cli/commands/doctor-embedding-hygiene.js +3 -15
- package/dist/src/cli/commands/doctor-fixes.js +87 -0
- package/dist/src/cli/commands/doctor-registry.js +24 -1
- package/dist/src/cli/commands/doctor.js +1 -1
- package/dist/src/cli/commands/embeddings.js +17 -22
- package/dist/src/cli/commands/memory.js +13 -23
- package/dist/src/cli/embeddings/persistent-cache.js +44 -83
- package/dist/src/cli/init/moflo-init.js +40 -0
- package/dist/src/cli/mcp-tools/memory-tools.js +10 -3
- package/dist/src/cli/memory/bridge-core.js +256 -30
- package/dist/src/cli/memory/bridge-embedder.js +84 -3
- package/dist/src/cli/memory/bridge-entries.js +70 -6
- package/dist/src/cli/memory/controller-registry.js +7 -2
- package/dist/src/cli/memory/controllers/batch-operations.js +5 -1
- package/dist/src/cli/memory/controllers/hierarchical-memory.js +7 -2
- package/dist/src/cli/memory/controllers/mutation-guard.js +22 -2
- package/dist/src/cli/memory/daemon-backend.js +400 -0
- package/dist/src/cli/memory/daemon-write-client.js +192 -15
- package/dist/src/cli/memory/database-provider.js +57 -40
- package/dist/src/cli/memory/hnsw-persistence.js +6 -8
- package/dist/src/cli/memory/index.js +0 -1
- package/dist/src/cli/memory/memory-bridge.js +40 -8
- package/dist/src/cli/memory/memory-initializer.js +271 -211
- package/dist/src/cli/memory/rvf-migration.js +25 -11
- package/dist/src/cli/memory/sqlite-backend.js +573 -0
- package/dist/src/cli/memory/suppress-sqlite-warning.js +49 -0
- package/dist/src/cli/services/cherry-pick-learnings.js +32 -21
- package/dist/src/cli/services/daemon-dashboard.js +13 -1
- package/dist/src/cli/services/daemon-lock.js +58 -1
- package/dist/src/cli/services/daemon-memory-rpc.js +245 -10
- package/dist/src/cli/services/embeddings-migration.js +9 -12
- package/dist/src/cli/services/ephemeral-namespace-purge.js +21 -16
- package/dist/src/cli/services/learning-service.js +12 -20
- package/dist/src/cli/services/memory-db-integrity-repair.js +119 -0
- package/dist/src/cli/services/project-root.js +69 -9
- package/dist/src/cli/services/soft-delete-purge.js +6 -11
- package/dist/src/cli/services/sqljs-migration-store.js +4 -1
- package/dist/src/cli/services/subagent-bootstrap.js +1 -1
- package/dist/src/cli/shared/events/event-store.js +26 -55
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -4
- package/dist/src/cli/memory/sqljs-backend.js +0 -643
package/bin/index-guidance.mjs
CHANGED
|
@@ -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 {
|
|
29
|
-
import {
|
|
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
|
|
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
|
-
|
|
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,39 +217,6 @@ 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) {
|
|
262
|
-
const stmt = db.prepare('SELECT metadata FROM memory_entries WHERE key = ? AND namespace = ?');
|
|
263
|
-
stmt.bind([key, NAMESPACE]);
|
|
264
|
-
const entry = stmt.step() ? stmt.getAsObject() : null;
|
|
265
|
-
stmt.free();
|
|
266
|
-
if (entry?.metadata) {
|
|
267
|
-
try {
|
|
268
|
-
const meta = JSON.parse(entry.metadata);
|
|
269
|
-
return meta.contentHash;
|
|
270
|
-
} catch { /* ignore */ }
|
|
271
|
-
}
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
220
|
// #1053 S4: doc-* entries retired. Doc-level skip check now reads
|
|
276
221
|
// docContentHash off chunk-0 (every chunk carries it).
|
|
277
222
|
function getDocHashFromChunkZero(db, chunkPrefix) {
|
|
@@ -564,10 +509,9 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
|
|
|
564
509
|
const stats = statSync(filePath);
|
|
565
510
|
const relativePath = '/' + relative(projectRoot, filePath).replace(/\\/g, '/');
|
|
566
511
|
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
deleteByPrefix(db, chunkPrefix);
|
|
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.
|
|
571
515
|
db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key = ?`, [NAMESPACE, docKey]);
|
|
572
516
|
|
|
573
517
|
// #1053 S4: Chunker no longer writes doc-* entries. Audit found zero
|
|
@@ -580,6 +524,8 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
|
|
|
580
524
|
const chunks = chunkMarkdown(content, fileName);
|
|
581
525
|
|
|
582
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}-%`]);
|
|
583
529
|
return { docKey, status: 'indexed', chunks: 0 };
|
|
584
530
|
}
|
|
585
531
|
|
|
@@ -587,21 +533,17 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
|
|
|
587
533
|
const hierarchy = buildHierarchy(chunks, chunkPrefix);
|
|
588
534
|
const siblings = chunks.map((_, i) => `${chunkPrefix}-${i}`);
|
|
589
535
|
|
|
590
|
-
|
|
591
|
-
|
|
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) => {
|
|
592
544
|
const chunkKey = `${chunkPrefix}-${i}`;
|
|
593
|
-
|
|
594
|
-
// Build prev/next links
|
|
595
545
|
const prevChunk = i > 0 ? `${chunkPrefix}-${i - 1}` : null;
|
|
596
546
|
const nextChunk = i < chunks.length - 1 ? `${chunkPrefix}-${i + 1}` : null;
|
|
597
|
-
|
|
598
|
-
// #1053 S5: dropped extractOverlapContext + preamble wrapping. The
|
|
599
|
-
// preambles were a workaround for missing traversal — once memory_get_neighbors
|
|
600
|
-
// is wired (S2), prevChunk/nextChunk metadata + a real call is the
|
|
601
|
-
// alternative path. Saved ~25-30% bloat per chunk on disk and in
|
|
602
|
-
// embeddings.
|
|
603
|
-
|
|
604
|
-
// Get hierarchical relationships
|
|
605
547
|
const hierInfo = hierarchy[chunkKey];
|
|
606
548
|
|
|
607
549
|
const chunkMetadata = {
|
|
@@ -646,16 +588,25 @@ function indexFile(db, filePath, keyPrefix, options = {}) {
|
|
|
646
588
|
// #1053 S5: title heading + chunk body. No prev/next preamble.
|
|
647
589
|
const searchableContent = `# ${chunk.title}\n\n${chunk.content}`;
|
|
648
590
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
+
});
|
|
657
604
|
|
|
658
|
-
|
|
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}`);
|
|
659
610
|
}
|
|
660
611
|
|
|
661
612
|
return { docKey, status: 'indexed', chunks: chunks.length };
|
package/bin/index-patterns.mjs
CHANGED
|
@@ -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 {
|
|
32
|
-
import {
|
|
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
|
|
80
|
-
|
|
81
|
-
const
|
|
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
|
-
|
|
118
|
-
writeFileSync(DB_PATH, Buffer.from(data));
|
|
100
|
+
db.save();
|
|
119
101
|
}
|
|
120
102
|
|
|
121
103
|
function countNamespace(db) {
|
package/bin/index-tests.mjs
CHANGED
|
@@ -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 {
|
|
30
|
-
import {
|
|
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
|
|
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
|
-
|
|
128
|
-
writeFileSync(DB_PATH, Buffer.from(data));
|
|
109
|
+
db.save();
|
|
129
110
|
}
|
|
130
111
|
|
|
131
112
|
function countNamespace(db) {
|