@claude-flow/cli 3.38.8 → 3.38.11

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 (91) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/catalog-manifest.json +2 -2
  3. package/dist/src/commands/doctor.js +53 -8
  4. package/dist/src/commands/mcp.js +4 -3
  5. package/dist/src/init/helper-refresh.d.ts +16 -9
  6. package/dist/src/init/helper-refresh.js +177 -20
  7. package/dist/src/log-filters.d.ts +3 -3
  8. package/dist/src/mcp-server.d.ts +1 -0
  9. package/dist/src/mcp-server.js +39 -3
  10. package/dist/src/mcp-tools/neural-tools.js +4 -3
  11. package/dist/src/memory/memory-bridge.d.ts +8 -0
  12. package/dist/src/memory/memory-bridge.js +18 -0
  13. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  14. package/node_modules/@claude-flow/mcp/README.md +429 -0
  15. package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts +36 -0
  16. package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -0
  17. package/node_modules/@claude-flow/mcp/dist/connection-pool.js +273 -0
  18. package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -0
  19. package/node_modules/@claude-flow/mcp/dist/index.d.ts +75 -0
  20. package/node_modules/@claude-flow/mcp/dist/index.d.ts.map +1 -0
  21. package/node_modules/@claude-flow/mcp/dist/index.js +85 -0
  22. package/node_modules/@claude-flow/mcp/dist/index.js.map +1 -0
  23. package/node_modules/@claude-flow/mcp/dist/oauth.d.ts +146 -0
  24. package/node_modules/@claude-flow/mcp/dist/oauth.d.ts.map +1 -0
  25. package/node_modules/@claude-flow/mcp/dist/oauth.js +318 -0
  26. package/node_modules/@claude-flow/mcp/dist/oauth.js.map +1 -0
  27. package/node_modules/@claude-flow/mcp/dist/prompt-registry.d.ts +90 -0
  28. package/node_modules/@claude-flow/mcp/dist/prompt-registry.d.ts.map +1 -0
  29. package/node_modules/@claude-flow/mcp/dist/prompt-registry.js +209 -0
  30. package/node_modules/@claude-flow/mcp/dist/prompt-registry.js.map +1 -0
  31. package/node_modules/@claude-flow/mcp/dist/rate-limiter.d.ts +86 -0
  32. package/node_modules/@claude-flow/mcp/dist/rate-limiter.d.ts.map +1 -0
  33. package/node_modules/@claude-flow/mcp/dist/rate-limiter.js +197 -0
  34. package/node_modules/@claude-flow/mcp/dist/rate-limiter.js.map +1 -0
  35. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +144 -0
  36. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -0
  37. package/node_modules/@claude-flow/mcp/dist/resource-registry.js +405 -0
  38. package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -0
  39. package/node_modules/@claude-flow/mcp/dist/sampling.d.ts +102 -0
  40. package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -0
  41. package/node_modules/@claude-flow/mcp/dist/sampling.js +268 -0
  42. package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -0
  43. package/node_modules/@claude-flow/mcp/dist/schema-validator.d.ts +30 -0
  44. package/node_modules/@claude-flow/mcp/dist/schema-validator.d.ts.map +1 -0
  45. package/node_modules/@claude-flow/mcp/dist/schema-validator.js +182 -0
  46. package/node_modules/@claude-flow/mcp/dist/schema-validator.js.map +1 -0
  47. package/node_modules/@claude-flow/mcp/dist/server.d.ts +125 -0
  48. package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -0
  49. package/node_modules/@claude-flow/mcp/dist/server.js +849 -0
  50. package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -0
  51. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts +55 -0
  52. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -0
  53. package/node_modules/@claude-flow/mcp/dist/session-manager.js +252 -0
  54. package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -0
  55. package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts +81 -0
  56. package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -0
  57. package/node_modules/@claude-flow/mcp/dist/task-manager.js +337 -0
  58. package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -0
  59. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +96 -0
  60. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -0
  61. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +399 -0
  62. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -0
  63. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +60 -0
  64. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -0
  65. package/node_modules/@claude-flow/mcp/dist/transport/http.js +503 -0
  66. package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -0
  67. package/node_modules/@claude-flow/mcp/dist/transport/index.d.ts +50 -0
  68. package/node_modules/@claude-flow/mcp/dist/transport/index.d.ts.map +1 -0
  69. package/node_modules/@claude-flow/mcp/dist/transport/index.js +181 -0
  70. package/node_modules/@claude-flow/mcp/dist/transport/index.js.map +1 -0
  71. package/node_modules/@claude-flow/mcp/dist/transport/stdio.d.ts +43 -0
  72. package/node_modules/@claude-flow/mcp/dist/transport/stdio.d.ts.map +1 -0
  73. package/node_modules/@claude-flow/mcp/dist/transport/stdio.js +194 -0
  74. package/node_modules/@claude-flow/mcp/dist/transport/stdio.js.map +1 -0
  75. package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts +65 -0
  76. package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts.map +1 -0
  77. package/node_modules/@claude-flow/mcp/dist/transport/websocket.js +314 -0
  78. package/node_modules/@claude-flow/mcp/dist/transport/websocket.js.map +1 -0
  79. package/node_modules/@claude-flow/mcp/dist/types.d.ts +482 -0
  80. package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -0
  81. package/node_modules/@claude-flow/mcp/dist/types.js +40 -0
  82. package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -0
  83. package/node_modules/@claude-flow/mcp/package.json +50 -0
  84. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  85. package/node_modules/@claude-flow/security/dist/path-validator.d.ts +23 -0
  86. package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
  87. package/node_modules/@claude-flow/security/dist/path-validator.js +128 -5
  88. package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
  89. package/package.json +9 -2
  90. package/.claude/.proven-config-version +0 -1
  91. package/.claude/proven-config.json +0 -42
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.38.8",
3
+ "version": "3.38.11",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
6
6
  "hook-handler.cjs": "dae295fb9ae2626b89899c19a20cc911541af82b52d2eeb9b214d618b96e9a86",
