moflo 4.9.37 → 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-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 +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 +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 +102 -102
- 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 +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 +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 +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-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 +269 -209
- 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
|
@@ -8,24 +8,19 @@ import { existsSync, readFileSync, statSync } from 'fs';
|
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import { memoryDbCandidatePaths } from '../services/moflo-paths.js';
|
|
10
10
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
11
|
+
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
11
12
|
/** Skew (cached / live count delta) above which the cache is treated as stale. */
|
|
12
13
|
const VECTOR_STATS_SKEW_WARN_THRESHOLD = 0.2;
|
|
13
14
|
/**
|
|
14
|
-
* Open `dbPath` via
|
|
15
|
-
* rows that have an embedding. Returns null
|
|
16
|
-
*
|
|
17
|
-
* truth", letting the caller fall back to the cached stats rather
|
|
18
|
-
* a healthy DB as broken.
|
|
15
|
+
* Open `dbPath` via the unified node:sqlite factory and return the count of
|
|
16
|
+
* memory_entries rows that have an embedding. Returns null on any error
|
|
17
|
+
* (corrupt DB, missing column, schema mismatch) — every error is treated as
|
|
18
|
+
* "unknown truth", letting the caller fall back to the cached stats rather
|
|
19
|
+
* than masking a healthy DB as broken.
|
|
19
20
|
*/
|
|
20
21
|
async function countEmbeddedRowsFromDb(dbPath) {
|
|
21
22
|
try {
|
|
22
|
-
const
|
|
23
|
-
const initSqlJs = (await mofloImport('sql.js'))?.default;
|
|
24
|
-
if (!initSqlJs)
|
|
25
|
-
return null;
|
|
26
|
-
const SQL = await initSqlJs();
|
|
27
|
-
const buffer = readFileSync(dbPath);
|
|
28
|
-
const db = new SQL.Database(buffer);
|
|
23
|
+
const db = openDaemonDatabase(dbPath);
|
|
29
24
|
try {
|
|
30
25
|
const res = db.exec("SELECT COUNT(*) FROM memory_entries WHERE embedding IS NOT NULL AND embedding != ''");
|
|
31
26
|
const cell = res?.[0]?.values?.[0]?.[0];
|
|
@@ -137,21 +132,21 @@ export async function checkEmbeddings() {
|
|
|
137
132
|
message: `Memory DB initialized (v${info.version}, vectors enabled)`,
|
|
138
133
|
};
|
|
139
134
|
}
|
|
140
|
-
catch (
|
|
141
|
-
//
|
|
142
|
-
const sqlDetail = errorDetail(
|
|
135
|
+
catch (sqliteError) {
|
|
136
|
+
// node:sqlite open / introspection failed — fall back to file-size heuristic.
|
|
137
|
+
const sqlDetail = errorDetail(sqliteError);
|
|
143
138
|
try {
|
|
144
139
|
const stats = statSync(foundDbPath);
|
|
145
140
|
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
146
141
|
return {
|
|
147
142
|
name: 'Embeddings',
|
|
148
143
|
status: 'warn',
|
|
149
|
-
message: `Memory DB exists (${sizeMB} MB) — cannot verify vectors (
|
|
150
|
-
fix: '
|
|
144
|
+
message: `Memory DB exists (${sizeMB} MB) — cannot verify vectors (DB unreadable: ${sqlDetail})`,
|
|
145
|
+
fix: 'npx moflo embeddings init',
|
|
151
146
|
};
|
|
152
147
|
}
|
|
153
148
|
catch (statError) {
|
|
154
|
-
return { name: 'Embeddings', status: 'warn', message: `Unable to check:
|
|
149
|
+
return { name: 'Embeddings', status: 'warn', message: `Unable to check: DB read failed (${sqlDetail}), stat failed (${errorDetail(statError)})` };
|
|
155
150
|
}
|
|
156
151
|
}
|
|
157
152
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Version Skew doctor check (epic #1054.S5 / #1059).
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the failure mode that silently masked the #1054 bug class for two
|
|
5
|
+
* version bumps: a long-lived daemon that survived `npm install moflo@<new>`
|
|
6
|
+
* keeps running pre-upgrade code while the on-disk package.json reads `<new>`.
|
|
7
|
+
*
|
|
8
|
+
* Distinct failure mode — NOT buried in "stale cache". Consumes the same
|
|
9
|
+
* signal the launcher acts on (`bin/session-start-launcher.mjs` section 3a-pre)
|
|
10
|
+
* via `getDaemonLockPayload` so the diagnosis and the auto-recycle path share
|
|
11
|
+
* one source of truth.
|
|
12
|
+
*
|
|
13
|
+
* @module cli/commands/doctor-checks-version-skew
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from 'fs';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
import { getDaemonLockPayload, readOwnMofloVersion } from '../services/daemon-lock.js';
|
|
18
|
+
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the installed package version from `node_modules/moflo/package.json`.
|
|
21
|
+
* Falls back to the daemon's own version (same package on consumers; same
|
|
22
|
+
* dogfood layout in this repo).
|
|
23
|
+
*/
|
|
24
|
+
function readInstalledVersion(cwd) {
|
|
25
|
+
const pkgPath = join(cwd, 'node_modules', 'moflo', 'package.json');
|
|
26
|
+
if (existsSync(pkgPath)) {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
29
|
+
if (typeof pkg.version === 'string')
|
|
30
|
+
return pkg.version;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// unreadable / malformed — fall through
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Dogfood path (this repo): no `node_modules/moflo`; read the root package
|
|
37
|
+
// via the same walker the daemon uses.
|
|
38
|
+
return readOwnMofloVersion() ?? null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Distinct doctor entry — fails when the running daemon's `version` (recorded
|
|
42
|
+
* in `.moflo/daemon.lock` by S2) does not match the installed package version.
|
|
43
|
+
*
|
|
44
|
+
* Pre-#1054 daemons have no `version` in their lock — treated as a mismatch
|
|
45
|
+
* because by construction they were launched before version publishing
|
|
46
|
+
* existed.
|
|
47
|
+
*
|
|
48
|
+
* No daemon running → pass with a neutral message (the daemon-status check
|
|
49
|
+
* already owns the "not running" diagnosis).
|
|
50
|
+
*/
|
|
51
|
+
export async function checkDaemonVersionSkew(cwd = process.cwd()) {
|
|
52
|
+
const name = 'Daemon Version Skew';
|
|
53
|
+
try {
|
|
54
|
+
const installed = readInstalledVersion(cwd);
|
|
55
|
+
if (!installed) {
|
|
56
|
+
return {
|
|
57
|
+
name,
|
|
58
|
+
status: 'warn',
|
|
59
|
+
message: 'Cannot resolve installed moflo version (no node_modules/moflo/package.json)',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const payload = getDaemonLockPayload(cwd);
|
|
63
|
+
if (!payload) {
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
status: 'pass',
|
|
67
|
+
message: `No daemon running — installed v${installed}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const observed = payload.version ?? '<pre-1054 / unknown>';
|
|
71
|
+
if (payload.version === installed) {
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
status: 'pass',
|
|
75
|
+
message: `Daemon v${observed} matches installed v${installed} (PID ${payload.pid})`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
status: 'fail',
|
|
81
|
+
message: `Daemon (PID ${payload.pid}) running v${observed} but installed package is v${installed}. ` +
|
|
82
|
+
`Stale pre-upgrade daemon — every write it makes is against pre-upgrade code paths (#1054 bug class).`,
|
|
83
|
+
fix: 'npx moflo daemon stop && npx moflo daemon start',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
return {
|
|
88
|
+
name,
|
|
89
|
+
status: 'warn',
|
|
90
|
+
message: `Unable to check version skew: ${errorDetail(e, { firstLineOnly: true })}`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=doctor-checks-version-skew.js.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writers Audit runtime doctor check (epic #1054.S5 / #1059).
|
|
3
|
+
*
|
|
4
|
+
* Runtime sibling of S1's static lint (`tests/system/moflo-db-writer-audit.test.ts`).
|
|
5
|
+
* Enumerates running node processes whose command line invokes one of the
|
|
6
|
+
* known cross-process writers (build-embeddings, migrations) and fails if any
|
|
7
|
+
* are alive while the daemon owns the lock.
|
|
8
|
+
*
|
|
9
|
+
* Why not lsof / handle.exe?
|
|
10
|
+
* - `lsof` not installed on every Linux distro and not on Windows.
|
|
11
|
+
* - `handle.exe` (Sysinternals) requires manual install.
|
|
12
|
+
* - `openfiles.exe` is disabled by default on Windows and requires a reboot to
|
|
13
|
+
* enable.
|
|
14
|
+
*
|
|
15
|
+
* Command-line signature scan is cross-platform, dependency-free, and matches
|
|
16
|
+
* the writers we actually care about — S3 ported them all to the daemon-offline
|
|
17
|
+
* pattern, so any of them being alive concurrently with the daemon is a
|
|
18
|
+
* regression of S3's wrapper logic.
|
|
19
|
+
*
|
|
20
|
+
* @module cli/commands/doctor-checks-writers-audit
|
|
21
|
+
*/
|
|
22
|
+
import { execSync } from 'child_process';
|
|
23
|
+
import { existsSync, readFileSync } from 'fs';
|
|
24
|
+
import { join } from 'path';
|
|
25
|
+
import { getDaemonLockHolder } from '../services/daemon-lock.js';
|
|
26
|
+
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
27
|
+
const SCAN_TIMEOUT_MS_WIN = 10_000;
|
|
28
|
+
const SCAN_TIMEOUT_MS_POSIX = 5_000;
|
|
29
|
+
const CMDLINE_CAPTURE_LEN = 300;
|
|
30
|
+
/**
|
|
31
|
+
* Command-line fragments that identify a CROSS-PROCESS moflo.db writer.
|
|
32
|
+
* These should never run while the daemon owns the lock (S3 wraps every
|
|
33
|
+
* invocation in an explicit daemon-stop). Indexer scripts (index-guidance,
|
|
34
|
+
* index-tests, etc.) are daemon-spawned children — they appear in
|
|
35
|
+
* `background-pids.json` and are filtered out below.
|
|
36
|
+
*/
|
|
37
|
+
const FOREIGN_WRITER_PATTERNS = [
|
|
38
|
+
/build-embeddings\.mjs/i,
|
|
39
|
+
/bin[\\\/]migrations[\\\/][^\s"']+\.mjs/i,
|
|
40
|
+
/lib[\\\/]db-repair\.mjs/i,
|
|
41
|
+
];
|
|
42
|
+
function readTrackedBackgroundPids(cwd) {
|
|
43
|
+
const result = new Set();
|
|
44
|
+
const registryFile = join(cwd, '.moflo', 'background-pids.json');
|
|
45
|
+
try {
|
|
46
|
+
if (!existsSync(registryFile))
|
|
47
|
+
return result;
|
|
48
|
+
const entries = JSON.parse(readFileSync(registryFile, 'utf-8'));
|
|
49
|
+
if (!Array.isArray(entries))
|
|
50
|
+
return result;
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry && typeof entry.pid === 'number' && entry.pid > 0) {
|
|
53
|
+
result.add(entry.pid);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch { /* unreadable — treat as empty */ }
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function enumerateNodeProcesses() {
|
|
61
|
+
try {
|
|
62
|
+
if (process.platform === 'win32') {
|
|
63
|
+
const csv = execSync('powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"Name=\'node.exe\'\\" | Select-Object ProcessId,CommandLine | ConvertTo-Csv -NoTypeInformation"', { encoding: 'utf-8', timeout: SCAN_TIMEOUT_MS_WIN, windowsHide: true });
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const line of csv.split(/\r?\n/)) {
|
|
66
|
+
const m = line.match(/^"(\d+)","?(.*?)"?$/);
|
|
67
|
+
if (!m)
|
|
68
|
+
continue;
|
|
69
|
+
const pid = parseInt(m[1], 10);
|
|
70
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
71
|
+
continue;
|
|
72
|
+
out.push({ pid, cmdline: m[2].replace(/""/g, '"').slice(0, CMDLINE_CAPTURE_LEN) });
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
// POSIX
|
|
77
|
+
const ps = execSync('ps -ww -eo pid,command', { encoding: 'utf-8', timeout: SCAN_TIMEOUT_MS_POSIX });
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const line of ps.split(/\r?\n/)) {
|
|
80
|
+
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
81
|
+
if (!m)
|
|
82
|
+
continue;
|
|
83
|
+
const cmd = m[2];
|
|
84
|
+
if (!/\bnode\b/.test(cmd))
|
|
85
|
+
continue;
|
|
86
|
+
const pid = parseInt(m[1], 10);
|
|
87
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
88
|
+
continue;
|
|
89
|
+
out.push({ pid, cmdline: cmd.slice(0, CMDLINE_CAPTURE_LEN) });
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function formatCmdline(raw, max = 80) {
|
|
98
|
+
const cleaned = raw.replace(/^"?[^"\s]*node(?:\.exe)?"?\s+/i, '').trim();
|
|
99
|
+
return cleaned.length > max ? cleaned.slice(0, max - 1) + '…' : cleaned;
|
|
100
|
+
}
|
|
101
|
+
export function findForeignWriters(procs, daemonPid, trackedPids, selfPid) {
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const p of procs) {
|
|
104
|
+
if (p.pid === selfPid)
|
|
105
|
+
continue;
|
|
106
|
+
if (p.pid === daemonPid)
|
|
107
|
+
continue;
|
|
108
|
+
if (trackedPids.has(p.pid))
|
|
109
|
+
continue;
|
|
110
|
+
for (const re of FOREIGN_WRITER_PATTERNS) {
|
|
111
|
+
const m = p.cmdline.match(re);
|
|
112
|
+
if (m) {
|
|
113
|
+
out.push({ ...p, matchedPattern: m[0] });
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Pass: daemon down (single-writer invariant has nothing to enforce) OR no
|
|
122
|
+
* foreign writers detected.
|
|
123
|
+
*
|
|
124
|
+
* Fail: daemon owns the lock AND a non-daemon, non-tracked node process is
|
|
125
|
+
* running a known cross-process writer. Lists every offender so the user can
|
|
126
|
+
* SIGKILL them by PID.
|
|
127
|
+
*/
|
|
128
|
+
export async function checkWritersAudit(cwd = process.cwd()) {
|
|
129
|
+
const name = 'Writers Audit';
|
|
130
|
+
try {
|
|
131
|
+
const daemonPid = getDaemonLockHolder(cwd);
|
|
132
|
+
if (daemonPid === null) {
|
|
133
|
+
return {
|
|
134
|
+
name,
|
|
135
|
+
status: 'pass',
|
|
136
|
+
message: 'Daemon not running — single-writer invariant trivially satisfied',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const procs = enumerateNodeProcesses();
|
|
140
|
+
const trackedPids = readTrackedBackgroundPids(cwd);
|
|
141
|
+
const foreign = findForeignWriters(procs, daemonPid, trackedPids, process.pid);
|
|
142
|
+
if (foreign.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
name,
|
|
145
|
+
status: 'pass',
|
|
146
|
+
message: `Daemon PID ${daemonPid} is sole writer; no foreign writers detected`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const detail = foreign
|
|
150
|
+
.map((p) => `pid=${p.pid} (${p.matchedPattern}): ${formatCmdline(p.cmdline)}`)
|
|
151
|
+
.join(' | ');
|
|
152
|
+
return {
|
|
153
|
+
name,
|
|
154
|
+
status: 'fail',
|
|
155
|
+
message: `${foreign.length} non-daemon writer(s) running concurrently with daemon (PID ${daemonPid}): ${detail}. ` +
|
|
156
|
+
`Each will clobber the daemon's sql.js snapshot on flush — #1054 bug class.`,
|
|
157
|
+
fix: process.platform === 'win32'
|
|
158
|
+
? `taskkill /F /PID ${foreign.map((p) => p.pid).join(' /PID ')}`
|
|
159
|
+
: `kill ${foreign.map((p) => p.pid).join(' ')}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
return {
|
|
164
|
+
name,
|
|
165
|
+
status: 'warn',
|
|
166
|
+
message: `Unable to audit writers: ${errorDetail(e, { firstLineOnly: true })}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=doctor-checks-writers-audit.js.map
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
25
25
|
import { existsSync } from 'fs';
|
|
26
26
|
import { CANONICAL_EMBEDDING_MODEL } from '../embeddings/migration/types.js';
|
|
27
|
-
import { mofloImport } from '../services/moflo-require.js';
|
|
28
27
|
import { memoryDbCandidatePaths } from '../services/moflo-paths.js';
|
|
28
|
+
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
29
29
|
/**
|
|
30
30
|
* Known neural-model labels that all share the all-MiniLM-L6-v2 384-dim
|
|
31
31
|
* vector space. The Story-2 migration retags any of these to the
|
|
@@ -59,7 +59,7 @@ export async function checkEmbeddingHygiene() {
|
|
|
59
59
|
return {
|
|
60
60
|
name: 'Embedding hygiene',
|
|
61
61
|
status: 'pass',
|
|
62
|
-
message: 'Cannot inspect memory DB (
|
|
62
|
+
message: 'Cannot inspect memory DB (open failed)',
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
if (groups.length === 0) {
|
|
@@ -127,21 +127,9 @@ function resolveMemoryDb() {
|
|
|
127
127
|
return memoryDbCandidatePaths(process.cwd()).find((p) => existsSync(p)) ?? null;
|
|
128
128
|
}
|
|
129
129
|
async function loadModelGroups(dbPath) {
|
|
130
|
-
const fs = await import('fs');
|
|
131
|
-
let initSqlJs;
|
|
132
|
-
try {
|
|
133
|
-
initSqlJs = (await mofloImport('sql.js'))?.default;
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
if (!initSqlJs)
|
|
139
|
-
return null;
|
|
140
130
|
let db;
|
|
141
131
|
try {
|
|
142
|
-
|
|
143
|
-
const buffer = fs.readFileSync(dbPath);
|
|
144
|
-
db = new SQL.Database(buffer);
|
|
132
|
+
db = openDaemonDatabase(dbPath);
|
|
145
133
|
}
|
|
146
134
|
catch {
|
|
147
135
|
return null;
|
|
@@ -169,6 +169,36 @@ export async function autoFixCheck(check) {
|
|
|
169
169
|
catch { /* best effort */ }
|
|
170
170
|
return runFixCommand('npx moflo daemon start');
|
|
171
171
|
},
|
|
172
|
+
// Epic #1054.S5 / #1059 — SIGTERM the stale daemon and let the launcher's
|
|
173
|
+
// existing respawn path (mirrored as `npx moflo daemon start`) pick up the
|
|
174
|
+
// installed-version code. Mirrors `recycleDaemon` in
|
|
175
|
+
// bin/session-start-launcher.mjs so the auto-fix matches the launcher's
|
|
176
|
+
// behavior exactly.
|
|
177
|
+
'Daemon Version Skew': async () => {
|
|
178
|
+
const cwd = process.cwd();
|
|
179
|
+
const { getDaemonLockPayload } = await import('../services/daemon-lock.js');
|
|
180
|
+
const payload = getDaemonLockPayload(cwd);
|
|
181
|
+
if (payload?.pid && payload.pid > 0) {
|
|
182
|
+
try {
|
|
183
|
+
process.kill(payload.pid, 'SIGTERM');
|
|
184
|
+
}
|
|
185
|
+
catch { /* already dead */ }
|
|
186
|
+
}
|
|
187
|
+
const lockFile = join(cwd, '.moflo', 'daemon.lock');
|
|
188
|
+
try {
|
|
189
|
+
if (existsSync(lockFile))
|
|
190
|
+
unlinkSync(lockFile);
|
|
191
|
+
}
|
|
192
|
+
catch { /* ok */ }
|
|
193
|
+
return runFixCommand('npx moflo daemon start');
|
|
194
|
+
},
|
|
195
|
+
'Embedding Coverage Truth': async () => {
|
|
196
|
+
// Same as the existing Embeddings fix — rebuild the cache by re-running
|
|
197
|
+
// the embeddings pipeline. Routes through `npx moflo` so the consumer
|
|
198
|
+
// CLI resolution stays consistent across platforms (see
|
|
199
|
+
// feedback_cross_platform_mandatory).
|
|
200
|
+
return runFixCommand('npx moflo embeddings init --force');
|
|
201
|
+
},
|
|
172
202
|
'MCP Servers': async () => {
|
|
173
203
|
return runFixCommand('claude mcp add moflo -- npx -y moflo mcp start');
|
|
174
204
|
},
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { checkSubagentHealth, checkSpellExecution, checkMcpToolInvocation, checkHookExecution, checkMcpSpellIntegration, checkGateHealth, checkHookBlockDrift, checkMofloDbBridge, } from './doctor-checks-deep.js';
|
|
8
8
|
import { checkEmbeddingHygiene } from './doctor-embedding-hygiene.js';
|
|
9
|
+
import { checkDaemonVersionSkew } from './doctor-checks-version-skew.js';
|
|
10
|
+
import { checkEmbeddingCoverageTruth } from './doctor-checks-coverage-truth.js';
|
|
11
|
+
import { checkWritersAudit } from './doctor-checks-writers-audit.js';
|
|
9
12
|
import { checkSwarmFunctional, checkHiveMindFunctional, } from './doctor-checks-swarm.js';
|
|
10
13
|
import { checkMemoryAccessFunctional } from './doctor-checks-memory-access.js';
|
|
11
14
|
import { checkBuildTools, checkClaudeCode, checkDiskSpace, checkGit, checkGitRepo, checkNodeVersion, checkNpmVersion, } from './doctor-checks-runtime.js';
|
|
@@ -33,10 +36,13 @@ export const allChecks = [
|
|
|
33
36
|
checkMofloYamlCompliance,
|
|
34
37
|
checkStatusLine,
|
|
35
38
|
checkDaemonStatus,
|
|
39
|
+
checkDaemonVersionSkew,
|
|
36
40
|
checkDaemonWriteRouting,
|
|
41
|
+
checkWritersAudit,
|
|
37
42
|
checkMemoryDatabase,
|
|
38
43
|
checkEmbeddings,
|
|
39
44
|
checkEmbeddingHygiene,
|
|
45
|
+
checkEmbeddingCoverageTruth,
|
|
40
46
|
checkTestDirs,
|
|
41
47
|
checkMcpServers,
|
|
42
48
|
checkDiskSpace,
|
|
@@ -77,11 +83,19 @@ export const componentMap = {
|
|
|
77
83
|
'statusline': checkStatusLine,
|
|
78
84
|
'status-line': checkStatusLine,
|
|
79
85
|
'daemon': checkDaemonStatus,
|
|
86
|
+
'daemon-version-skew': checkDaemonVersionSkew,
|
|
87
|
+
'version-skew': checkDaemonVersionSkew,
|
|
88
|
+
'skew': checkDaemonVersionSkew,
|
|
80
89
|
'daemon-write-routing': checkDaemonWriteRouting,
|
|
81
90
|
'write-routing': checkDaemonWriteRouting,
|
|
91
|
+
'writers-audit': checkWritersAudit,
|
|
92
|
+
'writers': checkWritersAudit,
|
|
82
93
|
'memory': checkMemoryDatabase,
|
|
83
94
|
'embeddings': checkEmbeddings,
|
|
84
95
|
'embedding-hygiene': checkEmbeddingHygiene,
|
|
96
|
+
'embedding-coverage': checkEmbeddingCoverageTruth,
|
|
97
|
+
'coverage': checkEmbeddingCoverageTruth,
|
|
98
|
+
'coverage-truth': checkEmbeddingCoverageTruth,
|
|
85
99
|
'hygiene': checkEmbeddingHygiene,
|
|
86
100
|
'git': checkGit,
|
|
87
101
|
'mcp': checkMcpServers,
|
|
@@ -40,7 +40,7 @@ export const doctorCommand = {
|
|
|
40
40
|
{
|
|
41
41
|
name: 'component',
|
|
42
42
|
short: 'c',
|
|
43
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, embeddings, git, mcp, claude, disk, typescript, semantic, intelligence, swarm, hive-mind)',
|
|
43
|
+
description: 'Check specific component (version, version-skew, node, npm, config, daemon, writers-audit, memory, embeddings, coverage-truth, git, mcp, claude, disk, typescript, semantic, intelligence, swarm, hive-mind)',
|
|
44
44
|
type: 'string',
|
|
45
45
|
},
|
|
46
46
|
{
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* Created with ❤️ by motailz.com
|
|
14
14
|
*/
|
|
15
15
|
import { output } from '../output.js';
|
|
16
|
-
import { mofloImport } from '../services/moflo-require.js';
|
|
17
16
|
import { runEmbeddingsMigrationIfNeeded } from '../services/embeddings-migration.js';
|
|
18
17
|
import { memoryDbPath, MEMORY_DB_FILE, MOFLO_DIR } from '../services/moflo-paths.js';
|
|
19
18
|
import * as embeddings from '../embeddings/index.js';
|
|
19
|
+
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
20
20
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
21
21
|
const DEFAULT_DB_PATH_FLAG = `${MOFLO_DIR}/${MEMORY_DB_FILE}`;
|
|
22
22
|
// Generate subcommand - REAL implementation
|
|
@@ -90,7 +90,7 @@ const generateCommand = {
|
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
};
|
|
93
|
-
// Search subcommand - REAL implementation using
|
|
93
|
+
// Search subcommand - REAL implementation using node:sqlite via openDaemonDatabase
|
|
94
94
|
const searchCommand = {
|
|
95
95
|
name: 'search',
|
|
96
96
|
description: 'Semantic similarity search',
|
|
@@ -131,11 +131,8 @@ const searchCommand = {
|
|
|
131
131
|
output.printInfo('Run: claude-flow memory init');
|
|
132
132
|
return { success: false, exitCode: 1 };
|
|
133
133
|
}
|
|
134
|
-
//
|
|
135
|
-
const
|
|
136
|
-
const SQL = await initSqlJs();
|
|
137
|
-
const fileBuffer = fs.readFileSync(fullDbPath);
|
|
138
|
-
const db = new SQL.Database(fileBuffer);
|
|
134
|
+
// node:sqlite via the unified factory (Phase 5 / #1084).
|
|
135
|
+
const db = openDaemonDatabase(fullDbPath);
|
|
139
136
|
const startTime = Date.now();
|
|
140
137
|
// Generate embedding for query
|
|
141
138
|
const { generateEmbedding } = await import('../memory/memory-initializer.js');
|
|
@@ -343,7 +340,7 @@ const compareCommand = {
|
|
|
343
340
|
}
|
|
344
341
|
},
|
|
345
342
|
};
|
|
346
|
-
// Collections subcommand - REAL implementation using
|
|
343
|
+
// Collections subcommand - REAL implementation using node:sqlite via openDaemonDatabase
|
|
347
344
|
const collectionsCommand = {
|
|
348
345
|
name: 'collections',
|
|
349
346
|
description: 'Manage embedding collections (namespaces)',
|
|
@@ -374,11 +371,8 @@ const collectionsCommand = {
|
|
|
374
371
|
output.writeln(output.dim('No collections yet - initialize memory first'));
|
|
375
372
|
return { success: true, data: [] };
|
|
376
373
|
}
|
|
377
|
-
//
|
|
378
|
-
const
|
|
379
|
-
const SQL = await initSqlJs();
|
|
380
|
-
const fileBuffer = fs.readFileSync(fullDbPath);
|
|
381
|
-
const db = new SQL.Database(fileBuffer);
|
|
374
|
+
// node:sqlite via the unified factory (Phase 5 / #1084).
|
|
375
|
+
const db = openDaemonDatabase(fullDbPath);
|
|
382
376
|
// Get collection stats from database
|
|
383
377
|
const statsQuery = db.exec(`
|
|
384
378
|
SELECT
|
|
@@ -1159,17 +1153,18 @@ const cacheCommand = {
|
|
|
1159
1153
|
else {
|
|
1160
1154
|
sqliteSize = `${sizeBytes} B`;
|
|
1161
1155
|
}
|
|
1162
|
-
// Try to count real entries via
|
|
1156
|
+
// Try to count real entries via node:sqlite
|
|
1163
1157
|
try {
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1158
|
+
const db = openDaemonDatabase(resolvedDbPath);
|
|
1159
|
+
try {
|
|
1160
|
+
const result = db.exec('SELECT COUNT(*) as count FROM embeddings');
|
|
1161
|
+
if (result.length > 0 && result[0].values.length > 0) {
|
|
1162
|
+
sqliteEntries = result[0].values[0][0];
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
finally {
|
|
1166
|
+
db.close();
|
|
1171
1167
|
}
|
|
1172
|
-
db.close();
|
|
1173
1168
|
}
|
|
1174
1169
|
catch {
|
|
1175
1170
|
// Estimate entries from file size (~1600 bytes per entry for 384-dim embeddings)
|
|
@@ -7,8 +7,7 @@ import * as pathModule from 'path';
|
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { select, confirm, input } from '../prompt.js';
|
|
9
9
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
10
|
-
import {
|
|
11
|
-
import { atomicWriteFileSync } from '../services/atomic-file-write.js';
|
|
10
|
+
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
12
11
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
13
12
|
// Memory backends
|
|
14
13
|
const BACKENDS = [
|
|
@@ -1265,23 +1264,10 @@ const initMemoryCommand = {
|
|
|
1265
1264
|
const DB_FILENAME = 'memory.db';
|
|
1266
1265
|
const SWARM_DIR = '.swarm';
|
|
1267
1266
|
async function openDb(cwd) {
|
|
1268
|
-
const fs = await import('fs');
|
|
1269
1267
|
const path = await import('path');
|
|
1270
|
-
const initSqlJs = (await mofloImport('sql.js')).default;
|
|
1271
|
-
const SQL = await initSqlJs();
|
|
1272
1268
|
const dbPath = path.join(cwd, SWARM_DIR, DB_FILENAME);
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1276
|
-
}
|
|
1277
|
-
let db;
|
|
1278
|
-
if (fs.existsSync(dbPath)) {
|
|
1279
|
-
const buffer = fs.readFileSync(dbPath);
|
|
1280
|
-
db = new SQL.Database(buffer);
|
|
1281
|
-
}
|
|
1282
|
-
else {
|
|
1283
|
-
db = new SQL.Database();
|
|
1284
|
-
}
|
|
1269
|
+
// openDaemonDatabase ensures the parent directory exists and applies WAL.
|
|
1270
|
+
const db = openDaemonDatabase(dbPath);
|
|
1285
1271
|
// Ensure table exists
|
|
1286
1272
|
db.run(`
|
|
1287
1273
|
CREATE TABLE IF NOT EXISTS memory_entries (
|
|
@@ -1307,10 +1293,14 @@ async function openDb(cwd) {
|
|
|
1307
1293
|
`);
|
|
1308
1294
|
db.run(`CREATE INDEX IF NOT EXISTS idx_memory_key_ns ON memory_entries(key, namespace)`);
|
|
1309
1295
|
db.run(`CREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_entries(namespace)`);
|
|
1310
|
-
return { db, dbPath
|
|
1296
|
+
return { db, dbPath };
|
|
1311
1297
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Close the DB handle. node:sqlite + WAL has already persisted every prior
|
|
1300
|
+
* `db.run` incrementally — the explicit atomicWriteFileSync sql.js used to
|
|
1301
|
+
* need is gone (Phase 5 / #1084).
|
|
1302
|
+
*/
|
|
1303
|
+
function saveAndCloseDb(db, _dbPath) {
|
|
1314
1304
|
db.close();
|
|
1315
1305
|
}
|
|
1316
1306
|
function batchGenerateId() {
|
|
@@ -1937,9 +1927,9 @@ const rebuildIndexCommand = {
|
|
|
1937
1927
|
}
|
|
1938
1928
|
failed++;
|
|
1939
1929
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1930
|
+
// node:sqlite + WAL persists each db.run incrementally — the
|
|
1931
|
+
// periodic batch flush sql.js needed here was the export-+-rewrite
|
|
1932
|
+
// pattern Phase 5 (#1084) killed. No flush needed.
|
|
1943
1933
|
}
|
|
1944
1934
|
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1945
1935
|
// Final stats
|