@claude-flow/cli 3.7.0-alpha.8 → 3.7.0-alpha.80

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 (291) hide show
  1. package/.claude/agents/github/release-manager.md +2 -2
  2. package/.claude/agents/github/release-swarm.md +1 -1
  3. package/.claude/agents/github/repo-architect.md +2 -2
  4. package/.claude/agents/github/swarm-pr.md +1 -1
  5. package/.claude/agents/github/workflow-automation.md +2 -2
  6. package/.claude/commands/github/code-review-swarm.md +1 -1
  7. package/.claude/commands/github/issue-tracker.md +3 -3
  8. package/.claude/commands/github/release-manager.md +5 -3
  9. package/.claude/commands/github/release-swarm.md +1 -1
  10. package/.claude/commands/github/repo-architect.md +2 -2
  11. package/.claude/commands/github/swarm-issue.md +4 -1
  12. package/.claude/commands/github/swarm-pr.md +6 -3
  13. package/.claude/commands/github/sync-coordinator.md +3 -1
  14. package/.claude/commands/github/workflow-automation.md +2 -2
  15. package/.claude/helpers/github-safe.js +95 -60
  16. package/.claude/helpers/github-setup.sh +26 -9
  17. package/.claude/helpers/hook-handler.cjs +12 -4
  18. package/.claude/helpers/statusline.cjs +31 -2
  19. package/.claude/helpers/statusline.js +35 -4
  20. package/.claude/settings.json +1 -1
  21. package/.claude/skills/dual-mode/README.md +71 -0
  22. package/.claude/skills/dual-mode/dual-collect.md +103 -0
  23. package/.claude/skills/dual-mode/dual-coordinate.md +85 -0
  24. package/.claude/skills/dual-mode/dual-spawn.md +81 -0
  25. package/.claude/skills/flow-nexus-neural/SKILL.md +0 -11
  26. package/.claude/skills/flow-nexus-platform/SKILL.md +2 -5
  27. package/.claude/skills/flow-nexus-swarm/SKILL.md +0 -6
  28. package/.claude/skills/github-code-review/SKILL.md +2 -17
  29. package/.claude/skills/github-multi-repo/SKILL.md +4 -16
  30. package/.claude/skills/github-project-management/SKILL.md +18 -33
  31. package/.claude/skills/github-release-management/SKILL.md +4 -21
  32. package/.claude/skills/github-workflow-automation/SKILL.md +8 -26
  33. package/.claude/skills/reasoningbank-intelligence/SKILL.md +2 -2
  34. package/.claude/skills/sparc-methodology/SKILL.md +2 -11
  35. package/.claude/skills/stream-chain/SKILL.md +0 -3
  36. package/.claude/skills/swarm-advanced/SKILL.md +2 -5
  37. package/.claude/skills/swarm-orchestration/SKILL.md +1 -1
  38. package/.claude/skills/verification-quality/SKILL.md +120 -78
  39. package/README.md +57 -38
  40. package/bin/cli.js +15 -2
  41. package/bin/mcp-server.js +1 -1
  42. package/dist/src/__probe.d.ts +2 -0
  43. package/dist/src/__probe.d.ts.map +1 -0
  44. package/dist/src/__probe.js +5 -0
  45. package/dist/src/__probe.js.map +1 -0
  46. package/dist/src/commands/agent-wasm.js +2 -2
  47. package/dist/src/commands/agent-wasm.js.map +1 -1
  48. package/dist/src/commands/benchmark-cosign.d.ts +29 -0
  49. package/dist/src/commands/benchmark-cosign.d.ts.map +1 -0
  50. package/dist/src/commands/benchmark-cosign.js +222 -0
  51. package/dist/src/commands/benchmark-cosign.js.map +1 -0
  52. package/dist/src/commands/benchmark-verify.d.ts +21 -0
  53. package/dist/src/commands/benchmark-verify.d.ts.map +1 -0
  54. package/dist/src/commands/benchmark-verify.js +202 -0
  55. package/dist/src/commands/benchmark-verify.js.map +1 -0
  56. package/dist/src/commands/daemon.d.ts +20 -0
  57. package/dist/src/commands/daemon.d.ts.map +1 -1
  58. package/dist/src/commands/daemon.js +366 -7
  59. package/dist/src/commands/daemon.js.map +1 -1
  60. package/dist/src/commands/doctor.d.ts.map +1 -1
  61. package/dist/src/commands/doctor.js +224 -46
  62. package/dist/src/commands/doctor.js.map +1 -1
  63. package/dist/src/commands/embeddings.d.ts.map +1 -1
  64. package/dist/src/commands/embeddings.js +18 -9
  65. package/dist/src/commands/embeddings.js.map +1 -1
  66. package/dist/src/commands/hive-mind.d.ts.map +1 -1
  67. package/dist/src/commands/hive-mind.js +25 -7
  68. package/dist/src/commands/hive-mind.js.map +1 -1
  69. package/dist/src/commands/hooks.d.ts.map +1 -1
  70. package/dist/src/commands/hooks.js +56 -29
  71. package/dist/src/commands/hooks.js.map +1 -1
  72. package/dist/src/commands/init.d.ts.map +1 -1
  73. package/dist/src/commands/init.js +21 -1
  74. package/dist/src/commands/init.js.map +1 -1
  75. package/dist/src/commands/memory.d.ts.map +1 -1
  76. package/dist/src/commands/memory.js +128 -3
  77. package/dist/src/commands/memory.js.map +1 -1
  78. package/dist/src/commands/start.js +1 -1
  79. package/dist/src/commands/start.js.map +1 -1
  80. package/dist/src/commands/swarm.js +1 -1
  81. package/dist/src/commands/swarm.js.map +1 -1
  82. package/dist/src/commands/task.d.ts.map +1 -1
  83. package/dist/src/commands/task.js +8 -4
  84. package/dist/src/commands/task.js.map +1 -1
  85. package/dist/src/config-adapter.js +1 -1
  86. package/dist/src/config-adapter.js.map +1 -1
  87. package/dist/src/index.d.ts +5 -1
  88. package/dist/src/index.d.ts.map +1 -1
  89. package/dist/src/index.js +61 -18
  90. package/dist/src/index.js.map +1 -1
  91. package/dist/src/init/claudemd-generator.d.ts.map +1 -1
  92. package/dist/src/init/claudemd-generator.js +1 -0
  93. package/dist/src/init/claudemd-generator.js.map +1 -1
  94. package/dist/src/init/executor.d.ts.map +1 -1
  95. package/dist/src/init/executor.js +133 -0
  96. package/dist/src/init/executor.js.map +1 -1
  97. package/dist/src/init/helpers-generator.d.ts +1 -0
  98. package/dist/src/init/helpers-generator.d.ts.map +1 -1
  99. package/dist/src/init/helpers-generator.js +19 -2
  100. package/dist/src/init/helpers-generator.js.map +1 -1
  101. package/dist/src/init/mcp-generator.js +4 -4
  102. package/dist/src/init/mcp-generator.js.map +1 -1
  103. package/dist/src/init/settings-generator.d.ts.map +1 -1
  104. package/dist/src/init/settings-generator.js +84 -19
  105. package/dist/src/init/settings-generator.js.map +1 -1
  106. package/dist/src/init/statusline-generator.d.ts.map +1 -1
  107. package/dist/src/init/statusline-generator.js +75 -31
  108. package/dist/src/init/statusline-generator.js.map +1 -1
  109. package/dist/src/init/types.d.ts +30 -0
  110. package/dist/src/init/types.d.ts.map +1 -1
  111. package/dist/src/init/types.js +18 -5
  112. package/dist/src/init/types.js.map +1 -1
  113. package/dist/src/mcp-client.d.ts.map +1 -1
  114. package/dist/src/mcp-client.js +12 -0
  115. package/dist/src/mcp-client.js.map +1 -1
  116. package/dist/src/mcp-server.d.ts.map +1 -1
  117. package/dist/src/mcp-server.js +38 -5
  118. package/dist/src/mcp-server.js.map +1 -1
  119. package/dist/src/mcp-tools/agent-execute-core.d.ts +3 -2
  120. package/dist/src/mcp-tools/agent-execute-core.d.ts.map +1 -1
  121. package/dist/src/mcp-tools/agent-execute-core.js +157 -83
  122. package/dist/src/mcp-tools/agent-execute-core.js.map +1 -1
  123. package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
  124. package/dist/src/mcp-tools/agent-tools.js +119 -11
  125. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  126. package/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
  127. package/dist/src/mcp-tools/agentdb-tools.js +101 -24
  128. package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
  129. package/dist/src/mcp-tools/analyze-tools.js +6 -6
  130. package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
  131. package/dist/src/mcp-tools/autopilot-tools.js +10 -10
  132. package/dist/src/mcp-tools/autopilot-tools.js.map +1 -1
  133. package/dist/src/mcp-tools/browser-session-tools.d.ts.map +1 -1
  134. package/dist/src/mcp-tools/browser-session-tools.js +18 -7
  135. package/dist/src/mcp-tools/browser-session-tools.js.map +1 -1
  136. package/dist/src/mcp-tools/browser-tools.js +23 -23
  137. package/dist/src/mcp-tools/browser-tools.js.map +1 -1
  138. package/dist/src/mcp-tools/claims-tools.js +12 -12
  139. package/dist/src/mcp-tools/claims-tools.js.map +1 -1
  140. package/dist/src/mcp-tools/config-tools.js +6 -6
  141. package/dist/src/mcp-tools/config-tools.js.map +1 -1
  142. package/dist/src/mcp-tools/coordination-tools.js +7 -7
  143. package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
  144. package/dist/src/mcp-tools/daa-tools.js +8 -8
  145. package/dist/src/mcp-tools/daa-tools.js.map +1 -1
  146. package/dist/src/mcp-tools/embeddings-tools.js +10 -10
  147. package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
  148. package/dist/src/mcp-tools/github-tools.js +5 -5
  149. package/dist/src/mcp-tools/github-tools.js.map +1 -1
  150. package/dist/src/mcp-tools/guidance-tools.js +21 -21
  151. package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
  152. package/dist/src/mcp-tools/hive-consensus-runtime.d.ts +149 -0
  153. package/dist/src/mcp-tools/hive-consensus-runtime.d.ts.map +1 -0
  154. package/dist/src/mcp-tools/hive-consensus-runtime.js +296 -0
  155. package/dist/src/mcp-tools/hive-consensus-runtime.js.map +1 -0
  156. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
  157. package/dist/src/mcp-tools/hive-mind-tools.js +53 -9
  158. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
  159. package/dist/src/mcp-tools/hooks-tools.d.ts +2 -0
  160. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  161. package/dist/src/mcp-tools/hooks-tools.js +179 -46
  162. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  163. package/dist/src/mcp-tools/managed-agent-tools.d.ts +22 -0
  164. package/dist/src/mcp-tools/managed-agent-tools.d.ts.map +1 -0
  165. package/dist/src/mcp-tools/managed-agent-tools.js +357 -0
  166. package/dist/src/mcp-tools/managed-agent-tools.js.map +1 -0
  167. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  168. package/dist/src/mcp-tools/memory-tools.js +499 -68
  169. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  170. package/dist/src/mcp-tools/neural-tools.js +6 -6
  171. package/dist/src/mcp-tools/neural-tools.js.map +1 -1
  172. package/dist/src/mcp-tools/performance-tools.js +6 -6
  173. package/dist/src/mcp-tools/performance-tools.js.map +1 -1
  174. package/dist/src/mcp-tools/progress-tools.js +4 -4
  175. package/dist/src/mcp-tools/progress-tools.js.map +1 -1
  176. package/dist/src/mcp-tools/ruvllm-tools.d.ts.map +1 -1
  177. package/dist/src/mcp-tools/ruvllm-tools.js +27 -11
  178. package/dist/src/mcp-tools/ruvllm-tools.js.map +1 -1
  179. package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
  180. package/dist/src/mcp-tools/security-tools.js +34 -9
  181. package/dist/src/mcp-tools/security-tools.js.map +1 -1
  182. package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
  183. package/dist/src/mcp-tools/session-tools.js +130 -6
  184. package/dist/src/mcp-tools/session-tools.js.map +1 -1
  185. package/dist/src/mcp-tools/swarm-tools.d.ts +28 -0
  186. package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
  187. package/dist/src/mcp-tools/swarm-tools.js +80 -9
  188. package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
  189. package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
  190. package/dist/src/mcp-tools/system-tools.js +91 -18
  191. package/dist/src/mcp-tools/system-tools.js.map +1 -1
  192. package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
  193. package/dist/src/mcp-tools/task-tools.js +55 -7
  194. package/dist/src/mcp-tools/task-tools.js.map +1 -1
  195. package/dist/src/mcp-tools/terminal-tools.js +5 -5
  196. package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
  197. package/dist/src/mcp-tools/transfer-tools.js +11 -11
  198. package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
  199. package/dist/src/mcp-tools/wasm-agent-tools.js +11 -11
  200. package/dist/src/mcp-tools/wasm-agent-tools.js.map +1 -1
  201. package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
  202. package/dist/src/mcp-tools/workflow-tools.js +118 -10
  203. package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
  204. package/dist/src/memory/ann-router-registry.d.ts +61 -0
  205. package/dist/src/memory/ann-router-registry.d.ts.map +1 -0
  206. package/dist/src/memory/ann-router-registry.js +72 -0
  207. package/dist/src/memory/ann-router-registry.js.map +1 -0
  208. package/dist/src/memory/diskann-registry.d.ts +56 -0
  209. package/dist/src/memory/diskann-registry.d.ts.map +1 -0
  210. package/dist/src/memory/diskann-registry.js +88 -0
  211. package/dist/src/memory/diskann-registry.js.map +1 -0
  212. package/dist/src/memory/memory-bridge.d.ts +4 -0
  213. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  214. package/dist/src/memory/memory-bridge.js +49 -8
  215. package/dist/src/memory/memory-bridge.js.map +1 -1
  216. package/dist/src/memory/memory-initializer.d.ts +12 -0
  217. package/dist/src/memory/memory-initializer.d.ts.map +1 -1
  218. package/dist/src/memory/memory-initializer.js +98 -19
  219. package/dist/src/memory/memory-initializer.js.map +1 -1
  220. package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
  221. package/dist/src/memory/sona-optimizer.js +3 -0
  222. package/dist/src/memory/sona-optimizer.js.map +1 -1
  223. package/dist/src/parser.d.ts +9 -0
  224. package/dist/src/parser.d.ts.map +1 -1
  225. package/dist/src/parser.js +11 -0
  226. package/dist/src/parser.js.map +1 -1
  227. package/dist/src/plugins/store/discovery.d.ts +15 -4
  228. package/dist/src/plugins/store/discovery.d.ts.map +1 -1
  229. package/dist/src/plugins/store/discovery.js +40 -18
  230. package/dist/src/plugins/store/discovery.js.map +1 -1
  231. package/dist/src/ruvector/agent-wasm.d.ts.map +1 -1
  232. package/dist/src/ruvector/agent-wasm.js +4 -1
  233. package/dist/src/ruvector/agent-wasm.js.map +1 -1
  234. package/dist/src/ruvector/coverage-tools.js +6 -6
  235. package/dist/src/ruvector/coverage-tools.js.map +1 -1
  236. package/dist/src/services/headless-worker-executor.d.ts +6 -0
  237. package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
  238. package/dist/src/services/headless-worker-executor.js +63 -6
  239. package/dist/src/services/headless-worker-executor.js.map +1 -1
  240. package/dist/src/services/worker-daemon.d.ts +80 -2
  241. package/dist/src/services/worker-daemon.d.ts.map +1 -1
  242. package/dist/src/services/worker-daemon.js +372 -11
  243. package/dist/src/services/worker-daemon.js.map +1 -1
  244. package/dist/tsconfig.tsbuildinfo +1 -1
  245. package/package.json +12 -8
  246. package/.claude/agents/core/coder.md +0 -453
  247. package/.claude/agents/core/researcher.md +0 -369
  248. package/.claude/agents/core/reviewer.md +0 -520
  249. package/.claude/agents/core/tester.md +0 -512
  250. package/.claude/agents/goal/goal-planner.md +0 -73
  251. package/.claude/agents/v3/adr-architect.md +0 -184
  252. package/.claude/agents/v3/memory-specialist.md +0 -995
  253. package/.claude/agents/v3/security-auditor.md +0 -771
  254. package/.claude/agents/v3/sparc-orchestrator.md +0 -182
  255. package/.claude/commands/flow-nexus/app-store.md +0 -124
  256. package/.claude/commands/flow-nexus/challenges.md +0 -120
  257. package/.claude/commands/flow-nexus/login-registration.md +0 -65
  258. package/.claude/commands/flow-nexus/neural-network.md +0 -134
  259. package/.claude/commands/flow-nexus/payments.md +0 -116
  260. package/.claude/commands/flow-nexus/sandbox.md +0 -83
  261. package/.claude/commands/flow-nexus/swarm.md +0 -87
  262. package/.claude/commands/flow-nexus/user-tools.md +0 -152
  263. package/.claude/commands/flow-nexus/workflow.md +0 -115
  264. package/.claude/skills/agentic-jujutsu/SKILL.md +0 -645
  265. package/.claude/skills/aidefence-scan.md +0 -151
  266. package/.claude/skills/aidefence.yaml +0 -297
  267. package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
  268. package/.claude/skills/performance-analysis/SKILL.md +0 -563
  269. package/.claude/skills/secure-review.md +0 -181
  270. package/.claude/skills/worker-benchmarks/skill.md +0 -135
  271. package/.claude/skills/worker-integration/skill.md +0 -154
  272. package/dist/src/ruvector/flash-attention.d.ts +0 -195
  273. package/dist/src/ruvector/flash-attention.d.ts.map +0 -1
  274. package/dist/src/ruvector/flash-attention.js +0 -643
  275. package/dist/src/ruvector/flash-attention.js.map +0 -1
  276. package/dist/src/ruvector/moe-router.d.ts +0 -206
  277. package/dist/src/ruvector/moe-router.d.ts.map +0 -1
  278. package/dist/src/ruvector/moe-router.js +0 -626
  279. package/dist/src/ruvector/moe-router.js.map +0 -1
  280. package/dist/src/services/event-stream.d.ts +0 -25
  281. package/dist/src/services/event-stream.d.ts.map +0 -1
  282. package/dist/src/services/event-stream.js +0 -27
  283. package/dist/src/services/event-stream.js.map +0 -1
  284. package/dist/src/services/loop-worker-runner.d.ts +0 -16
  285. package/dist/src/services/loop-worker-runner.d.ts.map +0 -1
  286. package/dist/src/services/loop-worker-runner.js +0 -34
  287. package/dist/src/services/loop-worker-runner.js.map +0 -1
  288. package/dist/src/services/runtime-capabilities.d.ts +0 -22
  289. package/dist/src/services/runtime-capabilities.d.ts.map +0 -1
  290. package/dist/src/services/runtime-capabilities.js +0 -45
  291. package/dist/src/services/runtime-capabilities.js.map +0 -1