@@ -8,6 +8,6 @@
8
8
  "statusline.cjs": "0457fe53f8cd2c56458ff178392536a5868efd1a573665fa43bc01d2d95ca677"
9
9
  }
10
10
  },
11
- "signature": "EoEEuSnNwaVCAmnwd7Evq+V2sD0Zz5cOqqZO4PecBOchzrcimyqFqkz4eOyhYF6iIhEtQ8+XsOvZKFKMbtKIDg==",
11
+ "signature": "rNkivgIb+R5mwQcChrNF+m4lsfPPlKBn4oizP9Q7nuuIi8en/J3NJYf5l6mPRfEc4vdlqnnf0+Oid/007092Dw==",
12
12
  "algorithm": "ed25519"
13
13
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 4,
4
- "generatedAt": "2026-08-12T22:31:25.682Z",
5
- "gitSha": "5efd5937",
4
+ "generatedAt": "2026-08-14T23:30:39.000Z",
5
+ "gitSha": "45e65b5d",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 397,
@@ -361,6 +361,38 @@ function isMemoryDbEncryptedAtRest(dbPath) {
361
361
  return false;
362
362
  }
363
363
  }
364
+ /**
365
+ * Distinguish an unavailable native addon from a database that the native
366
+ * driver opened and found malformed. Importing better-sqlite3 can succeed even
367
+ * when its postinstall script was skipped: the JavaScript wrapper loads, then
368
+ * the first Database construction throws because better_sqlite3.node is absent
369
+ * or incompatible with the active Node ABI.
370
+ */
371
+ function isNativeSqliteBindingUnavailable(error) {
372
+ const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
373
+ return [
374
+ /Could not locate the bindings file/i,
375
+ /better_sqlite3\.node/i,
376
+ /NODE_MODULE_VERSION/i,
377
+ /compiled against a different Node\.js version/i,
378
+ /ERR_DLOPEN_FAILED/i,
379
+ /invalid ELF header/i,
380
+ /wrong ELF class/i,
381
+ /not a valid Win32 application/i,
382
+ /Module did not self-register/i,
383
+ /no suitable image found/i,
384
+ ].some((pattern) => pattern.test(message));
385
+ }
386
+ function nativeBindingUnavailableCheck(name, dbPath, error, suffix) {
387
+ const details = error instanceof Error ? error.message : String(error);
388
+ const summary = details.split(/\r?\n/, 1)[0].replace(/\s+Tried:\s*$/i, '');
389
+ return {
390
+ name,
391
+ status: 'warn',
392
+ message: `${dbPath} — better-sqlite3 package found, but its native binding is unavailable: ${summary} ${suffix}`,
393
+ fix: 'reinstall with npm install scripts enabled or run `npm rebuild better-sqlite3`; then rerun this check',
394
+ };
395
+ }
364
396
  // #2737 part 1 — bare `doctor` (no --component flag) never actually opened
365
397
  // memory.db: checkMemoryDatabase above is existsSync()+statSync() only, so
366
398
  // it PASSES on any file that exists and can be stat'd, corrupt or not, and
@@ -416,8 +448,8 @@ async function checkMemoryStructuralIntegrity() {
416
448
  return {
417
449
  name: NAME,
418
450
  status: 'warn',
419
- message: `${dbPath} — better-sqlite3 not installed; structural-only check skipped (optional native module)`,
420
- fix: 'npm install better-sqlite3 (enables WAL-aware structural checks on every `doctor` run)',
451
+ message: `${dbPath} — better-sqlite3 not installed; native WAL-backed persistence and the structural-only check are unavailable`,
452
+ fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
421
453
  };
422
454
  }
