@claude-flow/cli 3.44.0 → 3.46.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.
Files changed (74) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/README.md +2 -0
  3. package/bin/cli.js +13 -0
  4. package/bin/mcp-server.js +16 -0
  5. package/catalog-manifest.json +2 -2
  6. package/dist/src/commands/doctor.d.ts +12 -0
  7. package/dist/src/commands/doctor.js +200 -3
  8. package/dist/src/commands/embeddings.js +50 -7
  9. package/dist/src/commands/init.js +5 -2
  10. package/dist/src/commands/mcp.js +27 -4
  11. package/dist/src/mcp-server.d.ts +50 -6
  12. package/dist/src/mcp-server.js +267 -33
  13. package/dist/src/mcp-tools/agent-execute-core.js +7 -0
  14. package/dist/src/mcp-tools/agentdb-tools.js +20 -8
  15. package/dist/src/mcp-tools/browser-tools.js +30 -23
  16. package/dist/src/mcp-tools/memory-tools.js +9 -4
  17. package/dist/src/mcp-tools/metaharness-tools.js +1 -1
  18. package/dist/src/mcp-tools/policy-enforcer.d.ts +4 -4
  19. package/dist/src/mcp-tools/policy-enforcer.js +4 -4
  20. package/dist/src/mcp-tools/terminal-tools.js +11 -1
  21. package/dist/src/mcp-tools/workflow-tools.js +1 -1
  22. package/dist/src/memory/ewc-consolidation.d.ts +16 -1
  23. package/dist/src/memory/ewc-consolidation.js +63 -13
  24. package/dist/src/memory/intelligence.d.ts +6 -2
  25. package/dist/src/memory/intelligence.js +18 -11
  26. package/dist/src/memory/memory-bridge.js +84 -0
  27. package/dist/src/ruvector/model-router.js +19 -1
  28. package/dist/src/services/distill-oracle.js +2 -1
  29. package/dist/src/services/git-workspace-identity.js +5 -8
  30. package/dist/src/services/policy-runtime.js +114 -4
  31. package/dist/src/services/worker-daemon.js +8 -3
  32. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  33. package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.d.ts.map +1 -1
  34. package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js +2 -6
  35. package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js.map +1 -1
  36. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
  37. package/node_modules/@claude-flow/mcp/dist/transport/http.js +14 -1
  38. package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
  39. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts +40 -0
  40. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts.map +1 -0
  41. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js +100 -0
  42. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js.map +1 -0
  43. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  44. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts +1 -0
  45. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts.map +1 -1
  46. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js +1 -0
  47. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js.map +1 -1
  48. package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -0
  49. package/node_modules/@claude-flow/security/dist/safe-git.d.ts +22 -0
  50. package/node_modules/@claude-flow/security/dist/safe-git.d.ts.map +1 -0
  51. package/node_modules/@claude-flow/security/dist/safe-git.js +63 -0
  52. package/node_modules/@claude-flow/security/dist/safe-git.js.map +1 -0
  53. package/package.json +5 -5
  54. package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
  55. package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +1 -1
  56. package/plugins/ruflo-metaharness/scripts/_darwin.mjs +84 -12
  57. package/plugins/ruflo-metaharness/scripts/_harness.mjs +10 -4
  58. package/plugins/ruflo-metaharness/scripts/_invoke.mjs +120 -6
  59. package/plugins/ruflo-metaharness/scripts/_redblue.mjs +33 -6
  60. package/plugins/ruflo-metaharness/scripts/audit-list.mjs +11 -10
  61. package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +8 -7
  62. package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +2 -3
  63. package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +8 -7
  64. package/plugins/ruflo-metaharness/scripts/similarity.mjs +9 -8
  65. package/plugins/ruflo-metaharness/scripts/smoke.sh +31 -0
  66. package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +319 -0
  67. package/plugins/ruflo-metaharness/scripts/test-pin-alignment.mjs +182 -0
  68. package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +4 -2
  69. package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
  70. package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +1 -1
  71. package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +1 -1
  72. package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
  73. package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +3 -1
  74. package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.44.0",
3
+ "version": "3.46.0",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
6
6
  "hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
@@ -9,6 +9,6 @@
9
9
  "router.js": "b6998397e7883191b62229ccdc66fb87f1ccca1d5b2039ecb8c1e686d1ad81f8"
10
10
  }
11
11
  },
