claude-flow 3.59.12 → 3.59.14

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.
@@ -24,9 +24,10 @@ const helpersDir = __dirname;
24
24
  const helperRoot = path.resolve(helpersDir, '..', '..');
25
25
 
26
26
  // Resolve an installed @claude-flow/cli (or ruflo) bin — mirrors
27
- // statusline-generator.ts's resolveCliBin() candidate list. Used only to
28
- // spawn the detached funnel-refresh helper below; failures are silent (no
29
- // candidate found just means the refresh never fires this session).
27
+ // statusline-generator.ts's resolveCliBin() candidate list, then looks for a
28
+ // global install (resolveGlobalCliBin() below, #3368). Used only to spawn the
29
+ // detached funnel-refresh helper below; failures are silent (no candidate
30
+ // found just means the refresh falls back to npx).
30
31
  //
31
32
  // Verifies dist/src/index.js exists alongside bin/cli.js, not just the bin
32
33
  // itself — Claude Code's own plugin marketplace mechanism installs by
@@ -39,7 +40,17 @@ const helperRoot = path.resolve(helpersDir, '..', '..');
39
40
  // first every time and spawnDetachedFunnelRefresh() below had no fallback,
40
41
  // so the promo/disclosure row could never populate for any marketplace
41
42
  // install, on any OS.
