@claude-flow/cli 3.41.0 → 3.41.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,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.41.0",
3
+ "version": "3.41.2",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
6
6
  "hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
@@ -8,6 +8,6 @@
8
8
  "statusline.cjs": "4a48353b4f1566fa4379b00fd0321b6676a22b6cc91fbbd8380ac5183d619468"
9
9
  }
10
10
  },
11
- "signature": "lJIU7TlbDO4FdWAceZ1eddxBvAn8zCSnrN+SNhhSpbeDv4jFgUWHFkYj27+O1DGRRDoebonbSRLrzbcWP9hHCQ==",
11
+ "signature": "cmz1Yt39LkaWY+8JH1BOtqD27A3TYoFbpHnMGTpKZDzpuhgq1RQCF9P90z1yrnnStOSX0e01meDsHeRoGQgtAA==",
12
12
  "algorithm": "ed25519"
13
13
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 6,
4
- "generatedAt": "2026-09-10T11:59:15.567Z",
5
- "gitSha": "cb10bf61",
4
+ "generatedAt": "2026-09-10T19:46:29.600Z",
5
+ "gitSha": "df87b0db",
6
6
  "catalog": {
7
7
  "agents": 167,
8
8
  "tools": 418,
@@ -7,6 +7,25 @@ import { select, confirm, input } from '../prompt.js';
7
7
  import { callMCPTool, MCPClientError } from '../mcp-client.js';
8
8
  import { distillCommand } from './memory-distill.js';
9
9
  import { backupCommand } from './memory-backup.js';
10
+ import { countSiblingStoreRows } from '../memory/sibling-store.js';
11
+ import { resolveDbPath } from '../memory/memory-initializer.js';
12
+ /**
13
+ * #3228: a miss in one store is not a miss in the memory.
14
+ *
15
+ * 3.41.1 disclosed the sibling AgentDB store from `memory list` only. `retrieve`,
16
+ * `search` and `stats` kept reporting a clean negative — "Key not found", "No
17
+ * results" — while the rows sat in the file this interface does not read. On the
18
+ * reported Windows install that is 31,673 rows in the other store answering
19
+ * `found:false`. A confident negative is worse than an error, because nothing
20
+ * prompts anyone to look further.
21
+ */
22
+ async function warnIfSiblingHasRows(pathFlag) {
23
+ const unread = await countSiblingStoreRows(resolveDbPath(pathFlag));
24
+ if (unread && unread.rows > 0) {
25
+ output.printWarning(`This read covered one store. ${unread.rows} entries are in ${unread.path} and were not searched. ` +
26
+ `That store is written by the MCP/AgentDB path; read it with --path ${unread.path}.`);
27
+ }
28
+ }
10
29
  // Memory backends
11
30
  const BACKENDS = [
12
31
  { value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing (150x-12,500x faster)' },
@@ -285,6 +304,7 @@ const retrieveCommand = {
285
304
  }
286
305
  if (!result.found || !result.entry) {
287
306
  output.printWarning(`Key not found: ${key}`);
307
+ await warnIfSiblingHasRows(ctx.flags.path);
288
308
  return { success: false, exitCode: 1, data: { key, found: false } };
289
309
  }
290
310
  const entry = result.entry;
@@ -665,6 +685,7 @@ const searchCommand = {
665
685
  output.writeln();
666
686
  if (results.length === 0) {
667
687
  output.printWarning('No results found');
688
+ await warnIfSiblingHasRows(ctx.flags.path);
668
689
  output.writeln(output.dim('Try: claude-flow memory store -k "key" --value "data"'));
669
690
  return { success: true, data: [] };
670
691
  }
@@ -744,6 +765,7 @@ const listCommand = {
744
765
  output.writeln();
745
766
  if (entries.length === 0) {
746
767
  output.printWarning('No entries found');
768
+ await warnIfSiblingHasRows(ctx.flags.path);
747
769
  output.printInfo('Store data: claude-flow memory store -k "key" --value "data"');
748
770
  return { success: true, data: [] };
749
771
  }
@@ -760,6 +782,15 @@ const listCommand = {
760
782
  });
761
783
  output.writeln();
762
784
  output.printInfo(`Showing ${entries.length} of ${listResult.total} entries`);
785
+ // #3196: AgentDB owns a sibling store next to this one. `total` counts only
786
+ // the file we read, so a bare count reads as "this is everything" while rows
787
+ // sit unreadable next door. Silence would be recoverable; a confident wrong
788
+ // total is not, because nothing prompts anyone to look further.
789
+ const unread = await countSiblingStoreRows(resolveDbPath(ctx.flags.path));
790
+ if (unread && unread.rows > 0) {
791
+ output.printWarning(`${unread.rows} more entries are in ${unread.path} and were not read here. ` +
792
+ `That store is written by the MCP/AgentDB path; read it with --path ${unread.path}.`);
793
+ }
763
794
  return { success: true, data: listResult.entries };
764
795
  }
765
796
  catch (error) {
@@ -34,6 +34,10 @@ export declare function shouldDisableNativeBridge(platform?: NodeJS.Platform, en
34
34
  * noise. Suppress the banners, keep the bad news.
35
35
  */
36
36
  export declare function shouldSuppressInitLog(msg: string): boolean;
37
+ /** Test seam: forget cached registries so a test can exercise a fresh open. */
38
+ export declare function _resetRegistryCacheForTest(): void;
39
+ /** #3196: the sibling store AgentDB owns next to a given sql.js database. */
40
+ export declare function siblingAgentDbPath(dbPath: string): string | null;
37
41
  /**
38
42
  * Create/migrate the bridge's `memory_entries` table on `db`.
39
43
  *
@@ -19,9 +19,33 @@
19
19
  import * as path from 'path';
20
20
  import * as crypto from 'crypto';
21
21
  import { createRequire } from 'node:module';
22
- // ===== Lazy singleton =====
23
- let registryPromise = null;
24
- let registryInstance = null;
22
+ // ===== Lazy registry cache, keyed by database path =====
23
+ /**
24
+ * #3196: this cache is keyed by resolved database path, and that is the whole
25
+ * point of it.
26
+ *
27
+ * It used to be a single global instance. The first caller to touch the bridge
28
+ * decided which file the process would use, and every later caller's explicit
29
+ * `dbPath` was accepted and then silently ignored — `getRegistry()` returned the
30
+ * already-built instance without ever comparing paths. A `memory store --path A`
31
+ * following an MCP write therefore read and wrote B, reported success, and left
32
+ * two valid corpora that neither interface could see whole.
33
+ *
34
+ * Keying by path makes an explicit path mean what it says. Two paths in one
35
+ * process are two registries, which is the behaviour the CLI's `--path` flag and
36
+ * `CLAUDE_FLOW_DB_PATH` have always advertised.
37
+ */
38
+ const registryPromises = new Map();
39
+ const registryInstances = new Map();
40
+ /**
41
+ * Test seam: when set, every path resolves to this registry.
42
+ *
43
+ * Kept separate from the path cache on purpose. A test installs a fake registry
44
+ * and then calls a bridge function with its own temp `dbPath`; keying the
45
+ * override by path would mean the seam only worked for callers that happened to
46
+ * pass the same path the seam guessed, which is how #2968's fixture broke.
47
+ */
48
+ let testRegistryOverride = null;
25
49
  let bridgeAvailable = null;
26
50
  // #2652/#2120: rows created before the status column existed receive NULL
27
51
  // during migration. They are live rows, not tombstones. Every user-facing
@@ -172,10 +196,18 @@ async function getRegistry(dbPath) {
172
196
  : 'AgentDB native bridge disabled by CLAUDE_FLOW_DISABLE_BRIDGE=1';
173
197
  return null;
174
198
  }
199
+ if (testRegistryOverride)
200
+ return testRegistryOverride;
175
201
  if (bridgeAvailable === false)
176
202
  return null;
177
- if (registryInstance)
178
- return registryInstance;
203
+ // Resolve first, then cache on the resolved value: `undefined`, a relative
204
+ // path and its absolute form must not become three different registries over
205
+ // the same file.
206
+ const resolvedPath = dbPath ? path.resolve(dbPath) : getAgentDbPath();
207
+ const cached = registryInstances.get(resolvedPath);
208
+ if (cached)
209
+ return cached;
210
+ let registryPromise = registryPromises.get(resolvedPath);
179
211
  if (!registryPromise) {
180
212
  registryPromise = (async () => {
181
213
  try {
@@ -193,7 +225,7 @@ async function getRegistry(dbPath) {
193
225
  try {
194
226
  await registry.initialize({
195
227
  // #2786: use agentdb-memory.db (plaintext) so native better-sqlite3 doesn't hit the encrypted memory.db.
196
- dbPath: dbPath || getAgentDbPath(),
228
+ dbPath: resolvedPath,
197
229
  embeddingModel: 'Xenova/all-MiniLM-L6-v2',
198
230
  dimension: 384,
199
231
  vectorBackend: 'auto',
@@ -440,7 +472,7 @@ async function getRegistry(dbPath) {
440
472
  catch {
441
473
  // Top-level catch — registry stays usable even if post-init wiring fails wholesale.
442
474
  }
443
- registryInstance = registry;
475
+ registryInstances.set(resolvedPath, registry);
444
476
  bridgeAvailable = true;
445
477
  bridgeFailureReason = null;
446
478
  return registry;
@@ -451,13 +483,29 @@ async function getRegistry(dbPath) {
451
483
  // makes the resulting sql.js-fallback refusal undiagnosable.
452
484
  bridgeFailureReason = err instanceof Error ? err.message : String(err);
453
485
  bridgeAvailable = false;
454
- registryPromise = null;
486
+ registryPromises.delete(resolvedPath);
455
487
  return null;
456
488
  }
457
489
  })();
490
+ registryPromises.set(resolvedPath, registryPromise);
458
491
  }
459
492
  return registryPromise;
460
493
  }
494
+ /** Test seam: forget cached registries so a test can exercise a fresh open. */
495
+ export function _resetRegistryCacheForTest() {
496
+ registryPromises.clear();
497
+ registryInstances.clear();
498
+ testRegistryOverride = null;
499
+ bridgeAvailable = null;
500
+ bridgeFailureReason = null;
501
+ }
502
+ /** #3196: the sibling store AgentDB owns next to a given sql.js database. */
503
+ export function siblingAgentDbPath(dbPath) {
504
+ if (!dbPath || dbPath === ':memory:')
505
+ return null;
506
+ const sibling = path.join(path.dirname(path.resolve(dbPath)), 'agentdb-memory.db');
507
+ return path.resolve(dbPath) === sibling ? null : sibling;
508
+ }
461
509
  // ===== Phase 2: BM25 hybrid scoring =====
462
510
  /**
463
511
  * BM25 scoring for keyword-based search.
@@ -1738,8 +1786,9 @@ export function getBridgeFailureReason() {
1738
1786
  * independent of package build order without changing production startup.
1739
1787
  */
1740
1788
  export function __setMemoryBridgeRegistryForTests(registry) {
1741
- registryInstance = registry;
1742
- registryPromise = registry ? Promise.resolve(registry) : null;
1789
+ registryPromises.clear();
1790
+ registryInstances.clear();
1791
+ testRegistryOverride = registry;
1743
1792
  bridgeAvailable = registry ? true : null;
1744
1793
  bridgeFailureReason = null;
1745
1794
  }
@@ -1753,16 +1802,19 @@ export function __setMemoryBridgeRegistryForTests(registry) {
1753
1802
  * therefore had no recovery path short of a restart.
1754
1803
  */
1755
1804
  export async function shutdownBridge() {
1756
- if (registryInstance) {
1805
+ // #3196: every cached registry owns an open database handle, so shutting down
1806
+ // one of several would leave the rest holding files open.
1807
+ for (const registry of registryInstances.values()) {
1757
1808
  try {
1758
- await registryInstance.shutdown();
1809
+ await registry.shutdown();
1759
1810
  }
1760
1811
  catch {
1761
1812
  // Best-effort
1762
1813
  }
1763
1814
  }
1764
- registryInstance = null;
1765
- registryPromise = null;
1815
+ registryInstances.clear();
1816
+ registryPromises.clear();
1817
+ testRegistryOverride = null;
1766
1818
  bridgeAvailable = null;
1767
1819
  bridgeFailureReason = null;
1768
1820
  }
@@ -0,0 +1,11 @@
1
+ export interface SiblingStoreReport {
2
+ path: string;
3
+ rows: number;
4
+ }
5
+ /**
6
+ * Count rows in the sibling AgentDB store, read-only. Returns null when there
7
+ * is no sibling, it does not exist, or it cannot be read — an unreadable store
8
+ * is not evidence of an empty one, so we stay silent rather than claim zero.
9
+ */
10
+ export declare function countSiblingStoreRows(dbPath: string): Promise<SiblingStoreReport | null>;
11
+ //# sourceMappingURL=sibling-store.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * #3196: report the store this interface is NOT reading.
3
+ *
4
+ * `memory.db` (sql.js, encrypted at rest when enabled) and `agentdb-memory.db`
5
+ * (native better-sqlite3, plaintext) are deliberately separate files — see
6
+ * #2786; pointing native at an encrypted file fails and silently disables the
7
+ * learning system. Both are legitimate stores, and a read of one is not a read
8
+ * of the other.
9
+ *
10
+ * The danger is not the split. It is a count that describes one file as though
11
+ * it described the memory. This module exists so the CLI can say what it did
12
+ * not read, without opening, migrating or modifying that file.
13
+ */
14
+ import { existsSync } from 'node:fs';
15
+ import { siblingAgentDbPath } from './memory-bridge.js';
16
+ /**
17
+ * Count rows in the sibling AgentDB store, read-only. Returns null when there
18
+ * is no sibling, it does not exist, or it cannot be read — an unreadable store
19
+ * is not evidence of an empty one, so we stay silent rather than claim zero.
20
+ */
21
+ export async function countSiblingStoreRows(dbPath) {
22
+ const sibling = siblingAgentDbPath(dbPath);
23
+ if (!sibling || !existsSync(sibling))
24
+ return null;
25
+ try {
26
+ const require = (await import('node:module')).createRequire(import.meta.url);
27
+ // Optional native dependency: absence must degrade to silence, never throw.
28
+ const Database = require('better-sqlite3');
29
+ const db = new Database(sibling, { readonly: true, fileMustExist: true });
30
+ try {
31
+ const table = db
32
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries'")
33
+ .get();
34
+ if (!table)
35
+ return null;
36
+ const row = db
37
+ .prepare("SELECT COUNT(*) AS n FROM memory_entries WHERE (status = 'active' OR status IS NULL)")
38
+ .get();
39
+ const rows = Number(row?.n ?? 0);
40
+ return rows > 0 ? { path: sibling, rows } : null;
41
+ }
42
+ finally {
43
+ try {
44
+ db.close();
45
+ }
46
+ catch { /* best effort */ }
47
+ }
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ //# sourceMappingURL=sibling-store.js.map
@@ -54,14 +54,32 @@ export function isDaemonAlive(projectRoot) {
54
54
  }
55
55
  /** Project-local opt-out: `{ "daemon": { "autostart": false } }` in claude-flow.config.json. */
56
56
  function autostartDisabledByProjectConfig(projectRoot) {
57
- try {
58
- const raw = fs.readFileSync(path.join(projectRoot, 'claude-flow.config.json'), 'utf-8');
59
- const cfg = JSON.parse(raw);
60
- return cfg?.daemon?.autostart === false;
61
- }
62
- catch {
63
- return false; // absent/malformed config = not disabled
57
+ // #3278: read BOTH files, and both spellings.
58
+ //
59
+ // This used to consult only `claude-flow.config.json` with the key
60
+ // `daemon.autostart`. But `init` generates `.claude/settings.json` with
61
+ // `claudeFlow.daemon.autoStart` — a different file and a different capital S —
62
+ // and writes it `false` under the comment "Opt-in only — prevents unintended
63
+ // token consumption (#1427, #1330)". So the one setting a fresh project
64
+ // actually ships was never read, and the runtime spent tokens the generated
65
+ // config existed to prevent. Accept either spelling in either file: a user who
66
+ // wrote the word "autostart: false" anywhere sensible meant it.
67
+ const readsFalse = (v) => v === false;
68
+ for (const [file, pick] of [
69
+ ['claude-flow.config.json', (c) => c?.daemon],
70
+ [path.join('.claude', 'settings.json'), (c) => c?.claudeFlow?.daemon],
71
+ ]) {
72
+ try {
73
+ const cfg = JSON.parse(fs.readFileSync(path.join(projectRoot, file), 'utf-8'));
74
+ const d = pick(cfg);
75
+ if (readsFalse(d?.autostart) || readsFalse(d?.autoStart))
76
+ return true;
77
+ }
78
+ catch {
79
+ // absent/malformed config = this file says nothing; keep checking the others
80
+ }
64
81
  }
82
+ return false;
65
83
  }
66
84
  function autostartDisabled(projectRoot) {
67
85
  if (/^(0|false|no|off)$/i.test(process.env.RUFLO_DAEMON_AUTOSTART ?? ''))
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.41.0",
3
+ "version": "3.41.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",