@@ -34,6 +34,9 @@ export class WorkerDaemon extends EventEmitter {
34
34
  config;
35
35
  workers = new Map();
36
36
  timers = new Map();
37
+ // #1845: separate timer for the MCP-dispatch queue poller. Kept off
38
+ // the per-worker map so stop() clears both kinds without confusion.
39
+ queuePollTimer;
37
40
  running = false;
38
41
  startedAt;
39
42
  projectRoot;
@@ -76,6 +79,9 @@ export class WorkerDaemon extends EventEmitter {
76
79
  };
77
80
  // Setup graceful shutdown handlers
78
81
  this.setupShutdownHandlers();
82
+ // #1855: install crash handlers so uncaught exceptions and unhandled
83
+ // rejections don't leak the PID file or orphan child processes.
84
+ this.installCrashHandlers();
79
85
  // Ensure directories exist
80
86
  if (!existsSync(claudeFlowDir)) {
81
87
  mkdirSync(claudeFlowDir, { recursive: true });
@@ -101,14 +107,19 @@ export class WorkerDaemon extends EventEmitter {
101
107
  this.headlessAvailable = await this.headlessExecutor.isAvailable();
102
108
  if (this.headlessAvailable) {
103
109
  this.log('info', 'Claude Code headless mode available - AI workers enabled');
104
- // Forward headless executor events
110
+ // Forward headless executor events. #1855: also snapshot the
111
+ // active child PIDs to disk on every transition so the next
112
+ // lifetime can reap orphans after a hard crash.
105
113
  this.headlessExecutor.on('execution:start', (data) => {
114
+ this.writeChildrenSnapshot();
106
115
  this.emit('headless:start', data);
107
116
  });
108
117
  this.headlessExecutor.on('execution:complete', (data) => {
118
+ this.writeChildrenSnapshot();
109
119
  this.emit('headless:complete', data);
110
120
  });
111
121
  this.headlessExecutor.on('execution:error', (data) => {
122
+ this.writeChildrenSnapshot();
112
123
  this.emit('headless:error', data);
113
124
  });
114
125
  this.headlessExecutor.on('output', (data) => {
@@ -169,22 +180,52 @@ export class WorkerDaemon extends EventEmitter {
169
180
  return cpus().length || 1;
170
181
  }
171
182
  /**
172
- * Read daemon-specific config from .claude-flow/config.json
173
- * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'
183
+ * Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
184
+ * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
185
+ * #1844: prefer JSON when both exist (existing behavior) but fall back
186
+ * to YAML so operators using the v3 canonical YAML format aren't silently
187
+ * ignored. The chosen path is logged at info level.
174
188
  */
175
189
  readDaemonConfigFromFile(claudeFlowDir) {
176
- const configPath = join(claudeFlowDir, 'config.json');
177
- if (!existsSync(configPath)) {
178
- // Warn if config.yaml exists but config.json does not (#1395 Bug 4)
179
- const yamlPath = join(claudeFlowDir, 'config.yaml');
180
- const ymlPath = join(claudeFlowDir, 'config.yml');
181
- if (existsSync(yamlPath) || existsSync(ymlPath)) {
182
- this.log('warn', `Found ${existsSync(yamlPath) ? 'config.yaml' : 'config.yml'} but daemon reads only config.json — YAML config is being ignored. Convert to JSON or create config.json.`);
190
+ const jsonPath = join(claudeFlowDir, 'config.json');
191
+ const yamlPath = join(claudeFlowDir, 'config.yaml');
192
+ const ymlPath = join(claudeFlowDir, 'config.yml');
193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
+ let raw;
195
+ let chosenPath;
196
+ if (existsSync(jsonPath)) {
197
+ try {
198
+ raw = JSON.parse(readFileSync(jsonPath, 'utf-8'));
199
+ chosenPath = jsonPath;
200
+ }
201
+ catch {
202
+ return {};
203
+ }
204
+ }
205
+ else if (existsSync(yamlPath) || existsSync(ymlPath)) {
206
+ const yPath = existsSync(yamlPath) ? yamlPath : ymlPath;
207
+ try {
208
+ // Lazy-load yaml so the daemon doesn't hard-require it; if the
209
+ // dep isn't installed, fall back to the previous warn-only path.
210
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
211
+ const yamlMod = require('yaml');
212
+ const parsed = yamlMod.parse(readFileSync(yPath, 'utf-8'));
213
+ if (parsed && typeof parsed === 'object') {
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ raw = parsed;
216
+ chosenPath = yPath;
217
+ }
183
218
  }
219
+ catch {
220
+ this.log('warn', `Found ${yPath} but yaml parser unavailable. Install \`yaml\` or convert to JSON. Falling back to defaults.`);
221
+ return {};
222
+ }
223
+ }
224
+ if (!raw || !chosenPath) {
184
225
  return {};
185
226
  }
227
+ this.log('info', `Daemon config loaded from ${chosenPath}`);
186
228
  try {
187
- const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
188
229
  // Support both flat keys at root and nested under scopes.project
189
230
  const cfg = raw?.scopes?.project ?? raw;
190
231
  const rawCpuLoad = cfg['daemon.resourceThresholds.maxCpuLoad'] ?? raw['daemon.resourceThresholds.maxCpuLoad'];
@@ -216,6 +257,153 @@ export class WorkerDaemon extends EventEmitter {
216
257
  process.on('SIGINT', shutdown);
217
258
  process.on('SIGHUP', shutdown);
218
259
  }
260
+ /**
261
+ * #1855: install crash handlers for uncaught exceptions and unhandled
262
+ * rejections. Without these, a thrown error from any timer callback,
263
+ * worker logic path, or transitive import crashes the daemon process
264
+ * silently — the PID file leaks and any in-flight child processes
265
+ * orphan. With these, we log a structured crash record, run stop()
266
+ * to clean up, then exit 1 so the process actually dies (otherwise
267
+ * Node would crash anyway after the handler returns).
268
+ */
269
+ installCrashHandlers() {
270
+ const onCrash = (kind, err) => {
271
+ // Best-effort logging; never throw from inside the crash handler.
272
+ try {
273
+ this.writeCrashRecord(kind, err);
274
+ }
275
+ catch { /* nothing more we can do */ }
276
+ try {
277
+ // Synchronous stop — don't await; the process is dying. Just
278
+ // remove the PID file and snapshot state so the next start
279
+ // sees a clean slate.
280
+ this.removePidFile();
281
+ this.saveState();
282
+ // Snapshot any in-flight child PIDs one last time so the next
283
+ // lifetime can reap them.
284
+ this.writeChildrenSnapshot();
285
+ }
286
+ catch { /* ignore */ }
287
+ // Exit non-zero so supervisors / shells see the failure.
288
+ process.exit(1);
289
+ };
290
+ process.on('uncaughtException', (err) => onCrash('uncaughtException', err));
291
+ process.on('unhandledRejection', (err) => onCrash('unhandledRejection', err));
292
+ }
293
+ /**
294
+ * Append a structured crash record to .claude-flow/logs/crash.log.
295
+ * Inspectable by hand or via `ruflo daemon status` follow-ups.
296
+ */
297
+ writeCrashRecord(kind, err) {
298
+ const logDir = this.config.logDir;
299
+ if (!existsSync(logDir))
300
+ mkdirSync(logDir, { recursive: true });
301
+ const crashLog = join(logDir, 'crash.log');
302
+ const ts = new Date().toISOString();
303
+ const message = err instanceof Error ? err.message : String(err);
304
+ const stack = err instanceof Error && err.stack ? err.stack : '<no stack>';
305
+ const record = `[${ts}] [${kind}] pid=${process.pid} ${message}\n${stack}\n---\n`;
306
+ appendFileSync(crashLog, record, 'utf-8');
307
+ this.log('warn', `Daemon crashed (${kind}): ${message} — see ${crashLog}`);
308
+ }
309
+ /**
310
+ * Path to the on-disk children registry — list of headless worker
311
+ * child PIDs the daemon currently owns. #1855: written on every
312
+ * execution:start / :complete / :error transition; read by the next
313
+ * lifetime to reap orphans after a hard crash.
314
+ */
315
+ get childrenFile() {
316
+ return join(this.projectRoot, '.claude-flow', 'daemon-children.json');
317
+ }
318
+ /**
319
+ * #1856: detect workers that were mid-flight when the previous daemon
320
+ * lifetime ended. A mid-flight worker has `lastStartedAt > lastRun`
321
+ * (started after the last successful completion). On crash recovery
322
+ * we count these as failures so the run-counter math stays consistent
323
+ * (`runCount === successCount + failureCount`). Workers naturally
324
+ * retry at their next scheduled interval; we deliberately don't
325
+ * immediately re-run because the failure may have been deterministic.
326
+ */
327
+ detectMidFlightFailures() {
328
+ let detected = 0;
329
+ for (const [type, state] of this.workers.entries()) {
330
+ const startedAt = state.lastStartedAt?.getTime() ?? 0;
331
+ const lastRunAt = state.lastRun?.getTime() ?? 0;
332
+ // started after the last successful completion → was mid-flight
333
+ if (startedAt > 0 && startedAt > lastRunAt) {
334
+ state.failureCount++;
335
+ state.isRunning = false;
336
+ // Don't bump runCount — it was already incremented at start
337
+ this.log('info', `Worker ${type} was mid-flight at last crash (started ${state.lastStartedAt?.toISOString()}); counted as failure, will retry at next scheduled interval`);
338
+ detected++;
339
+ }
340
+ }
341
+ if (detected > 0) {
342
+ this.saveState();
343
+ }
344
+ }
345
+ /**
346
+ * Snapshot the currently-active headless worker child PIDs to disk.
347
+ * Best-effort; failures don't propagate.
348
+ */
349
+ writeChildrenSnapshot() {
350
+ if (!this.headlessExecutor)
351
+ return;
352
+ try {
353
+ const pids = this.headlessExecutor.getActiveChildPids();
354
+ const dir = join(this.projectRoot, '.claude-flow');
355
+ if (!existsSync(dir))
356
+ mkdirSync(dir, { recursive: true });
357
+ writeFileSync(this.childrenFile, JSON.stringify({ pids, daemonPid: process.pid, timestamp: new Date().toISOString() }, null, 2), 'utf-8');
358
+ }
359
+ catch { /* best-effort */ }
360
+ }
361
+ /**
362
+ * #1855: reap orphan headless worker children left behind by a
363
+ * previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
364
+ * SIGTERMs any PID still alive that doesn't belong to the current
365
+ * daemon, then truncates the file. Called at the top of `start()`
366
+ * so the next lifetime starts with a clean process tree.
367
+ */
368
+ reapOrphanedChildren() {
369
+ const file = this.childrenFile;
370
+ if (!existsSync(file))
371
+ return;
372
+ let snapshot;
373
+ try {
374
+ snapshot = JSON.parse(readFileSync(file, 'utf-8'));
375
+ }
376
+ catch {
377
+ try {
378
+ unlinkSync(file);
379
+ }
380
+ catch { /* ignore */ }
381
+ return;
382
+ }
383
+ const pids = Array.isArray(snapshot.pids) ? snapshot.pids : [];
384
+ let reaped = 0;
385
+ for (const pid of pids) {
386
+ if (typeof pid !== 'number' || pid <= 0)
387
+ continue;
388
+ if (pid === process.pid)
389
+ continue; // never our own PID
390
+ try {
391
+ process.kill(pid, 0); // is alive?
392
+ process.kill(pid, 'SIGTERM');
393
+ reaped++;
394
+ }
395
+ catch {
396
+ // already dead — fine
397
+ }
398
+ }
399
+ if (reaped > 0) {
400
+ this.log('info', `Reaped ${reaped} orphan headless worker child(ren) from previous lifetime`);
401
+ }
402
+ try {
403
+ unlinkSync(file);
404
+ }
405
+ catch { /* ignore */ }
406
+ }
219
407
  /**
220
408
  * Check if system resources allow worker execution
221
409
  */
@@ -299,12 +487,14 @@ export class WorkerDaemon extends EventEmitter {
299
487
  for (const [type, state] of Object.entries(saved.workers)) {
300
488
  const savedState = state;
301
489
  const lastRunValue = savedState.lastRun;
490
+ const lastStartedAtValue = savedState.lastStartedAt;
302
491
  this.workers.set(type, {
303
492
  runCount: savedState.runCount || 0,
304
493
  successCount: savedState.successCount || 0,
305
494
  failureCount: savedState.failureCount || 0,
306
495
  averageDurationMs: savedState.averageDurationMs || 0,
307
496
  lastRun: lastRunValue ? new Date(lastRunValue) : undefined,
497
+ lastStartedAt: lastStartedAtValue ? new Date(lastStartedAtValue) : undefined,
308
498
  nextRun: undefined,
309
499
  isRunning: false,
310
500
  });
@@ -337,6 +527,15 @@ export class WorkerDaemon extends EventEmitter {
337
527
  /**
338
528
  * Check if another daemon instance is already running.
339
529
  * Returns the existing PID if alive, or null if no daemon is running.
530
+ *
531
+ * #1853: ignore self-PID matches. The detached-spawn path in
532
+ * `commands/daemon.ts` writes the child's PID into the file as a
533
+ * fallback after a 500ms wait. If the child reaches `start()` slower
534
+ * than the parent's 500ms wait (observed on Node 25 / macOS 26), the
535
+ * child reads its own PID back from the file and concludes "another
536
+ * daemon is already running" — so it exits before scheduling workers
537
+ * and `daemon status` reports STOPPED forever. A daemon process is
538
+ * never "another instance" of itself; treat self-match as absence.
340
539
  */
341
540
  checkExistingDaemon() {
342
541
  if (!existsSync(this.pidFile))
@@ -345,6 +544,10 @@ export class WorkerDaemon extends EventEmitter {
345
544
  const pid = parseInt(readFileSync(this.pidFile, 'utf-8').trim(), 10);
346
545
  if (isNaN(pid))
347
546
  return null;
547
+ // #1853: a PID file containing our own PID is not "another daemon".
548
+ // Treat as absent so the start() path proceeds normally.
549
+ if (pid === process.pid)
550
+ return null;
348
551
  // Check if process is alive (signal 0 = existence check)
349
552
  process.kill(pid, 0);
350
553
  return pid; // Process is alive
@@ -391,6 +594,18 @@ export class WorkerDaemon extends EventEmitter {
391
594
  this.emit('warning', `Daemon already running (PID: ${existingPid})`);
392
595
  return;
393
596
  }
597
+ // #1855: reap orphan headless worker children left by a previous
598
+ // crashed lifetime, BEFORE we mark ourselves running and start
599
+ // accepting new work. The children file from the prior daemon's
600
+ // last-snapshot is the authoritative list.
601
+ this.reapOrphanedChildren();
602
+ // #1856: detect workers that were mid-flight at the previous crash
603
+ // and count them as failures so runCount/successCount/failureCount
604
+ // stay consistent. Workers retry naturally at their next scheduled
605
+ // interval — we don't immediately re-run them, which avoids a
606
+ // freshly-recovered daemon hammering the same code path that just
607
+ // killed it.
608
+ this.detectMidFlightFailures();
394
609
  this.running = true;
395
610
  this.startedAt = new Date();
396
611
  this.writePidFile();
@@ -401,10 +616,90 @@ export class WorkerDaemon extends EventEmitter {
401
616
  this.scheduleWorker(workerConfig);
402
617
  }
403
618
  }
619
+ // #1845: poll the MCP-dispatch queue directory so workers requested
620
+ // via mcp__hooks_worker-dispatch (in a separate process) actually
621
+ // execute here. Previously the dispatch wrote to a process-local Map
622
+ // that the daemon could never see.
623
+ this.queuePollTimer = setInterval(() => {
624
+ void this.processDispatchQueue();
625
+ }, 5_000);
626
+ if (typeof this.queuePollTimer.unref === 'function') {
627
+ this.queuePollTimer.unref();
628
+ }
404
629
  // Save state
405
630
  this.saveState();
406
631
  this.log('info', `Daemon started (PID: ${process.pid}, CPUs: ${cpus().length}, workers: ${this.config.workers.filter(w => w.enabled).length}, maxCpuLoad: ${this.config.resourceThresholds.maxCpuLoad}, minFreeMemoryPercent: ${this.config.resourceThresholds.minFreeMemoryPercent}%)`);
407
632
  }
633
+ /**
634
+ * #1845: ingest queue entries written by mcp__hooks_worker-dispatch.
635
+ * Each entry is a JSON file at `.claude-flow/daemon-queue/<id>.json`
636
+ * with `{ workerId, trigger, context, enqueuedAt }`. We move processed
637
+ * files to `.claude-flow/daemon-queue/.processed/` so the daemon never
638
+ * re-runs the same dispatch and operators can inspect history.
639
+ */
640
+ async processDispatchQueue() {
641
+ if (!this.running)
642
+ return;
643
+ const queueDir = join(this.projectRoot, '.claude-flow', 'daemon-queue');
644
+ if (!existsSync(queueDir))
645
+ return;
646
+ let entries;
647
+ try {
648
+ const fs = await import('fs');
649
+ entries = fs.readdirSync(queueDir).filter((n) => n.endsWith('.json'));
650
+ }
651
+ catch {
652
+ return;
653
+ }
654
+ if (entries.length === 0)
655
+ return;
656
+ const fs = await import('fs');
657
+ const processedDir = join(queueDir, '.processed');
658
+ if (!existsSync(processedDir)) {
659
+ try {
660
+ fs.mkdirSync(processedDir, { recursive: true });
661
+ }
662
+ catch { /* race ok */ }
663
+ }
664
+ for (const entry of entries) {
665
+ const src = join(queueDir, entry);
666
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
667
+ let payload;
668
+ try {
669
+ payload = JSON.parse(fs.readFileSync(src, 'utf-8'));
670
+ }
671
+ catch {
672
+ // Malformed entry — quarantine so we don't loop on it
673
+ try {
674
+ fs.renameSync(src, join(processedDir, `bad-${entry}`));
675
+ }
676
+ catch { /* nothing more we can do */ }
677
+ continue;
678
+ }
679
+ const trigger = payload?.trigger;
680
+ const workerId = payload?.workerId;
681
+ if (!trigger || !this.config.workers.some((w) => w.type === trigger)) {
682
+ try {
683
+ fs.renameSync(src, join(processedDir, `unknown-${entry}`));
684
+ }
685
+ catch { /* ok */ }
686
+ continue;
687
+ }
688
+ try {
689
+ this.log('info', `Dequeued ${trigger}${workerId ? ` (id=${workerId})` : ''} from MCP dispatch queue`);
690
+ await this.triggerWorker(trigger);
691
+ }
692
+ catch (err) {
693
+ this.log('warn', `Queued worker ${trigger} failed: ${err.message}`);
694
+ }
695
+ finally {
696
+ try {
697
+ fs.renameSync(src, join(processedDir, entry));
698
+ }
699
+ catch { /* ignore */ }
700
+ }
701
+ }
702
+ }
408
703
  /**
409
704
  * Stop the daemon and all workers
410
705
  */
@@ -420,6 +715,11 @@ export class WorkerDaemon extends EventEmitter {
420
715
  this.log('info', `Stopped worker: ${type}`);
421
716
  }
422
717
  this.timers.clear();
718
+ // #1845: stop the MCP-dispatch queue poller too.
719
+ if (this.queuePollTimer) {
720
+ clearInterval(this.queuePollTimer);
721
+ this.queuePollTimer = undefined;
722
+ }
423
723
  this.running = false;
424
724
  this.removePidFile();
425
725
  this.saveState();
@@ -500,6 +800,8 @@ export class WorkerDaemon extends EventEmitter {
500
800
  // Track running worker
501
801
  this.runningWorkers.add(workerConfig.type);
502
802
  state.isRunning = true;
803
+ state.lastStartedAt = new Date(); // #1856: timestamp the start
804
+ this.saveState(); // persist before we run anything
503
805
  this.emit('worker:start', { workerId, type: workerConfig.type });
504
806
  this.log('info', `Starting worker: ${workerConfig.type} (${this.runningWorkers.size}/${this.config.maxConcurrent} concurrent)`);
505
807
  try {
@@ -607,6 +909,17 @@ export class WorkerDaemon extends EventEmitter {
607
909
  try {
608
910
  this.log('info', `Running ${workerConfig.type} in headless mode (Claude Code AI)`);
609
911
  const result = await this.headlessExecutor.execute(workerConfig.type);
912
+ // #1793: persist the headless result to the same metrics files the
913
+ // local workers write to. Without this, AI-mode runs produced rich
914
+ // parsedOutput that lived only in `.claude-flow/logs/headless/*` and
915
+ // never reached `.claude-flow/metrics/<name>.json` — `memory stats`
916
+ // and downstream consumers saw nothing despite successful runs.
917
+ try {
918
+ this.persistHeadlessResult(workerConfig.type, result);
919
+ }
920
+ catch (persistError) {
921
+ this.log('warn', `Failed to persist headless result for ${workerConfig.type}: ${persistError.message}`);
922
+ }
610
923
  return {
611
924
  mode: 'headless',
612
925
  ...result,
@@ -651,6 +964,53 @@ export class WorkerDaemon extends EventEmitter {
651
964
  return { status: 'unknown worker type', mode: 'local' };
652
965
  }
653
966
  }
967
+ /**
968
+ * #1793: persist a headless worker result to the same metrics file the
969
+ * local fallback writes to. Without this, AI-mode workers produced rich
970
+ * structured output (audit findings, perf signals, test-gap analysis)
971
+ * that lived only in `.claude-flow/logs/headless/*_result.log` and was
972
+ * invisible to `npx ruflo memory stats` or the metrics consumers.
973
+ *
974
+ * The mapping mirrors the `*Local` worker implementations below so a
975
+ * single consumer path works regardless of execution mode.
976
+ */
977
+ persistHeadlessResult(workerType, result) {
978
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
979
+ if (!existsSync(metricsDir))
980
+ mkdirSync(metricsDir, { recursive: true });
981
+ // Filename mirrors the local-mode worker writes (security-audit.json,
982
+ // performance.json, test-gaps.json) so a downstream reader doesn't
983
+ // care which mode produced the data.
984
+ const filenameMap = {
985
+ audit: 'security-audit.json',
986
+ optimize: 'performance.json',
987
+ testgaps: 'test-gaps.json',
988
+ document: 'documentation.json',
989
+ refactor: 'refactor.json',
990
+ deepdive: 'deepdive.json',
991
+ ultralearn: 'ultralearn.json',
992
+ predict: 'predictions.json',
993
+ };
994
+ const filename = filenameMap[workerType] ?? `${workerType}.json`;
995
+ const metricsFile = join(metricsDir, filename);
996
+ const persisted = {
997
+ timestamp: result.timestamp instanceof Date ? result.timestamp.toISOString() : new Date().toISOString(),
998
+ mode: 'headless',
999
+ workerType,
1000
+ model: result.model,
1001
+ durationMs: result.durationMs,
1002
+ tokensUsed: result.tokensUsed,
1003
+ executionId: result.executionId,
1004
+ success: result.success,
1005
+ // Structured findings live here when the worker emits JSON (e.g. the
1006
+ // audit worker's vulnerability list). Fall back to a raw-output
1007
+ // pointer so consumers can still locate the full log.
1008
+ findings: result.parsedOutput ?? null,
1009
+ rawOutputPreview: typeof result.output === 'string' ? result.output.slice(0, 2000) : undefined,
1010
+ rawOutputLength: typeof result.output === 'string' ? result.output.length : 0,
1011
+ };
1012
+ writeFileSync(metricsFile, JSON.stringify(persisted, null, 2));
1013
+ }
654
1014
  // Worker implementations
655
1015
  async runMapWorker() {
656
1016
  // Scan project structure and update metrics
@@ -893,6 +1253,7 @@ export class WorkerDaemon extends EventEmitter {
893
1253
  {
894
1254
  ...state,
895
1255
  lastRun: state.lastRun?.toISOString(),
1256
+ lastStartedAt: state.lastStartedAt?.toISOString(),
896
1257
  nextRun: state.nextRun?.toISOString(),
897
1258
  }
898
1259
  ])),