43
+ // session-restore asks up to three times (first-run enable, then the two
44
+ // refreshes). The answer cannot change inside one short-lived hook process,
45
+ // so resolve once: `undefined` means "not looked up yet", null means "no
46
+ // local or global CLI — use npx".
47
+ let cliBinCache;
42
48
  function resolveCliBinForHook() {
49
+ if (cliBinCache === undefined) cliBinCache = resolveCliBinUncached();
50
+ return cliBinCache;
51
+ }
52
+
53
+ function resolveCliBinUncached() {
43
54
  try {
44
55
  const home = os.homedir();
45
56
  const candidates = [
@@ -53,16 +64,219 @@ function resolveCliBinForHook() {
53
64
  path.join(helpersDir, '..', '..', 'bin', 'cli.js'),
54
65
  ];
55
66
  for (const p of candidates) {
56
- try {
57
- if (fs.existsSync(p) && fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'))) {
58
- return p;
59
- }
60
- } catch (e) { /* try next candidate */ }
67
+ if (isRunnableCli(p)) return p;
61
68
  }
69
+ return resolveGlobalCliBin();
62
70
  } catch (e) { /* ignore */ }
63
71
  return null;
64
72
  }
65
73
 
74
+ function isRunnableCli(p) {
75
+ try {
76
+ return fs.existsSync(p) && fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
77
+ } catch (e) { return false; }
78
+ }
79
+
80
+ // #3368: a global install — the README's `npm install -g ruflo@latest` — keeps
81
+ // the CLI at <prefix>/lib/node_modules/ruflo/node_modules/@claude-flow/cli,
82
+ // which none of the project-relative candidates above cover, so on those
83
+ // machines every refresh fell back to npx: a second, separately cached copy
84
+ // of the CLI that follows the registry's latest rather than the version the
85
+ // user pinned (the same npx-cache skew as #3306), and that cannot start
86
+ // offline on a cold cache.
87
+ //
88
+ // Three lookups, fs calls only (no child process), reached only after every
89
+ // candidate above missed:
90
+ // 1. npm's global prefix, resolved the way npm resolves it
91
+ // (npmGlobalPrefix() below). First because it does not depend on PATH:
92
+ // a hook inherits the host process's PATH, which need not contain the
93
+ // prefix's bin/ even when the user's interactive shell does (observed:
94
+ // the Claude Code process spawning hooks lacked ~/.npm-global/bin while
95
+ // a login zsh on the same machine had it).
96
+ // 2. `ruflo`, then `claude-flow`, on PATH — the order plugins/ruflo-core/
97
+ // scripts/ruflo-hook.cjs uses — for globals npm did not install (yarn
98
+ // classic, bun). Only a symlinked bin counts, and only when it lands in
99
+ // a package that declares one of the three known names, so a wrapper
100
+ // script or a version-manager shim (Volta) is never mistaken for an
101
+ // install. The <dir>/node_modules/<name> layout npm uses on Windows,
102
+ // where ruflo.cmd sits beside node_modules\ruflo (what resolveNpmShim()
103
+ // in ruflo-hook.cjs maps), is only consulted on win32: on POSIX every
104
+ // manager symlinks, and probing it there would follow a Windows install
105
+ // exposed to WSL through the appended Windows PATH.
106
+ // Relative PATH entries (`.` or an empty entry) are skipped, so a bare
107
+ // `./ruflo` in the working directory is never picked up.
108
+ // 3. ~/.npm-global, npm's documented no-sudo prefix, when 1 and 2 both
109
+ // missed: the prefix setting may live where this hook's node never
110
+ // reads it, and a wrapper script on PATH is skipped by 2.
111
+ // `env`/`platform`/`execPath` are parameters so any layout is testable
112
+ // from any OS.
113
+ function resolveGlobalCliBin(env = process.env, platform = process.platform, execPath = process.execPath) {
114
+ const prefix = npmGlobalPrefix(env, platform, execPath);
115
+ if (prefix) {
116
+ const hit = cliUnderPrefix(prefix, platform);
117
+ if (hit) return hit;
118
+ }
119
+ const dirs = String(env.PATH || env.Path || '').split(platform === 'win32' ? ';' : ':');
120
+ const exts = platform === 'win32' ? ['.cmd', ''] : [''];
121
+ for (const name of ['ruflo', 'claude-flow']) {
122
+ for (const dir of dirs) {
123
+ if (!dir || !path.isAbsolute(dir)) continue;
124
+ for (const ext of exts) {
125
+ const shim = path.join(dir, name + ext);
126
+ if (!fs.existsSync(shim)) continue;
127
+ const roots = [];
128
+ try {
129
+ if (fs.lstatSync(shim).isSymbolicLink()) {
130
+ const root = path.dirname(path.dirname(fs.realpathSync(shim)));
131
+ if (isKnownPackageRoot(root)) roots.push(root);
132
+ }
133
+ } catch (e) { /* unreadable link — fall back to the layout roots */ }
134
+ if (platform === 'win32') {
135
+ roots.push(path.join(dir, 'node_modules', name));
136
+ if (name === 'claude-flow') roots.push(path.join(dir, 'node_modules', '@claude-flow', 'cli'));
137
+ }
138
+ for (const root of roots) {
139
+ const hit = cliInPackage(root);
140
+ if (hit) return hit;
141
+ }
142
+ }
143
+ }
144
+ }
145
+ // Last resort: ~/.npm-global, the prefix npm's own docs recommend for
146
+ // global installs without sudo ("Resolving EACCES permissions errors").
147
+ // The setting that points npm there can live where this hook's node never
148
+ // looks (another node's builtin npmrc, or a wrapper script that sets it
149
+ // per call), and a `ruflo` wrapper script on PATH is skipped above by
150
+ // design. Without this, such a machine fell back to npx @latest on every
151
+ // hook. Checked only after the configured prefix and PATH both miss, so it
152
+ // can never override either.
153
+ const home = env.HOME || env.USERPROFILE;
154
+ if (home && path.isAbsolute(home)) {
155
+ const hit = cliUnderPrefix(path.join(home, '.npm-global'), platform);
156
+ if (hit) return hit;
157
+ }
158
+ return null;
159
+ }
160
+
161
+ // The CLI of a global install under an npm prefix: <prefix>/lib/node_modules
162
+ // on POSIX, <prefix>\node_modules on Windows, trying the three published
163
+ // package names in order.
164
+ function cliUnderPrefix(prefix, platform) {
165
+ const modules = platform === 'win32'
166
+ ? path.join(prefix, 'node_modules')
167
+ : path.join(prefix, 'lib', 'node_modules');
168
+ for (const pkg of ['ruflo', 'claude-flow', path.join('@claude-flow', 'cli')]) {
169
+ const hit = cliInPackage(path.join(modules, pkg));
170
+ if (hit) return hit;
171
+ }
172
+ return null;
173
+ }
174
+
175
+ // A bin on PATH only counts as an install when the package it points into
176
+ // declares one of the three published names. This is a narrower check than
177
+ // resolveNpmShim() in ruflo-hook.cjs, which never reads `name`: that one takes
178
+ // the package name from the shim's own filename, then validates package.json's
179
+ // `bin` entry and that its realpath stays inside the package. Here the bin has
180
+ // already been resolved to a package root, so the manifest `name` is what
181
+ // identifies it.
182
+ function isKnownPackageRoot(root) {
183
+ try {
184
+ const name = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8')).name;
185
+ return name === 'ruflo' || name === 'claude-flow' || name === '@claude-flow/cli';
186
+ } catch (e) { return false; }
187
+ }
188
+
189
+ // Map an installed ruflo / claude-flow / @claude-flow/cli package root to the
190
+ // CLI it runs: <root>/bin/cli.js (@claude-flow/cli itself),
191
+ // <root>/v3/@claude-flow/cli/bin/cli.js (the claude-flow umbrella bundles
192
+ // it), or @claude-flow/cli in the node_modules npm nests inside <root> —
193
+ // or, for the flat layout yarn classic and bun use, in the node_modules
194
+ // folder that holds <root> itself. That is the dependency edge
195
+ // ruflo/bin/ruflo.js's findCliPath() follows. The search stops at that one
196
+ // enclosing node_modules: walking further up would reach unrelated trees,
197
+ // such as a stray ~/node_modules left by an `npm i` in a home directory.
198
+ // Every hit must still pass isRunnableCli().
199
+ function cliInPackage(root) {
200
+ if (!fs.existsSync(root)) return null;
201
+ const candidates = [
202
+ path.join(root, 'bin', 'cli.js'),
203
+ path.join(root, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
204
+ path.join(root, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
205
+ ];
206
+ for (let dir = path.dirname(root), i = 0; i < 4 && dir !== path.dirname(dir); i++, dir = path.dirname(dir)) {
207
+ if (path.basename(dir) === 'node_modules') {
208
+ candidates.push(path.join(dir, '@claude-flow', 'cli', 'bin', 'cli.js'));
209
+ break;
210
+ }
211
+ }
212
+ return candidates.find(isRunnableCli) || null;
213
+ }
214
+
215
+ // npm's global prefix without spawning npm (`npm prefix -g` measured 70-80 ms
216
+ // per call, paid on every SessionStart that gets this far). Same precedence
217
+ // npm applies:
218
+ // 1. npm_config_prefix in the environment (npm accepts either case);
219
+ // 2. `prefix=` in the user npmrc ($npm_config_userconfig, else ~/.npmrc),
220
+ // where `npm config set prefix ~/.npm-global` (the npm docs' fix for
221
+ // EACCES on global installs) writes it;
222
+ // 3. `prefix=` in npm's builtin npmrc (<npm package>/npmrc), found through
223
+ // the `npm` link beside node: Homebrew writes `prefix = /opt/homebrew`
224
+ // there (its process.execPath is the Cellar path, so step 4 alone would
225
+ // be wrong), and the Node.js Windows installer ships
226
+ // `prefix=${APPDATA}\npm`;
227
+ // 4. npm's default: $PREFIX, else the directory above node's bin/ (POSIX)
228
+ // or node.exe's own directory (Windows).
229
+ // Values follow npm's ini handling for the cases that decide a path: a
230
+ // quoted value is taken literally, an unquoted `;`/`#` starts a comment,
231
+ // keys below a `[section]` header are ignored, `${VAR}` expands when the
232
+ // variable is set (and `${VAR?}` to empty when it is not) but otherwise
233
+ // stays literal like npm — which leaves the value relative, so the
234
+ // isAbsolute() check below rejects it instead of pointing at the drive root.
235
+ // Not emulated: escaped characters and npm's other ini corner cases; each of
236
+ // those produces a miss, never a wrong CLI. The npmrc is read into memory,
237
+ // but only the value of its last top-level `prefix =` line is kept: no other
238
+ // line (an `_authToken`, say) is returned, stored or logged.
239
+ function npmGlobalPrefix(env = process.env, platform = process.platform, execPath = process.execPath) {
240
+ const home = (platform === 'win32' ? env.USERPROFILE : env.HOME) || os.homedir();
241
+ const readPrefix = (file) => {
242
+ let value = null;
243
+ try {
244
+ for (const line of fs.readFileSync(file, 'utf-8').split(/\r?\n/)) {
245
+ if (/^\s*\[/.test(line)) break; // only the top-level section applies
246
+ const m = /^\s*prefix\s*=\s*(.*?)\s*$/.exec(line);
247
+ if (m) value = m[1]; // last one wins, as in npm's ini parser
248
+ }
249
+ } catch (e) { /* no such npmrc */ }
250
+ return value;
251
+ };
252
+ const nodeDir = path.dirname(execPath);
253
+ // npm's builtin npmrc sits in npm's own package dir. On Windows that is
254
+ // node_modules\npm beside node.exe; elsewhere follow the `npm` link beside
255
+ // node (<npm>/bin/npm-cli.js) — on Homebrew it points out of the node keg
256
+ // to /opt/homebrew/lib/node_modules/npm.
257
+ let builtinRc = path.join(nodeDir, 'node_modules', 'npm', 'npmrc');
258
+ if (platform !== 'win32') {
259
+ try {
260
+ builtinRc = path.join(path.dirname(path.dirname(fs.realpathSync(path.join(nodeDir, 'npm')))), 'npmrc');
261
+ } catch (e) { /* no npm beside node */ }
262
+ }
263
+ const raw = env.npm_config_prefix || env.NPM_CONFIG_PREFIX
264
+ || readPrefix(env.npm_config_userconfig || env.NPM_CONFIG_USERCONFIG || path.join(home, '.npmrc'))
265
+ || readPrefix(builtinRc)
266
+ || env.PREFIX
267
+ || (platform === 'win32' ? nodeDir : path.dirname(nodeDir));
268
+ let value = String(raw).trim();
269
+ const quoted = /^(["'])([\s\S]*)\1$/.exec(value);
270
+ if (quoted) value = quoted[2];
271
+ else value = value.replace(/(^|[^\\])[;#].*$/, '$1').trim();
272
+ value = value
273
+ .replace(/\$\{([^}?]+)(\?)?\}/g, (whole, name, optional) => (
274
+ env[name] !== undefined ? env[name] : (optional ? '' : whole)
275
+ ))
276
+ .replace(/^~(?=$|[\\/])/, home);
277
+ return path.isAbsolute(value) ? path.resolve(value) : null;
278
+ }
279
+
66
280
  // Fire-and-forget doesn't work when the CALLER is itself a short-lived
67
281
  // subprocess (confirmed live: two consecutive statusline renders 5s apart
68
282
  // both saw an empty funnel-messages-cache, because the async HTTPS fetch
@@ -74,8 +288,8 @@ function resolveCliBinForHook() {
74
288
  // chance to write the cache. Never awaited here — must not add to
75
289
  // SessionStart's own timeout budget.
76
290
  //
77
- // No usable local candidate (resolveCliBinForHook() returned null) falls
78
- // back to npx: this call is detached/unref'd, so a slower npx cold-start
291
+ // No usable local or global candidate (resolveCliBinForHook() returned null)
292
+ // falls back to npx: this call is detached/unref'd, so a slower npx cold-start
79
293
  // costs nothing perceptible — unlike the statusline's own synchronous
80
294
  // render path, where local-first exists purely for per-render latency.
81
295
  // `--prefer-offline` avoids a registry round trip for the tarball when
@@ -1060,4 +1274,7 @@ if (require.main === module) {
1060
1274
  // Which sibling helpers loaded (all CommonJS, shipped as .cjs — #3555).
1061
1275
  const loadedHelpers = { router: !!router, session: !!session, memory: !!memory, intelligence: !!intelligence };
1062
1276
 
1063
- module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS, resolveCliBinForHook, cliSpawnArgs, helperRoot, scanSessionStartSettingsForRisk, srFormatRiskFindingsForAutomaticBoot, srScanSettingsForRisk, srScanCommandStringForRisk, srScanAllowRuleForRisk, SR_DANGEROUS_COMMAND_WORDS, SR_SHELL_EXEC_WORDS, SR_DOWNLOADER_WORDS, SR_STANDALONE_RISK_PATTERNS };
1277
+ // resolveGlobalCliBin/npmGlobalPrefix are exported for
1278
+ // hook-handler-global-cli.test.ts; resolveCliBinForHook for anyone checking
1279
+ // what a given machine resolves to without running a hook (#3368).
1280
+ module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS, resolveCliBinForHook, resolveGlobalCliBin, npmGlobalPrefix, cliSpawnArgs, helperRoot, scanSessionStartSettingsForRisk, srFormatRiskFindingsForAutomaticBoot, srScanSettingsForRisk, srScanCommandStringForRisk, srScanAllowRuleForRisk, SR_DANGEROUS_COMMAND_WORDS, SR_SHELL_EXEC_WORDS, SR_DOWNLOADER_WORDS, SR_STANDALONE_RISK_PATTERNS };
@@ -1,5 +1,5 @@
1
1
  {
2
- "adoptedAt": 1792615534268,
2
+ "adoptedAt": 1792643533692,
3
3
  "championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
4
4
  "manifest": {
5
5
  "schema": "ruflo.proven-config/v1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.59.12",
3
+ "version": "3.59.14",
4
4
  "workspaces": [
5
5
  "v3/@claude-flow/codex",
6
6
  "v3/@claude-flow/mcp",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-21T20:45:54.432Z",
5
- "gitSha": "c7646541",
4
+ "generatedAt": "2026-10-22T04:32:33.371Z",
5
+ "gitSha": "88bdc9a6",
6
6
  "catalog": {
7
7
  "agents": 176,
8
8
  "tools": 432,
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module @claude-flow/cli/mcp-tools/claims
8
8
  */
9
- import type { MCPTool } from './types.js';
9
+ import { type MCPTool } from './types.js';
10
10
  export declare const claimsTools: MCPTool[];
11
11
  export default claimsTools;
12
12
  //# sourceMappingURL=claims-tools.d.ts.map
@@ -6,19 +6,20 @@
6
6
  *
7
7
  * @module @claude-flow/cli/mcp-tools/claims
8
8
  */
9
+ import { getProjectCwd } from './types.js';
9
10
  import { readClaimsStore } from '../services/claims-store.js';
10
11
  import { validateIdentifier, validateText } from './validate-input.js';
11
12
  // File-based persistence
12
13
  import { existsSync, writeFileSync, mkdirSync } from 'fs';
13
- import { join, resolve } from 'path';
14
+ import { resolve } from 'path';
14
15
  const CLAIMS_DIR = '.claude-flow/claims';
15
16
  const CLAIMS_FILE = 'claims.json';
16
17
  const DEFAULT_MAX_CLAIMS = 5;
17
18
  function getClaimsPath() {
18
- return resolve(join(CLAIMS_DIR, CLAIMS_FILE));
19
+ return resolve(getProjectCwd(), CLAIMS_DIR, CLAIMS_FILE);
19
20
  }
20
21
  function ensureClaimsDir() {
21
- const dir = resolve(CLAIMS_DIR);
22
+ const dir = resolve(getProjectCwd(), CLAIMS_DIR);
22
23
  if (!existsSync(dir)) {
23
24
  mkdirSync(dir, { recursive: true });
24
25
  }
@@ -21,6 +21,17 @@ import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.
21
21
  const MEMORY_DIR = '.swarm';
22
22
  const LEGACY_MEMORY_FILE = 'store.json';
23
23
  const LEGACY_MEMORY_DIR = '.claude-flow/memory';
24
+ /**
25
+ * #3196: allow an MCP caller to select the same explicit store the CLI accepts
26
+ * via `--path`. No path means the existing MCP default is unchanged; this is
27
+ * deliberately not an implicit `CLAUDE_FLOW_DB_PATH` override because the
28
+ * native/encrypted CLI store and the AgentDB sibling are separate backends.
29
+ */
30
+ function resolveDbPathOption(inputDbPath) {
31
+ return typeof inputDbPath === 'string' && inputDbPath.trim().length > 0
32
+ ? resolve(inputDbPath)
33
+ : undefined;
34
+ }
24
35
  const MIGRATION_MARKER = '.migrated-to-sqlite';
25
36
  function getMemoryDir() {
26
37
  return resolve(MEMORY_DIR);
@@ -439,6 +450,7 @@ export const memoryTools = [
439
450
  enum: ['user_claim', 'agent_output', 'system_observation', 'tool_result', 'unknown'],
440
451
  description: 'ADR-323: who/what produced this value, so shared-namespace retrieval can filter by trust level instead of conflating a user\'s stated claim with an agent\'s own output. Default: "unknown".',
441
452
  },
453
+ dbPath: { type: 'string', description: 'Explicit SQLite store to read/write; omit to use the MCP default (the native/AgentDB sibling).' },
442
454
  },
443
455
  required: ['key', 'value'],
444
456
  },
@@ -447,7 +459,9 @@ export const memoryTools = [
447
459
  if (missingKey) {
448
460
  return { success: false, key: input.key, stored: false, hasEmbedding: false, ...missingKey };
449
461
  }
450
- await ensureInitialized();
462
+ const dbPath = resolveDbPathOption(input.dbPath);
463
+ if (!dbPath)
464
+ await ensureInitialized();
451
465
  const { storeEntry } = await getMemoryFunctions();
452
466
  const key = input.key;
453
467
  const namespace = input.namespace || 'default';
@@ -486,6 +500,7 @@ export const memoryTools = [
486
500
  ttl,
487
501
  upsert,
488
502
  provenanceType,
503
+ dbPath,
489
504
  });
490
505
  const duration = performance.now() - startTime;
491
506
  return {
@@ -522,6 +537,7 @@ export const memoryTools = [
522
537
  properties: {
523
538
  key: { type: 'string', description: 'Memory key' },
524
539
  namespace: { type: 'string', description: 'Namespace (default: "default")' },
540
+ dbPath: { type: 'string', description: 'Explicit SQLite store to read/write; omit to use the MCP default (the native/AgentDB sibling).' },
525
541
  },
526
542
  required: ['key'],
527
543
  },
@@ -530,13 +546,15 @@ export const memoryTools = [
530
546
  if (missingKey) {
531
547
  return { key: input.key, namespace: input.namespace, value: null, found: false, ...missingKey };
532
548
  }
533
- await ensureInitialized();
549
+ const dbPath = resolveDbPathOption(input.dbPath);
550
+ if (!dbPath)
551
+ await ensureInitialized();
534
552
  const { getEntry } = await getMemoryFunctions();
535
553
  const key = input.key;
536
554
  const namespace = input.namespace || 'default';
537
555
  validateMemoryInput(key, undefined, undefined, namespace);
538
556
  try {
539
- const result = await getEntry({ key, namespace });
557
+ const result = await getEntry({ key, namespace, dbPath });
540
558
  if (result.found && result.entry) {
541
559
  // Try to parse JSON value
542
560
  let value = result.entry.content;
@@ -594,6 +612,7 @@ export const memoryTools = [
594
612
  items: { type: 'string', enum: ['user_claim', 'agent_output', 'system_observation', 'tool_result', 'unknown'] },
595
613
  description: 'ADR-323: restrict results to these provenance types (e.g. exclude user_claim when fact-checking). Omit for no filtering. Enforced for standard and SmartRetrieval searches.',
596
614
  },
615
+ dbPath: { type: 'string', description: 'Explicit SQLite store to read/write; omit to use the MCP default (the native/AgentDB sibling).' },
597
616
  },
598
617
  required: ['query'],
599
618
  },
@@ -602,7 +621,9 @@ export const memoryTools = [
602
621
  if (missingQuery) {
603
622
  return { query: input.query, results: [], total: 0, ...missingQuery };
604
623
  }
605
- await ensureInitialized();
624
+ const dbPath = resolveDbPathOption(input.dbPath);
625
+ if (!dbPath)
626
+ await ensureInitialized();
606
627
  const { searchEntries } = await getMemoryFunctions();
607
628
  const query = input.query;
608
629
  const provenanceFilter = input.provenance_filter;
@@ -649,6 +670,7 @@ export const memoryTools = [
649
670
  limit: req.limit || limit * 3,
650
671
  threshold: req.threshold ?? threshold,
651
672
  provenanceFilter,
673
+ dbPath,
652
674
  });
653
675
  return {
654
676
  results: r.results.map(e => ({
@@ -693,7 +715,7 @@ export const memoryTools = [
693
715
  };
694
716
  });
695
717
  const smartNearMisses = results.length === 0 && thresholdWasDefault
696
- ? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
718
+ ? await nearMisses(searchEntries, { query, namespace, provenanceFilter, dbPath })
697
719
  : undefined;
698
720
  return {
699
721
  query,
@@ -721,6 +743,7 @@ export const memoryTools = [
721
743
  limit,
722
744
  threshold,
723
745
  provenanceFilter,
746
+ dbPath,
724
747
  });
725
748
  if (!result.success) {
726
749
  return { query, results: [], total: 0, error: result.error };
@@ -744,7 +767,7 @@ export const memoryTools = [
744
767
  };
745
768
  });
746
769
  const nearMissInfo = results.length === 0 && thresholdWasDefault
747
- ? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
770
+ ? await nearMisses(searchEntries, { query, namespace, provenanceFilter, dbPath })
748
771
  : undefined;
749
772
  return {
750
773
  query,
@@ -775,6 +798,7 @@ export const memoryTools = [
775
798
  properties: {
776
799
  key: { type: 'string', description: 'Memory key' },
777
800
  namespace: { type: 'string', description: 'Namespace (default: "default")' },
801
+ dbPath: { type: 'string', description: 'Explicit SQLite store to read/write; omit to use the MCP default (the native/AgentDB sibling).' },
778
802
  },
779
803
  required: ['key'],
780
804
  },
@@ -783,13 +807,15 @@ export const memoryTools = [
783
807
  if (missingKey) {
784
808
  return { success: false, key: input.key, namespace: input.namespace, deleted: false, ...missingKey };
785
809
  }
786
- await ensureInitialized();
810
+ const dbPath = resolveDbPathOption(input.dbPath);
811
+ if (!dbPath)
812
+ await ensureInitialized();
787
813
  const { deleteEntry } = await getMemoryFunctions();
788
814
  const key = input.key;
789
815
  const namespace = input.namespace || 'default';
790
816
  validateMemoryInput(key, undefined, undefined, namespace);
791
817
  try {
792
- const result = await deleteEntry({ key, namespace });
818
+ const result = await deleteEntry({ key, namespace, dbPath });
793
819
  return {
794
820
  success: result.deleted,
795
821
  key,
@@ -825,10 +851,13 @@ export const memoryTools = [
825
851
  },
826
852
  limit: { type: 'number', description: 'Maximum results (default: 50)' },
827
853
  offset: { type: 'number', description: 'Offset for pagination (default: 0)' },
854
+ dbPath: { type: 'string', description: 'Explicit SQLite store to read/write; omit to use the MCP default (the native/AgentDB sibling).' },
828
855
  },
829
856
  },
830
857
  handler: async (input) => {
831
- await ensureInitialized();
858
+ const dbPath = resolveDbPathOption(input.dbPath);
859
+ if (!dbPath)
860
+ await ensureInitialized();
832
861
  const { listEntries } = await getMemoryFunctions();
833
862
  const namespace = input.namespace;
834
863
  const tags = Array.isArray(input.tags)
@@ -847,6 +876,7 @@ export const memoryTools = [
847
876
  tags,
848
877
  limit,
849
878
  offset,
879
+ dbPath,
850
880
  });
851
881
  const entries = result.entries.map(e => ({
852
882
  key: e.key,
@@ -884,14 +914,14 @@ export const memoryTools = [
884
914
  inputSchema: {
885
915
  type: 'object',
886
916
  properties: {
887
- dbPath: { type: 'string', description: 'Database file to inspect; omitted uses the MCP store default' },
917
+ dbPath: { type: 'string', description: 'Explicit SQLite store to read/write; omit to use the MCP default (the native/AgentDB sibling).' },
888
918
  },
889
919
  },
890
920
  handler: async (input) => {
891
921
  if (input.dbPath !== undefined && (typeof input.dbPath !== 'string' || !input.dbPath.trim())) {
892
922
  return memoryStatsUnavailable('dbPath must be a non-empty string');
893
923
  }
894
- const dbPath = typeof input.dbPath === 'string' ? resolve(input.dbPath) : undefined;
924
+ const dbPath = resolveDbPathOption(input.dbPath);
895
925
  // An explicit read must not initialize or migrate the unrelated default store.
896
926
  if (!dbPath)
897
927
  await ensureInitialized();
@@ -583,7 +583,8 @@ export const workflowTools = [
583
583
  }
584
584
  // Sort by creation date (newest first)
585
585
  workflows.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
586
- // Apply limit
586
+ // Count the matches before the limit cuts the page down
587
+ const total = workflows.length;
587
588
  const limit = input.limit || 20;
588
589
  workflows = workflows.slice(0, limit);
589
590
  return {
@@ -595,7 +596,7 @@ export const workflowTools = [
595
596
  createdAt: w.createdAt,
596
597
  completedAt: w.completedAt,
597
598
  })),
598
- total: workflows.length,
599
+ total,
599
600
  filters: { status: input.status },
600
601
  };
601
602
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.59.12",
3
+ "version": "3.59.14",
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",