12
- "signature": "vl9y84CRx7XaIXMi3iD1q3UVqeUvGsvpwjXkhMuA42myuVhCbgtnSNM9W8M3Pv9Odqy5qucwwDsz7MB7mUqDBg==",
12
+ "signature": "LwKXka3qAKGSIVpcv4vpg5vlv8CFHolvyCQwzW0hNvPIn7Un3+qpidc7drdoDzsrNyzx3gnZJBFEWeXxlTabDA==",
13
13
  "algorithm": "ed25519"
14
14
  }
package/README.md CHANGED
@@ -19,6 +19,8 @@
19
19
 
20
20
  # Ruflo
21
21
 
22
+ [English](README.md) · [简体中文](README.zh-CN.md)
23
+
22
24
  **An agent meta-harness for Claude Code and Codex.**
23
25
 
24
26
  [![RuFlo Explained — build an AI team that plans, remembers, tests, and improves](docs/assets/ruflo-explained/ch14.jpg)](docs/ruflo-explained.md)
package/bin/cli.js CHANGED
@@ -164,6 +164,8 @@ const isMCPMode = !process.stdin.isTTY
164
164
  if (isMCPMode) {
165
165
  // Run MCP server mode
166
166
  const { listMCPTools, callMCPTool, hasTool } = await import('../dist/src/mcp-client.js');
167
+ const { isPolicyEnforcementEnabled, loadMcpPolicy, evaluateToolCall } =
168
+ await import('../dist/src/mcp-tools/policy-enforcer.js');
167
169
 
168
170
  const VERSION = '3.0.0';
169
171
  const sessionId = `mcp-${Date.now()}-${randomUUID().slice(0, 8)}`;
@@ -287,6 +289,17 @@ if (isMCPMode) {
287
289
  };
288
290
  }
289
291
 
