@claude-flow/cli 3.30.1 → 3.30.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
3.30.
|
|
1
|
+
3.30.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.30.
|
|
3
|
+
"version": "3.30.3",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "e3e1033b24704992ddef6b31c7fa9dd7fcd9e1af7935dd77ef73402b916b31e6",
|
|
6
6
|
"hook-handler.cjs": "dc926a1a585abac85941347894632a800a9c4394e99166726017643e5b3c5950",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"statusline.cjs": "7d7bdee732e1c75b863e409afb86ce0d712f4919245e990d806da6752015bffa"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "ZRtXSuItCR6KfO1d+F5U8uguarmYnZVUlqSCi55EIEGj+aD3WMYd0dlUKLrQ89+ZbBCWEThmK5ZuXT4RgxSCBg==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -247,6 +247,244 @@ async function checkMemoryDatabase() {
|
|
|
247
247
|
}
|
|
248
248
|
return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'claude-flow memory configure --backend hybrid' };
|
|
249
249
|
}
|
|
250
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
251
|
+
// #2677 — memory doctor: functional checks (stuinfla)
|
|
252
|
+
//
|
|
253
|
+
// The existing `checkMemoryDatabase` above asserts existence + statability
|
|
254
|
+
// only, so it CANNOT distinguish a healthy DB from a 99.97%-empty or
|
|
255
|
+
// SQLite-malformed one. Stuinfla reported both cases live (81-store fleet).
|
|
256
|
+
// The three checks below layer functional assertions on top, ordered so
|
|
257
|
+
// the earliest chain-break is always the first red the user sees:
|
|
258
|
+
// 1. Integrity — can sql.js open it AND does PRAGMA integrity_check pass?
|
|
259
|
+
// 2. Content — do most memory_entries rows carry non-empty content?
|
|
260
|
+
// 3. Embedding coverage — do most rows have a vector? (unembedded rows are
|
|
261
|
+
// both unrecallable AND undistillable per ADR-174)
|
|
262
|
+
// Ordering matters: content ratio is meaningless on a DB that can't open;
|
|
263
|
+
// embedding coverage is meaningless on rows with no content. First red wins.
|
|
264
|
+
//
|
|
265
|
+
// Recall probe (stuinfla check 4) requires actual write+search+delete round
|
|
266
|
+
// trips through the CLI's own memory pipeline — deferred to a follow-up PR
|
|
267
|
+
// to keep this one purely additive and safe.
|
|
268
|
+
//
|
|
269
|
+
// Design rules (also from stuinfla's report):
|
|
270
|
+
// - "A check that cannot fail protects nothing" — every check has a
|
|
271
|
+
// demonstrable red state.
|
|
272
|
+
// - "UNKNOWN is never PASS" — if the check can't RUN, report warn/fail,
|
|
273
|
+
// never a reassuring pass. Encrypted-DB case gets warn with the caveat
|
|
274
|
+
// spelled out; corruption gets fail.
|
|
275
|
+
// - "Print the measurement, not a checkmark" — messages include the
|
|
276
|
+
// ratios so operators see the shape of the problem.
|
|
277
|
+
/** Resolve the same DB path checkMemoryDatabase above uses. Returns null
|
|
278
|
+
* if no candidate exists (in which case none of these checks should
|
|
279
|
+
* fire — checkMemoryDatabase will already have surfaced the missing DB). */
|
|
280
|
+
async function resolveMemoryDbPath() {
|
|
281
|
+
const candidates = [];
|
|
282
|
+
try {
|
|
283
|
+
const { getMemoryRoot } = await import('../memory/memory-initializer.js');
|
|
284
|
+
candidates.push(join(getMemoryRoot(), 'memory.db'));
|
|
285
|
+
}
|
|
286
|
+
catch { /* fall through to legacy candidates */ }
|
|
287
|
+
candidates.push('.swarm/memory.db', '.claude-flow/memory.db', 'data/memory/memory.db', 'data/memory.db');
|
|
288
|
+
for (const p of candidates)
|
|
289
|
+
if (existsSync(p))
|
|
290
|
+
return p;
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
/** Open a sql.js Database over an on-disk file, returning null when the
|
|
294
|
+
* file can't be opened as a SQLite database (encrypted / corrupted / not
|
|
295
|
+
* a database). Callers decide whether that's warn or fail. */
|
|
296
|
+
async function tryOpenSqlJs(dbPath) {
|
|
297
|
+
try {
|
|
298
|
+
const initSqlJs = (await import('sql.js')).default ?? (await import('sql.js'));
|
|
299
|
+
const SQL = await (typeof initSqlJs === 'function' ? initSqlJs() : initSqlJs.default());
|
|
300
|
+
const { readFileSync } = await import('fs');
|
|
301
|
+
const buf = readFileSync(dbPath);
|
|
302
|
+
return new SQL.Database(new Uint8Array(buf));
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Check 1 — sql.js can open it AND PRAGMA integrity_check returns 'ok'.
|
|
309
|
+
// Two fail modes handled distinctly per "UNKNOWN is never PASS":
|
|
310
|
+
// - Open fails: warn ("cannot open; encrypted DB or corrupt — doctor
|
|
311
|
+
// can't distinguish from this side")
|
|
312
|
+
// - Open succeeds but pragma != 'ok': fail (definite corruption)
|
|
313
|
+
async function checkMemoryIntegrity() {
|
|
314
|
+
const dbPath = await resolveMemoryDbPath();
|
|
315
|
+
if (!dbPath)
|
|
316
|
+
return { name: 'Memory Integrity', status: 'warn', message: 'no memory.db found (see Memory Database check above)' };
|
|
317
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
318
|
+
if (!db) {
|
|
319
|
+
return {
|
|
320
|
+
name: 'Memory Integrity',
|
|
321
|
+
status: 'warn',
|
|
322
|
+
message: `${dbPath} — sql.js can't open (encrypted DB or corrupt; doctor can't tell which from outside)`,
|
|
323
|
+
fix: 'if encrypted: expected. if not: back up + `claude-flow memory init --force` to rebuild',
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
const res = db.exec('PRAGMA integrity_check');
|
|
328
|
+
const rows = res[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
329
|
+
if (rows.length === 1 && rows[0] === 'ok') {
|
|
330
|
+
return { name: 'Memory Integrity', status: 'pass', message: `${dbPath} — PRAGMA integrity_check: ok` };
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
name: 'Memory Integrity',
|
|
334
|
+
status: 'fail',
|
|
335
|
+
message: `${dbPath} — PRAGMA integrity_check: ${rows.slice(0, 3).join('; ')}${rows.length > 3 ? ` (+${rows.length - 3} more)` : ''}`,
|
|
336
|
+
fix: 'back up .swarm/memory.db then `claude-flow memory init --force`',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
const msg = e.message || String(e);
|
|
341
|
+
const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
|
|
342
|
+
return {
|
|
343
|
+
name: 'Memory Integrity',
|
|
344
|
+
status: 'warn',
|
|
345
|
+
message: encryptedOrCorrupt
|
|
346
|
+
? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
|
|
347
|
+
: `${dbPath} — probe threw: ${msg}`,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
try {
|
|
352
|
+
db.close();
|
|
353
|
+
}
|
|
354
|
+
catch { /* best-effort */ }
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Check 2 — memory_entries rows should mostly carry non-empty content.
|
|
358
|
+
// Stuinfla's live case: 11,133 rows, 3 with content (0.03%). Threshold 95%
|
|
359
|
+
// is the number he proposed. Values below → fail with the exact ratio in
|
|
360
|
+
// the message ("Print the measurement, not a checkmark").
|
|
361
|
+
async function checkMemoryContent() {
|
|
362
|
+
const dbPath = await resolveMemoryDbPath();
|
|
363
|
+
if (!dbPath)
|
|
364
|
+
return { name: 'Memory Content', status: 'warn', message: 'no memory.db found' };
|
|
365
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
366
|
+
if (!db)
|
|
367
|
+
return { name: 'Memory Content', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
|
|
368
|
+
try {
|
|
369
|
+
const tables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries'")[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
370
|
+
if (tables.length === 0)
|
|
371
|
+
return { name: 'Memory Content', status: 'warn', message: 'no memory_entries table in DB — schema mismatch or empty init' };
|
|
372
|
+
const r = db.exec("SELECT count(*), sum(CASE WHEN length(trim(coalesce(content,'')))>0 THEN 1 ELSE 0 END) FROM memory_entries");
|
|
373
|
+
const total = Number(r[0]?.values?.[0]?.[0] ?? 0);
|
|
374
|
+
const populated = Number(r[0]?.values?.[0]?.[1] ?? 0);
|
|
375
|
+
if (total === 0)
|
|
376
|
+
return { name: 'Memory Content', status: 'pass', message: `${dbPath} — 0 rows (fresh DB, expected)` };
|
|
377
|
+
const ratio = populated / total;
|
|
378
|
+
const pct = (ratio * 100).toFixed(2);
|
|
379
|
+
const detail = `content ${populated}/${total} (${pct}%)`;
|
|
380
|
+
if (ratio < 0.95) {
|
|
381
|
+
return {
|
|
382
|
+
name: 'Memory Content',
|
|
383
|
+
status: 'fail',
|
|
384
|
+
message: `${dbPath} — ${detail} below 95% floor`,
|
|
385
|
+
fix: 'schema drift likely (rename of value→content or similar). check migration state via `claude-flow migrate status`',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return { name: 'Memory Content', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
const msg = e.message || String(e);
|
|
392
|
+
const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
|
|
393
|
+
return {
|
|
394
|
+
name: 'Memory Content',
|
|
395
|
+
status: 'warn',
|
|
396
|
+
message: encryptedOrCorrupt
|
|
397
|
+
? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
|
|
398
|
+
: `${dbPath} — probe threw: ${msg}`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
try {
|
|
403
|
+
db.close();
|
|
404
|
+
}
|
|
405
|
+
catch { /* best-effort */ }
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Check 3 — most memory_entries with content should also carry an embedding
|
|
409
|
+
// vector. Rows without a vector are BOTH unrecallable (no similarity search
|
|
410
|
+
// can find them) AND undistillable (ADR-174's distill skips rows with no
|
|
411
|
+
// parseable vector). Same 95% threshold + fail-with-ratio pattern as check 2.
|
|
412
|
+
//
|
|
413
|
+
// Embedding storage varies across ruflo installs (agentdb migrations, HNSW
|
|
414
|
+
// index vs inline vector column). Discovers the shape via schema probes,
|
|
415
|
+
// falls back to warn if the schema is unrecognized (better than a false
|
|
416
|
+
// pass, per "UNKNOWN is never PASS").
|
|
417
|
+
async function checkMemoryEmbeddingCoverage() {
|
|
418
|
+
const dbPath = await resolveMemoryDbPath();
|
|
419
|
+
if (!dbPath)
|
|
420
|
+
return { name: 'Memory Embedding Coverage', status: 'warn', message: 'no memory.db found' };
|
|
421
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
422
|
+
if (!db)
|
|
423
|
+
return { name: 'Memory Embedding Coverage', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
|
|
424
|
+
try {
|
|
425
|
+
// Schema-shape discovery. Three candidate columns / tables we've seen
|
|
426
|
+
// across the agentdb / ruvector history — first match wins.
|
|
427
|
+
const cols = db.exec("PRAGMA table_info(memory_entries)");
|
|
428
|
+
const colNames = new Set((cols[0]?.values ?? []).map((v) => String(v[1])));
|
|
429
|
+
let vectorPredicate = null;
|
|
430
|
+
if (colNames.has('embedding'))
|
|
431
|
+
vectorPredicate = "embedding IS NOT NULL AND length(embedding) > 0";
|
|
432
|
+
else if (colNames.has('vector'))
|
|
433
|
+
vectorPredicate = "vector IS NOT NULL AND length(vector) > 0";
|
|
434
|
+
if (!vectorPredicate) {
|
|
435
|
+
const otherTables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('vector_indexes','embeddings','memory_embeddings')")[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
436
|
+
if (otherTables.length === 0) {
|
|
437
|
+
return {
|
|
438
|
+
name: 'Memory Embedding Coverage',
|
|
439
|
+
status: 'warn',
|
|
440
|
+
message: 'no embedding column/table recognized (agentdb schema mismatch or older format) — doctor cannot measure',
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const tbl = otherTables[0];
|
|
444
|
+
const r = db.exec(`SELECT count(*) FROM memory_entries m WHERE EXISTS (SELECT 1 FROM ${tbl} e WHERE e.memory_id = m.id OR e.entry_id = m.id OR e.id = m.id)`);
|
|
445
|
+
const withEmbedding = Number(r[0]?.values?.[0]?.[0] ?? 0);
|
|
446
|
+
const total = Number(db.exec("SELECT count(*) FROM memory_entries WHERE length(trim(coalesce(content,'')))>0")[0]?.values?.[0]?.[0] ?? 0);
|
|
447
|
+
if (total === 0)
|
|
448
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — 0 content rows (nothing to embed)` };
|
|
449
|
+
const ratio = withEmbedding / total;
|
|
450
|
+
const pct = (ratio * 100).toFixed(2);
|
|
451
|
+
const detail = `embedded ${withEmbedding}/${total} (${pct}%) via ${tbl}`;
|
|
452
|
+
if (ratio < 0.95)
|
|
453
|
+
return { name: 'Memory Embedding Coverage', status: 'fail', message: `${dbPath} — ${detail} below 95% floor`, fix: 'unembedded rows are unrecallable + undistillable — re-run `claude-flow memory embed --namespace <name>` for populated namespaces' };
|
|
454
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
455
|
+
}
|
|
456
|
+
// Inline embedding column
|
|
457
|
+
const r = db.exec(`SELECT count(*), sum(CASE WHEN ${vectorPredicate} THEN 1 ELSE 0 END) FROM memory_entries WHERE length(trim(coalesce(content,'')))>0`);
|
|
458
|
+
const total = Number(r[0]?.values?.[0]?.[0] ?? 0);
|
|
459
|
+
const withEmb = Number(r[0]?.values?.[0]?.[1] ?? 0);
|
|
460
|
+
if (total === 0)
|
|
461
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — 0 content rows (nothing to embed)` };
|
|
462
|
+
const ratio = withEmb / total;
|
|
463
|
+
const pct = (ratio * 100).toFixed(2);
|
|
464
|
+
const detail = `embedded ${withEmb}/${total} (${pct}%)`;
|
|
465
|
+
if (ratio < 0.95) {
|
|
466
|
+
return { name: 'Memory Embedding Coverage', status: 'fail', message: `${dbPath} — ${detail} below 95% floor`, fix: 'unembedded rows are unrecallable + undistillable — re-run `claude-flow memory embed --namespace <name>` for populated namespaces' };
|
|
467
|
+
}
|
|
468
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
469
|
+
}
|
|
470
|
+
catch (e) {
|
|
471
|
+
const msg = e.message || String(e);
|
|
472
|
+
const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
|
|
473
|
+
return {
|
|
474
|
+
name: 'Memory Embedding Coverage',
|
|
475
|
+
status: 'warn',
|
|
476
|
+
message: encryptedOrCorrupt
|
|
477
|
+
? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
|
|
478
|
+
: `${dbPath} — probe threw: ${msg}`,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
try {
|
|
483
|
+
db.close();
|
|
484
|
+
}
|
|
485
|
+
catch { /* best-effort */ }
|
|
486
|
+
}
|
|
487
|
+
}
|
|
250
488
|
// #2545: Check that the self-learning bridge can actually load @claude-flow/memory
|
|
251
489
|
// the SAME way the SessionStart auto-memory hook does. On the documented `npx ruflo`
|
|
252
490
|
// path the package lands in the npx cache — unreachable from the project — so the
|
|
@@ -1210,6 +1448,13 @@ export const doctorCommand = {
|
|
|
1210
1448
|
checkFunnel, // ADR-305 — effective funnel state + deciding precedence source
|
|
1211
1449
|
checkProxy, // ADR-313 — Meta LLM Proxy sponsored-downtime health
|
|
1212
1450
|
];
|
|
1451
|
+
// #2677: `--component memory` now runs the whole memory-health suite,
|
|
1452
|
+
// not just the existence check. Values can be a single check or an
|
|
1453
|
+
// array — expanded at execution time. Stuinfla's report showed the
|
|
1454
|
+
// existence-only check reporting PASS on a 99.97%-empty and even a
|
|
1455
|
+
// SQLite-malformed DB; the array here layers integrity → content →
|
|
1456
|
+
// embedding coverage over the existing existence probe, ordered so
|
|
1457
|
+
// the earliest chain-break is always the first red the user sees.
|
|
1213
1458
|
const componentMap = {
|
|
1214
1459
|
'version': checkVersionFreshness,
|
|
1215
1460
|
'freshness': checkVersionFreshness,
|
|
@@ -1219,7 +1464,12 @@ export const doctorCommand = {
|
|
|
1219
1464
|
'config': checkConfigFile,
|
|
1220
1465
|
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
1221
1466
|
'daemon': checkDaemonStatus,
|
|
1222
|
-
'memory':
|
|
1467
|
+
'memory': [
|
|
1468
|
+
checkMemoryDatabase, // existing: exists + statable (unchanged)
|
|
1469
|
+
checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
|
|
1470
|
+
checkMemoryContent, // #2677 check 2: memory_entries content coverage
|
|
1471
|
+
checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
|
|
1472
|
+
],
|
|
1223
1473
|
'learning': checkLearningBridge, // #2545
|
|
1224
1474
|
'learning-bridge': checkLearningBridge, // #2545
|
|
1225
1475
|
'api': checkApiKeys,
|
|
@@ -1238,7 +1488,8 @@ export const doctorCommand = {
|
|
|
1238
1488
|
};
|
|
1239
1489
|
let checksToRun = allChecks;
|
|
1240
1490
|
if (component && componentMap[component]) {
|
|
1241
|
-
|
|
1491
|
+
const entry = componentMap[component];
|
|
1492
|
+
checksToRun = Array.isArray(entry) ? entry : [entry];
|
|
1242
1493
|
}
|
|
1243
1494
|
const results = [];
|
|
1244
1495
|
const fixes = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.30.
|
|
3
|
+
"version": "3.30.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|