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.
- package/.claude/guidance/shipped/moflo-agent-rules.md +12 -0
- package/.claude/guidance/shipped/moflo-memory-protocol.md +34 -0
- package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +22 -11
- package/.claude/guidance/shipped/moflo-root-cause-discipline.md +47 -0
- package/.claude/guidance/shipped/moflo-subagents.md +4 -0
- package/.claude/helpers/gate.cjs +3 -3
- package/.claude/helpers/statusline.cjs +69 -33
- package/.claude/helpers/subagent-bootstrap.json +1 -1
- package/.claude/helpers/subagent-start.cjs +1 -1
- package/.claude/skills/eldar/SKILL.md +8 -0
- package/bin/build-embeddings.mjs +6 -20
- package/bin/cli.js +5 -0
- package/bin/gate.cjs +3 -3
- 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 +59 -119
- package/bin/index-patterns.mjs +6 -24
- package/bin/index-tests.mjs +4 -23
- package/bin/lib/db-repair.mjs +4 -25
- 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 +52 -0
- package/bin/migrations/strip-context-preambles.mjs +95 -0
- package/bin/run-migrations.mjs +1 -10
- package/bin/semantic-search.mjs +11 -19
- package/bin/session-start-launcher.mjs +102 -100
- package/bin/simplify-classify.cjs +38 -17
- package/dist/src/cli/commands/daemon.js +38 -11
- package/dist/src/cli/commands/doctor-checks-coverage-truth.js +136 -0
- package/dist/src/cli/commands/doctor-checks-memory-access.js +244 -5
- 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 +30 -0
- package/dist/src/cli/commands/doctor-registry.js +14 -0
- 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 +54 -75
- package/dist/src/cli/embeddings/persistent-cache.js +44 -83
- package/dist/src/cli/init/claudemd-generator.js +4 -0
- package/dist/src/cli/init/moflo-init.js +40 -0
- package/dist/src/cli/mcp-tools/memory-tools.js +177 -32
- package/dist/src/cli/memory/bridge-core.js +256 -30
- package/dist/src/cli/memory/bridge-entries.js +76 -8
- 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 +286 -220
- 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 +6 -11
- package/dist/src/cli/services/learning-service.js +12 -20
- 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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DatabaseProvider
|
|
2
|
+
* DatabaseProvider — moflo's IMemoryBackend factory.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
4
|
+
* Phase 5 (#1084) removed the sql.js backend. Selection now collapses to:
|
|
5
|
+
* - `node-sqlite` (Node 22+ built-in, the only SQLite backend)
|
|
6
|
+
* - `rvf` (pure-TS fallback when node:sqlite is somehow unavailable)
|
|
7
|
+
* - `json` (last-resort file storage when nothing else works)
|
|
8
8
|
*
|
|
9
9
|
* @module v3/memory/database-provider
|
|
10
10
|
*/
|
|
11
11
|
import { platform } from 'node:os';
|
|
12
12
|
import { existsSync } from 'node:fs';
|
|
13
|
-
import {
|
|
13
|
+
import { SqliteBackend } from './sqlite-backend.js';
|
|
14
14
|
/**
|
|
15
15
|
* Detect platform and recommend provider
|
|
16
16
|
*/
|
|
@@ -19,8 +19,9 @@ function detectPlatform() {
|
|
|
19
19
|
const isWindows = os === 'win32';
|
|
20
20
|
const isMacOS = os === 'darwin';
|
|
21
21
|
const isLinux = os === 'linux';
|
|
22
|
-
//
|
|
23
|
-
|
|
22
|
+
// Phase 4 (#1083) flipped the default to node:sqlite (built into Node 22+,
|
|
23
|
+
// moflo's minimum). Phase 5 (#1084) deletes the sql.js backend + dep.
|
|
24
|
+
const recommendedProvider = 'node-sqlite';
|
|
24
25
|
return {
|
|
25
26
|
os,
|
|
26
27
|
isWindows,
|
|
@@ -35,55 +36,60 @@ function detectPlatform() {
|
|
|
35
36
|
async function testRvf() {
|
|
36
37
|
return true;
|
|
37
38
|
}
|
|
38
|
-
/** better-sqlite3 removed — sql.js is the only SQLite backend */
|
|
39
39
|
/**
|
|
40
|
-
* Test if
|
|
40
|
+
* Test if the built-in node:sqlite engine is available (Node 22+).
|
|
41
|
+
*
|
|
42
|
+
* Loads the suppress-sqlite-warning side-effect BEFORE the probe import so
|
|
43
|
+
* the once-per-process ExperimentalWarning never fires (#1098). A probe
|
|
44
|
+
* that prints the warning to stderr defeats every consumer's "clean
|
|
45
|
+
* startup" expectation even when the rest of the run is healthy.
|
|
41
46
|
*/
|
|
42
|
-
async function
|
|
47
|
+
async function testNodeSqlite() {
|
|
43
48
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const testDb = new SQL.Database();
|
|
47
|
-
testDb.close();
|
|
49
|
+
await import('./suppress-sqlite-warning.js');
|
|
50
|
+
await import('node:sqlite');
|
|
48
51
|
return true;
|
|
49
52
|
}
|
|
50
|
-
catch
|
|
53
|
+
catch {
|
|
51
54
|
return false;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
/**
|
|
55
|
-
* Select best available provider
|
|
58
|
+
* Select best available provider.
|
|
59
|
+
*
|
|
60
|
+
* Phase 5 (#1084) collapsed the chain — sql.js is gone, so the order is
|
|
61
|
+
* just: explicit override → node:sqlite → RVF → JSON. Passing `'sql.js'`
|
|
62
|
+
* explicitly is a hard error.
|
|
56
63
|
*/
|
|
57
64
|
async function selectProvider(preferred, verbose = false) {
|
|
65
|
+
if (preferred === 'sql.js') {
|
|
66
|
+
throw new Error(`DatabaseProvider: sql.js was removed in Phase 5 (#1084). ` +
|
|
67
|
+
`Use 'node-sqlite' (the new default) or omit the provider entirely.`);
|
|
68
|
+
}
|
|
58
69
|
if (preferred && preferred !== 'auto') {
|
|
59
70
|
if (verbose) {
|
|
60
71
|
console.log(`[DatabaseProvider] Using explicitly specified provider: ${preferred}`);
|
|
61
72
|
}
|
|
62
73
|
return preferred;
|
|
63
74
|
}
|
|
64
|
-
|
|
75
|
+
if (await testNodeSqlite()) {
|
|
76
|
+
if (verbose) {
|
|
77
|
+
console.log('[DatabaseProvider] node:sqlite available — using new default');
|
|
78
|
+
}
|
|
79
|
+
return 'node-sqlite';
|
|
80
|
+
}
|
|
81
|
+
// node:sqlite missing is the "broken install" signal — surface it whenever
|
|
82
|
+
// verbose is on so the fallback chain doesn't silently regress consumers
|
|
83
|
+
// to a slower backend without anyone noticing.
|
|
65
84
|
if (verbose) {
|
|
66
|
-
console.
|
|
67
|
-
console.log(`[DatabaseProvider] Recommended provider: ${platformInfo.recommendedProvider}`);
|
|
85
|
+
console.warn('[DatabaseProvider] node:sqlite unavailable — check Node version (22+ required); falling back to RVF');
|
|
68
86
|
}
|
|
69
|
-
// Try RVF first (always available via pure-TS fallback)
|
|
70
87
|
if (await testRvf()) {
|
|
71
|
-
if (verbose) {
|
|
72
|
-
console.log('[DatabaseProvider] RVF backend available');
|
|
73
|
-
}
|
|
74
88
|
return 'rvf';
|
|
75
89
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (verbose) {
|
|
79
|
-
console.log('[DatabaseProvider] sql.js available and working');
|
|
80
|
-
}
|
|
81
|
-
return 'sql.js';
|
|
82
|
-
}
|
|
83
|
-
else if (verbose) {
|
|
84
|
-
console.log('[DatabaseProvider] sql.js not available, using JSON fallback');
|
|
90
|
+
if (verbose) {
|
|
91
|
+
console.warn('[DatabaseProvider] node:sqlite + RVF unavailable — falling back to JSON');
|
|
85
92
|
}
|
|
86
|
-
// Final fallback to JSON
|
|
87
93
|
return 'json';
|
|
88
94
|
}
|
|
89
95
|
/**
|
|
@@ -112,7 +118,7 @@ async function selectProvider(preferred, verbose = false) {
|
|
|
112
118
|
* ```
|
|
113
119
|
*/
|
|
114
120
|
export async function createDatabase(path, options = {}) {
|
|
115
|
-
const { provider = 'auto', verbose = false, walMode = true, optimize = true, defaultNamespace = 'default', maxEntries = 1000000, autoPersistInterval = 5000, wasmPath, } = options;
|
|
121
|
+
const { provider = 'auto', verbose = false, walMode: _walMode = true, optimize = true, defaultNamespace = 'default', maxEntries = 1000000, autoPersistInterval = 5000, wasmPath: _wasmPath, } = options;
|
|
116
122
|
// Select provider
|
|
117
123
|
const selectedProvider = await selectProvider(provider, verbose);
|
|
118
124
|
if (verbose) {
|
|
@@ -122,6 +128,13 @@ export async function createDatabase(path, options = {}) {
|
|
|
122
128
|
let backend;
|
|
123
129
|
switch (selectedProvider) {
|
|
124
130
|
case 'sql.js': {
|
|
131
|
+
// selectProvider() guards against 'sql.js' as an explicit preference,
|
|
132
|
+
// but a stale caller could still land here if `auto` resolution drifted
|
|
133
|
+
// (it can't — left only for exhaustive-check safety).
|
|
134
|
+
throw new Error(`DatabaseProvider: sql.js was removed in Phase 5 (#1084). ` +
|
|
135
|
+
`This case is unreachable; if you see this error, file a bug.`);
|
|
136
|
+
}
|
|
137
|
+
case 'node-sqlite': {
|
|
125
138
|
const config = {
|
|
126
139
|
databasePath: path,
|
|
127
140
|
optimize,
|
|
@@ -129,9 +142,8 @@ export async function createDatabase(path, options = {}) {
|
|
|
129
142
|
maxEntries,
|
|
130
143
|
verbose,
|
|
131
144
|
autoPersistInterval,
|
|
132
|
-
wasmPath,
|
|
133
145
|
};
|
|
134
|
-
backend = new
|
|
146
|
+
backend = new SqliteBackend(config);
|
|
135
147
|
break;
|
|
136
148
|
}
|
|
137
149
|
case 'rvf': {
|
|
@@ -167,13 +179,18 @@ export function getPlatformInfo() {
|
|
|
167
179
|
return detectPlatform();
|
|
168
180
|
}
|
|
169
181
|
/**
|
|
170
|
-
* Check which providers are available
|
|
182
|
+
* Check which providers are available.
|
|
183
|
+
*
|
|
184
|
+
* `sqlJs` / `betterSqlite3` are retained for API stability but always
|
|
185
|
+
* report `false` — Phase 5 (#1084) deleted the sql.js backend and
|
|
186
|
+
* better-sqlite3 was never wired.
|
|
171
187
|
*/
|
|
172
188
|
export async function getAvailableProviders() {
|
|
173
189
|
return {
|
|
174
190
|
rvf: true,
|
|
175
|
-
betterSqlite3: false,
|
|
176
|
-
sqlJs:
|
|
191
|
+
betterSqlite3: false,
|
|
192
|
+
sqlJs: false,
|
|
193
|
+
nodeSqlite: await testNodeSqlite(),
|
|
177
194
|
json: true,
|
|
178
195
|
};
|
|
179
196
|
}
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as fs from 'node:fs';
|
|
18
18
|
import * as path from 'node:path';
|
|
19
|
-
import { mofloImport } from '../services/moflo-require.js';
|
|
20
19
|
import { atomicWriteFileSync } from '../services/atomic-file-write.js';
|
|
21
20
|
import { HnswLite } from './hnsw-lite.js';
|
|
22
21
|
import { parseEmbeddingJson } from './controllers/_shared.js';
|
|
23
22
|
import { hnswIndexPath } from '../services/moflo-paths.js';
|
|
23
|
+
import { openDaemonDatabase } from './daemon-backend.js';
|
|
24
24
|
/**
|
|
25
25
|
* Build an HnswLite from every active row in `dbPath` that has an embedding,
|
|
26
26
|
* then atomically write the sidecar to `<projectRoot>/.moflo/hnsw.index`.
|
|
@@ -37,13 +37,11 @@ export async function buildAndWriteHnswSidecar(dbPath, projectRoot, options = {}
|
|
|
37
37
|
if (!fs.existsSync(dbPath)) {
|
|
38
38
|
throw new Error(`buildAndWriteHnswSidecar: db not found at ${dbPath}`);
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const buf = fs.readFileSync(dbPath);
|
|
46
|
-
const db = new SQL.Database(buf);
|
|
40
|
+
// node:sqlite via the unified factory — Phase 5 (#1084) replaced the
|
|
41
|
+
// sql.js readFileSync + new SQL.Database round-trip with a direct open
|
|
42
|
+
// through openDaemonDatabase. WAL writes incrementally so there's nothing
|
|
43
|
+
// to flush back here; the sidecar persistence below is unaffected.
|
|
44
|
+
const db = openDaemonDatabase(dbPath);
|
|
47
45
|
const hnsw = new HnswLite(dimensions, m, efConstruction, metric);
|
|
48
46
|
let skipped = 0;
|
|
49
47
|
try {
|
|
@@ -61,7 +61,6 @@ export { ControllerRegistry, INIT_LEVELS } from './controller-registry.js';
|
|
|
61
61
|
export { CONTROLLER_SPECS } from './controller-specs.js';
|
|
62
62
|
// ===== Core Components =====
|
|
63
63
|
export { MofloDbAdapter } from './moflo-db-adapter.js';
|
|
64
|
-
export { SqlJsBackend } from './sqljs-backend.js';
|
|
65
64
|
export { RvfBackend } from './rvf-backend.js';
|
|
66
65
|
export { HnswLite, cosineSimilarity } from './hnsw-lite.js';
|
|
67
66
|
export { buildAndWriteHnswSidecar, tryLoadHnswSidecar } from './hnsw-persistence.js';
|
|
@@ -119,17 +119,49 @@ export async function bridgeAddToHNSW(id, embedding, entry, dbPath) {
|
|
|
119
119
|
// Bridge-produced vectors come from getBridgeEmbedder() (fastembed, 384-dim).
|
|
120
120
|
// Pre-#648 this column was hardcoded to 'Xenova/all-MiniLM-L6-v2' which
|
|
121
121
|
// misrepresented the producing model — fixed to the canonical bridge label.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
//
|
|
123
|
+
// Pre-#1067: this issued `INSERT OR REPLACE INTO memory_entries (...)` with
|
|
124
|
+
// only the embedding-related columns. When `storeEntry`'s direct-write
|
|
125
|
+
// fallback first inserted a row carrying `metadata`/`tags`/`expires_at`
|
|
126
|
+
// and then called `addToHNSWIndex(...)` for the same id, REPLACE clobbered
|
|
127
|
+
// the row — wiping the metadata column to NULL and re-introducing the
|
|
128
|
+
// "first chunk has no navigation" smoke failure even though serialiseMetadata
|
|
129
|
+
// produced the right JSON one statement earlier. UPDATE-by-id preserves the
|
|
130
|
+
// pre-existing row's metadata/tags/expires_at/access_count while still
|
|
131
|
+
// back-filling embedding columns for callers that supplied them.
|
|
132
|
+
const updated = ctx.db.prepare(`
|
|
133
|
+
UPDATE memory_entries
|
|
134
|
+
SET embedding = ?,
|
|
135
|
+
embedding_dimensions = ?,
|
|
136
|
+
embedding_model = ?,
|
|
137
|
+
updated_at = ?
|
|
138
|
+
WHERE id = ?
|
|
128
139
|
`).run([
|
|
129
|
-
id, entry.key, entry.namespace, entry.content,
|
|
130
140
|
embeddingJson, embedding.length, BRIDGE_EMBEDDING_MODEL,
|
|
131
|
-
now,
|
|
141
|
+
now, id,
|
|
132
142
|
]);
|
|
143
|
+
// If no row was matched by id (rare: a rebuild path passing an embedding
|
|
144
|
+
// for a key that never went through storeEntry first) fall back to an
|
|
145
|
+
// INSERT carrying every column the bridge can populate, so the row still
|
|
146
|
+
// becomes searchable. Metadata/tags/expires_at stay NULL because the
|
|
147
|
+
// caller didn't supply them — same shape `bridgeStoreEntry` would write
|
|
148
|
+
// for a no-metadata caller.
|
|
149
|
+
const rowCount = typeof updated.changes === 'number'
|
|
150
|
+
? updated.changes
|
|
151
|
+
: (ctx.db.getRowsModified?.() ?? 0);
|
|
152
|
+
if (rowCount === 0) {
|
|
153
|
+
ctx.db.prepare(`
|
|
154
|
+
INSERT INTO memory_entries (
|
|
155
|
+
id, key, namespace, content, type,
|
|
156
|
+
embedding, embedding_dimensions, embedding_model,
|
|
157
|
+
created_at, updated_at, status
|
|
158
|
+
) VALUES (?, ?, ?, ?, 'semantic', ?, ?, ?, ?, ?, 'active')
|
|
159
|
+
`).run([
|
|
160
|
+
id, entry.key, entry.namespace, entry.content,
|
|
161
|
+
embeddingJson, embedding.length, BRIDGE_EMBEDDING_MODEL,
|
|
162
|
+
now, now,
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
133
165
|
persistBridgeDb(ctx.db, dbPath);
|
|
134
166
|
return true;
|
|
135
167
|
});
|