@claude-flow/cli 3.30.1 → 3.30.2

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.0
1
+ 3.30.1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.30.1",
3
+ "version": "3.30.2",
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": "6Dw13BYh3EAjBOefodVYha+5dpmcAxralqJIfT07xi9WUvhtDqwISpOVRWHMSdHM+XL1YGRh8I071X1XDB3zDw==",
11
+ "signature": "8F6VOTqOQJWReV9dx8QTOaGZ9A9wz1ZXjfrWM/4Gdr3t7cAZeGboa/DWseGUpt8zpyrOoYkuomJBdSC05B/zBA==",
12
12
  "algorithm": "ed25519"
13
13
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 1,
4
- "generatedAt": "2026-07-14T20:11:46.876Z",
5
- "gitSha": "5fa2a3ce",
4
+ "generatedAt": "2026-07-14T20:51:31.912Z",
5
+ "gitSha": "5fb50348",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 387,
@@ -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': checkMemoryDatabase,
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
- checksToRun = [componentMap[component]];
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.1",
3
+ "version": "3.30.2",
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",