423
455
  let db;
@@ -425,6 +457,9 @@ async function checkMemoryStructuralIntegrity() {
425
457
  db = new Database(dbPath, { readonly: true, fileMustExist: true });
426
458
  }
427
459
  catch (e) {
460
+ if (isNativeSqliteBindingUnavailable(e)) {
461
+ return nativeBindingUnavailableCheck(NAME, dbPath, e, '[structural-only; durable WAL-backed persistence unavailable]');
462
+ }
428
463
  const msg = e.message || String(e);
429
464
  // Encryption already ruled out above — an unencrypted file
430
465
  // better-sqlite3 can't even open is definitive corruption.
@@ -498,9 +533,9 @@ async function checkMemoryIntegrity() {
498
533
  }
499
534
  // Prefer native better-sqlite3 (WAL-aware, full integrity_check). Module
500
535
  // load is isolated in its own try/catch so ONLY "not installed" falls
501
- // through to the sql.js fallback below once the module loaded, any
502
- // open/query failure is resolved (fail) right here, not silently
503
- // reclassified as "sql.js fallback, main-image-only" by an outer catch.
536
+ // through to the sql.js fallback below. A loaded JavaScript wrapper can
537
+ // still lack its native binding (#2968); that capability failure is a warn,
538
+ // while genuine open/query failures remain authoritative failures here.
504
539
  let Database;
505
540
  try {
506
541
  Database = (await import('better-sqlite3')).default;
@@ -514,6 +549,9 @@ async function checkMemoryIntegrity() {
514
549
  db = new Database(dbPath, { readonly: true, fileMustExist: true });
515
550
  }
516
551
  catch (e) {
552
+ if (isNativeSqliteBindingUnavailable(e)) {
553
+ return nativeBindingUnavailableCheck('Memory Integrity', dbPath, e, '[native WAL-backed persistence unavailable]');
554
+ }
517
555
  const msg = e.message || String(e);
518
556
  return {
519
557
  name: 'Memory Integrity',
@@ -574,7 +612,12 @@ async function checkMemoryIntegrity() {
574
612
  const res = db.exec('PRAGMA integrity_check');
575
613
  const rows = res[0]?.values?.map((v) => String(v[0])) ?? [];
576
614
  if (rows.length === 1 && rows[0] === 'ok') {
577
- return { name: 'Memory Integrity', status: 'pass', message: `${dbPath} — PRAGMA integrity_check: ok [main-image-only fallback — sql.js can't see WAL-only data; install better-sqlite3 for full coverage]` };
615
+ return {
616
+ name: 'Memory Integrity',
617
+ status: 'warn',
618
+ message: `${dbPath} — PRAGMA integrity_check: ok, but only for the main image [sql.js fallback; native WAL-backed persistence unavailable]`,
619
+ fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
620
+ };
578
621
  }
579
622
  return {
580
623
  name: 'Memory Integrity',
@@ -885,8 +928,10 @@ async function checkApiKeys() {
885
928
  found.push(key);
886
929
  }
887
930
  }
888
- // Detect Claude Code environment — API keys are managed internally
889
- const inClaudeCode = !!(process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
931
+ // Detect Claude Code environment — API keys are managed internally.
932
+ // Claude Code sets CLAUDECODE (no underscore); CLAUDE_CODE is kept for
933
+ // compatibility with anything else that might set it.
934
+ const inClaudeCode = !!(process.env.CLAUDECODE || process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
890
935
  if (found.includes('ANTHROPIC_API_KEY') || found.includes('CLAUDE_API_KEY')) {
891
936
  return { name: 'API Keys', status: 'pass', message: `Found: ${found.join(', ')}` };
892
937
  }
@@ -64,8 +64,7 @@ const startCommand = {
64
64
  {
65
65
  name: 'tools',
66
66
  description: 'Tools to advertise (comma-separated categories, prefixes, exact names, or "all")',
67
- type: 'string',
68
- default: 'all'
67
+ type: 'string'
69
68
  },
70
69
  {
71
70
  name: 'daemon',
@@ -92,7 +91,9 @@ const startCommand = {
92
91
  const port = ctx.flags.port ?? 3000;
93
92
  const host = ctx.flags.host ?? 'localhost';
94
93
  const transport = ctx.flags.transport ?? 'stdio';
95
- const tools = ctx.flags.tools || 'all';
94
+ const tools = ctx.flags.tools
95
+ || process.env.CLAUDE_FLOW_MCP_TOOLS
96
+ || 'all';
96
97
  const daemon = ctx.flags.daemon ?? false;
97
98
  const force = ctx.flags.force ?? false;
98
99
  output.writeln();
@@ -1,4 +1,15 @@
1
1
  export declare const HELPERS_STAMP_FILE = ".helpers-version";
2
+ export declare const HELPERS_REFRESH_LOCK_FILE = ".helpers-refresh.lock";
3
+ interface RefreshOptions {
4
+ sourceDirOverride?: string;
5
+ pubkeyPemOverride?: string;
6
+ versionOverride?: string;
7
+ alsoRefreshGlobal?: boolean;
8
+ beforeWriteOverride?: () => void | Promise<void>;
9
+ lockWaitMsOverride?: number;
10
+ lockRetryMsOverride?: number;
11
+ malformedLockStaleMsOverride?: number;
12
+ }
2
13
  /**
3
14
  * ruflo-owned helpers that carry hook logic (or the render surface for the
4
15
  * funnel disclosure row) and must track the package version. Adding to this
@@ -40,9 +51,9 @@ export declare function getInstalledCliVersion(): string;
40
51
  * running `daemon start` (or any command) would see its own older version !=
41
52
  * the project's newer stamp and silently overwrite hand-fixed
42
53
  * `hook-handler.cjs`/`intelligence.cjs` with its own older, already-superseded
43
- * bundled copies. Comparing with `semver.gt` instead of `!==` makes that
44
- * impossible: an older or equal installed version is always a no-op,
45
- * regardless of how it got invoked.
54
+ * bundled copies. The semver guard is therefore re-evaluated while holding a
55
+ * per-directory cross-process lock; otherwise an older process can pass the
56
+ * guard first but finish writing last.
46
57
  *
47
58
  * `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
48
59
  * `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled
@@ -59,12 +70,7 @@ export declare function getInstalledCliVersion(): string;
59
70
  * compat with pre-3.31.3 callers). If the global pass ran, its own result is
60
71
  * carried in the optional `global` field.
61
72
  */
62
- export declare function autoRefreshHelpersIfStale(cwd: string, opts?: {
63
- sourceDirOverride?: string;
64
- pubkeyPemOverride?: string;
65
- versionOverride?: string;
66
- alsoRefreshGlobal?: boolean;
67
- }): Promise<{
73
+ export declare function autoRefreshHelpersIfStale(cwd: string, opts?: RefreshOptions): Promise<{
68
74
  refreshed: boolean;
69
75
  from?: string;
70
76
  to?: string;
@@ -76,4 +82,5 @@ export declare function autoRefreshHelpersIfStale(cwd: string, opts?: {
76
82
  blocked?: string;
77
83
  };
78
84
  }>;
85
+ export {};
79
86
  //# sourceMappingURL=helper-refresh.d.ts.map
@@ -53,6 +53,11 @@ function findPackageRoot(startDir, maxUp = 6) {
53
53
  return null;
54
54
  }
55
55
  export const HELPERS_STAMP_FILE = '.helpers-version';
56
+ export const HELPERS_REFRESH_LOCK_FILE = '.helpers-refresh.lock';
57
+ const DEFAULT_LOCK_WAIT_MS = 2_000;
58
+ const DEFAULT_LOCK_RETRY_MS = 10;
59
+ const DEFAULT_MALFORMED_LOCK_STALE_MS = 5 * 60_000;
60
+ let tempFileCounter = 0;
56
61
  /**
57
62
  * ruflo-owned helpers that carry hook logic (or the render surface for the
58
63
  * funnel disclosure row) and must track the package version. Adding to this
@@ -67,6 +72,139 @@ export const CRITICAL_HELPERS = [
67
72
  // existing installs on the next `ruflo` command, not only fresh `ruflo init`.
68
73
  'statusline.cjs',
69
74
  ];
75
+ function errorCode(error) {
76
+ return typeof error === 'object' && error !== null && 'code' in error
77
+ ? String(error.code)
78
+ : undefined;
79
+ }
80
+ function processIsAlive(pid) {
81
+ try {
82
+ process.kill(pid, 0);
83
+ return true;
84
+ }
85
+ catch (error) {
86
+ return errorCode(error) !== 'ESRCH';
87
+ }
88
+ }
89
+ function removeAbandonedLock(lockPath, malformedStaleMs) {
90
+ try {
91
+ const parsed = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
92
+ if (Number.isSafeInteger(parsed.pid) && Number(parsed.pid) > 0) {
93
+ if (processIsAlive(Number(parsed.pid)))
94
+ return false;
95
+ fs.unlinkSync(lockPath);
96
+ return true;
97
+ }
98
+ }
99
+ catch (error) {
100
+ if (errorCode(error) === 'ENOENT')
101
+ return true;
102
+ }
103
+ // A process can die after the exclusive create but before writing metadata.
104
+ // Only reclaim such malformed locks after a generous age; valid locks with a
105
+ // live PID are never age-evicted, so a slow refresh cannot lose ownership.
106
+ try {
107
+ if (Date.now() - fs.statSync(lockPath).mtimeMs >= malformedStaleMs) {
108
+ fs.unlinkSync(lockPath);
109
+ return true;
110
+ }
111
+ }
112
+ catch (error) {
113
+ if (errorCode(error) === 'ENOENT')
114
+ return true;
115
+ }
116
+ return false;
117
+ }
118
+ async function acquireRefreshLock(helpersDir, opts) {
119
+ const lockPath = path.join(helpersDir, HELPERS_REFRESH_LOCK_FILE);
120
+ const waitMs = opts.lockWaitMsOverride ?? DEFAULT_LOCK_WAIT_MS;
121
+ const retryMs = opts.lockRetryMsOverride ?? DEFAULT_LOCK_RETRY_MS;
122
+ const malformedStaleMs = opts.malformedLockStaleMsOverride ?? DEFAULT_MALFORMED_LOCK_STALE_MS;
123
+ const deadline = Date.now() + Math.max(0, waitMs);
124
+ while (true) {
125
+ const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
126
+ let fd;
127
+ let created = false;
128
+ try {
129
+ fd = fs.openSync(lockPath, 'wx');
130
+ created = true;
131
+ fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, token }), 'utf-8');
132
+ fs.closeSync(fd);
133
+ fd = undefined;
134
+ return () => {
135
+ try {
136
+ const current = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
137
+ if (current.token === token)
138
+ fs.unlinkSync(lockPath);
139
+ }
140
+ catch { /* best-effort release; a replaced lock is never removed */ }
141
+ };
142
+ }
143
+ catch (error) {
144
+ if (fd !== undefined) {
145
+ try {
146
+ fs.closeSync(fd);
147
+ }
148
+ catch { /* best-effort */ }
149
+ }
150
+ if (created) {
151
+ try {
152
+ fs.unlinkSync(lockPath);
153
+ }
154
+ catch { /* best-effort */ }
155
+ }
156
+ if (errorCode(error) !== 'EEXIST')
157
+ throw error;
158
+ if (removeAbandonedLock(lockPath, malformedStaleMs))
159
+ continue;
160
+ if (Date.now() >= deadline)
161
+ return null;
162
+ await new Promise((resolve) => setTimeout(resolve, Math.max(1, retryMs)));
163
+ }
164
+ }
165
+ }
166
+ function temporarySiblingPath(target) {
167
+ tempFileCounter += 1;
168
+ return `${target}.tmp-${process.pid}-${Date.now()}-${tempFileCounter}`;
169
+ }
170
+ function atomicCopyFileSync(source, target, mode) {
171
+ const temporary = temporarySiblingPath(target);
172
+ try {
173
+ fs.copyFileSync(source, temporary, fs.constants.COPYFILE_EXCL);
174
+ if (mode) {
175
+ try {
176
+ fs.chmodSync(temporary, mode);
177
+ }
178
+ catch { /* non-fatal */ }
179
+ }
180
+ fs.renameSync(temporary, target);
181
+ }
182
+ finally {
183
+ try {
184
+ fs.unlinkSync(temporary);
185
+ }
186
+ catch { /* already renamed or never created */ }
187
+ }
188
+ }
189
+ function atomicWriteFileSync(target, content, mode) {
190
+ const temporary = temporarySiblingPath(target);
191
+ try {
192
+ fs.writeFileSync(temporary, content, { encoding: 'utf-8', flag: 'wx' });
193
+ if (mode) {
194
+ try {
195
+ fs.chmodSync(temporary, mode);
196
+ }
197
+ catch { /* non-fatal */ }
198
+ }
199
+ fs.renameSync(temporary, target);
200
+ }
201
+ finally {
202
+ try {
203
+ fs.unlinkSync(temporary);
204
+ }
205
+ catch { /* already renamed or never created */ }
206
+ }
207
+ }
70
208
  /** Installed @claude-flow/cli version — the value the helpers are stamped with. */
71
209
  export function getInstalledCliVersion() {
72
210
  try {
@@ -148,20 +286,16 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
148
286
  let wrote = false;
149
287
  for (const name of toCopy) {
150
288
  const tp = path.join(helpersDir, name);
151
- fs.copyFileSync(path.join(source, name), tp);
152
- try {
153
- fs.chmodSync(tp, '755');
154
- }
155
- catch { /* non-fatal */ }
289
+ atomicCopyFileSync(path.join(source, name), tp, '755');
156
290
  wrote = true;
157
291
  }
158
292
  try {
159
- fs.copyFileSync(path.join(source, HELPERS_MANIFEST_FILE), path.join(helpersDir, HELPERS_MANIFEST_FILE));
293
+ atomicCopyFileSync(path.join(source, HELPERS_MANIFEST_FILE), path.join(helpersDir, HELPERS_MANIFEST_FILE));
160
294
  }
161
295
  catch { /* non-fatal */ }
162
296
  if (wrote) {
163
297
  try {
164
- fs.writeFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version, 'utf-8');
298
+ atomicWriteFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version);
165
299
  }
166
300
  catch { /* non-fatal */ }
167
301
  }
@@ -187,16 +321,12 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
187
321
  let wrote = false;
188
322
  for (const [name, content] of Object.entries(files)) {
189
323
  const tp = path.join(helpersDir, name);
190
- fs.writeFileSync(tp, content, 'utf-8');
191
- try {
192
- fs.chmodSync(tp, '755');
193
- }
194
- catch { /* non-fatal */ }
324
+ atomicWriteFileSync(tp, content, '755');
195
325
  wrote = true;
196
326
  }
197
327
  if (wrote) {
198
328
  try {
199
- fs.writeFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version, 'utf-8');
329
+ atomicWriteFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version);
200
330
  }
201
331
  catch { /* non-fatal */ }
202
332
  }
@@ -216,9 +346,9 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
216
346
  * running `daemon start` (or any command) against THIS project directory
217
347
  * would see its own older version != the project's newer stamp and silently
218
348
  * overwrite hand-fixed `hook-handler.cjs`/`intelligence.cjs` with its own
219
- * older, already-superseded bundled copies. Comparing with `semver.gt`
220
- * instead of `!==` makes that impossible: an older or equal installed
221
- * version is always a no-op, regardless of how it got invoked.
349
+ * older, already-superseded bundled copies. The semver guard is therefore
350
+ * re-evaluated while holding a per-directory cross-process lock; otherwise
351
+ * an older process can pass the guard first but finish writing last.
222
352
  *
223
353
  * `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
224
354
  * `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled to
@@ -231,7 +361,7 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
231
361
  * signed fixture and get real, deterministic coverage of the verify → hash →
232
362
  * copy logic without depending on that.
233
363
  */
234
- async function refreshOneHelpersDir(helpersDir, version, opts) {
364
+ async function refreshOneHelpersDirLocked(helpersDir, version, opts) {
235
365
  if (!fs.existsSync(path.join(helpersDir, 'hook-handler.cjs')))
236
366
  return { refreshed: false };
237
367
  // .LOCKED marker: users developing ruflo itself (or any project with
@@ -258,6 +388,7 @@ async function refreshOneHelpersDir(helpersDir, version, opts) {
258
388
  // would silently DOWNGRADE the helpers. Skip, untouched.
259
389
  return { refreshed: false };
260
390
  }
391
+ await opts.beforeWriteOverride?.();
261
392
  const res = await writeCriticalHelpers(helpersDir, version, {
262
393
  sourceDirOverride: opts.sourceDirOverride,
263
394
  pubkeyPemOverride: opts.pubkeyPemOverride,
@@ -266,6 +397,32 @@ async function refreshOneHelpersDir(helpersDir, version, opts) {
266
397
  return { refreshed: false, blocked: res.blocked };
267
398
  return res.wrote ? { refreshed: true, from: stamped || '(unstamped)', to: version } : { refreshed: false };
268
399
  }
400
+ async function refreshOneHelpersDir(helpersDir, version, opts) {
401
+ if (!fs.existsSync(path.join(helpersDir, 'hook-handler.cjs')))
402
+ return { refreshed: false };
403
+ // Respect the repository opt-out before creating even a transient lock file
404
+ // in a directory whose helpers are intentionally maintained by hand. Keep
405
+ // the check in the locked path too in case the marker appears while waiting.
406
+ if (fs.existsSync(path.join(helpersDir, '.LOCKED'))) {
407
+ return { refreshed: false, blocked: '.LOCKED marker present — refresh skipped (delete to re-enable)' };
408
+ }
409
+ try {
410
+ if (fs.readFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), 'utf-8').trim() === version)
411
+ return { refreshed: false };
412
+ }
413
+ catch { /* unstamped: continue to the locked path */ }
414
+ const releaseLock = await acquireRefreshLock(helpersDir, opts);
415
+ if (!releaseLock)
416
+ return { refreshed: false, blocked: 'helper refresh already in progress' };
417
+ try {
418
+ // Re-read the stamp and .LOCKED marker under the cross-process lock. A
419
+ // concurrent newer CLI may have completed while this caller waited.
420
+ return await refreshOneHelpersDirLocked(helpersDir, version, opts);
421
+ }
422
+ finally {
423
+ releaseLock();
424
+ }
425
+ }
269
426
  /**
270
427
  * On CLI startup, refresh critical helpers if their stamp is older than the
271
428
  * installed CLI version. Two passes:
@@ -298,9 +455,9 @@ async function refreshOneHelpersDir(helpersDir, version, opts) {
298
455
  * running `daemon start` (or any command) would see its own older version !=
299
456
  * the project's newer stamp and silently overwrite hand-fixed
300
457
  * `hook-handler.cjs`/`intelligence.cjs` with its own older, already-superseded
301
- * bundled copies. Comparing with `semver.gt` instead of `!==` makes that
302
- * impossible: an older or equal installed version is always a no-op,
303
- * regardless of how it got invoked.
458
+ * bundled copies. The semver guard is therefore re-evaluated while holding a
459
+ * per-directory cross-process lock; otherwise an older process can pass the
460
+ * guard first but finish writing last.
304
461
  *
305
462
  * `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
306
463
  * `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled
@@ -40,7 +40,7 @@ declare const STDERR_REDIRECT_PREFIXES: string[];
40
40
  declare const AGENTDB_MOCK_FALLBACK_DROP_PREFIXES: string[];
41
41
  declare const shouldRedirectToStderr: (msg: unknown) => boolean;
42
42
  declare const isAgentdbMockFallbackNoise: (msg: unknown) => boolean;
43
- declare const origWarn: (...data: any[]) => void;
44
- declare const origLog: (...data: any[]) => void;
45
- declare const origError: (...data: any[]) => void;
43
+ declare const origWarn: (message?: any, ...optionalParams: any[]) => void;
44
+ declare const origLog: (message?: any, ...optionalParams: any[]) => void;
45
+ declare const origError: (message?: any, ...optionalParams: any[]) => void;
46
46
  //# sourceMappingURL=log-filters.d.ts.map
@@ -84,6 +84,7 @@ export declare class MCPServerManager extends EventEmitter {
84
84
  private server?;
85
85
  private startTime?;
86
86
  private healthCheckInterval?;
87
+ private mcpServers;
87
88
  constructor(options?: MCPServerOptions);
88
89
  /**
89
90
  * Start the MCP server
@@ -101,6 +101,7 @@ export class MCPServerManager extends EventEmitter {
101
101
  server;
102
102
  startTime;
103
103
  healthCheckInterval;
104
+ mcpServers = [];
104
105
  constructor(options = {}) {
105
106
  super();
106
107
  // `options.tools`, populated by the `mcp start --tools` CLI flag, is
@@ -185,6 +186,11 @@ export class MCPServerManager extends EventEmitter {
185
186
  });
186
187
  this.server = undefined;
187
188
  }
189
+ if (this.mcpServers.length > 0) {
190
+ const servers = this.mcpServers;
191
+ this.mcpServers = [];
192
+ await Promise.all(servers.map((server) => server.stop()));
193
+ }
188
194
  // Remove PID file
189
195
  await this.removePidFile();
190
196
  this.startTime = undefined;
@@ -560,18 +566,48 @@ export class MCPServerManager extends EventEmitter {
560
566
  warn: (msg, data) => this.emit('log', { level: 'warn', msg, data }),
561
567
  error: (msg, data) => this.emit('log', { level: 'error', msg, data }),
562
568
  };
569
+ const { listMCPTools, callMCPTool } = await import('./mcp-client.js');
570
+ const fallbackSessionId = `http-${randomUUID()}`;
571
+ const cliTools = filterAdvertisedMcpTools(listMCPTools(), this.options.tools).map((tool) => ({
572
+ name: tool.name,
573
+ description: tool.description,
574
+ inputSchema: tool.inputSchema,
575
+ category: tool.category,
576
+ tags: tool.tags,
577
+ version: tool.version,
578
+ cacheable: tool.cacheable,
579
+ cacheTTL: tool.cacheTTL,
580
+ handler: async (input, context) => {
581
+ try {
582
+ const result = await callMCPTool(tool.name, input || {}, { sessionId: context?.sessionId || fallbackSessionId });
583
+ trackRequest(tool.name, true);
584
+ return result;
585
+ }
586
+ catch (error) {
587
+ trackRequest(tool.name, false);
588
+ throw error;
589
+ }
590
+ },
591
+ }));
592
+ // Use one MCP server with two HTTP transports for the localhost default.
593
+ // Both loopback sockets therefore share sessions, tools, and notifications.
594
+ const dualLoopback = this.options.host === 'localhost';
563
595
  const mcpServer = createMCPServer({
564
596
  name: 'Claude-Flow MCP Server V3',
565
597
  version: '3.0.0',
566
598
  transport: this.options.transport,
567
- host: this.options.host,
599
+ host: dualLoopback ? '127.0.0.1' : this.options.host,
600
+ additionalHosts: dualLoopback && this.options.transport === 'http' ? ['::1'] : undefined,
568
601
  port: this.options.port,
569
602
  enableMetrics: true,
570
603
  enableCaching: true,
571
604
  }, logger);
605
+ const registration = mcpServer.registerTools(cliTools);
606
+ if (registration.failed.length > 0) {
607
+ throw new Error(`Failed to register MCP tools: ${registration.failed.join(', ')}`);
608
+ }
572
609
  await mcpServer.start();
573
- // Store reference for stopping
574
- this._mcpServer = mcpServer;
610
+ this.mcpServers = [mcpServer];
575
611
  }
576
612
  /**
577
613
  * Wait for server to be ready
@@ -199,11 +199,12 @@ function ensureEmbeddings() {
199
199
  })();
200
200
  return embeddingsPromise;
201
201
  }
202
- // Storage paths
202
+ // Storage paths. Note: `.claude-flow/neural/patterns.json` is a SEPARATE
203
+ // file owned by the ReasoningBank store in memory/intelligence.ts — this
204
+ // module only ever reads/writes MODELS_FILE, never a patterns.json.
203
205
  const STORAGE_DIR = '.claude-flow';
204
206
  const NEURAL_DIR = 'neural';
205
207
  const MODELS_FILE = 'models.json';
206
- const PATTERNS_FILE = 'patterns.json';
207
208
  function getNeuralDir() {
208
209
  return join(getProjectCwd(), STORAGE_DIR, NEURAL_DIR);
209
210
  }
@@ -262,7 +263,7 @@ export function getNeuralStoreStats() {
262
263
  patternCount: patterns.length,
263
264
  byType,
264
265
  modelCount: Object.values(store.models ?? {}).length,
265
- source: '.claude-flow/neural/patterns.json (loadNeuralStore)',
266
+ source: '.claude-flow/neural/models.json (loadNeuralStore)',
266
267
  };
267
268
  }
268
269
  /**
@@ -16,6 +16,14 @@
16
16
  *
17
17
  * @module v3/cli/memory-bridge
18
18
  */
19
+ /**
20
+ * #3024: AgentDB's optional native controller stack can abort the whole Node
21
+ * process on Windows during registry initialization (a Rust allocation panic),
22
+ * which cannot be caught by JavaScript. Keep the CLI/MCP server on the
23
+ * sql.js + local-embedding path there until the native dependency is proven
24
+ * safe. Advanced users and CI can opt back in explicitly for diagnosis.
25
+ */
26
+ export declare function shouldDisableNativeBridge(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): boolean;
19
27
  /**
20
28
  * Should this init-time log line be swallowed?
21
29
  *
@@ -38,6 +38,18 @@ const ACTIVE_MEMORY_ROW_SQL = `(status = 'active' OR status IS NULL)`;
38
38
  * better-sqlite3 bridge") the caller has no way to check.
39
39
  */
40
40
  let bridgeFailureReason = null;
41
+ /**
42
+ * #3024: AgentDB's optional native controller stack can abort the whole Node
43
+ * process on Windows during registry initialization (a Rust allocation panic),
44
+ * which cannot be caught by JavaScript. Keep the CLI/MCP server on the
45
+ * sql.js + local-embedding path there until the native dependency is proven
46
+ * safe. Advanced users and CI can opt back in explicitly for diagnosis.
47
+ */
48
+ export function shouldDisableNativeBridge(platform = process.platform, env = process.env) {
49
+ if (env.CLAUDE_FLOW_DISABLE_BRIDGE === '1')
50
+ return true;
51
+ return platform === 'win32' && env.CLAUDE_FLOW_ENABLE_NATIVE_BRIDGE_ON_WINDOWS !== '1';
52
+ }
41
53
  /**
42
54
  * ADR-323: reuse memory-initializer's provenance-type allowlist rather than
43
55
  * duplicating it (drift risk). Lazy CJS require for the same circular-ESM-
@@ -154,6 +166,12 @@ export function shouldSuppressInitLog(msg) {
154
166
  * Returns null if @claude-flow/memory is not available.
155
167
  */
156
168
  async function getRegistry(dbPath) {
169
+ if (shouldDisableNativeBridge()) {
170
+ bridgeFailureReason = process.platform === 'win32'
171
+ ? 'AgentDB native bridge disabled on Windows after #3024; set CLAUDE_FLOW_ENABLE_NATIVE_BRIDGE_ON_WINDOWS=1 to opt in'
172
+ : 'AgentDB native bridge disabled by CLAUDE_FLOW_DISABLE_BRIDGE=1';
173
+ return null;
174
+ }
157
175
  if (bridgeAvailable === false)
158
176
  return null;
159
177
  if (registryInstance)
File without changes