292
+ if (isPolicyEnforcementEnabled()) {
293
+ const check = evaluateToolCall(loadMcpPolicy(), sessionId, toolName);
294
+ if (!check.allowed) {
295
+ return {
296
+ jsonrpc: '2.0',
297
+ id: message.id,
298
+ error: { code: -32001, message: `Policy denied: ${check.reason}` },
299
+ };
300
+ }
301
+ }
302
+
290
303
  try {
291
304
  const result = await callMCPTool(toolName, toolParams, { sessionId });
292
305
  return {
package/bin/mcp-server.js CHANGED
@@ -28,6 +28,11 @@ console.log = (...args) => {
28
28
  };
29
29
 
30
30
  import { listMCPTools, callMCPTool, hasTool } from '../dist/src/mcp-client.js';
31
+ import {
32
+ isPolicyEnforcementEnabled,
33
+ loadMcpPolicy,
34
+ evaluateToolCall,
35
+ } from '../dist/src/mcp-tools/policy-enforcer.js';
31
36
 
32
37
  const VERSION = '3.0.0';
33
38
  const sessionId = `mcp-${Date.now()}-${randomUUID().slice(0, 8)}`;
@@ -173,6 +178,17 @@ async function handleMessage(message) {
173
178
  };
174
179
  }
175
180
 
181
+ if (isPolicyEnforcementEnabled()) {
182
+ const check = evaluateToolCall(loadMcpPolicy(), sessionId, toolName);
183
+ if (!check.allowed) {
184
+ return {
185
+ jsonrpc: '2.0',
186
+ id: message.id,
187
+ error: { code: -32001, message: `Policy denied: ${check.reason}` },
188
+ };
189
+ }
190
+ }
191
+
176
192
  try {
177
193
  const result = await callMCPTool(toolName, toolParams, { sessionId });
178
194
  return {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 6,
4
- "generatedAt": "2026-09-23T18:40:54.000Z",
5
- "gitSha": "0a96fb88",
4
+ "generatedAt": "2026-09-26T22:32:39.004Z",
5
+ "gitSha": "39ea61fd",
6
6
  "catalog": {
7
7
  "agents": 167,
8
8
  "tools": 418,
@@ -11,7 +11,19 @@ interface HealthCheck {
11
11
  message: string;
12
12
  fix?: string;
13
13
  }
14
+ /** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
15
+ export declare function evaluateAgentBrowserVersion(rawOutput: string): HealthCheck;
16
+ export declare function checkAgentBrowserVersion(probe?: () => Promise<string>): Promise<HealthCheck>;
14
17
  export declare function checkMemoryPersistenceDriver(): Promise<HealthCheck>;
18
+ /**
19
+ * #3392: pure verdict for "does the @claude-flow/memory the CLI loads satisfy
20
+ * the range the CLI declares?". `npx @claude-flow/cli@latest` reuses one npx
21
+ * cache directory across CLI versions, and npm keeps an already-installed
22
+ * dependency that still satisfies a caret range, so a stale memory could
23
+ * survive a CLI upgrade with no error. Exported for unit testing.
24
+ */
25
+ export declare function evaluateMemoryPackageVersion(declared: string | null, installed: string | null): HealthCheck;
26
+ export declare function checkMemoryPackageVersion(): Promise<HealthCheck>;
15
27
  export declare const doctorCommand: Command;
16
28
  export default doctorCommand;
17
29
  //# sourceMappingURL=doctor.d.ts.map
@@ -9,13 +9,15 @@ import { existsSync, readFileSync, statSync, openSync, readSync, closeSync } fro
9
9
  import { join, dirname } from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { createHash } from 'crypto';
12
- import { execSync, exec } from 'child_process';
12
+ import { execSync, exec, execFile } from 'child_process';
13
13
  import { promisify } from 'util';
14
14
  import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
15
15
  import { isEncryptedBlob } from '../encryption/vault.js';
16
- import { resolveMemoryPackageFromProject, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
16
+ import * as semver from 'semver';
17
+ import { resolveMemoryPackageFromProject, resolveMemoryPackageFromCli, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
17
18
  // Promisified exec with proper shell and env inheritance for cross-platform support
18
19
  const execAsync = promisify(exec);
20
+ const execFileAsync = promisify(execFile);
19
21
  /**
20
22
  * Execute command asynchronously with proper environment inheritance
21
23
  * Critical for Windows where PATH may not be inherited properly
@@ -61,6 +63,39 @@ async function checkNpmVersion() {
61
63
  return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
62
64
  }
63
65
  }
66
+ /** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
67
+ export function evaluateAgentBrowserVersion(rawOutput) {
68
+ const name = 'agent-browser CLI (ADR-122)';
69
+ const fix = 'npm install -g agent-browser@latest';
70
+ const versionOutput = rawOutput.trim().replace(/\x1b\[[0-9;]*m/g, '');
71
+ const match = /^(?:agent-browser(?:\s+version)?\s+)?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/i.exec(versionOutput);
72
+ const version = match ? semver.parse(match[1]) : null;
73
+ if (!version) {
74
+ return { name, status: 'warn', message: `Unable to parse agent-browser version${versionOutput ? `: ${versionOutput.slice(0, 120)}` : ' (empty output)'}. Browser MCP tools may be unavailable.`, fix };
75
+ }
76
+ if (semver.lt(version, '0.27.0')) {
77
+ return { name, status: 'warn', message: `v${version.version} is below the ADR-122 v0.27.0 minimum for browser MCP tools`, fix };
78
+ }
79
+ return { name, status: 'pass', message: `v${version.version} meets the ADR-122 v0.27.0 minimum for browser MCP tools` };
80
+ }
81
+ export async function checkAgentBrowserVersion(probe = async () => {
82
+ const { stdout } = await execFileAsync('agent-browser', ['--version'], {
83
+ encoding: 'utf8', timeout: 3000, maxBuffer: 16 * 1024, windowsHide: true,
84
+ });
85
+ return String(stdout);
86
+ }) {
87
+ try {
88
+ return evaluateAgentBrowserVersion(await probe());
89
+ }
90
+ catch (error) {
91
+ const name = 'agent-browser CLI (ADR-122)';
92
+ const fix = 'npm install -g agent-browser@latest';
93
+ const code = error.code;
94
+ return code === 'ENOENT'
95
+ ? { name, status: 'warn', message: 'Not found on PATH; @claude-flow/browser and browser MCP tools need agent-browser v0.27.0 or newer', fix }
96
+ : { name, status: 'warn', message: `Version check failed: ${error instanceof Error ? error.message : String(error)}`, fix };
97
+ }
98
+ }
64
99
  // Check config file
65
100
  async function checkConfigFile() {
66
101
  // JSON configs (parse-validated). The first three are LEGACY shapes from
@@ -305,6 +340,22 @@ async function resolveMemoryDbPath() {
305
340
  return p;
306
341
  return null;
307
342
  }
343
+ /** Native AgentDB writes to a plaintext sibling of the sql.js memory file.
344
+ * Probe only stores that already exist: projects without the native bridge
345
+ * should not acquire an extra warning just by running doctor (#3195). */
346
+ async function existingNativeAgentDbPaths() {
347
+ const candidates = new Set();
348
+ try {
349
+ const { getMemoryRoot, resolveDbPath } = await import('../memory/memory-initializer.js');
350
+ candidates.add(join(getMemoryRoot(), 'agentdb-memory.db'));
351
+ // Explicit CLAUDE_FLOW_DB_PATH callers may put the sibling elsewhere.
352
+ candidates.add(join(dirname(resolveDbPath()), 'agentdb-memory.db'));
353
+ }
354
+ catch {
355
+ candidates.add(join(process.cwd(), '.swarm', 'agentdb-memory.db'));
356
+ }
357
+ return [...candidates].filter((path) => existsSync(path));
358
+ }
308
359
  /** Open a sql.js Database over an on-disk file, returning null when the
309
360
  * file can't be opened as a SQLite database (encrypted / corrupted / not
310
361
  * a database). Callers decide whether that's warn or fail. */
@@ -506,6 +557,79 @@ async function checkMemoryStructuralIntegrity() {
506
557
  catch { /* best-effort */ }
507
558
  }
508
559
  }
560
+ /** Check the AgentDB authority independently from the legacy memory.db.
561
+ * Opening read-only with SQLite keeps WAL commits visible without mutating
562
+ * either store; the existing legacy health checks retain their own scope. */
563
+ async function checkNativeAgentDbStructuralIntegrity(dbPath) {
564
+ const NAME = 'Native AgentDB Structural Integrity (quick_check)';
565
+ if (!existsSync(dbPath)) {
566
+ return { name: NAME, status: 'warn', message: `${dbPath} disappeared before the native check could run` };
567
+ }
568
+ if (isMemoryDbEncryptedAtRest(dbPath)) {
569
+ return {
570
+ name: NAME,
571
+ status: 'fail',
572
+ message: `${dbPath} — RFE1-encrypted; native AgentDB requires a plaintext SQLite file`,
573
+ fix: `back up ${dbPath} and restore a usable native AgentDB database`,
574
+ };
575
+ }
576
+ let Database;
577
+ try {
578
+ Database = (await import('better-sqlite3')).default;
579
+ }
580
+ catch {
581
+ return {
582
+ name: NAME,
583
+ status: 'warn',
584
+ message: `${dbPath} — better-sqlite3 not installed; native AgentDB structural health is unverified`,
585
+ fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
586
+ };
587
+ }
588
+ let db;
589
+ try {
590
+ db = new Database(dbPath, { readonly: true, fileMustExist: true });
591
+ }
592
+ catch (error) {
593
+ if (isNativeSqliteBindingUnavailable(error)) {
594
+ return nativeBindingUnavailableCheck(NAME, dbPath, error, '[native AgentDB structural health unverified]');
595
+ }
596
+ const message = error instanceof Error ? error.message : String(error);
597
+ return {
598
+ name: NAME,
599
+ status: 'fail',
600
+ message: `${dbPath} — better-sqlite3 failed to open: ${message} [native AgentDB]`,
601
+ fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
602
+ };
603
+ }
604
+ try {
605
+ const rows = db.pragma('quick_check');
606
+ const values = rows.map((row) => String(Object.values(row)[0]));
607
+ if (values.length === 1 && values[0] === 'ok') {
608
+ return { name: NAME, status: 'pass', message: `${dbPath} — PRAGMA quick_check: ok [native AgentDB, WAL-aware]` };
609
+ }
610
+ return {
611
+ name: NAME,
612
+ status: 'fail',
613
+ message: `${dbPath} — PRAGMA quick_check: ${values.slice(0, 3).join('; ')}${values.length > 3 ? ` (+${values.length - 3} more)` : ''} [native AgentDB]`,
614
+ fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
615
+ };
616
+ }
617
+ catch (error) {
618
+ const message = error instanceof Error ? error.message : String(error);
619
+ return {
620
+ name: NAME,
621
+ status: 'fail',
622
+ message: `${dbPath} — quick_check probe threw: ${message} [native AgentDB]`,
623
+ fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
624
+ };
625
+ }
626
+ finally {
627
+ try {
628
+ db.close();
629
+ }
630
+ catch { /* best-effort */ }
631
+ }
632
+ }
509
633
  // #2968/#3321 — read-only native SQLite capability probe. A skipped
510
634
  // postinstall can leave the wrapper importable but its binding unavailable.
511
635
  // Schema size cannot identify the runtime driver or the database's history:
@@ -1045,6 +1169,70 @@ async function checkLearningBridge() {
1045
1169
  fix: 'npm i -D @claude-flow/memory (optional dep appears absent — likely --omit=optional install)',
1046
1170
  };
1047
1171
  }
1172
+ /**
1173
+ * #3392: pure verdict for "does the @claude-flow/memory the CLI loads satisfy
1174
+ * the range the CLI declares?". `npx @claude-flow/cli@latest` reuses one npx
1175
+ * cache directory across CLI versions, and npm keeps an already-installed
1176
+ * dependency that still satisfies a caret range, so a stale memory could
1177
+ * survive a CLI upgrade with no error. Exported for unit testing.
1178
+ */
1179
+ export function evaluateMemoryPackageVersion(declared, installed) {
1180
+ const NAME = '@claude-flow/memory version';
1181
+ if (!declared) {
1182
+ return { name: NAME, status: 'warn', message: 'could not read the @claude-flow/memory range declared by @claude-flow/cli' };
1183
+ }
1184
+ if (!installed) {
1185
+ return {
1186
+ name: NAME,
1187
+ status: 'warn',
1188
+ message: `@claude-flow/memory is not resolvable from the CLI (declared ${declared}) — memory features fall back to degraded paths`,
1189
+ fix: `npm install @claude-flow/memory@${declared} --include=optional`,
1190
+ };
1191
+ }
1192
+ if (!semver.validRange(declared) || !semver.valid(installed)) {
1193
+ return { name: NAME, status: 'warn', message: `cannot compare installed ${installed} against declared ${declared}` };
1194
+ }
1195
+ if (semver.satisfies(installed, declared, { includePrerelease: true })) {
1196
+ return { name: NAME, status: 'pass', message: `v${installed} satisfies declared ${declared}` };
1197
+ }
1198
+ // warn, not fail: a dev/hoisted layout can legitimately differ, and doctor's
1199
+ // exit code must not depend on which copy a package manager happened to hoist.
1200
+ return {
1201
+ name: NAME,
1202
+ status: 'warn',
1203
+ message: `installed v${installed} does not satisfy declared ${declared} — a stale cached copy is running, so fixes shipped in a newer @claude-flow/memory are silently absent`,
1204
+ fix: `rm -rf "$(npm config get cache)/_npx" && npx @claude-flow/cli@latest doctor # or: npm install @claude-flow/memory@${declared}`,
1205
+ };
1206
+ }
1207
+ export async function checkMemoryPackageVersion() {
1208
+ try {
1209
+ // Walk up from this module to the CLI package root (npx cache, global,
1210
+ // project-local and monorepo dev all resolve the same way).
1211
+ let declared = null;
1212
+ let dir = dirname(fileURLToPath(import.meta.url));
1213
+ for (let i = 0; i < 8 && declared === null; i++) {
1214
+ const pj = join(dir, 'package.json');
1215
+ if (existsSync(pj)) {
1216
+ try {
1217
+ const pkg = JSON.parse(readFileSync(pj, 'utf-8'));
1218
+ if (pkg.name === '@claude-flow/cli') {
1219
+ declared = pkg.optionalDependencies?.['@claude-flow/memory'] ?? pkg.dependencies?.['@claude-flow/memory'] ?? null;
1220
+ break;
1221
+ }
1222
+ }
1223
+ catch { /* keep walking */ }
1224
+ }
1225
+ dir = dirname(dir);
1226
+ }
1227
+ // Resolve exactly as the CLI's own runtime does (its module context),
1228
+ // not from process.cwd() — that would report the project's copy instead.
1229
+ const distPath = resolveMemoryPackageFromCli();
1230
+ return evaluateMemoryPackageVersion(declared, distPath ? readMemoryPackageVersion(distPath) : null);
1231
+ }
1232
+ catch (err) {
1233
+ return { name: '@claude-flow/memory version', status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
1234
+ }
1235
+ }
1048
1236
  // Check API keys
1049
1237
  async function checkApiKeys() {
1050
1238
  const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
@@ -2272,7 +2460,7 @@ export const doctorCommand = {
2272
2460
  {
2273
2461
  name: 'component',
2274
2462
  short: 'c',
2275
- description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
2463
+ description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
2276
2464
  type: 'string'
2277
2465
  },
2278
2466
  {
@@ -2379,11 +2567,14 @@ export const doctorCommand = {
2379
2567
  output.writeln(output.dim('System diagnostics and health check'));
2380
2568
  output.writeln(output.dim('─'.repeat(50)));
2381
2569
  output.writeln();
2570
+ const nativeAgentDbChecks = (await existingNativeAgentDbPaths())
2571
+ .map((dbPath) => () => checkNativeAgentDbStructuralIntegrity(dbPath));
2382
2572
  const allChecks = [
2383
2573
  checkVersionFreshness,
2384
2574
  checkNodeVersion,
2385
2575
  checkNpmVersion,
2386
2576
  checkClaudeCode,
2577
+ checkAgentBrowserVersion, // ADR-122 Phase 0 — browser runtime compatibility
2387
2578
  checkGit,
2388
2579
  checkGitRepo,
2389
2580
  checkConfigFile,
@@ -2391,8 +2582,10 @@ export const doctorCommand = {
2391
2582
  checkDaemonStatus,
2392
2583
  checkMemoryDatabase,
2393
2584
  checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
2585
+ ...nativeAgentDbChecks, // #3195 — verify each present native authority file
2394
2586
  checkMemoryPersistenceDriver, // #2968/#3321 — read-only native capability probe
2395
2587
  checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
2588
+ checkMemoryPackageVersion, // #3392 — loaded @claude-flow/memory must satisfy the CLI's declared range
2396
2589
  checkApiKeys,
2397
2590
  checkMcpServers,
2398
2591
  checkMcpSchemaOverhead, // #2726 — fixed tools/list prompt cost
@@ -2423,18 +2616,22 @@ export const doctorCommand = {
2423
2616
  'node': checkNodeVersion,
2424
2617
  'npm': checkNpmVersion,
2425
2618
  'claude': checkClaudeCode,
2619
+ 'browser': checkAgentBrowserVersion,
2426
2620
  'config': checkConfigFile,
2427
2621
  'stale-settings': checkStaleSettingsNpx, // #2448
2428
2622
  'daemon': checkDaemonStatus,
2429
2623
  'memory': [
2430
2624
  checkMemoryDatabase, // existing: exists + statable (unchanged)
2431
2625
  checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
2626
+ ...nativeAgentDbChecks, // #3195: present AgentDB authority, checked independently
2432
2627
  checkMemoryPersistenceDriver, // #2968/#3321: read-only native capability probe
2628
+ checkMemoryPackageVersion, // #3392: loaded memory package satisfies the declared range
2433
2629
  checkMemoryContent, // #2677 check 2: memory_entries content coverage
2434
2630
  checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
2435
2631
  checkMemoryReflexionCoverage, // #2677 check 6: episodes are retrievable
2436
2632
  checkMemoryCritiqueCoverage, // #2677 check 6: feedback carries lessons
2437
2633
  ],
2634
+ 'memory-package': checkMemoryPackageVersion, // #3392
2438
2635
  'learning': checkLearningBridge, // #2545
2439
2636
  'learning-bridge': checkLearningBridge, // #2545
2440
2637
  'api': checkApiKeys,
@@ -679,7 +679,12 @@ const initCommand = {
679
679
  if (!fs.existsSync(modelDir)) {
680
680
  fs.mkdirSync(modelDir, { recursive: true });
681
681
  }
682
- // Download model if requested
682
+ // Download model if requested. #3376: record what actually happened
683
+ // instead of assuming the download ran — `getEmbeddings()` returns null
684
+ // whenever the optional @claude-flow/embeddings package is absent, which
685
+ // is every plain `npm i -g ruflo` install.
686
+ let modelDownloaded = false;
687
+ let modelSkipReason = null;
683
688
  if (download) {
684
689
  spinner.setText(`Downloading ONNX model: ${model}...`);
685
690
  const embeddings = await getEmbeddings();
@@ -687,13 +692,19 @@ const initCommand = {
687
692
  await embeddings.downloadEmbeddingModel(model, modelDir, (p) => {
688
693
  spinner.setText(`Downloading ${model}... ${p.percent.toFixed(0)}%`);
689
694
  });
695
+ modelDownloaded = true;
690
696
  }
691
697
  else {
692
- // Embeddings package not available — skip download
693
- await new Promise(r => setTimeout(r, 500));
694
- output.writeln(output.dim(' (Skipped — @claude-flow/embeddings not installed)'));
698
+ // No download happened. That is the outcome of this command, not an
699
+ // aside — reported below via spinner.fail + a warning, never as a
700
+ // dimmed note on a success path. No fake progress delay: there is
701
+ // nothing in progress.
702
+ modelSkipReason = '@claude-flow/embeddings is not installed';
695
703
  }
696
704
  }
705
+ else {
706
+ modelSkipReason = 'download disabled by --no-download';
707
+ }
697
708
  // Write embeddings config
698
709
  spinner.setText('Writing configuration...');
699
710
  const dimension = model.includes('mpnet') ? 768 : 384;
@@ -702,6 +713,12 @@ const initCommand = {
702
713
  modelPath: modelDir,
703
714
  dimension,
704
715
  cacheSize,
716
+ // #3376: additive, optional fields. `modelPath` keeps its old meaning
717
+ // (the directory the model belongs in), but it no longer implies a
718
+ // model is in it — `modelDownloaded` says whether one is. Readers that
719
+ // predate these keys are unaffected; they ignore unknown properties.
720
+ modelDownloaded,
721
+ ...(modelSkipReason ? { modelSkipReason } : {}),
705
722
  hyperbolic: {
706
723
  enabled: hyperbolic,
707
724
  curvature,
@@ -716,7 +733,15 @@ const initCommand = {
716
733
  initialized: new Date().toISOString(),
717
734
  };
718
735
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
719
- spinner.succeed('Embedding subsystem initialized');
736
+ // A download that was asked for and did not happen is a failed init:
737
+ // the only substantive step of `embeddings init` did not run. #3376.
738
+ const downloadSkipped = download && !modelDownloaded;
739
+ if (downloadSkipped) {
740
+ spinner.fail('Embedding model NOT downloaded — embedding subsystem is not ready');
741
+ }
742
+ else {
743
+ spinner.succeed('Embedding subsystem initialized');
744
+ }
720
745
  output.writeln();
721
746
  output.printTable({
722
747
  columns: [
@@ -725,6 +750,7 @@ const initCommand = {
725
750
  ],
726
751
  data: [
727
752
  { setting: 'Model', value: model },
753
+ { setting: 'Model Status', value: modelDownloaded ? output.success('Downloaded') : output.error('NOT downloaded') },
728
754
  { setting: 'Dimension', value: String(dimension) },
729
755
  { setting: 'Cache Size', value: String(cacheSize) + ' entries' },
730
756
  { setting: 'Hyperbolic', value: hyperbolic ? `${output.success('Enabled')} (c=${curvature})` : output.dim('Disabled') },
@@ -733,6 +759,20 @@ const initCommand = {
733
759
  { setting: 'Config', value: configPath },
734
760
  ],
735
761
  });
762
+ if (downloadSkipped) {
763
+ output.writeln();
764
+ output.printWarning(`No embedding model was installed: ${modelSkipReason}.`);
765
+ output.printWarning('Until a real embedder is present, embeddings fall back to hash vectors, which carry no semantic meaning.');
766
+ output.printInfo(`Configuration was still written to ${configPath}, recording "modelDownloaded": false.`);
767
+ output.writeln();
768
+ output.writeln(output.dim('To finish initializing:'));
769
+ output.printList([
770
+ 'npm install @claude-flow/embeddings - the package this command downloads with; then: embeddings init --force',
771
+ 'npm install ruvector - the other real embedder ruflo accepts; needs no download step',
772
+ 'embeddings init --no-download --force - keep this configuration without a model (exits 0)',
773
+ ]);
774
+ return { success: false, exitCode: 1, data: config };
775
+ }
736
776
  output.writeln();
737
777
  if (hyperbolic) {
738
778
  output.printBox([
@@ -1182,8 +1222,11 @@ const modelsCommand = {
1182
1222
  }
1183
1223
  }
1184
1224
  else {
1185
- await new Promise(r => setTimeout(r, 500));
1186
- spinner.succeed(`Download skipped — @claude-flow/embeddings not installed`);
1225
+ // #3376, same class as `init`: nothing was downloaded, so this is a
1226
+ // failure, not a success with a caveat. No fake progress delay either.
1227
+ spinner.fail(`Download skipped — @claude-flow/embeddings is not installed`);
1228
+ output.printWarning(`"${download}" was not downloaded. Install @claude-flow/embeddings to enable model downloads.`);
1229
+ return { success: false, exitCode: 1 };
1187
1230
  }
1188
1231
  return { success: true };
1189
1232
  }
@@ -113,7 +113,9 @@ export function runCodexInitializerCli(cwd, options) {
113
113
  // init) and scripted `--format json` output.
114
114
  async function maybeAutoDetectCodex(ctx, options) {
115
115
  try {
116
- if (ctx.flags['no-codex-detect'] === true)
116
+ // The CLI parser turns `--no-codex-detect` into `codexDetect: false`.
117
+ // Retain the literal key for programmatic CommandContext callers.
118
+ if (ctx.flags.codexDetect === false || ctx.flags['no-codex-detect'] === true)
117
119
  return;
118
120
  if (ctx.flags.format === 'json')
119
121
  return; // scripted output stays pure
@@ -295,7 +297,8 @@ function isBloatedRufloSkillDir(dir) {
295
297
  }
296
298
  async function maybeInstallSkillsSh(ctx) {
297
299
  try {
298
- if (ctx.flags['no-skills-sh'] === true)
300
+ // The CLI parser turns `--no-skills-sh` into `skillsSh: false`.
301
+ if (ctx.flags.skillsSh === false || ctx.flags['no-skills-sh'] === true)
299
302
  return;
300
303
  if (ctx.flags.format === 'json')
301
304
  return;
@@ -66,6 +66,11 @@ const startCommand = {
66
66
  description: 'Tools to advertise (comma-separated categories, prefixes, exact names, or "all")',
67
67
  type: 'string'
68
68
  },
69
+ {
70
+ name: 'request-timeout-ms',
71
+ description: 'HTTP request timeout in milliseconds (default 30000; env RUFLO_MCP_REQUEST_TIMEOUT_MS)',
72
+ type: 'number'
73
+ },
69
74
  {
70
75
  name: 'daemon',
71
76
  short: 'd',
@@ -84,6 +89,7 @@ const startCommand = {
84
89
  examples: [
85
90
  { command: 'claude-flow mcp start', description: 'Start with defaults (stdio)' },
86
91
  { command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' },
92
+ { command: 'claude-flow mcp start -t http --request-timeout-ms 120000', description: 'Allow slower cold-start HTTP tools' },
87
93
  { command: 'claude-flow mcp start -d', description: 'Start as daemon' },
88
94
  { command: 'claude-flow mcp start -f', description: 'Force restart (kill existing)' }
89
95
  ],
@@ -94,6 +100,15 @@ const startCommand = {
94
100
  const tools = ctx.flags.tools
95
101
  || process.env.CLAUDE_FLOW_MCP_TOOLS
96
102
  || 'all';
103
+ const timeoutInput = ctx.flags.requestTimeoutMs
104
+ ?? ctx.flags['request-timeout-ms']
105
+ ?? process.env.RUFLO_MCP_REQUEST_TIMEOUT_MS;
106
+ const requestTimeoutMs = timeoutInput === undefined ? undefined : Number(timeoutInput);
107
+ if (requestTimeoutMs !== undefined &&
108
+ (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1 || requestTimeoutMs > 3_600_000)) {
109
+ output.printError('Request timeout must be an integer from 1 to 3600000 milliseconds');
110
+ return { success: false, exitCode: 1 };
111
+ }
97
112
  const daemon = ctx.flags.daemon ?? false;
98
113
  const force = ctx.flags.force ?? false;
99
114
  output.writeln();
@@ -104,10 +119,17 @@ const startCommand = {
104
119
  // PID file exists, which would cause us to SIGKILL ourselves)
105
120
  const existingStatus = await getMCPServerStatus();
106
121
  const isSelfDetected = existingStatus.pid === process.pid;
107
- if (existingStatus.running && !isSelfDetected) {
108
- // For stdio transport, always force restart since we can't health check it
109
- // For other transports, check health unless --force is specified
110
- const shouldForceRestart = force || transport === 'stdio';
122
+ // #3364: only a port-bound transport (http/websocket) is single-instance.
123
+ // A stdio server belongs to the client that spawned it, over that client's
124
+ // own pipes, and any number run side by side. The PID file is a single
125
+ // slot per os.tmpdir(), so for stdio "already running" only ever meant
126
+ // "some other server is recorded" — and force-restarting SIGKILLed it
127
+ // (another terminal's stdio server, or a live http server). Stdio now
128
+ // kills a recorded server only when explicitly asked with --force.
129
+ const isSingleInstance = transport !== 'stdio';
130
+ if (existingStatus.running && !isSelfDetected && (isSingleInstance || force)) {
131
+ // --force always restarts; otherwise check health (http/websocket)
132
+ const shouldForceRestart = force;
111
133
  if (!shouldForceRestart) {
112
134
  // Verify the server is actually healthy/responsive
113
135
  const manager = getServerManager();
@@ -143,6 +165,7 @@ const startCommand = {
143
165
  host,
144
166
  port,
145
167
  tools: !tools || tools === 'all' ? 'all' : tools.split(','),
168
+ ...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
146
169
  daemonize: daemon,
147
170
  };
148
171
  try {