@claude-flow/cli 3.32.3 → 3.32.4

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 (683) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/catalog-manifest.json +2 -2
  3. package/dist/src/agenticow/speculative-exploration.d.ts +148 -0
  4. package/dist/src/agenticow/speculative-exploration.js +218 -0
  5. package/dist/src/appliance/gguf-engine.d.ts +91 -0
  6. package/dist/src/appliance/gguf-engine.js +425 -0
  7. package/dist/src/appliance/ruvllm-bridge.d.ts +102 -0
  8. package/dist/src/appliance/ruvllm-bridge.js +292 -0
  9. package/dist/src/appliance/rvfa-builder.d.ts +44 -0
  10. package/dist/src/appliance/rvfa-builder.js +329 -0
  11. package/dist/src/appliance/rvfa-distribution.d.ts +97 -0
  12. package/dist/src/appliance/rvfa-distribution.js +370 -0
  13. package/dist/src/appliance/rvfa-format.d.ts +111 -0
  14. package/dist/src/appliance/rvfa-format.js +393 -0
  15. package/dist/src/appliance/rvfa-runner.d.ts +69 -0
  16. package/dist/src/appliance/rvfa-runner.js +237 -0
  17. package/dist/src/appliance/rvfa-signing.d.ts +123 -0
  18. package/dist/src/appliance/rvfa-signing.js +347 -0
  19. package/dist/src/auth/client.d.ts +89 -0
  20. package/dist/src/auth/client.js +242 -0
  21. package/dist/src/auth/constants.d.ts +7 -0
  22. package/dist/src/auth/constants.js +7 -0
  23. package/dist/src/auth/scopes.d.ts +14 -0
  24. package/dist/src/auth/scopes.js +21 -0
  25. package/dist/src/auth/security-bridge.d.ts +36 -0
  26. package/dist/src/auth/security-bridge.js +42 -0
  27. package/dist/src/auth/session.d.ts +20 -0
  28. package/dist/src/auth/session.js +32 -0
  29. package/dist/src/auth/state.d.ts +19 -0
  30. package/dist/src/auth/state.js +53 -0
  31. package/dist/src/auth/types.d.ts +27 -0
  32. package/dist/src/auth/types.js +11 -0
  33. package/dist/src/autopilot-state.d.ts +77 -0
  34. package/dist/src/autopilot-state.js +271 -0
  35. package/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
  36. package/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
  37. package/dist/src/benchmarks/gaia-agent.d.ts +198 -0
  38. package/dist/src/benchmarks/gaia-agent.js +651 -0
  39. package/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
  40. package/dist/src/benchmarks/gaia-causal-memory.js +281 -0
  41. package/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
  42. package/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
  43. package/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
  44. package/dist/src/benchmarks/gaia-convergence.js +260 -0
  45. package/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
  46. package/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
  47. package/dist/src/benchmarks/gaia-critic.d.ts +123 -0
  48. package/dist/src/benchmarks/gaia-critic.js +312 -0
  49. package/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
  50. package/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
  51. package/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
  52. package/dist/src/benchmarks/gaia-decomposer.js +350 -0
  53. package/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
  54. package/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
  55. package/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
  56. package/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
  57. package/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
  58. package/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
  59. package/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
  60. package/dist/src/benchmarks/gaia-hardness/features.js +170 -0
  61. package/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
  62. package/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
  63. package/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
  64. package/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
  65. package/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
  66. package/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
  67. package/dist/src/benchmarks/gaia-judge.d.ts +88 -0
  68. package/dist/src/benchmarks/gaia-judge.js +437 -0
  69. package/dist/src/benchmarks/gaia-loader.d.ts +87 -0
  70. package/dist/src/benchmarks/gaia-loader.js +326 -0
  71. package/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
  72. package/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
  73. package/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
  74. package/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
  75. package/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
  76. package/dist/src/benchmarks/gaia-tools/index.js +36 -0
  77. package/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
  78. package/dist/src/benchmarks/gaia-tools/types.js +12 -0
  79. package/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
  80. package/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
  81. package/dist/src/benchmarks/gaia-voting.d.ts +88 -0
  82. package/dist/src/benchmarks/gaia-voting.js +297 -0
  83. package/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
  84. package/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
  85. package/dist/src/benchmarks/pretrain/index.d.ts +58 -0
  86. package/dist/src/benchmarks/pretrain/index.js +404 -0
  87. package/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
  88. package/dist/src/business-pods/bbs-budget-tracker.js +358 -0
  89. package/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
  90. package/dist/src/business-pods/domain-affinity-policy.js +65 -0
  91. package/dist/src/business-pods/pod-schema.d.ts +96 -0
  92. package/dist/src/business-pods/pod-schema.js +225 -0
  93. package/dist/src/commands/advisor.d.ts +15 -0
  94. package/dist/src/commands/advisor.js +94 -0
  95. package/dist/src/commands/agent-wasm.d.ts +14 -0
  96. package/dist/src/commands/agent-wasm.js +333 -0
  97. package/dist/src/commands/agent.d.ts +8 -0
  98. package/dist/src/commands/agent.js +927 -0
  99. package/dist/src/commands/analyze.d.ts +19 -0
  100. package/dist/src/commands/analyze.js +2047 -0
  101. package/dist/src/commands/announcements.d.ts +17 -0
  102. package/dist/src/commands/announcements.js +0 -0
  103. package/dist/src/commands/appliance-advanced.d.ts +9 -0
  104. package/dist/src/commands/appliance-advanced.js +215 -0
  105. package/dist/src/commands/appliance.d.ts +8 -0
  106. package/dist/src/commands/appliance.js +404 -0
  107. package/dist/src/commands/auth.d.ts +15 -0
  108. package/dist/src/commands/auth.js +244 -0
  109. package/dist/src/commands/autopilot.d.ts +15 -0
  110. package/dist/src/commands/autopilot.js +407 -0
  111. package/dist/src/commands/benchmark.d.ts +10 -0
  112. package/dist/src/commands/benchmark.js +460 -0
  113. package/dist/src/commands/claims.d.ts +10 -0
  114. package/dist/src/commands/claims.js +620 -0
  115. package/dist/src/commands/cleanup.d.ts +13 -0
  116. package/dist/src/commands/cleanup.js +250 -0
  117. package/dist/src/commands/completions.d.ts +10 -0
  118. package/dist/src/commands/completions.js +539 -0
  119. package/dist/src/commands/config.d.ts +8 -0
  120. package/dist/src/commands/config.js +428 -0
  121. package/dist/src/commands/daemon.d.ts +59 -0
  122. package/dist/src/commands/daemon.js +1733 -0
  123. package/dist/src/commands/deployment.d.ts +10 -0
  124. package/dist/src/commands/deployment.js +672 -0
  125. package/dist/src/commands/doctor.d.ts +10 -0
  126. package/dist/src/commands/doctor.js +1902 -0
  127. package/dist/src/commands/eject.d.ts +33 -0
  128. package/dist/src/commands/eject.js +195 -0
  129. package/dist/src/commands/embeddings.d.ts +18 -0
  130. package/dist/src/commands/embeddings.js +1623 -0
  131. package/dist/src/commands/funnel.d.ts +18 -0
  132. package/dist/src/commands/funnel.js +302 -0
  133. package/dist/src/commands/gaia-bench.d.ts +40 -0
  134. package/dist/src/commands/gaia-bench.js +597 -0
  135. package/dist/src/commands/guidance.d.ts +8 -0
  136. package/dist/src/commands/guidance.js +556 -0
  137. package/dist/src/commands/hive-mind.d.ts +11 -0
  138. package/dist/src/commands/hive-mind.js +1319 -0
  139. package/dist/src/commands/hooks.d.ts +8 -0
  140. package/dist/src/commands/hooks.js +4606 -0
  141. package/dist/src/commands/index.d.ts +118 -0
  142. package/dist/src/commands/index.js +366 -0
  143. package/dist/src/commands/init.d.ts +13 -0
  144. package/dist/src/commands/init.js +1297 -0
  145. package/dist/src/commands/issues.d.ts +21 -0
  146. package/dist/src/commands/issues.js +567 -0
  147. package/dist/src/commands/mcp.d.ts +11 -0
  148. package/dist/src/commands/mcp.js +732 -0
  149. package/dist/src/commands/memory-backup.d.ts +11 -0
  150. package/dist/src/commands/memory-backup.js +46 -0
  151. package/dist/src/commands/memory-distill.d.ts +27 -0
  152. package/dist/src/commands/memory-distill.js +374 -0
  153. package/dist/src/commands/memory.d.ts +8 -0
  154. package/dist/src/commands/memory.js +1596 -0
  155. package/dist/src/commands/metaharness.d.ts +39 -0
  156. package/dist/src/commands/metaharness.js +215 -0
  157. package/dist/src/commands/migrate.d.ts +8 -0
  158. package/dist/src/commands/migrate.js +742 -0
  159. package/dist/src/commands/neural.d.ts +10 -0
  160. package/dist/src/commands/neural.js +4331 -0
  161. package/dist/src/commands/performance.d.ts +10 -0
  162. package/dist/src/commands/performance.js +583 -0
  163. package/dist/src/commands/plugins.d.ts +11 -0
  164. package/dist/src/commands/plugins.js +826 -0
  165. package/dist/src/commands/process.d.ts +10 -0
  166. package/dist/src/commands/process.js +694 -0
  167. package/dist/src/commands/progress.d.ts +11 -0
  168. package/dist/src/commands/progress.js +259 -0
  169. package/dist/src/commands/providers.d.ts +10 -0
  170. package/dist/src/commands/providers.js +502 -0
  171. package/dist/src/commands/proxy-lifecycle.d.ts +12 -0
  172. package/dist/src/commands/proxy-lifecycle.js +232 -0
  173. package/dist/src/commands/proxy.d.ts +21 -0
  174. package/dist/src/commands/proxy.js +398 -0
  175. package/dist/src/commands/route.d.ts +16 -0
  176. package/dist/src/commands/route.js +822 -0
  177. package/dist/src/commands/ruvector/backup.d.ts +11 -0
  178. package/dist/src/commands/ruvector/backup.js +747 -0
  179. package/dist/src/commands/ruvector/benchmark.d.ts +11 -0
  180. package/dist/src/commands/ruvector/benchmark.js +490 -0
  181. package/dist/src/commands/ruvector/import.d.ts +18 -0
  182. package/dist/src/commands/ruvector/import.js +373 -0
  183. package/dist/src/commands/ruvector/index.d.ts +29 -0
  184. package/dist/src/commands/ruvector/index.js +129 -0
  185. package/dist/src/commands/ruvector/init.d.ts +11 -0
  186. package/dist/src/commands/ruvector/init.js +467 -0
  187. package/dist/src/commands/ruvector/migrate.d.ts +11 -0
  188. package/dist/src/commands/ruvector/migrate.js +498 -0
  189. package/dist/src/commands/ruvector/optimize.d.ts +11 -0
  190. package/dist/src/commands/ruvector/optimize.js +505 -0
  191. package/dist/src/commands/ruvector/pg-utils.d.ts +14 -0
  192. package/dist/src/commands/ruvector/pg-utils.js +41 -0
  193. package/dist/src/commands/ruvector/setup.d.ts +18 -0
  194. package/dist/src/commands/ruvector/setup.js +765 -0
  195. package/dist/src/commands/ruvector/status.d.ts +11 -0
  196. package/dist/src/commands/ruvector/status.js +479 -0
  197. package/dist/src/commands/security.d.ts +10 -0
  198. package/dist/src/commands/security.js +1012 -0
  199. package/dist/src/commands/session.d.ts +8 -0
  200. package/dist/src/commands/session.js +757 -0
  201. package/dist/src/commands/settings.d.ts +19 -0
  202. package/dist/src/commands/settings.js +180 -0
  203. package/dist/src/commands/spinner.d.ts +16 -0
  204. package/dist/src/commands/spinner.js +329 -0
  205. package/dist/src/commands/start.d.ts +8 -0
  206. package/dist/src/commands/start.js +418 -0
  207. package/dist/src/commands/status.d.ts +8 -0
  208. package/dist/src/commands/status.js +608 -0
  209. package/dist/src/commands/swarm.d.ts +8 -0
  210. package/dist/src/commands/swarm.js +891 -0
  211. package/dist/src/commands/task.d.ts +8 -0
  212. package/dist/src/commands/task.js +675 -0
  213. package/dist/src/commands/transfer-store.d.ts +13 -0
  214. package/dist/src/commands/transfer-store.js +428 -0
  215. package/dist/src/commands/update.d.ts +8 -0
  216. package/dist/src/commands/update.js +276 -0
  217. package/dist/src/commands/verify.d.ts +19 -0
  218. package/dist/src/commands/verify.js +261 -0
  219. package/dist/src/commands/version.d.ts +42 -0
  220. package/dist/src/commands/version.js +106 -0
  221. package/dist/src/commands/workflow.d.ts +8 -0
  222. package/dist/src/commands/workflow.js +617 -0
  223. package/dist/src/config/harness-feedback-applier.d.ts +50 -0
  224. package/dist/src/config/harness-feedback-applier.js +122 -0
  225. package/dist/src/config/proven-config-refresh.d.ts +39 -0
  226. package/dist/src/config/proven-config-refresh.js +154 -0
  227. package/dist/src/config/proven-config-rvfa.d.ts +23 -0
  228. package/dist/src/config/proven-config-rvfa.js +73 -0
  229. package/dist/src/config/proven-config.d.ts +86 -0
  230. package/dist/src/config/proven-config.js +176 -0
  231. package/dist/src/config-adapter.d.ts +15 -0
  232. package/dist/src/config-adapter.js +186 -0
  233. package/dist/src/encryption/vault.d.ts +94 -0
  234. package/dist/src/encryption/vault.js +172 -0
  235. package/dist/src/fs-secure.d.ts +86 -0
  236. package/dist/src/fs-secure.js +133 -0
  237. package/dist/src/funnel/advisor-tip.d.ts +58 -0
  238. package/dist/src/funnel/advisor-tip.js +92 -0
  239. package/dist/src/funnel/attribution.d.ts +37 -0
  240. package/dist/src/funnel/attribution.js +101 -0
  241. package/dist/src/funnel/consent.d.ts +22 -0
  242. package/dist/src/funnel/consent.js +58 -0
  243. package/dist/src/funnel/credit-errors.d.ts +31 -0
  244. package/dist/src/funnel/credit-errors.js +88 -0
  245. package/dist/src/funnel/credit-notifier.d.ts +44 -0
  246. package/dist/src/funnel/credit-notifier.js +74 -0
  247. package/dist/src/funnel/disclosure.d.ts +47 -0
  248. package/dist/src/funnel/disclosure.js +109 -0
  249. package/dist/src/funnel/enrollment.d.ts +36 -0
  250. package/dist/src/funnel/enrollment.js +64 -0
  251. package/dist/src/funnel/environment.d.ts +17 -0
  252. package/dist/src/funnel/environment.js +39 -0
  253. package/dist/src/funnel/event-transport.d.ts +51 -0
  254. package/dist/src/funnel/event-transport.js +199 -0
  255. package/dist/src/funnel/events.d.ts +42 -0
  256. package/dist/src/funnel/events.js +150 -0
  257. package/dist/src/funnel/index.d.ts +21 -0
  258. package/dist/src/funnel/index.js +21 -0
  259. package/dist/src/funnel/insights.d.ts +50 -0
  260. package/dist/src/funnel/insights.js +120 -0
  261. package/dist/src/funnel/local-signals.d.ts +20 -0
  262. package/dist/src/funnel/local-signals.js +77 -0
  263. package/dist/src/funnel/message-transport.d.ts +51 -0
  264. package/dist/src/funnel/message-transport.js +149 -0
  265. package/dist/src/funnel/messages.d.ts +55 -0
  266. package/dist/src/funnel/messages.js +160 -0
  267. package/dist/src/funnel/payout.d.ts +40 -0
  268. package/dist/src/funnel/payout.js +60 -0
  269. package/dist/src/funnel/power-saver-notifier.d.ts +44 -0
  270. package/dist/src/funnel/power-saver-notifier.js +92 -0
  271. package/dist/src/funnel/precedence.d.ts +16 -0
  272. package/dist/src/funnel/precedence.js +85 -0
  273. package/dist/src/funnel/promo.d.ts +41 -0
  274. package/dist/src/funnel/promo.js +144 -0
  275. package/dist/src/funnel/rate-limit-notifier.d.ts +55 -0
  276. package/dist/src/funnel/rate-limit-notifier.js +102 -0
  277. package/dist/src/funnel/rotation.d.ts +19 -0
  278. package/dist/src/funnel/rotation.js +70 -0
  279. package/dist/src/funnel/state.d.ts +13 -0
  280. package/dist/src/funnel/state.js +52 -0
  281. package/dist/src/funnel/toggle-cooldown.d.ts +17 -0
  282. package/dist/src/funnel/toggle-cooldown.js +32 -0
  283. package/dist/src/funnel/types.d.ts +98 -0
  284. package/dist/src/funnel/types.js +26 -0
  285. package/dist/src/index.d.ts +81 -0
  286. package/dist/src/index.js +609 -0
  287. package/dist/src/infrastructure/in-memory-repositories.d.ts +68 -0
  288. package/dist/src/infrastructure/in-memory-repositories.js +264 -0
  289. package/dist/src/init/claudemd-generator.d.ts +16 -0
  290. package/dist/src/init/claudemd-generator.js +368 -0
  291. package/dist/src/init/executor.d.ts +41 -0
  292. package/dist/src/init/executor.js +2142 -0
  293. package/dist/src/init/helper-refresh.d.ts +79 -0
  294. package/dist/src/init/helper-refresh.js +347 -0
  295. package/dist/src/init/helper-signing.d.ts +37 -0
  296. package/dist/src/init/helper-signing.js +67 -0
  297. package/dist/src/init/helpers-generator.d.ts +87 -0
  298. package/dist/src/init/helpers-generator.js +1456 -0
  299. package/dist/src/init/index.d.ts +13 -0
  300. package/dist/src/init/index.js +15 -0
  301. package/dist/src/init/mcp-generator.d.ts +26 -0
  302. package/dist/src/init/mcp-generator.js +126 -0
  303. package/dist/src/init/memory-package-resolver.d.ts +53 -0
  304. package/dist/src/init/memory-package-resolver.js +118 -0
  305. package/dist/src/init/settings-generator.d.ts +14 -0
  306. package/dist/src/init/settings-generator.js +462 -0
  307. package/dist/src/init/statusline-generator.d.ts +28 -0
  308. package/dist/src/init/statusline-generator.js +177 -0
  309. package/dist/src/init/types.d.ts +331 -0
  310. package/dist/src/init/types.js +276 -0
  311. package/dist/src/log-filters.d.ts +46 -0
  312. package/dist/src/log-filters.js +107 -0
  313. package/dist/src/mcp-client.d.ts +92 -0
  314. package/dist/src/mcp-client.js +397 -0
  315. package/dist/src/mcp-server.d.ts +163 -0
  316. package/dist/src/mcp-server.js +751 -0
  317. package/dist/src/mcp-tools/agent-execute-core.d.ts +115 -0
  318. package/dist/src/mcp-tools/agent-execute-core.js +587 -0
  319. package/dist/src/mcp-tools/agent-tools.d.ts +9 -0
  320. package/dist/src/mcp-tools/agent-tools.js +837 -0
  321. package/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
  322. package/dist/src/mcp-tools/agentbbs-tools.js +394 -0
  323. package/dist/src/mcp-tools/agentdb-tools.d.ts +35 -0
  324. package/dist/src/mcp-tools/agentdb-tools.js +1473 -0
  325. package/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
  326. package/dist/src/mcp-tools/agenticow-loader.js +105 -0
  327. package/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
  328. package/dist/src/mcp-tools/agenticow-speculate-tools.js +209 -0
  329. package/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
  330. package/dist/src/mcp-tools/agenticow-tools.js +360 -0
  331. package/dist/src/mcp-tools/analyze-tools.d.ts +38 -0
  332. package/dist/src/mcp-tools/analyze-tools.js +346 -0
  333. package/dist/src/mcp-tools/auto-install.d.ts +83 -0
  334. package/dist/src/mcp-tools/auto-install.js +131 -0
  335. package/dist/src/mcp-tools/autopilot-tools.d.ts +12 -0
  336. package/dist/src/mcp-tools/autopilot-tools.js +231 -0
  337. package/dist/src/mcp-tools/browser-intent-tools.d.ts +162 -0
  338. package/dist/src/mcp-tools/browser-intent-tools.js +548 -0
  339. package/dist/src/mcp-tools/browser-session-tools.d.ts +27 -0
  340. package/dist/src/mcp-tools/browser-session-tools.js +398 -0
  341. package/dist/src/mcp-tools/browser-tools.d.ts +21 -0
  342. package/dist/src/mcp-tools/browser-tools.js +760 -0
  343. package/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
  344. package/dist/src/mcp-tools/business-pod-tools.js +169 -0
  345. package/dist/src/mcp-tools/claims-tools.d.ts +12 -0
  346. package/dist/src/mcp-tools/claims-tools.js +863 -0
  347. package/dist/src/mcp-tools/config-tools.d.ts +8 -0
  348. package/dist/src/mcp-tools/config-tools.js +411 -0
  349. package/dist/src/mcp-tools/coordination-tools.d.ts +13 -0
  350. package/dist/src/mcp-tools/coordination-tools.js +729 -0
  351. package/dist/src/mcp-tools/daa-tools.d.ts +13 -0
  352. package/dist/src/mcp-tools/daa-tools.js +534 -0
  353. package/dist/src/mcp-tools/embeddings-tools.d.ts +9 -0
  354. package/dist/src/mcp-tools/embeddings-tools.js +904 -0
  355. package/dist/src/mcp-tools/github-tools.d.ts +9 -0
  356. package/dist/src/mcp-tools/github-tools.js +659 -0
  357. package/dist/src/mcp-tools/guidance-tools.d.ts +15 -0
  358. package/dist/src/mcp-tools/guidance-tools.js +639 -0
  359. package/dist/src/mcp-tools/hive-mind-tools.d.ts +8 -0
  360. package/dist/src/mcp-tools/hive-mind-tools.js +953 -0
  361. package/dist/src/mcp-tools/hooks-tools.d.ts +65 -0
  362. package/dist/src/mcp-tools/hooks-tools.js +4836 -0
  363. package/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
  364. package/dist/src/mcp-tools/http-fetch-tools.js +329 -0
  365. package/dist/src/mcp-tools/index.d.ts +34 -0
  366. package/dist/src/mcp-tools/index.js +40 -0
  367. package/dist/src/mcp-tools/managed-agent-tools.d.ts +22 -0
  368. package/dist/src/mcp-tools/managed-agent-tools.js +357 -0
  369. package/dist/src/mcp-tools/memory-tools.d.ts +14 -0
  370. package/dist/src/mcp-tools/memory-tools.js +1330 -0
  371. package/dist/src/mcp-tools/metaharness-tools.d.ts +51 -0
  372. package/dist/src/mcp-tools/metaharness-tools.js +684 -0
  373. package/dist/src/mcp-tools/neural-tools.d.ts +54 -0
  374. package/dist/src/mcp-tools/neural-tools.js +1168 -0
  375. package/dist/src/mcp-tools/performance-tools.d.ts +16 -0
  376. package/dist/src/mcp-tools/performance-tools.js +675 -0
  377. package/dist/src/mcp-tools/progress-tools.d.ts +14 -0
  378. package/dist/src/mcp-tools/progress-tools.js +348 -0
  379. package/dist/src/mcp-tools/request-tracker.d.ts +17 -0
  380. package/dist/src/mcp-tools/request-tracker.js +27 -0
  381. package/dist/src/mcp-tools/ruvllm-tools.d.ts +9 -0
  382. package/dist/src/mcp-tools/ruvllm-tools.js +355 -0
  383. package/dist/src/mcp-tools/security-tools.d.ts +18 -0
  384. package/dist/src/mcp-tools/security-tools.js +556 -0
  385. package/dist/src/mcp-tools/session-tools.d.ts +8 -0
  386. package/dist/src/mcp-tools/session-tools.js +517 -0
  387. package/dist/src/mcp-tools/swarm-tools.d.ts +37 -0
  388. package/dist/src/mcp-tools/swarm-tools.js +390 -0
  389. package/dist/src/mcp-tools/system-tools.d.ts +13 -0
  390. package/dist/src/mcp-tools/system-tools.js +688 -0
  391. package/dist/src/mcp-tools/task-tools.d.ts +8 -0
  392. package/dist/src/mcp-tools/task-tools.js +487 -0
  393. package/dist/src/mcp-tools/terminal-tools.d.ts +8 -0
  394. package/dist/src/mcp-tools/terminal-tools.js +306 -0
  395. package/dist/src/mcp-tools/testgen-tools.d.ts +26 -0
  396. package/dist/src/mcp-tools/testgen-tools.js +168 -0
  397. package/dist/src/mcp-tools/tool-loop-guardrail.d.ts +31 -0
  398. package/dist/src/mcp-tools/tool-loop-guardrail.js +71 -0
  399. package/dist/src/mcp-tools/transfer-tools.d.ts +14 -0
  400. package/dist/src/mcp-tools/transfer-tools.js +447 -0
  401. package/dist/src/mcp-tools/types.d.ts +8 -0
  402. package/dist/src/mcp-tools/types.js +8 -0
  403. package/dist/src/mcp-tools/validate-input.d.ts +9 -0
  404. package/dist/src/mcp-tools/validate-input.js +9 -0
  405. package/dist/src/mcp-tools/wasm-agent-tools.d.ts +13 -0
  406. package/dist/src/mcp-tools/wasm-agent-tools.js +840 -0
  407. package/dist/src/mcp-tools/workflow-tools.d.ts +8 -0
  408. package/dist/src/mcp-tools/workflow-tools.js +884 -0
  409. package/dist/src/memory/bge-embedder.d.ts +25 -0
  410. package/dist/src/memory/bge-embedder.js +121 -0
  411. package/dist/src/memory/cross-encoder-rerank.d.ts +33 -0
  412. package/dist/src/memory/cross-encoder-rerank.js +123 -0
  413. package/dist/src/memory/embedding-policy.d.ts +21 -0
  414. package/dist/src/memory/embedding-policy.js +30 -0
  415. package/dist/src/memory/embedding-quantization.d.ts +62 -0
  416. package/dist/src/memory/embedding-quantization.js +156 -0
  417. package/dist/src/memory/ewc-consolidation.d.ts +305 -0
  418. package/dist/src/memory/ewc-consolidation.js +611 -0
  419. package/dist/src/memory/graph-edge-writer.d.ts +95 -0
  420. package/dist/src/memory/graph-edge-writer.js +217 -0
  421. package/dist/src/memory/hybrid-retrieval.d.ts +77 -0
  422. package/dist/src/memory/hybrid-retrieval.js +192 -0
  423. package/dist/src/memory/intelligence.d.ts +405 -0
  424. package/dist/src/memory/intelligence.js +1316 -0
  425. package/dist/src/memory/lucene-bm25.d.ts +19 -0
  426. package/dist/src/memory/lucene-bm25.js +308 -0
  427. package/dist/src/memory/memory-bridge.d.ts +537 -0
  428. package/dist/src/memory/memory-bridge.js +2460 -0
  429. package/dist/src/memory/memory-initializer.d.ts +556 -0
  430. package/dist/src/memory/memory-initializer.js +3021 -0
  431. package/dist/src/memory/neural-package-bridge.d.ts +48 -0
  432. package/dist/src/memory/neural-package-bridge.js +87 -0
  433. package/dist/src/memory/rabitq-index.d.ts +60 -0
  434. package/dist/src/memory/rabitq-index.js +242 -0
  435. package/dist/src/memory/sona-optimizer.d.ts +267 -0
  436. package/dist/src/memory/sona-optimizer.js +779 -0
  437. package/dist/src/memory/structured-distill.d.ts +48 -0
  438. package/dist/src/memory/structured-distill.js +125 -0
  439. package/dist/src/output.d.ts +9 -0
  440. package/dist/src/output.js +9 -0
  441. package/dist/src/parser.d.ts +89 -0
  442. package/dist/src/parser.js +516 -0
  443. package/dist/src/plugins/manager.d.ts +133 -0
  444. package/dist/src/plugins/manager.js +415 -0
  445. package/dist/src/plugins/store/discovery.d.ts +99 -0
  446. package/dist/src/plugins/store/discovery.js +1224 -0
  447. package/dist/src/plugins/store/index.d.ts +76 -0
  448. package/dist/src/plugins/store/index.js +141 -0
  449. package/dist/src/plugins/store/search.d.ts +46 -0
  450. package/dist/src/plugins/store/search.js +230 -0
  451. package/dist/src/plugins/store/types.d.ts +279 -0
  452. package/dist/src/plugins/store/types.js +7 -0
  453. package/dist/src/plugins/tests/demo-plugin-store.d.ts +7 -0
  454. package/dist/src/plugins/tests/demo-plugin-store.js +126 -0
  455. package/dist/src/plugins/tests/standalone-test.d.ts +12 -0
  456. package/dist/src/plugins/tests/standalone-test.js +188 -0
  457. package/dist/src/plugins/tests/test-plugin-store.d.ts +7 -0
  458. package/dist/src/plugins/tests/test-plugin-store.js +206 -0
  459. package/dist/src/production/circuit-breaker.d.ts +101 -0
  460. package/dist/src/production/circuit-breaker.js +241 -0
  461. package/dist/src/production/error-handler.d.ts +92 -0
  462. package/dist/src/production/error-handler.js +299 -0
  463. package/dist/src/production/index.d.ts +23 -0
  464. package/dist/src/production/index.js +18 -0
  465. package/dist/src/production/monitoring.d.ts +161 -0
  466. package/dist/src/production/monitoring.js +356 -0
  467. package/dist/src/production/rate-limiter.d.ts +80 -0
  468. package/dist/src/production/rate-limiter.js +201 -0
  469. package/dist/src/production/retry.d.ts +48 -0
  470. package/dist/src/production/retry.js +179 -0
  471. package/dist/src/prompt.d.ts +44 -0
  472. package/dist/src/prompt.js +501 -0
  473. package/dist/src/proxy/install.d.ts +29 -0
  474. package/dist/src/proxy/install.js +135 -0
  475. package/dist/src/proxy/lifecycle.d.ts +61 -0
  476. package/dist/src/proxy/lifecycle.js +249 -0
  477. package/dist/src/proxy/paths.d.ts +34 -0
  478. package/dist/src/proxy/paths.js +70 -0
  479. package/dist/src/proxy/release.d.ts +47 -0
  480. package/dist/src/proxy/release.js +138 -0
  481. package/dist/src/proxy/token-bridge.d.ts +5 -0
  482. package/dist/src/proxy/token-bridge.js +61 -0
  483. package/dist/src/proxy/verify.d.ts +44 -0
  484. package/dist/src/proxy/verify.js +68 -0
  485. package/dist/src/runtime/headless.d.ts +60 -0
  486. package/dist/src/runtime/headless.js +284 -0
  487. package/dist/src/runtime/parent-death-watchdog.d.ts +42 -0
  488. package/dist/src/runtime/parent-death-watchdog.js +70 -0
  489. package/dist/src/ruvector/agent-wasm.d.ts +228 -0
  490. package/dist/src/ruvector/agent-wasm.js +463 -0
  491. package/dist/src/ruvector/ast-analyzer.d.ts +67 -0
  492. package/dist/src/ruvector/ast-analyzer.js +277 -0
  493. package/dist/src/ruvector/codemods/engine.d.ts +45 -0
  494. package/dist/src/ruvector/codemods/engine.js +291 -0
  495. package/dist/src/ruvector/codemods/scope-analysis.d.ts +29 -0
  496. package/dist/src/ruvector/codemods/scope-analysis.js +162 -0
  497. package/dist/src/ruvector/coverage-router.d.ts +160 -0
  498. package/dist/src/ruvector/coverage-router.js +531 -0
  499. package/dist/src/ruvector/coverage-tools.d.ts +33 -0
  500. package/dist/src/ruvector/coverage-tools.js +157 -0
  501. package/dist/src/ruvector/diff-classifier.d.ts +175 -0
  502. package/dist/src/ruvector/diff-classifier.js +699 -0
  503. package/dist/src/ruvector/diskann-backend.d.ts +78 -0
  504. package/dist/src/ruvector/diskann-backend.js +310 -0
  505. package/dist/src/ruvector/enhanced-model-router.d.ts +172 -0
  506. package/dist/src/ruvector/enhanced-model-router.js +577 -0
  507. package/dist/src/ruvector/graph-analyzer.d.ts +187 -0
  508. package/dist/src/ruvector/graph-analyzer.js +929 -0
  509. package/dist/src/ruvector/graph-backend.d.ts +79 -0
  510. package/dist/src/ruvector/graph-backend.js +220 -0
  511. package/dist/src/ruvector/index.d.ts +38 -0
  512. package/dist/src/ruvector/index.js +86 -0
  513. package/dist/src/ruvector/lora-adapter.d.ts +292 -0
  514. package/dist/src/ruvector/lora-adapter.js +710 -0
  515. package/dist/src/ruvector/model-prices.d.ts +50 -0
  516. package/dist/src/ruvector/model-prices.js +72 -0
  517. package/dist/src/ruvector/model-router.d.ts +408 -0
  518. package/dist/src/ruvector/model-router.js +1162 -0
  519. package/dist/src/ruvector/neural-router.d.ts +182 -0
  520. package/dist/src/ruvector/neural-router.js +825 -0
  521. package/dist/src/ruvector/output-verifier.d.ts +83 -0
  522. package/dist/src/ruvector/output-verifier.js +277 -0
  523. package/dist/src/ruvector/q-learning-router.d.ts +227 -0
  524. package/dist/src/ruvector/q-learning-router.js +721 -0
  525. package/dist/src/ruvector/router-calibrator.d.ts +65 -0
  526. package/dist/src/ruvector/router-calibrator.js +120 -0
  527. package/dist/src/ruvector/router-parallel-recorder.d.ts +127 -0
  528. package/dist/src/ruvector/router-parallel-recorder.js +183 -0
  529. package/dist/src/ruvector/router-trajectory.d.ts +194 -0
  530. package/dist/src/ruvector/router-trajectory.js +281 -0
  531. package/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
  532. package/dist/src/ruvector/run-transcript-recorder.js +209 -0
  533. package/dist/src/ruvector/ruvllm-wasm.d.ts +179 -0
  534. package/dist/src/ruvector/ruvllm-wasm.js +379 -0
  535. package/dist/src/ruvector/semantic-router.d.ts +77 -0
  536. package/dist/src/ruvector/semantic-router.js +178 -0
  537. package/dist/src/ruvector/task-embedder.d.ts +56 -0
  538. package/dist/src/ruvector/task-embedder.js +237 -0
  539. package/dist/src/ruvector/trajectory-tree.d.ts +113 -0
  540. package/dist/src/ruvector/trajectory-tree.js +237 -0
  541. package/dist/src/ruvector/vector-db.d.ts +73 -0
  542. package/dist/src/ruvector/vector-db.js +301 -0
  543. package/dist/src/ruvector/wasm-embedder.d.ts +13 -0
  544. package/dist/src/ruvector/wasm-embedder.js +143 -0
  545. package/dist/src/security/builtin-aidefence.d.ts +34 -0
  546. package/dist/src/security/builtin-aidefence.js +86 -0
  547. package/dist/src/services/agentic-flow-bridge.d.ts +50 -0
  548. package/dist/src/services/agentic-flow-bridge.js +95 -0
  549. package/dist/src/services/ai-job-dedup.d.ts +61 -0
  550. package/dist/src/services/ai-job-dedup.js +136 -0
  551. package/dist/src/services/checkpoint-gate.d.ts +140 -0
  552. package/dist/src/services/checkpoint-gate.js +223 -0
  553. package/dist/src/services/claim-service.d.ts +204 -0
  554. package/dist/src/services/claim-service.js +818 -0
  555. package/dist/src/services/config-file-manager.d.ts +37 -0
  556. package/dist/src/services/config-file-manager.js +224 -0
  557. package/dist/src/services/container-worker-pool.d.ts +204 -0
  558. package/dist/src/services/container-worker-pool.js +589 -0
  559. package/dist/src/services/daemon-autostart.d.ts +17 -0
  560. package/dist/src/services/daemon-autostart.js +102 -0
  561. package/dist/src/services/distill-oracle.d.ts +190 -0
  562. package/dist/src/services/distill-oracle.js +349 -0
  563. package/dist/src/services/distill-tuning.d.ts +111 -0
  564. package/dist/src/services/distill-tuning.js +510 -0
  565. package/dist/src/services/evolve-proof.d.ts +253 -0
  566. package/dist/src/services/evolve-proof.js +343 -0
  567. package/dist/src/services/fable-harness.d.ts +208 -0
  568. package/dist/src/services/fable-harness.js +388 -0
  569. package/dist/src/services/git-workspace-identity.d.ts +42 -0
  570. package/dist/src/services/git-workspace-identity.js +99 -0
  571. package/dist/src/services/global-ai-budget.d.ts +135 -0
  572. package/dist/src/services/global-ai-budget.js +415 -0
  573. package/dist/src/services/harness-benchmark.d.ts +68 -0
  574. package/dist/src/services/harness-benchmark.js +94 -0
  575. package/dist/src/services/harness-canary.d.ts +60 -0
  576. package/dist/src/services/harness-canary.js +69 -0
  577. package/dist/src/services/harness-corpus-harvester.d.ts +51 -0
  578. package/dist/src/services/harness-corpus-harvester.js +74 -0
  579. package/dist/src/services/harness-flywheel-generations.d.ts +115 -0
  580. package/dist/src/services/harness-flywheel-generations.js +360 -0
  581. package/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
  582. package/dist/src/services/harness-flywheel-runtime.js +92 -0
  583. package/dist/src/services/harness-flywheel.d.ts +48 -0
  584. package/dist/src/services/harness-flywheel.js +207 -0
  585. package/dist/src/services/harness-frozen-eval.d.ts +22 -0
  586. package/dist/src/services/harness-frozen-eval.js +66 -0
  587. package/dist/src/services/harness-hosts.d.ts +40 -0
  588. package/dist/src/services/harness-hosts.js +88 -0
  589. package/dist/src/services/harness-improvement-ledger.d.ts +63 -0
  590. package/dist/src/services/harness-improvement-ledger.js +101 -0
  591. package/dist/src/services/harness-loop.d.ts +53 -0
  592. package/dist/src/services/harness-loop.js +85 -0
  593. package/dist/src/services/harness-qualification.d.ts +66 -0
  594. package/dist/src/services/harness-qualification.js +143 -0
  595. package/dist/src/services/harness-replay.d.ts +37 -0
  596. package/dist/src/services/harness-replay.js +92 -0
  597. package/dist/src/services/harness-verify.d.ts +33 -0
  598. package/dist/src/services/harness-verify.js +26 -0
  599. package/dist/src/services/harness-worker.d.ts +23 -0
  600. package/dist/src/services/harness-worker.js +66 -0
  601. package/dist/src/services/headless-worker-executor.d.ts +358 -0
  602. package/dist/src/services/headless-worker-executor.js +1269 -0
  603. package/dist/src/services/index.d.ts +13 -0
  604. package/dist/src/services/index.js +11 -0
  605. package/dist/src/services/memory-backup.d.ts +53 -0
  606. package/dist/src/services/memory-backup.js +198 -0
  607. package/dist/src/services/memory-distillation.d.ts +41 -0
  608. package/dist/src/services/memory-distillation.js +277 -0
  609. package/dist/src/services/native-training.d.ts +68 -0
  610. package/dist/src/services/native-training.js +141 -0
  611. package/dist/src/services/registry-api.d.ts +58 -0
  612. package/dist/src/services/registry-api.js +146 -0
  613. package/dist/src/services/repo-supervisor.d.ts +70 -0
  614. package/dist/src/services/repo-supervisor.js +228 -0
  615. package/dist/src/services/ruvector-training.d.ts +222 -0
  616. package/dist/src/services/ruvector-training.js +693 -0
  617. package/dist/src/services/swarm-memory-branches.d.ts +135 -0
  618. package/dist/src/services/swarm-memory-branches.js +213 -0
  619. package/dist/src/services/weight-eft.d.ts +305 -0
  620. package/dist/src/services/weight-eft.js +296 -0
  621. package/dist/src/services/worker-daemon.d.ts +439 -0
  622. package/dist/src/services/worker-daemon.js +1865 -0
  623. package/dist/src/services/worker-queue.d.ts +194 -0
  624. package/dist/src/services/worker-queue.js +513 -0
  625. package/dist/src/services/workspace-lease.d.ts +55 -0
  626. package/dist/src/services/workspace-lease.js +191 -0
  627. package/dist/src/suggest.d.ts +53 -0
  628. package/dist/src/suggest.js +200 -0
  629. package/dist/src/transfer/anonymization/index.d.ts +25 -0
  630. package/dist/src/transfer/anonymization/index.js +175 -0
  631. package/dist/src/transfer/deploy-seraphine.d.ts +13 -0
  632. package/dist/src/transfer/deploy-seraphine.js +205 -0
  633. package/dist/src/transfer/export.d.ts +25 -0
  634. package/dist/src/transfer/export.js +113 -0
  635. package/dist/src/transfer/index.d.ts +12 -0
  636. package/dist/src/transfer/index.js +31 -0
  637. package/dist/src/transfer/ipfs/client.d.ts +109 -0
  638. package/dist/src/transfer/ipfs/client.js +307 -0
  639. package/dist/src/transfer/ipfs/upload.d.ts +95 -0
  640. package/dist/src/transfer/ipfs/upload.js +413 -0
  641. package/dist/src/transfer/models/seraphine.d.ts +72 -0
  642. package/dist/src/transfer/models/seraphine.js +373 -0
  643. package/dist/src/transfer/serialization/cfp.d.ts +49 -0
  644. package/dist/src/transfer/serialization/cfp.js +183 -0
  645. package/dist/src/transfer/storage/gcs.d.ts +82 -0
  646. package/dist/src/transfer/storage/gcs.js +272 -0
  647. package/dist/src/transfer/storage/index.d.ts +6 -0
  648. package/dist/src/transfer/storage/index.js +6 -0
  649. package/dist/src/transfer/store/discovery.d.ts +84 -0
  650. package/dist/src/transfer/store/discovery.js +382 -0
  651. package/dist/src/transfer/store/download.d.ts +70 -0
  652. package/dist/src/transfer/store/download.js +334 -0
  653. package/dist/src/transfer/store/index.d.ts +84 -0
  654. package/dist/src/transfer/store/index.js +153 -0
  655. package/dist/src/transfer/store/publish.d.ts +76 -0
  656. package/dist/src/transfer/store/publish.js +294 -0
  657. package/dist/src/transfer/store/registry.d.ts +58 -0
  658. package/dist/src/transfer/store/registry.js +285 -0
  659. package/dist/src/transfer/store/search.d.ts +54 -0
  660. package/dist/src/transfer/store/search.js +232 -0
  661. package/dist/src/transfer/store/tests/standalone-test.d.ts +12 -0
  662. package/dist/src/transfer/store/tests/standalone-test.js +190 -0
  663. package/dist/src/transfer/store/types.d.ts +193 -0
  664. package/dist/src/transfer/store/types.js +6 -0
  665. package/dist/src/transfer/test-seraphine.d.ts +6 -0
  666. package/dist/src/transfer/test-seraphine.js +105 -0
  667. package/dist/src/transfer/tests/test-store.d.ts +7 -0
  668. package/dist/src/transfer/tests/test-store.js +214 -0
  669. package/dist/src/transfer/types.d.ts +245 -0
  670. package/dist/src/transfer/types.js +6 -0
  671. package/dist/src/types.d.ts +13 -0
  672. package/dist/src/types.js +13 -0
  673. package/dist/src/update/checker.d.ts +34 -0
  674. package/dist/src/update/checker.js +191 -0
  675. package/dist/src/update/executor.d.ts +33 -0
  676. package/dist/src/update/executor.js +217 -0
  677. package/dist/src/update/index.d.ts +33 -0
  678. package/dist/src/update/index.js +64 -0
  679. package/dist/src/update/rate-limiter.d.ts +20 -0
  680. package/dist/src/update/rate-limiter.js +96 -0
  681. package/dist/src/update/validator.d.ts +17 -0
  682. package/dist/src/update/validator.js +123 -0
  683. package/package.json +1 -1
@@ -0,0 +1,1865 @@
1
+ /**
2
+ * Worker Daemon Service
3
+ * Node.js-based background worker system that auto-runs like shell daemons
4
+ *
5
+ * Workers:
6
+ * - map: Codebase mapping (5 min interval)
7
+ * - audit: Security analysis (10 min interval)
8
+ * - optimize: Performance optimization (15 min interval)
9
+ * - consolidate: Memory distillation — memory_entries -> episodes/reasoning_patterns/
10
+ * causal_edges (30 min interval, ADR-174; RUFLO_DAEMON_NO_DISTILL=1 / --no-distill to skip)
11
+ * - testgaps: Test coverage analysis (20 min interval)
12
+ */
13
+ import { EventEmitter } from 'events';
14
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, unlinkSync, renameSync } from 'fs';
15
+ import { cpus } from 'os';
16
+ import { join } from 'path';
17
+ import { HeadlessWorkerExecutor, isHeadlessWorker, } from './headless-worker-executor.js';
18
+ // ADR-174 M3: the consolidate worker below drives the real DISTILL/CONSOLIDATE
19
+ // pass instead of writing a hardcoded { patternsConsolidated: 0 } stub. The
20
+ // service itself is owned/frozen elsewhere (memory-distillation.ts) — it is
21
+ // incremental (rowid cursor), non-destructive, transactional, and
22
+ // quick_check-gated, so it's safe to call unconditionally on every tick.
23
+ import { runDistillation, defaultMemoryDbPath } from './memory-distillation.js';
24
+ import { backupMemoryDb } from './memory-backup.js';
25
+ import { resolveGitWorkspaceIdentity } from './git-workspace-identity.js';
26
+ import { getWorkspaceLeaseRegistry } from './workspace-lease.js';
27
+ import { getRepoSupervisorRegistry } from './repo-supervisor.js';
28
+ // Default worker configurations with improved intervals (P0 fix: map 5min -> 15min)
29
+ const DEFAULT_WORKERS = [
30
+ { type: 'map', intervalMs: 15 * 60 * 1000, offsetMs: 0, priority: 'normal', description: 'Codebase mapping', enabled: true },
31
+ { type: 'audit', intervalMs: 10 * 60 * 1000, offsetMs: 2 * 60 * 1000, priority: 'critical', description: 'Security analysis', enabled: true },
32
+ { type: 'optimize', intervalMs: 15 * 60 * 1000, offsetMs: 4 * 60 * 1000, priority: 'high', description: 'Performance optimization', enabled: true },
33
+ { type: 'consolidate', intervalMs: 30 * 60 * 1000, offsetMs: 6 * 60 * 1000, priority: 'low', description: 'Memory distillation (ADR-174)', enabled: true },
34
+ { type: 'testgaps', intervalMs: 20 * 60 * 1000, offsetMs: 8 * 60 * 1000, priority: 'normal', description: 'Test coverage analysis', enabled: true },
35
+ { type: 'backup', intervalMs: 24 * 60 * 60 * 1000, offsetMs: 10 * 60 * 1000, priority: 'low', description: 'Nightly memory DB backup (WAL-safe, rotated)', enabled: true },
36
+ { type: 'harness', intervalMs: 6 * 60 * 60 * 1000, offsetMs: 12 * 60 * 1000, priority: 'low', description: 'Self-optimizing harness loop (opt-in RUFLO_HARNESS_LOOP, $0-default)', enabled: true },
37
+ { type: 'predict', intervalMs: 10 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Predictive preloading', enabled: false },
38
+ { type: 'document', intervalMs: 60 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Auto-documentation', enabled: false },
39
+ ];
40
+ // Worker timeout — must exceed the longest per-worker headless timeout (15 min for audit/refactor).
41
+ // Previously 5 min, which caused orphan processes when daemon timeout fired before executor timeout (#1117).
42
+ const DEFAULT_WORKER_TIMEOUT_MS = 16 * 60 * 1000;
43
+ // ADR-174 M3: hard cap on rows the `consolidate` worker distills in a single
44
+ // tick. The distillation service is cursor-driven (per-namespace rowid) and
45
+ // batches in transactions of `batchSize`, so a capped `maxEntries` guarantees
46
+ // this worker returns well within DEFAULT_WORKER_TIMEOUT_MS even against a
47
+ // large backlog — the cursor just picks up where it left off next tick
48
+ // (every 30 min per DEFAULT_WORKERS), draining an arbitrarily large backlog
49
+ // over several ticks instead of blocking on one.
50
+ const CONSOLIDATE_MAX_ENTRIES_PER_TICK = 1000;
51
+ const CONSOLIDATE_BATCH_SIZE = 200;
52
+ // ADR-174 M5: platform-default distillation config, chosen by the M4 self-
53
+ // optimization grid-search (scripts/tune-distill.mjs) on the real ~7.9k-entry
54
+ // corpus. Winner: batchSize=200, dedupDistance=0.2 (held-out MRR@10 0.753 vs
55
+ // 0.749 baseline — measured on-par, not a large uplift; the payoff is the
56
+ // populated substrate + trainable model). Override per-run via `memory distill`.
57
+ const CONSOLIDATE_DEDUP_DISTANCE = 0.2;
58
+ // ADR-174 M3 opt-out: set to skip the real distillation pass entirely (e.g.
59
+ // constrained CI hosts, or a user who wants the daemon's other workers but
60
+ // not this one without touching persisted worker-enabled state). Mirrors the
61
+ // `--no-distill` flag on `daemon start` (see commands/daemon.ts), which sets
62
+ // this env var on the forked/foreground daemon process.
63
+ const NO_DISTILL_ENV = 'RUFLO_DAEMON_NO_DISTILL';
64
+ // #2356 — Self-terminating lifecycle defaults. A background daemon with no
65
+ // upper bound on its lifetime runs until the box reboots; in the field this
66
+ // leaked tens of thousands of headless `claude --print` sweeps over many days
67
+ // (one observed daemon ran 19 days). A 12h default age cap (matching the
68
+ // pacphi/ruflo-machine-ref kit's proven value) heals a forgotten daemon within
69
+ // half a day; set RUFLO_DAEMON_TTL_SECS=0 (or `--ttl 0`) to opt out. Idle
70
+ // shutdown is opt-in (0 = disabled) since a legitimately quiet daemon is not a leak.
71
+ const DEFAULT_DAEMON_TTL_MS = 12 * 60 * 60 * 1000;
72
+ const DEFAULT_DAEMON_IDLE_SHUTDOWN_MS = 0;
73
+ /**
74
+ * Read a non-negative seconds value from an env var and return it as ms.
75
+ * Unlike the `parseInt(x) || default` idiom used elsewhere, an explicit `0`
76
+ * is honored (it disables the corresponding limit) rather than falling back
77
+ * to the default. Invalid / negative / absent values fall back.
78
+ */
79
+ function readEnvSecsAsMs(name, defaultMs) {
80
+ const raw = process.env[name];
81
+ if (raw === undefined || raw.trim() === '')
82
+ return defaultMs;
83
+ const secs = Number.parseInt(raw, 10);
84
+ if (!Number.isFinite(secs) || secs < 0)
85
+ return defaultMs;
86
+ return secs * 1000;
87
+ }
88
+ /**
89
+ * Worker Daemon - Manages background workers with Node.js
90
+ */
91
+ export class WorkerDaemon extends EventEmitter {
92
+ config;
93
+ workers = new Map();
94
+ timers = new Map();
95
+ // #1845: separate timer for the MCP-dispatch queue poller. Kept off
96
+ // the per-worker map so stop() clears both kinds without confusion.
97
+ queuePollTimer;
98
+ // #2356: separate timer that enforces the daemon's max-age TTL + idle
99
+ // shutdown. Cleared in stop() alongside the worker/queue timers.
100
+ lifecycleTimer;
101
+ running = false;
102
+ startedAt;
103
+ projectRoot;
104
+ runningWorkers = new Set(); // Track concurrent workers
105
+ pendingWorkers = []; // Queue for deferred workers
106
+ // Headless execution support
107
+ headlessExecutor = null;
108
+ headlessAvailable = false;
109
+ // #2251 — Promise that resolves once initHeadlessExecutor() has finished
110
+ // probing `claude --version` and constructed the executor. The constructor
111
+ // kicks off init fire-and-forget; without awaiting this on the trigger
112
+ // path, `ruflo daemon trigger -w <worker>` runs before headlessAvailable
113
+ // is set and falls through to the local stub in ~2ms.
114
+ headlessInitPromise = Promise.resolve();
115
+ // Preserve the original constructor config so we can detect explicit overrides
116
+ // during state restoration (R1: constructor config takes priority over stale state)
117
+ originalConfig;
118
+ // #2661 root-fix — resolved once (git identity doesn't change at runtime)
119
+ // and reused for lease heartbeats + supervisor election, both gated on
120
+ // aiWorkersEnabled since they only matter for the recurring AI schedule.
121
+ gitIdentity = null;
122
+ lastSupervisorRenewalMs = 0;
123
+ constructor(projectRoot, config) {
124
+ super();
125
+ this.projectRoot = projectRoot;
126
+ this.originalConfig = config;
127
+ const claudeFlowDir = join(projectRoot, '.claude-flow');
128
+ // Read daemon config from .claude-flow/config.json (Layer B)
129
+ const fileConfig = this.readDaemonConfigFromFile(claudeFlowDir);
130
+ // CPU-proportional smart default instead of hardcoded 2.0
131
+ const cpuCount = WorkerDaemon.getEffectiveCpuCount();
132
+ let smartMaxCpuLoad = Math.max(cpuCount * 0.8, 2.0); // Floor of 2.0 for single-CPU machines
133
+ // #2110 — WSL2 reports `/proc/loadavg` values that include Windows-side
134
+ // process counts mapped into the Linux kernel. Real load on a 4-CPU
135
+ // WSL2 host can be 200-400 even when the Linux side is idle. The
136
+ // default gate of `cpuCount * 0.8` always trips, deferring every
137
+ // worker as "CPU load too high" while the daemon reports healthy.
138
+ // Bump the floor to 1000 when WSL is detected so the gate is
139
+ // effectively disabled (real load on Linux side rarely exceeds 100
140
+ // even under heavy contention).
141
+ if (WorkerDaemon.isWslEnvironment()) {
142
+ smartMaxCpuLoad = Math.max(smartMaxCpuLoad, 1000);
143
+ }
144
+ // Platform-aware default: macOS os.freemem() excludes reclaimable file cache,
145
+ // so reported "free" is much lower than actually available memory.
146
+ // Linux reports available memory (including reclaimable cache) more accurately.
147
+ const defaultMinFreeMemory = process.platform === 'darwin' ? 5 : 10;
148
+ // Priority: constructor arg > config.json > smart default
149
+ // For resourceThresholds, merge field-by-field so partial overrides
150
+ // (e.g. only --max-cpu-load) still pick up defaults for other fields.
151
+ this.config = {
152
+ autoStart: config?.autoStart ?? fileConfig.autoStart ?? false,
153
+ logDir: config?.logDir ?? join(claudeFlowDir, 'logs'),
154
+ stateFile: config?.stateFile ?? join(claudeFlowDir, 'daemon-state.json'),
155
+ maxConcurrent: config?.maxConcurrent ?? fileConfig.maxConcurrent ?? 2,
156
+ workerTimeoutMs: config?.workerTimeoutMs ?? fileConfig.workerTimeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS,
157
+ resourceThresholds: {
158
+ maxCpuLoad: config?.resourceThresholds?.maxCpuLoad ?? fileConfig.maxCpuLoad ?? smartMaxCpuLoad,
159
+ minFreeMemoryPercent: config?.resourceThresholds?.minFreeMemoryPercent ?? fileConfig.minFreeMemoryPercent ?? defaultMinFreeMemory,
160
+ },
161
+ // #2356 — precedence: constructor arg > config.json (daemon.ttlSecs) >
162
+ // env (RUFLO_DAEMON_TTL_SECS) > built-in default. readEnvSecsAsMs folds
163
+ // env-or-default and honors an explicit 0 (disable).
164
+ ttlMs: config?.ttlMs ?? fileConfig.ttlMs ?? readEnvSecsAsMs('RUFLO_DAEMON_TTL_SECS', DEFAULT_DAEMON_TTL_MS),
165
+ idleShutdownMs: config?.idleShutdownMs ?? fileConfig.idleShutdownMs ?? readEnvSecsAsMs('RUFLO_DAEMON_IDLE_SECS', DEFAULT_DAEMON_IDLE_SHUTDOWN_MS),
166
+ // #2661 — AI workers are opt-in: flag > config.json > env > OFF.
167
+ // Deliberately NOT restored from daemon-state.json (initializeWorkerStates
168
+ // whitelist) so a stale state file can never resurrect consent.
169
+ aiWorkersEnabled: config?.aiWorkersEnabled
170
+ ?? fileConfig.aiWorkersEnabled
171
+ ?? (process.env.RUFLO_DAEMON_AI_WORKERS === '1'),
172
+ workers: config?.workers ?? DEFAULT_WORKERS,
173
+ };
174
+ // Setup graceful shutdown handlers
175
+ this.setupShutdownHandlers();
176
+ // #1855: install crash handlers so uncaught exceptions and unhandled
177
+ // rejections don't leak the PID file or orphan child processes.
178
+ this.installCrashHandlers();
179
+ // Ensure directories exist
180
+ if (!existsSync(claudeFlowDir)) {
181
+ mkdirSync(claudeFlowDir, { recursive: true });
182
+ }
183
+ if (!existsSync(this.config.logDir)) {
184
+ mkdirSync(this.config.logDir, { recursive: true });
185
+ }
186
+ // Initialize worker states
187
+ this.initializeWorkerStates();
188
+ // Initialize headless executor (async, non-blocking) — capture the
189
+ // promise so the trigger path (#2251) can await it before checking
190
+ // `headlessAvailable`. Scheduled fires hit a long-running daemon and
191
+ // are unaffected; the on-demand `trigger` path was racing this init.
192
+ this.headlessInitPromise = this.initHeadlessExecutor().catch((err) => {
193
+ this.log('warn', `Headless executor init failed: ${err}`);
194
+ });
195
+ }
196
+ /**
197
+ * Initialize headless executor if Claude Code is available
198
+ */
199
+ async initHeadlessExecutor() {
200
+ // #2661 — scheduled AI workers require explicit consent. Without it,
201
+ // don't even probe `claude --version`: headlessAvailable stays false,
202
+ // every worker runs its $0 local path, and a default install produces
203
+ // zero autonomous Claude launches regardless of worktree count.
204
+ if (!this.config.aiWorkersEnabled) {
205
+ this.log('info', 'AI workers disabled (default) - all workers run local-only. Enable with `daemon start --headless`, daemon.aiWorkers.enabled=true, or RUFLO_DAEMON_AI_WORKERS=1 (#2661)');
206
+ return;
207
+ }
208
+ try {
209
+ this.headlessExecutor = new HeadlessWorkerExecutor(this.projectRoot, {
210
+ maxConcurrent: this.config.maxConcurrent,
211
+ });
212
+ this.headlessAvailable = await this.headlessExecutor.isAvailable();
213
+ if (this.headlessAvailable) {
214
+ this.log('info', 'Claude Code headless mode available - AI workers enabled');
215
+ // Forward headless executor events. #1855: also snapshot the
216
+ // active child PIDs to disk on every transition so the next
217
+ // lifetime can reap orphans after a hard crash.
218
+ this.headlessExecutor.on('execution:start', (data) => {
219
+ this.writeChildrenSnapshot();
220
+ this.emit('headless:start', data);
221
+ });
222
+ this.headlessExecutor.on('execution:complete', (data) => {
223
+ this.writeChildrenSnapshot();
224
+ this.emit('headless:complete', data);
225
+ });
226
+ this.headlessExecutor.on('execution:error', (data) => {
227
+ this.writeChildrenSnapshot();
228
+ this.emit('headless:error', data);
229
+ });
230
+ this.headlessExecutor.on('output', (data) => {
231
+ this.emit('headless:output', data);
232
+ });
233
+ }
234
+ else {
235
+ this.log('info', 'Claude Code not found - AI workers will run in local fallback mode');
236
+ }
237
+ }
238
+ catch (error) {
239
+ this.log('warn', `Failed to initialize headless executor: ${error}`);
240
+ this.headlessAvailable = false;
241
+ }
242
+ }
243
+ /**
244
+ * Check if headless execution is available
245
+ */
246
+ isHeadlessAvailable() {
247
+ return this.headlessAvailable;
248
+ }
249
+ /**
250
+ * Get headless executor instance
251
+ */
252
+ getHeadlessExecutor() {
253
+ return this.headlessExecutor;
254
+ }
255
+ /**
256
+ * Detect effective CPU count for the current environment.
257
+ *
258
+ * Inside Docker / K8s containers, os.cpus().length reports the HOST cpu
259
+ * count, not the container limit (Node.js #28762 — wontfix). We read
260
+ * cgroup v2 / v1 quota files first so the maxCpuLoad threshold stays
261
+ * meaningful under resource-limited containers.
262
+ */
263
+ /**
264
+ * #2110 — detect WSL2 / WSL1 so the CPU-load gate can use a sane
265
+ * default. `/proc/loadavg` on WSL maps in Windows-side process counts
266
+ * and routinely reports values 100-1000x larger than real Linux load.
267
+ *
268
+ * Detection order:
269
+ * 1. `WSL_DISTRO_NAME` env var (set by Microsoft's WSL launcher)
270
+ * 2. `WSL_INTEROP` env var (set by recent WSL2)
271
+ * 3. `/proc/sys/kernel/osrelease` contains "microsoft" or "WSL"
272
+ * (kernel build marker; survives env stripping)
273
+ */
274
+ static isWslEnvironment() {
275
+ if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP)
276
+ return true;
277
+ try {
278
+ const osrelease = readFileSync('/proc/sys/kernel/osrelease', 'utf8').toLowerCase();
279
+ if (osrelease.includes('microsoft') || osrelease.includes('wsl'))
280
+ return true;
281
+ }
282
+ catch { /* not on Linux or /proc inaccessible */ }
283
+ return false;
284
+ }
285
+ static getEffectiveCpuCount() {
286
+ // 1. Try cgroup v2: /sys/fs/cgroup/cpu.max
287
+ try {
288
+ const cpuMax = readFileSync('/sys/fs/cgroup/cpu.max', 'utf8').trim();
289
+ const [quotaStr, periodStr] = cpuMax.split(' ');
290
+ if (quotaStr !== 'max') {
291
+ const quota = parseInt(quotaStr, 10);
292
+ const period = parseInt(periodStr, 10);
293
+ if (quota > 0 && period > 0)
294
+ return Math.ceil(quota / period);
295
+ }
296
+ }
297
+ catch { /* not in cgroup v2 */ }
298
+ // 2. Try cgroup v1: /sys/fs/cgroup/cpu/cpu.cfs_quota_us
299
+ try {
300
+ const quota = parseInt(readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us', 'utf8').trim(), 10);
301
+ const period = parseInt(readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us', 'utf8').trim(), 10);
302
+ if (quota > 0 && period > 0)
303
+ return Math.ceil(quota / period);
304
+ }
305
+ catch { /* not in cgroup v1 */ }
306
+ // 3. Fallback to os.cpus().length
307
+ return cpus().length || 1;
308
+ }
309
+ /**
310
+ * Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
311
+ * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
312
+ * #1844: prefer JSON when both exist (existing behavior) but fall back
313
+ * to YAML so operators using the v3 canonical YAML format aren't silently
314
+ * ignored. The chosen path is logged at info level.
315
+ */
316
+ readDaemonConfigFromFile(claudeFlowDir) {
317
+ const jsonPath = join(claudeFlowDir, 'config.json');
318
+ const yamlPath = join(claudeFlowDir, 'config.yaml');
319
+ const ymlPath = join(claudeFlowDir, 'config.yml');
320
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
321
+ let raw;
322
+ let chosenPath;
323
+ if (existsSync(jsonPath)) {
324
+ try {
325
+ raw = JSON.parse(readFileSync(jsonPath, 'utf-8'));
326
+ chosenPath = jsonPath;
327
+ }
328
+ catch {
329
+ return {};
330
+ }
331
+ }
332
+ else if (existsSync(yamlPath) || existsSync(ymlPath)) {
333
+ const yPath = existsSync(yamlPath) ? yamlPath : ymlPath;
334
+ try {
335
+ // Lazy-load yaml so the daemon doesn't hard-require it; if the
336
+ // dep isn't installed, fall back to the previous warn-only path.
337
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
338
+ const yamlMod = require('yaml');
339
+ const parsed = yamlMod.parse(readFileSync(yPath, 'utf-8'));
340
+ if (parsed && typeof parsed === 'object') {
341
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
342
+ raw = parsed;
343
+ chosenPath = yPath;
344
+ }
345
+ }
346
+ catch {
347
+ this.log('warn', `Found ${yPath} but yaml parser unavailable. Install \`yaml\` or convert to JSON. Falling back to defaults.`);
348
+ return {};
349
+ }
350
+ }
351
+ if (!raw || !chosenPath) {
352
+ return {};
353
+ }
354
+ this.log('info', `Daemon config loaded from ${chosenPath}`);
355
+ try {
356
+ // Support both flat keys at root and nested under scopes.project
357
+ const cfg = raw?.scopes?.project ?? raw;
358
+ const rawCpuLoad = cfg['daemon.resourceThresholds.maxCpuLoad'] ?? raw['daemon.resourceThresholds.maxCpuLoad'];
359
+ const rawMinMem = cfg['daemon.resourceThresholds.minFreeMemoryPercent'] ?? raw['daemon.resourceThresholds.minFreeMemoryPercent'];
360
+ const rawMaxConcurrent = cfg['daemon.maxConcurrent'] ?? raw['daemon.maxConcurrent'];
361
+ const rawTimeout = cfg['daemon.workerTimeoutMs'] ?? raw['daemon.workerTimeoutMs'];
362
+ // #2356 — lifecycle limits are configured in SECONDS in config.json
363
+ // (`daemon.ttlSecs` / `daemon.idleSecs`) for parity with the CLI flag
364
+ // and env var; stored internally as ms. An explicit 0 disables.
365
+ const rawTtl = cfg['daemon.ttlSecs'] ?? raw['daemon.ttlSecs'];
366
+ const rawIdle = cfg['daemon.idleSecs'] ?? raw['daemon.idleSecs'];
367
+ // #2661 — explicit opt-in for scheduled AI workers.
368
+ const rawAiEnabled = cfg['daemon.aiWorkers.enabled'] ?? raw['daemon.aiWorkers.enabled'];
369
+ return {
370
+ autoStart: typeof raw['daemon.autoStart'] === 'boolean' ? raw['daemon.autoStart'] : undefined,
371
+ maxConcurrent: (typeof rawMaxConcurrent === 'number' && rawMaxConcurrent > 0) ? rawMaxConcurrent : undefined,
372
+ workerTimeoutMs: (typeof rawTimeout === 'number' && rawTimeout > 0) ? rawTimeout : undefined,
373
+ maxCpuLoad: (typeof rawCpuLoad === 'number' && rawCpuLoad > 0 && rawCpuLoad < 1000) ? rawCpuLoad : undefined,
374
+ minFreeMemoryPercent: (typeof rawMinMem === 'number' && rawMinMem >= 0 && rawMinMem <= 100) ? rawMinMem : undefined,
375
+ ttlMs: (typeof rawTtl === 'number' && rawTtl >= 0) ? rawTtl * 1000 : undefined,
376
+ idleShutdownMs: (typeof rawIdle === 'number' && rawIdle >= 0) ? rawIdle * 1000 : undefined,
377
+ aiWorkersEnabled: typeof rawAiEnabled === 'boolean' ? rawAiEnabled : undefined,
378
+ };
379
+ }
380
+ catch {
381
+ return {};
382
+ }
383
+ }
384
+ /**
385
+ * Setup graceful shutdown handlers
386
+ */
387
+ setupShutdownHandlers() {
388
+ const shutdown = async () => {
389
+ this.log('info', 'Received shutdown signal, stopping daemon...');
390
+ await this.stop();
391
+ process.exit(0);
392
+ };
393
+ process.on('SIGTERM', shutdown);
394
+ process.on('SIGINT', shutdown);
395
+ process.on('SIGHUP', shutdown);
396
+ }
397
+ /**
398
+ * #1855: install crash handlers for uncaught exceptions and unhandled
399
+ * rejections. Without these, a thrown error from any timer callback,
400
+ * worker logic path, or transitive import crashes the daemon process
401
+ * silently — the PID file leaks and any in-flight child processes
402
+ * orphan. With these, we log a structured crash record, run stop()
403
+ * to clean up, then exit 1 so the process actually dies (otherwise
404
+ * Node would crash anyway after the handler returns).
405
+ */
406
+ installCrashHandlers() {
407
+ const onCrash = (kind, err) => {
408
+ // Best-effort logging; never throw from inside the crash handler.
409
+ try {
410
+ this.writeCrashRecord(kind, err);
411
+ }
412
+ catch { /* nothing more we can do */ }
413
+ try {
414
+ // Synchronous stop — don't await; the process is dying. Just
415
+ // remove the PID file and snapshot state so the next start
416
+ // sees a clean slate.
417
+ this.removePidFile();
418
+ this.saveState();
419
+ // Snapshot any in-flight child PIDs one last time so the next
420
+ // lifetime can reap them.
421
+ this.writeChildrenSnapshot();
422
+ }
423
+ catch { /* ignore */ }
424
+ // Exit non-zero so supervisors / shells see the failure.
425
+ process.exit(1);
426
+ };
427
+ process.on('uncaughtException', (err) => onCrash('uncaughtException', err));
428
+ process.on('unhandledRejection', (err) => onCrash('unhandledRejection', err));
429
+ }
430
+ /**
431
+ * Append a structured crash record to .claude-flow/logs/crash.log.
432
+ * Inspectable by hand or via `ruflo daemon status` follow-ups.
433
+ */
434
+ writeCrashRecord(kind, err) {
435
+ const logDir = this.config.logDir;
436
+ if (!existsSync(logDir))
437
+ mkdirSync(logDir, { recursive: true });
438
+ const crashLog = join(logDir, 'crash.log');
439
+ const ts = new Date().toISOString();
440
+ const message = err instanceof Error ? err.message : String(err);
441
+ const stack = err instanceof Error && err.stack ? err.stack : '<no stack>';
442
+ const record = `[${ts}] [${kind}] pid=${process.pid} ${message}\n${stack}\n---\n`;
443
+ appendFileSync(crashLog, record, 'utf-8');
444
+ this.log('warn', `Daemon crashed (${kind}): ${message} — see ${crashLog}`);
445
+ }
446
+ /**
447
+ * Path to the on-disk children registry — list of headless worker
448
+ * child PIDs the daemon currently owns. #1855: written on every
449
+ * execution:start / :complete / :error transition; read by the next
450
+ * lifetime to reap orphans after a hard crash.
451
+ */
452
+ get childrenFile() {
453
+ return join(this.projectRoot, '.claude-flow', 'daemon-children.json');
454
+ }
455
+ /**
456
+ * #1856: detect workers that were mid-flight when the previous daemon
457
+ * lifetime ended. A mid-flight worker has `lastStartedAt > lastRun`
458
+ * (started after the last successful completion). On crash recovery
459
+ * we count these as failures so the run-counter math stays consistent
460
+ * (`runCount === successCount + failureCount`). Workers naturally
461
+ * retry at their next scheduled interval; we deliberately don't
462
+ * immediately re-run because the failure may have been deterministic.
463
+ */
464
+ detectMidFlightFailures() {
465
+ let detected = 0;
466
+ for (const [type, state] of this.workers.entries()) {
467
+ const startedAt = state.lastStartedAt?.getTime() ?? 0;
468
+ const lastRunAt = state.lastRun?.getTime() ?? 0;
469
+ // started after the last successful completion → was mid-flight
470
+ if (startedAt > 0 && startedAt > lastRunAt) {
471
+ state.failureCount++;
472
+ state.isRunning = false;
473
+ // Don't bump runCount — it was already incremented at start
474
+ this.log('info', `Worker ${type} was mid-flight at last crash (started ${state.lastStartedAt?.toISOString()}); counted as failure, will retry at next scheduled interval`);
475
+ detected++;
476
+ }
477
+ }
478
+ if (detected > 0) {
479
+ this.saveState();
480
+ }
481
+ }
482
+ /**
483
+ * Snapshot the currently-active headless worker child PIDs to disk.
484
+ * Best-effort; failures don't propagate.
485
+ */
486
+ writeChildrenSnapshot() {
487
+ if (!this.headlessExecutor)
488
+ return;
489
+ try {
490
+ const pids = this.headlessExecutor.getActiveChildPids();
491
+ const dir = join(this.projectRoot, '.claude-flow');
492
+ if (!existsSync(dir))
493
+ mkdirSync(dir, { recursive: true });
494
+ writeFileSync(this.childrenFile, JSON.stringify({ pids, daemonPid: process.pid, timestamp: new Date().toISOString() }, null, 2), 'utf-8');
495
+ }
496
+ catch { /* best-effort */ }
497
+ }
498
+ /**
499
+ * #1855: reap orphan headless worker children left behind by a
500
+ * previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
501
+ * SIGTERMs any PID still alive that doesn't belong to the current
502
+ * daemon, then truncates the file. Called at the top of `start()`
503
+ * so the next lifetime starts with a clean process tree.
504
+ */
505
+ reapOrphanedChildren() {
506
+ const file = this.childrenFile;
507
+ if (!existsSync(file))
508
+ return;
509
+ let snapshot;
510
+ try {
511
+ snapshot = JSON.parse(readFileSync(file, 'utf-8'));
512
+ }
513
+ catch {
514
+ try {
515
+ unlinkSync(file);
516
+ }
517
+ catch { /* ignore */ }
518
+ return;
519
+ }
520
+ const pids = Array.isArray(snapshot.pids) ? snapshot.pids : [];
521
+ let reaped = 0;
522
+ for (const pid of pids) {
523
+ if (typeof pid !== 'number' || pid <= 0)
524
+ continue;
525
+ if (pid === process.pid)
526
+ continue; // never our own PID
527
+ try {
528
+ process.kill(pid, 0); // is alive?
529
+ process.kill(pid, 'SIGTERM');
530
+ reaped++;
531
+ }
532
+ catch {
533
+ // already dead — fine
534
+ }
535
+ }
536
+ if (reaped > 0) {
537
+ this.log('info', `Reaped ${reaped} orphan headless worker child(ren) from previous lifetime`);
538
+ }
539
+ try {
540
+ unlinkSync(file);
541
+ }
542
+ catch { /* ignore */ }
543
+ }
544
+ /**
545
+ * Check if system resources allow worker execution
546
+ */
547
+ async canRunWorker() {
548
+ const os = await import('os');
549
+ const cpuLoad = os.loadavg()[0];
550
+ const totalMem = os.totalmem();
551
+ const freeMem = os.freemem();
552
+ const freePercent = (freeMem / totalMem) * 100;
553
+ if (cpuLoad > this.config.resourceThresholds.maxCpuLoad) {
554
+ return { allowed: false, reason: `CPU load too high: ${cpuLoad.toFixed(2)}` };
555
+ }
556
+ if (freePercent < this.config.resourceThresholds.minFreeMemoryPercent) {
557
+ return { allowed: false, reason: `Memory too low: ${freePercent.toFixed(1)}% free` };
558
+ }
559
+ return { allowed: true };
560
+ }
561
+ /**
562
+ * Process pending workers queue
563
+ *
564
+ * When executeWorkerWithConcurrencyControl defers a worker (returns null),
565
+ * we break immediately to avoid a busy-wait loop — the deferred worker is
566
+ * already back on the pendingWorkers queue by that point. If no workers are
567
+ * currently running when we break, we schedule a backoff retry so the queue
568
+ * does not get permanently stuck.
569
+ */
570
+ async processPendingWorkers() {
571
+ while (this.pendingWorkers.length > 0 && this.runningWorkers.size < this.config.maxConcurrent) {
572
+ const workerType = this.pendingWorkers.shift();
573
+ const workerConfig = this.config.workers.find(w => w.type === workerType);
574
+ if (workerConfig) {
575
+ const result = await this.executeWorkerWithConcurrencyControl(workerConfig);
576
+ if (result === null) {
577
+ // Worker was deferred (resource pressure or concurrency limit).
578
+ // Break to avoid tight-looping — the next executeWorker() completion
579
+ // will call processPendingWorkers() again via the finally block.
580
+ if (this.runningWorkers.size === 0) {
581
+ // No workers running means nobody will trigger the finally-block
582
+ // callback, so schedule a backoff retry to avoid a stuck queue.
583
+ setTimeout(() => this.processPendingWorkers(), 30_000).unref();
584
+ }
585
+ break;
586
+ }
587
+ }
588
+ }
589
+ }
590
+ initializeWorkerStates() {
591
+ // Try to restore state from file
592
+ if (existsSync(this.config.stateFile)) {
593
+ try {
594
+ const saved = JSON.parse(readFileSync(this.config.stateFile, 'utf-8'));
595
+ // CRITICAL: Restore worker config (including enabled flag) from saved state
596
+ // This fixes #950: daemon enable command not persisting worker state
597
+ if (saved.config?.workers && Array.isArray(saved.config.workers)) {
598
+ for (const savedWorker of saved.config.workers) {
599
+ const workerConfig = this.config.workers.find(w => w.type === savedWorker.type);
600
+ if (workerConfig && typeof savedWorker.enabled === 'boolean') {
601
+ workerConfig.enabled = savedWorker.enabled;
602
+ }
603
+ }
604
+ }
605
+ // Restore resourceThresholds, maxConcurrent, workerTimeoutMs from saved state
606
+ // Only restore if valid numeric values within sane ranges
607
+ if (saved.config?.resourceThresholds && !this.originalConfig?.resourceThresholds) {
608
+ const rt = saved.config.resourceThresholds;
609
+ if (typeof rt.maxCpuLoad === 'number' && rt.maxCpuLoad > 0 && rt.maxCpuLoad < 1000) {
610
+ this.config.resourceThresholds.maxCpuLoad = rt.maxCpuLoad;
611
+ }
612
+ if (typeof rt.minFreeMemoryPercent === 'number' && rt.minFreeMemoryPercent >= 0 && rt.minFreeMemoryPercent <= 100) {
613
+ this.config.resourceThresholds.minFreeMemoryPercent = rt.minFreeMemoryPercent;
614
+ }
615
+ }
616
+ if (typeof saved.config?.maxConcurrent === 'number' && saved.config.maxConcurrent > 0) {
617
+ this.config.maxConcurrent = saved.config.maxConcurrent;
618
+ }
619
+ if (typeof saved.config?.workerTimeoutMs === 'number' && saved.config.workerTimeoutMs > 0) {
620
+ this.config.workerTimeoutMs = saved.config.workerTimeoutMs;
621
+ }
622
+ // Restore worker runtime states (runCount, successCount, etc.)
623
+ if (saved.workers) {
624
+ for (const [type, state] of Object.entries(saved.workers)) {
625
+ const savedState = state;
626
+ const lastRunValue = savedState.lastRun;
627
+ const lastStartedAtValue = savedState.lastStartedAt;
628
+ this.workers.set(type, {
629
+ runCount: savedState.runCount || 0,
630
+ successCount: savedState.successCount || 0,
631
+ failureCount: savedState.failureCount || 0,
632
+ averageDurationMs: savedState.averageDurationMs || 0,
633
+ lastRun: lastRunValue ? new Date(lastRunValue) : undefined,
634
+ lastStartedAt: lastStartedAtValue ? new Date(lastStartedAtValue) : undefined,
635
+ nextRun: undefined,
636
+ isRunning: false,
637
+ });
638
+ }
639
+ }
640
+ }
641
+ catch {
642
+ // Ignore parse errors, start fresh
643
+ }
644
+ }
645
+ // Initialize any missing workers
646
+ for (const workerConfig of this.config.workers) {
647
+ if (!this.workers.has(workerConfig.type)) {
648
+ this.workers.set(workerConfig.type, {
649
+ runCount: 0,
650
+ successCount: 0,
651
+ failureCount: 0,
652
+ averageDurationMs: 0,
653
+ isRunning: false,
654
+ });
655
+ }
656
+ }
657
+ }
658
+ /**
659
+ * Get the PID file path for singleton enforcement (#1395 Bug 3).
660
+ */
661
+ get pidFile() {
662
+ return join(this.projectRoot, '.claude-flow', 'daemon.pid');
663
+ }
664
+ /**
665
+ * Check if another daemon instance is already running.
666
+ * Returns the existing PID if alive, or null if no daemon is running.
667
+ *
668
+ * #1853: ignore self-PID matches. The detached-spawn path in
669
+ * `commands/daemon.ts` writes the child's PID into the file as a
670
+ * fallback after a 500ms wait. If the child reaches `start()` slower
671
+ * than the parent's 500ms wait (observed on Node 25 / macOS 26), the
672
+ * child reads its own PID back from the file and concludes "another
673
+ * daemon is already running" — so it exits before scheduling workers
674
+ * and `daemon status` reports STOPPED forever. A daemon process is
675
+ * never "another instance" of itself; treat self-match as absence.
676
+ */
677
+ checkExistingDaemon() {
678
+ if (!existsSync(this.pidFile))
679
+ return null;
680
+ try {
681
+ const pid = parseInt(readFileSync(this.pidFile, 'utf-8').trim(), 10);
682
+ if (isNaN(pid))
683
+ return null;
684
+ // #1853: a PID file containing our own PID is not "another daemon".
685
+ // Treat as absent so the start() path proceeds normally.
686
+ if (pid === process.pid)
687
+ return null;
688
+ // Check if process is alive (signal 0 = existence check)
689
+ process.kill(pid, 0);
690
+ return pid; // Process is alive
691
+ }
692
+ catch {
693
+ // Process is dead — clean up stale PID file
694
+ try {
695
+ unlinkSync(this.pidFile);
696
+ }
697
+ catch { /* ignore */ }
698
+ return null;
699
+ }
700
+ }
701
+ /**
702
+ * Write PID file for singleton enforcement.
703
+ */
704
+ writePidFile() {
705
+ const dir = join(this.projectRoot, '.claude-flow');
706
+ if (!existsSync(dir))
707
+ mkdirSync(dir, { recursive: true });
708
+ writeFileSync(this.pidFile, String(process.pid), 'utf-8');
709
+ }
710
+ /**
711
+ * Remove PID file on shutdown.
712
+ */
713
+ removePidFile() {
714
+ try {
715
+ unlinkSync(this.pidFile);
716
+ }
717
+ catch { /* ignore */ }
718
+ }
719
+ /**
720
+ * Start the daemon and all enabled workers
721
+ */
722
+ async start() {
723
+ if (this.running) {
724
+ this.emit('warning', 'Daemon already running');
725
+ return;
726
+ }
727
+ // PID singleton enforcement (#1395 Bug 3): prevent daemon accumulation
728
+ const existingPid = this.checkExistingDaemon();
729
+ if (existingPid !== null) {
730
+ this.log('info', `Daemon already running (PID: ${existingPid}), skipping start`);
731
+ this.emit('warning', `Daemon already running (PID: ${existingPid})`);
732
+ return;
733
+ }
734
+ // #1855: reap orphan headless worker children left by a previous
735
+ // crashed lifetime, BEFORE we mark ourselves running and start
736
+ // accepting new work. The children file from the prior daemon's
737
+ // last-snapshot is the authoritative list.
738
+ this.reapOrphanedChildren();
739
+ // #1856: detect workers that were mid-flight at the previous crash
740
+ // and count them as failures so runCount/successCount/failureCount
741
+ // stay consistent. Workers retry naturally at their next scheduled
742
+ // interval — we don't immediately re-run them, which avoids a
743
+ // freshly-recovered daemon hammering the same code path that just
744
+ // killed it.
745
+ this.detectMidFlightFailures();
746
+ this.running = true;
747
+ this.startedAt = new Date();
748
+ this.writePidFile();
749
+ this.emit('started', { pid: process.pid, startedAt: this.startedAt });
750
+ // #2661 root-fix — resolve repository identity and register/attempt
751
+ // election immediately at start, not just on the first 60s lifecycle
752
+ // tick, so `daemon status` reflects supervisor state right away. Only
753
+ // meaningful when AI workers are enabled (see field doc comment).
754
+ if (this.config.aiWorkersEnabled) {
755
+ this.gitIdentity = resolveGitWorkspaceIdentity(this.projectRoot);
756
+ void this.renewLeaseAndSupervisor();
757
+ }
758
+ // Schedule all enabled workers
759
+ for (const workerConfig of this.config.workers) {
760
+ if (workerConfig.enabled) {
761
+ this.scheduleWorker(workerConfig);
762
+ }
763
+ }
764
+ // #1845: poll the MCP-dispatch queue directory so workers requested
765
+ // via mcp__hooks_worker-dispatch (in a separate process) actually
766
+ // execute here. Previously the dispatch wrote to a process-local Map
767
+ // that the daemon could never see.
768
+ this.queuePollTimer = setInterval(() => {
769
+ void this.processDispatchQueue();
770
+ }, 5_000);
771
+ if (typeof this.queuePollTimer.unref === 'function') {
772
+ this.queuePollTimer.unref();
773
+ }
774
+ // #2356: self-terminating lifecycle. Without an upper bound on lifetime a
775
+ // forgotten daemon keeps dispatching headless worker sweeps for days.
776
+ this.startLifecycleMonitor();
777
+ // Save state
778
+ this.saveState();
779
+ 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}%)`);
780
+ }
781
+ /**
782
+ * #1845: ingest queue entries written by mcp__hooks_worker-dispatch.
783
+ * Each entry is a JSON file at `.claude-flow/daemon-queue/<id>.json`
784
+ * with `{ workerId, trigger, context, enqueuedAt }`. We move processed
785
+ * files to `.claude-flow/daemon-queue/.processed/` so the daemon never
786
+ * re-runs the same dispatch and operators can inspect history.
787
+ */
788
+ async processDispatchQueue() {
789
+ if (!this.running)
790
+ return;
791
+ const queueDir = join(this.projectRoot, '.claude-flow', 'daemon-queue');
792
+ if (!existsSync(queueDir))
793
+ return;
794
+ let entries;
795
+ try {
796
+ const fs = await import('fs');
797
+ entries = fs.readdirSync(queueDir).filter((n) => n.endsWith('.json'));
798
+ }
799
+ catch {
800
+ return;
801
+ }
802
+ if (entries.length === 0)
803
+ return;
804
+ const fs = await import('fs');
805
+ const processedDir = join(queueDir, '.processed');
806
+ if (!existsSync(processedDir)) {
807
+ try {
808
+ fs.mkdirSync(processedDir, { recursive: true });
809
+ }
810
+ catch { /* race ok */ }
811
+ }
812
+ for (const entry of entries) {
813
+ const src = join(queueDir, entry);
814
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
815
+ let payload;
816
+ try {
817
+ payload = JSON.parse(fs.readFileSync(src, 'utf-8'));
818
+ }
819
+ catch {
820
+ // Malformed entry — quarantine so we don't loop on it
821
+ try {
822
+ fs.renameSync(src, join(processedDir, `bad-${entry}`));
823
+ }
824
+ catch { /* nothing more we can do */ }
825
+ continue;
826
+ }
827
+ const trigger = payload?.trigger;
828
+ const workerId = payload?.workerId;
829
+ if (!trigger || !this.config.workers.some((w) => w.type === trigger)) {
830
+ try {
831
+ fs.renameSync(src, join(processedDir, `unknown-${entry}`));
832
+ }
833
+ catch { /* ok */ }
834
+ continue;
835
+ }
836
+ try {
837
+ this.log('info', `Dequeued ${trigger}${workerId ? ` (id=${workerId})` : ''} from MCP dispatch queue`);
838
+ await this.triggerWorker(trigger);
839
+ }
840
+ catch (err) {
841
+ this.log('warn', `Queued worker ${trigger} failed: ${err.message}`);
842
+ }
843
+ finally {
844
+ try {
845
+ fs.renameSync(src, join(processedDir, entry));
846
+ }
847
+ catch { /* ignore */ }
848
+ }
849
+ }
850
+ }
851
+ /**
852
+ * Stop the daemon and all workers
853
+ */
854
+ async stop() {
855
+ if (!this.running) {
856
+ this.emit('warning', 'Daemon not running');
857
+ return;
858
+ }
859
+ // Clear all timers (convert to array to avoid iterator issues)
860
+ const timerEntries = Array.from(this.timers.entries());
861
+ for (const [type, timer] of timerEntries) {
862
+ clearTimeout(timer);
863
+ this.log('info', `Stopped worker: ${type}`);
864
+ }
865
+ this.timers.clear();
866
+ // #1845: stop the MCP-dispatch queue poller too.
867
+ if (this.queuePollTimer) {
868
+ clearInterval(this.queuePollTimer);
869
+ this.queuePollTimer = undefined;
870
+ }
871
+ // #2356: stop the TTL/idle lifecycle monitor.
872
+ if (this.lifecycleTimer) {
873
+ clearInterval(this.lifecycleTimer);
874
+ this.lifecycleTimer = undefined;
875
+ }
876
+ // #2661 — reap in-flight headless `claude --print` children. They run
877
+ // detached (own process group on POSIX) and would otherwise outlive the
878
+ // daemon; `daemon stop --all` relies on SIGTERM → this path to cancel
879
+ // active Claude process groups.
880
+ if (this.headlessExecutor) {
881
+ try {
882
+ this.headlessExecutor.cancelAll();
883
+ }
884
+ catch { /* best-effort */ }
885
+ }
886
+ // #2661 root-fix — release this worktree's lease and, if held,
887
+ // supervisor status, so a sibling worktree's daemon can take over the
888
+ // schedule within its next tick instead of waiting out the 3-minute
889
+ // supervisor staleness window. Best-effort — a graceful release is an
890
+ // optimization; the staleness timeout is what actually bounds a crash.
891
+ if (this.config.aiWorkersEnabled && this.gitIdentity) {
892
+ const { repositoryId, worktreeRoot } = this.gitIdentity;
893
+ try {
894
+ await getRepoSupervisorRegistry().release(repositoryId, worktreeRoot);
895
+ await getWorkspaceLeaseRegistry().release(repositoryId, worktreeRoot);
896
+ }
897
+ catch { /* best-effort */ }
898
+ }
899
+ this.running = false;
900
+ this.removePidFile();
901
+ this.saveState();
902
+ this.emit('stopped', { stoppedAt: new Date() });
903
+ this.log('info', 'Daemon stopped');
904
+ }
905
+ /**
906
+ * #2356 — Self-terminating lifecycle monitor. A daemon with no upper bound
907
+ * on its lifetime is the documented root cause of multi-day token leaks:
908
+ * each interval worker spawns a headless `claude --print` sweep, so a daemon
909
+ * left running for days dispatches tens of thousands of sessions invisibly.
910
+ * This timer enforces a max age (`ttlMs`) and an optional idle window
911
+ * (`idleShutdownMs`), shutting the daemon down gracefully when either trips.
912
+ * Checked once a minute and `unref()`'d so it never keeps the process alive
913
+ * on its own. A no-op when both limits are disabled (0).
914
+ */
915
+ startLifecycleMonitor() {
916
+ const ttlMs = this.config.ttlMs;
917
+ const idleMs = this.config.idleShutdownMs;
918
+ // #2661 — unlike ttl/idle (both optional), the workspace-removal check
919
+ // always runs, so the monitor is no longer skipped when both limits are
920
+ // disabled. A daemon whose worktree was deleted must not keep running.
921
+ const CHECK_INTERVAL_MS = 60_000;
922
+ this.lifecycleTimer = setInterval(() => {
923
+ if (!this.running)
924
+ return;
925
+ const reason = this.lifecycleShutdownReason(Date.now());
926
+ if (reason) {
927
+ void this.selfShutdown(reason);
928
+ return;
929
+ }
930
+ // #2661 root-fix — renew this worktree's lease + supervisor status on
931
+ // the same cadence. Both windows (15min lease TTL, 3min supervisor
932
+ // staleness) comfortably outlive a single missed 60s tick.
933
+ if (this.config.aiWorkersEnabled) {
934
+ void this.renewLeaseAndSupervisor();
935
+ }
936
+ }, CHECK_INTERVAL_MS);
937
+ if (typeof this.lifecycleTimer.unref === 'function') {
938
+ this.lifecycleTimer.unref();
939
+ }
940
+ const parts = ['workspace-removal'];
941
+ if (ttlMs > 0)
942
+ parts.push(`ttl=${Math.round(ttlMs / 1000)}s`);
943
+ if (idleMs > 0)
944
+ parts.push(`idle=${Math.round(idleMs / 1000)}s`);
945
+ this.log('info', `Lifecycle monitor active (${parts.join(', ')})`);
946
+ }
947
+ /**
948
+ * Decide whether the daemon should self-shutdown, and why. Extracted from
949
+ * the lifecycle timer so it is testable without racing a 60s interval or
950
+ * calling process.exit().
951
+ *
952
+ * #2661 (invariant 6, containment form): a removed worktree makes its
953
+ * daemon ineligible within one check interval — the daemon detects that
954
+ * its workspace directory is gone and shuts down instead of continuing to
955
+ * schedule jobs against a deleted tree. The full lease architecture
956
+ * (supervisor-dispatched jobs, heartbeats) is follow-up work; this stops
957
+ * the leak where recreated/removed worktrees leave schedulers behind.
958
+ */
959
+ lifecycleShutdownReason(now) {
960
+ if (!existsSync(this.projectRoot)) {
961
+ return 'workspace directory removed (#2661)';
962
+ }
963
+ const ttlMs = this.config.ttlMs;
964
+ const idleMs = this.config.idleShutdownMs;
965
+ const startedMs = this.startedAt?.getTime() ?? now;
966
+ if (ttlMs > 0 && now - startedMs >= ttlMs) {
967
+ return `max age ${Math.round(ttlMs / 1000)}s reached`;
968
+ }
969
+ if (idleMs > 0) {
970
+ const lastActivity = this.lastWorkerActivityMs() ?? startedMs;
971
+ if (now - lastActivity >= idleMs) {
972
+ return `idle for ${Math.round(idleMs / 1000)}s (no worker activity)`;
973
+ }
974
+ }
975
+ return null;
976
+ }
977
+ /**
978
+ * Most recent worker start/finish time across all workers (epoch ms), or
979
+ * null if no worker has ever started. Used for idle-shutdown detection.
980
+ */
981
+ lastWorkerActivityMs() {
982
+ let latest = null;
983
+ for (const state of this.workers.values()) {
984
+ for (const t of [state.lastRun, state.lastStartedAt]) {
985
+ if (t) {
986
+ const ms = t.getTime();
987
+ if (latest === null || ms > latest)
988
+ latest = ms;
989
+ }
990
+ }
991
+ }
992
+ return latest;
993
+ }
994
+ /**
995
+ * Graceful self-shutdown triggered by the lifecycle monitor. Mirrors the
996
+ * signal-handler path (`stop()` then `process.exit(0)`) because the
997
+ * foreground keep-alive in the daemon command is a *ref'd* `setInterval`
998
+ * that would otherwise hold the process open after `stop()` clears the
999
+ * service timers — leaving a zombie that reports stopped but never exits.
1000
+ */
1001
+ async selfShutdown(reason) {
1002
+ this.log('info', `Daemon self-shutdown: ${reason}`);
1003
+ this.emit('self-shutdown', { reason });
1004
+ try {
1005
+ await this.stop();
1006
+ }
1007
+ catch { /* best-effort — we are exiting regardless */ }
1008
+ process.exit(0);
1009
+ }
1010
+ /**
1011
+ * #2661 root-fix — heartbeat this worktree's lease and attempt/renew
1012
+ * repository-supervisor election. Best-effort: any failure here must
1013
+ * never affect worker scheduling correctness (the budget ledger remains
1014
+ * the hard invariant regardless of supervisor state) — a daemon that
1015
+ * can't safely coordinate simply falls back to "not supervisor" and runs
1016
+ * its cheap local-only workers, same as any other lease-only worktree.
1017
+ */
1018
+ async renewLeaseAndSupervisor() {
1019
+ if (!this.gitIdentity)
1020
+ return;
1021
+ const { repositoryId, worktreeRoot } = this.gitIdentity;
1022
+ try {
1023
+ await getWorkspaceLeaseRegistry().heartbeat(repositoryId, worktreeRoot);
1024
+ await getRepoSupervisorRegistry().electOrRenew(repositoryId, worktreeRoot);
1025
+ this.lastSupervisorRenewalMs = Date.now();
1026
+ }
1027
+ catch { /* best-effort — see docstring */ }
1028
+ }
1029
+ /**
1030
+ * Cheap read-only check: does THIS daemon currently hold repository
1031
+ * supervisor status? Used to gate recurring headless (`claude --print`)
1032
+ * execution — see repo-supervisor.ts's module doc comment for the full
1033
+ * rationale. Never true when AI workers are disabled or the git identity
1034
+ * couldn't be resolved (e.g. a non-git directory).
1035
+ */
1036
+ isRepositorySupervisor() {
1037
+ if (!this.config.aiWorkersEnabled || !this.gitIdentity)
1038
+ return false;
1039
+ return getRepoSupervisorRegistry().isSupervisor(this.gitIdentity.repositoryId, this.gitIdentity.worktreeRoot);
1040
+ }
1041
+ /**
1042
+ * Get daemon status
1043
+ */
1044
+ getStatus() {
1045
+ let supervisor = null;
1046
+ if (this.config.aiWorkersEnabled && this.gitIdentity) {
1047
+ const { repositoryId, worktreeRoot } = this.gitIdentity;
1048
+ supervisor = {
1049
+ repositoryId,
1050
+ isSupervisor: getRepoSupervisorRegistry().isSupervisor(repositoryId, worktreeRoot),
1051
+ record: getRepoSupervisorRegistry().getRecord(repositoryId),
1052
+ activeLeases: getWorkspaceLeaseRegistry().listActive(repositoryId).length,
1053
+ };
1054
+ }
1055
+ return {
1056
+ running: this.running,
1057
+ pid: process.pid,
1058
+ startedAt: this.startedAt,
1059
+ workers: new Map(this.workers),
1060
+ config: this.config,
1061
+ supervisor,
1062
+ };
1063
+ }
1064
+ /**
1065
+ * Schedule a worker to run at intervals with staggered start
1066
+ */
1067
+ scheduleWorker(workerConfig) {
1068
+ const state = this.workers.get(workerConfig.type);
1069
+ const internalConfig = workerConfig;
1070
+ const staggerOffset = internalConfig.offsetMs || 0;
1071
+ // Calculate initial delay with stagger offset
1072
+ let initialDelay = staggerOffset;
1073
+ if (state.lastRun) {
1074
+ const timeSinceLastRun = Date.now() - state.lastRun.getTime();
1075
+ initialDelay = Math.max(staggerOffset, workerConfig.intervalMs - timeSinceLastRun);
1076
+ }
1077
+ state.nextRun = new Date(Date.now() + initialDelay);
1078
+ const runAndReschedule = async () => {
1079
+ if (!this.running)
1080
+ return;
1081
+ // Use concurrency-controlled execution (P0 fix)
1082
+ await this.executeWorkerWithConcurrencyControl(workerConfig);
1083
+ // Reschedule
1084
+ if (this.running) {
1085
+ const timer = setTimeout(runAndReschedule, workerConfig.intervalMs);
1086
+ this.timers.set(workerConfig.type, timer);
1087
+ state.nextRun = new Date(Date.now() + workerConfig.intervalMs);
1088
+ }
1089
+ };
1090
+ // Schedule first run with stagger offset
1091
+ const timer = setTimeout(runAndReschedule, initialDelay);
1092
+ this.timers.set(workerConfig.type, timer);
1093
+ this.log('info', `Scheduled ${workerConfig.type} (interval: ${workerConfig.intervalMs / 1000}s, first run in ${initialDelay / 1000}s)`);
1094
+ }
1095
+ /**
1096
+ * Execute a worker with concurrency control (P0 fix)
1097
+ */
1098
+ async executeWorkerWithConcurrencyControl(workerConfig) {
1099
+ // Check concurrency limit
1100
+ if (this.runningWorkers.size >= this.config.maxConcurrent) {
1101
+ this.log('info', `Worker ${workerConfig.type} deferred: max concurrent (${this.config.maxConcurrent}) reached`);
1102
+ this.pendingWorkers.push(workerConfig.type);
1103
+ this.emit('worker:deferred', { type: workerConfig.type, reason: 'max_concurrent' });
1104
+ return null;
1105
+ }
1106
+ // Check resource availability
1107
+ const resourceCheck = await this.canRunWorker();
1108
+ if (!resourceCheck.allowed) {
1109
+ this.log('info', `Worker ${workerConfig.type} deferred: ${resourceCheck.reason}`);
1110
+ this.pendingWorkers.push(workerConfig.type);
1111
+ this.emit('worker:deferred', { type: workerConfig.type, reason: resourceCheck.reason });
1112
+ return null;
1113
+ }
1114
+ return this.executeWorker(workerConfig);
1115
+ }
1116
+ /**
1117
+ * Execute a worker with timeout protection
1118
+ */
1119
+ async executeWorker(workerConfig, opts) {
1120
+ const state = this.workers.get(workerConfig.type);
1121
+ const workerId = `${workerConfig.type}_${Date.now()}`;
1122
+ const startTime = Date.now();
1123
+ // Track running worker
1124
+ this.runningWorkers.add(workerConfig.type);
1125
+ state.isRunning = true;
1126
+ state.lastStartedAt = new Date(); // #1856: timestamp the start
1127
+ this.saveState(); // persist before we run anything
1128
+ this.emit('worker:start', { workerId, type: workerConfig.type });
1129
+ this.log('info', `Starting worker: ${workerConfig.type} (${this.runningWorkers.size}/${this.config.maxConcurrent} concurrent)`);
1130
+ try {
1131
+ // Execute worker logic with timeout (P1 fix)
1132
+ // Pass cleanup callback to kill orphan child processes on timeout (#1117)
1133
+ const output = await this.runWithTimeout(() => this.runWorkerLogic(workerConfig, opts), this.config.workerTimeoutMs, `Worker ${workerConfig.type} timed out after ${this.config.workerTimeoutMs / 1000}s`, () => {
1134
+ // On timeout, cancel any headless execution to prevent orphan processes
1135
+ if (this.headlessExecutor) {
1136
+ this.headlessExecutor.cancelAll();
1137
+ }
1138
+ });
1139
+ const durationMs = Date.now() - startTime;
1140
+ // Update state
1141
+ state.runCount++;
1142
+ state.successCount++;
1143
+ state.lastRun = new Date();
1144
+ state.averageDurationMs = (state.averageDurationMs * (state.runCount - 1) + durationMs) / state.runCount;
1145
+ state.isRunning = false;
1146
+ const result = {
1147
+ workerId,
1148
+ type: workerConfig.type,
1149
+ success: true,
1150
+ durationMs,
1151
+ output,
1152
+ timestamp: new Date(),
1153
+ };
1154
+ this.emit('worker:complete', result);
1155
+ this.log('info', `Worker ${workerConfig.type} completed in ${durationMs}ms`);
1156
+ this.saveState();
1157
+ return result;
1158
+ }
1159
+ catch (error) {
1160
+ const durationMs = Date.now() - startTime;
1161
+ state.runCount++;
1162
+ state.failureCount++;
1163
+ state.lastRun = new Date();
1164
+ state.isRunning = false;
1165
+ const result = {
1166
+ workerId,
1167
+ type: workerConfig.type,
1168
+ success: false,
1169
+ durationMs,
1170
+ error: error instanceof Error ? error.message : String(error),
1171
+ timestamp: new Date(),
1172
+ };
1173
+ this.emit('worker:error', result);
1174
+ this.log('error', `Worker ${workerConfig.type} failed: ${result.error}`);
1175
+ this.saveState();
1176
+ return result;
1177
+ }
1178
+ finally {
1179
+ // Remove from running set and process queue
1180
+ this.runningWorkers.delete(workerConfig.type);
1181
+ this.processPendingWorkers();
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Run a function with timeout (P1 fix)
1186
+ * @param fn - The async function to execute
1187
+ * @param timeoutMs - Timeout in milliseconds
1188
+ * @param timeoutMessage - Error message on timeout
1189
+ * @param onTimeout - Optional cleanup callback invoked when timeout fires (#1117: kills orphan processes)
1190
+ */
1191
+ async runWithTimeout(fn, timeoutMs, timeoutMessage, onTimeout) {
1192
+ return new Promise((resolve, reject) => {
1193
+ let settled = false;
1194
+ const timer = setTimeout(() => {
1195
+ if (settled)
1196
+ return;
1197
+ settled = true;
1198
+ // Kill orphan child processes before rejecting (#1117)
1199
+ if (onTimeout) {
1200
+ try {
1201
+ onTimeout();
1202
+ }
1203
+ catch {
1204
+ // Ignore cleanup errors
1205
+ }
1206
+ }
1207
+ reject(new Error(timeoutMessage));
1208
+ }, timeoutMs);
1209
+ fn()
1210
+ .then((result) => {
1211
+ if (settled)
1212
+ return;
1213
+ settled = true;
1214
+ clearTimeout(timer);
1215
+ resolve(result);
1216
+ })
1217
+ .catch((error) => {
1218
+ if (settled)
1219
+ return;
1220
+ settled = true;
1221
+ clearTimeout(timer);
1222
+ reject(error);
1223
+ });
1224
+ });
1225
+ }
1226
+ /**
1227
+ * Run the actual worker logic
1228
+ */
1229
+ async runWorkerLogic(workerConfig, opts) {
1230
+ // Check if this is a headless worker type and headless execution is available.
1231
+ // #2661 — aiWorkersEnabled is re-checked here (not just at init) as
1232
+ // defence in depth: no code path may promote a worker to `claude --print`
1233
+ // without explicit consent.
1234
+ //
1235
+ // #2661 root-fix — the RECURRING schedule additionally requires this
1236
+ // daemon to be the elected repository supervisor (see repo-supervisor.ts):
1237
+ // ten worktree daemons of one repository must not each independently
1238
+ // decide "is it time to run audit" every tick. An explicit
1239
+ // `daemon trigger --headless` (opts.manualTrigger) is still honored
1240
+ // regardless of supervisor status — it's a one-off, user-initiated
1241
+ // action, still budget/dedup-gated the same as any other launch.
1242
+ const supervisorGateOk = opts?.manualTrigger === true || this.isRepositorySupervisor();
1243
+ if (this.config.aiWorkersEnabled && supervisorGateOk && isHeadlessWorker(workerConfig.type) && this.headlessAvailable && this.headlessExecutor) {
1244
+ try {
1245
+ this.log('info', `Running ${workerConfig.type} in headless mode (Claude Code AI)`);
1246
+ const result = await this.headlessExecutor.execute(workerConfig.type);
1247
+ // #2110 — `HeadlessWorkerExecutor.execute()` returns
1248
+ // `createErrorResult(...)` with `success: false` when
1249
+ // `isAvailable()` is false, instead of throwing. The previous
1250
+ // try/catch never fired in that path, and the result was
1251
+ // persisted as mode:"headless" despite being a stub. Downstream
1252
+ // dashboards / `memory stats` couldn't distinguish a real AI
1253
+ // run from a fallback. Treat falsy success the same as throw.
1254
+ const ok = result?.success === true;
1255
+ if (!ok) {
1256
+ const reason = result?.error ||
1257
+ result?.note ||
1258
+ 'headless executor reported success=false';
1259
+ this.log('warn', `Headless ${workerConfig.type} returned success=false (${String(reason).slice(0, 200)}); falling back to local mode`);
1260
+ this.emit('headless:fallback', {
1261
+ type: workerConfig.type,
1262
+ error: String(reason).slice(0, 500),
1263
+ });
1264
+ // Fall through to local switch.
1265
+ }
1266
+ else if (result.dedupSkipped) {
1267
+ // #2661 invariant 5 — the same job (repositoryId + HEAD + worker +
1268
+ // config) already succeeded within the freshness window, e.g. in a
1269
+ // sibling worktree. No model call happened; do NOT overwrite the
1270
+ // persisted metrics (which hold the real prior result) and do NOT
1271
+ // fall back to local — the work is already done.
1272
+ this.log('info', `Worker ${workerConfig.type} dedup-skipped (same repo+HEAD job ran recently in another worktree)`);
1273
+ return {
1274
+ mode: 'headless-dedup-skip',
1275
+ ...result,
1276
+ };
1277
+ }
1278
+ else {
1279
+ // #1793: persist the headless result to the same metrics files the
1280
+ // local workers write to. Without this, AI-mode runs produced rich
1281
+ // parsedOutput that lived only in `.claude-flow/logs/headless/*` and
1282
+ // never reached `.claude-flow/metrics/<name>.json` — `memory stats`
1283
+ // and downstream consumers saw nothing despite successful runs.
1284
+ try {
1285
+ this.persistHeadlessResult(workerConfig.type, result);
1286
+ }
1287
+ catch (persistError) {
1288
+ this.log('warn', `Failed to persist headless result for ${workerConfig.type}: ${persistError.message}`);
1289
+ }
1290
+ return {
1291
+ mode: 'headless',
1292
+ ...result,
1293
+ };
1294
+ }
1295
+ }
1296
+ catch (error) {
1297
+ this.log('warn', `Headless execution failed for ${workerConfig.type}, falling back to local mode`);
1298
+ this.emit('headless:fallback', {
1299
+ type: workerConfig.type,
1300
+ error: error instanceof Error ? error.message : String(error),
1301
+ });
1302
+ // Fall through to local execution
1303
+ }
1304
+ }
1305
+ // Local execution (fallback or for non-headless workers)
1306
+ switch (workerConfig.type) {
1307
+ case 'map':
1308
+ return this.runMapWorker();
1309
+ case 'audit':
1310
+ return this.runAuditWorkerLocal();
1311
+ case 'optimize':
1312
+ return this.runOptimizeWorkerLocal();
1313
+ case 'consolidate':
1314
+ return this.runConsolidateWorker();
1315
+ case 'backup':
1316
+ return this.runBackupWorker();
1317
+ case 'harness':
1318
+ return this.runHarnessWorker();
1319
+ case 'testgaps':
1320
+ return this.runTestGapsWorkerLocal();
1321
+ case 'predict':
1322
+ return this.runPredictWorkerLocal();
1323
+ case 'document':
1324
+ return this.runDocumentWorkerLocal();
1325
+ case 'ultralearn':
1326
+ return this.runUltralearnWorkerLocal();
1327
+ case 'refactor':
1328
+ return this.runRefactorWorkerLocal();
1329
+ case 'deepdive':
1330
+ return this.runDeepdiveWorkerLocal();
1331
+ case 'benchmark':
1332
+ return this.runBenchmarkWorkerLocal();
1333
+ case 'preload':
1334
+ return this.runPreloadWorkerLocal();
1335
+ default:
1336
+ return { status: 'unknown worker type', mode: 'local' };
1337
+ }
1338
+ }
1339
+ /**
1340
+ * #1793: persist a headless worker result to the same metrics file the
1341
+ * local fallback writes to. Without this, AI-mode workers produced rich
1342
+ * structured output (audit findings, perf signals, test-gap analysis)
1343
+ * that lived only in `.claude-flow/logs/headless/*_result.log` and was
1344
+ * invisible to `npx ruflo memory stats` or the metrics consumers.
1345
+ *
1346
+ * The mapping mirrors the `*Local` worker implementations below so a
1347
+ * single consumer path works regardless of execution mode.
1348
+ */
1349
+ persistHeadlessResult(workerType, result) {
1350
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1351
+ if (!existsSync(metricsDir))
1352
+ mkdirSync(metricsDir, { recursive: true });
1353
+ // Filename mirrors the local-mode worker writes (security-audit.json,
1354
+ // performance.json, test-gaps.json) so a downstream reader doesn't
1355
+ // care which mode produced the data.
1356
+ const filenameMap = {
1357
+ audit: 'security-audit.json',
1358
+ optimize: 'performance.json',
1359
+ testgaps: 'test-gaps.json',
1360
+ document: 'documentation.json',
1361
+ refactor: 'refactor.json',
1362
+ deepdive: 'deepdive.json',
1363
+ ultralearn: 'ultralearn.json',
1364
+ predict: 'predictions.json',
1365
+ };
1366
+ const filename = filenameMap[workerType] ?? `${workerType}.json`;
1367
+ const metricsFile = join(metricsDir, filename);
1368
+ const persisted = {
1369
+ timestamp: result.timestamp instanceof Date ? result.timestamp.toISOString() : new Date().toISOString(),
1370
+ mode: 'headless',
1371
+ workerType,
1372
+ model: result.model,
1373
+ durationMs: result.durationMs,
1374
+ tokensUsed: result.tokensUsed,
1375
+ executionId: result.executionId,
1376
+ success: result.success,
1377
+ // Structured findings live here when the worker emits JSON (e.g. the
1378
+ // audit worker's vulnerability list). Fall back to a raw-output
1379
+ // pointer so consumers can still locate the full log.
1380
+ findings: result.parsedOutput ?? null,
1381
+ rawOutputPreview: typeof result.output === 'string' ? result.output.slice(0, 2000) : undefined,
1382
+ rawOutputLength: typeof result.output === 'string' ? result.output.length : 0,
1383
+ };
1384
+ writeFileSync(metricsFile, JSON.stringify(persisted, null, 2));
1385
+ }
1386
+ // Worker implementations
1387
+ async runMapWorker() {
1388
+ // Scan project structure and update metrics
1389
+ const metricsFile = join(this.projectRoot, '.claude-flow', 'metrics', 'codebase-map.json');
1390
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1391
+ if (!existsSync(metricsDir)) {
1392
+ mkdirSync(metricsDir, { recursive: true });
1393
+ }
1394
+ const map = {
1395
+ timestamp: new Date().toISOString(),
1396
+ projectRoot: this.projectRoot,
1397
+ structure: {
1398
+ hasPackageJson: existsSync(join(this.projectRoot, 'package.json')),
1399
+ hasTsConfig: existsSync(join(this.projectRoot, 'tsconfig.json')),
1400
+ hasClaudeConfig: existsSync(join(this.projectRoot, '.claude')),
1401
+ hasClaudeFlow: existsSync(join(this.projectRoot, '.claude-flow')),
1402
+ },
1403
+ scannedAt: Date.now(),
1404
+ };
1405
+ writeFileSync(metricsFile, JSON.stringify(map, null, 2));
1406
+ return map;
1407
+ }
1408
+ /**
1409
+ * Local audit worker (fallback when headless unavailable)
1410
+ */
1411
+ async runAuditWorkerLocal() {
1412
+ // Basic security checks
1413
+ const auditFile = join(this.projectRoot, '.claude-flow', 'metrics', 'security-audit.json');
1414
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1415
+ if (!existsSync(metricsDir)) {
1416
+ mkdirSync(metricsDir, { recursive: true });
1417
+ }
1418
+ const audit = {
1419
+ timestamp: new Date().toISOString(),
1420
+ mode: 'local',
1421
+ checks: {
1422
+ envFilesProtected: !existsSync(join(this.projectRoot, '.env.local')),
1423
+ gitIgnoreExists: existsSync(join(this.projectRoot, '.gitignore')),
1424
+ noHardcodedSecrets: true, // Would need actual scanning
1425
+ },
1426
+ riskLevel: 'low',
1427
+ recommendations: [],
1428
+ note: 'Install Claude Code CLI for AI-powered security analysis',
1429
+ };
1430
+ writeFileSync(auditFile, JSON.stringify(audit, null, 2));
1431
+ return audit;
1432
+ }
1433
+ /**
1434
+ * Local optimize worker (fallback when headless unavailable)
1435
+ */
1436
+ async runOptimizeWorkerLocal() {
1437
+ // Update performance metrics
1438
+ const optimizeFile = join(this.projectRoot, '.claude-flow', 'metrics', 'performance.json');
1439
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1440
+ if (!existsSync(metricsDir)) {
1441
+ mkdirSync(metricsDir, { recursive: true });
1442
+ }
1443
+ const perf = {
1444
+ timestamp: new Date().toISOString(),
1445
+ mode: 'local',
1446
+ memoryUsage: process.memoryUsage(),
1447
+ uptime: process.uptime(),
1448
+ optimizations: {
1449
+ cacheHitRate: 0.78,
1450
+ avgResponseTime: 45,
1451
+ },
1452
+ note: 'Install Claude Code CLI for AI-powered optimization suggestions',
1453
+ };
1454
+ writeFileSync(optimizeFile, JSON.stringify(perf, null, 2));
1455
+ return perf;
1456
+ }
1457
+ /**
1458
+ * ADR-174 M3: memory consolidation — runs the real DISTILL/CONSOLIDATE pass
1459
+ * (memory-distillation.ts) against `.swarm/memory.db`, turning raw
1460
+ * `memory_entries` into `episodes` / `reasoning_patterns` /
1461
+ * `pattern_embeddings` / `causal_edges`. Previously this wrote a hardcoded
1462
+ * `{ patternsConsolidated: 0 }` stub and touched no database — the root
1463
+ * cause of the intelligence tables staying empty.
1464
+ *
1465
+ * Kept as `runConsolidateWorker` / worker type `'consolidate'` for
1466
+ * back-compat with existing `-w consolidate` scripts and docs.
1467
+ *
1468
+ * Safety:
1469
+ * - Bounded via CONSOLIDATE_MAX_ENTRIES_PER_TICK so a large backlog can
1470
+ * never approach DEFAULT_WORKER_TIMEOUT_MS — the incremental cursor in
1471
+ * runDistillation() drains a bounded slice per tick and picks up where
1472
+ * it left off on the next scheduled run.
1473
+ * - runDistillation() never throws (it catches internally and returns
1474
+ * `{ skipped }` / `{ corrupt: true }`), but this worker still wraps the
1475
+ * call defensively — a background worker must never crash the daemon.
1476
+ */
1477
+ async runConsolidateWorker() {
1478
+ const consolidateFile = join(this.projectRoot, '.claude-flow', 'metrics', 'consolidation.json');
1479
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1480
+ if (!existsSync(metricsDir)) {
1481
+ mkdirSync(metricsDir, { recursive: true });
1482
+ }
1483
+ // Opt-out: RUFLO_DAEMON_NO_DISTILL=1 (or `daemon start --no-distill`)
1484
+ // skips the real distillation pass entirely without touching persisted
1485
+ // worker-enabled state (see also: `daemon enable -w consolidate --disable`,
1486
+ // which disables the worker's schedule altogether).
1487
+ if (process.env[NO_DISTILL_ENV] === '1') {
1488
+ const disabledResult = {
1489
+ timestamp: new Date().toISOString(),
1490
+ distillationEnabled: false,
1491
+ note: `Distillation disabled via ${NO_DISTILL_ENV}=1 / --no-distill`,
1492
+ patternsConsolidated: 0,
1493
+ memoryCleaned: 0,
1494
+ duplicatesRemoved: 0,
1495
+ };
1496
+ writeFileSync(consolidateFile, JSON.stringify(disabledResult, null, 2));
1497
+ return disabledResult;
1498
+ }
1499
+ let report;
1500
+ try {
1501
+ report = await runDistillation({
1502
+ dbPath: defaultMemoryDbPath(this.projectRoot),
1503
+ maxEntries: CONSOLIDATE_MAX_ENTRIES_PER_TICK,
1504
+ batchSize: CONSOLIDATE_BATCH_SIZE,
1505
+ dedupDistance: CONSOLIDATE_DEDUP_DISTANCE,
1506
+ });
1507
+ }
1508
+ catch (error) {
1509
+ // Defensive only — runDistillation() is internally try/catch'd and
1510
+ // should never reach here. A worker must never crash the daemon.
1511
+ const message = error instanceof Error ? error.message : String(error);
1512
+ this.log('warn', `Consolidate worker: distillation threw unexpectedly: ${message}`);
1513
+ const errorResult = {
1514
+ timestamp: new Date().toISOString(),
1515
+ distillationEnabled: true,
1516
+ error: message,
1517
+ patternsConsolidated: 0,
1518
+ memoryCleaned: 0,
1519
+ duplicatesRemoved: 0,
1520
+ };
1521
+ writeFileSync(consolidateFile, JSON.stringify(errorResult, null, 2));
1522
+ return errorResult;
1523
+ }
1524
+ if (report.corrupt) {
1525
+ this.log('warn', `Consolidate worker: memory DB reports corruption — ${report.skipped ?? 'skipped'}`);
1526
+ }
1527
+ else if (report.skipped) {
1528
+ this.log('info', `Consolidate worker: distillation skipped (${report.skipped})`);
1529
+ }
1530
+ const result = {
1531
+ timestamp: new Date().toISOString(),
1532
+ distillationEnabled: true,
1533
+ // Mapping onto the pre-existing metrics shape (ADR-174 M3):
1534
+ patternsConsolidated: report.patterns,
1535
+ // rows drained from the incremental cursor this tick — distillation is
1536
+ // non-destructive (never mutates/deletes memory_entries), so "cleaned"
1537
+ // here means "processed into the intelligence tables", not removed.
1538
+ memoryCleaned: report.processed,
1539
+ // clustering collapses near-duplicate entries into a single pattern
1540
+ // rather than deleting them; this is the count that got merged away
1541
+ // instead of becoming their own distinct pattern.
1542
+ duplicatesRemoved: Math.max(0, report.processed - report.patterns),
1543
+ episodes: report.episodes,
1544
+ patternEmbeddings: report.patternEmbeddings,
1545
+ causalEdges: report.causalEdges,
1546
+ promoted: report.promoted,
1547
+ byProvenance: report.byProvenance,
1548
+ namespaces: report.namespaces,
1549
+ dryRun: report.dryRun,
1550
+ corrupt: report.corrupt ?? false,
1551
+ skipped: report.skipped,
1552
+ };
1553
+ writeFileSync(consolidateFile, JSON.stringify(result, null, 2));
1554
+ return result;
1555
+ }
1556
+ /**
1557
+ * Nightly memory-DB backup worker (24h interval). Takes a WAL-safe, consistent
1558
+ * snapshot of .swarm/memory.db with rotation (keep last N). Never throws — a
1559
+ * worker must not crash the daemon; a skip/error is written to the metrics
1560
+ * file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
1561
+ * RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
1562
+ */
1563
+ async runBackupWorker() {
1564
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1565
+ if (!existsSync(metricsDir))
1566
+ mkdirSync(metricsDir, { recursive: true });
1567
+ const backupFile = join(metricsDir, 'backup.json');
1568
+ let result;
1569
+ try {
1570
+ const keepEnv = Number(process.env.RUFLO_BACKUP_KEEP);
1571
+ const r = await backupMemoryDb({
1572
+ dbPath: defaultMemoryDbPath(this.projectRoot),
1573
+ keep: Number.isFinite(keepEnv) && keepEnv > 0 ? keepEnv : 7,
1574
+ gcs: process.env.RUFLO_BACKUP_GCS || undefined,
1575
+ });
1576
+ result = {
1577
+ timestamp: new Date().toISOString(),
1578
+ backedUp: r.backedUp,
1579
+ path: r.path,
1580
+ sizeBytes: r.sizeBytes ?? 0,
1581
+ rotatedAway: r.rotatedAway?.length ?? 0,
1582
+ gcsUri: r.gcsUri,
1583
+ skipped: r.skipped,
1584
+ };
1585
+ }
1586
+ catch (error) {
1587
+ const message = error instanceof Error ? error.message : String(error);
1588
+ this.log('warn', `Backup worker failed: ${message}`);
1589
+ result = { timestamp: new Date().toISOString(), backedUp: false, error: message };
1590
+ }
1591
+ writeFileSync(backupFile, JSON.stringify(result, null, 2));
1592
+ return result;
1593
+ }
1594
+ /**
1595
+ * Self-optimizing harness loop worker (ADR-176 phase 8). Strictly bounded:
1596
+ * OPT-IN (RUFLO_HARNESS_LOOP), $0-default (no optimizer/verifier wired => no
1597
+ * promotion), trajectory-capped, single-flight via the daemon, never throws.
1598
+ * On acceptance the (unsigned) champion is staged for the separate sign step.
1599
+ */
1600
+ async runHarnessWorker() {
1601
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1602
+ if (!existsSync(metricsDir))
1603
+ mkdirSync(metricsDir, { recursive: true });
1604
+ const harnessFile = join(metricsDir, 'harness-loop.json');
1605
+ let result;
1606
+ try {
1607
+ // ADR-176 flywheel (A-P3b autonomy loop): run ONE COMPOUNDING generation
1608
+ // on the install's REAL data — read the persisted champion as baseline,
1609
+ // gate a constrained candidate on the frozen self-supervised held-out with
1610
+ // the human-anchor guard + separate canary, and on a verified promotion
1611
+ // advance the champion so the NEXT tick compounds. Shadow-first (serve lags
1612
+ // one tick). Opt-in ($0 no-op without RUFLO_HARNESS_LOOP).
1613
+ const { runFlywheelGenerationWorker } = await import('./harness-flywheel-runtime.js');
1614
+ const gen = await runFlywheelGenerationWorker(this.projectRoot, { sample: 120 });
1615
+ let status = null;
1616
+ try {
1617
+ const { flywheelStatus } = await import('./harness-flywheel-generations.js');
1618
+ status = flywheelStatus(this.projectRoot);
1619
+ }
1620
+ catch { /* no lineage yet */ }
1621
+ if (gen.promoted)
1622
+ this.log('info', `Flywheel gen ${gen.generation}: PROMOTED (+${(gen.delta ?? 0).toFixed(4)} held-out, significant=${gen.significant}) — champion advanced`);
1623
+ result = { timestamp: new Date().toISOString(), flywheel: gen, lineage: status };
1624
+ }
1625
+ catch (error) {
1626
+ const message = error instanceof Error ? error.message : String(error);
1627
+ this.log('warn', `Harness worker failed: ${message}`);
1628
+ result = { timestamp: new Date().toISOString(), ran: false, error: message };
1629
+ }
1630
+ writeFileSync(harnessFile, JSON.stringify(result, null, 2));
1631
+ return result;
1632
+ }
1633
+ /**
1634
+ * Local testgaps worker (fallback when headless unavailable)
1635
+ */
1636
+ async runTestGapsWorkerLocal() {
1637
+ // Check for test coverage gaps
1638
+ const testGapsFile = join(this.projectRoot, '.claude-flow', 'metrics', 'test-gaps.json');
1639
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1640
+ if (!existsSync(metricsDir)) {
1641
+ mkdirSync(metricsDir, { recursive: true });
1642
+ }
1643
+ const result = {
1644
+ timestamp: new Date().toISOString(),
1645
+ mode: 'local',
1646
+ hasTestDir: existsSync(join(this.projectRoot, 'tests')) || existsSync(join(this.projectRoot, '__tests__')),
1647
+ estimatedCoverage: 'unknown',
1648
+ gaps: [],
1649
+ note: 'Install Claude Code CLI for AI-powered test gap analysis',
1650
+ };
1651
+ writeFileSync(testGapsFile, JSON.stringify(result, null, 2));
1652
+ return result;
1653
+ }
1654
+ /**
1655
+ * Local predict worker (fallback when headless unavailable)
1656
+ */
1657
+ async runPredictWorkerLocal() {
1658
+ return {
1659
+ timestamp: new Date().toISOString(),
1660
+ mode: 'local',
1661
+ predictions: [],
1662
+ preloaded: [],
1663
+ note: 'Install Claude Code CLI for AI-powered predictions',
1664
+ };
1665
+ }
1666
+ /**
1667
+ * Local document worker (fallback when headless unavailable)
1668
+ */
1669
+ async runDocumentWorkerLocal() {
1670
+ return {
1671
+ timestamp: new Date().toISOString(),
1672
+ mode: 'local',
1673
+ filesDocumented: 0,
1674
+ suggestedDocs: [],
1675
+ note: 'Install Claude Code CLI for AI-powered documentation generation',
1676
+ };
1677
+ }
1678
+ /**
1679
+ * Local ultralearn worker (fallback when headless unavailable)
1680
+ */
1681
+ async runUltralearnWorkerLocal() {
1682
+ return {
1683
+ timestamp: new Date().toISOString(),
1684
+ mode: 'local',
1685
+ patternsLearned: 0,
1686
+ insightsGained: [],
1687
+ note: 'Install Claude Code CLI for AI-powered deep learning',
1688
+ };
1689
+ }
1690
+ /**
1691
+ * Local refactor worker (fallback when headless unavailable)
1692
+ */
1693
+ async runRefactorWorkerLocal() {
1694
+ return {
1695
+ timestamp: new Date().toISOString(),
1696
+ mode: 'local',
1697
+ suggestions: [],
1698
+ duplicatesFound: 0,
1699
+ note: 'Install Claude Code CLI for AI-powered refactoring suggestions',
1700
+ };
1701
+ }
1702
+ /**
1703
+ * Local deepdive worker (fallback when headless unavailable)
1704
+ */
1705
+ async runDeepdiveWorkerLocal() {
1706
+ return {
1707
+ timestamp: new Date().toISOString(),
1708
+ mode: 'local',
1709
+ analysisDepth: 'shallow',
1710
+ findings: [],
1711
+ note: 'Install Claude Code CLI for AI-powered deep code analysis',
1712
+ };
1713
+ }
1714
+ /**
1715
+ * Local benchmark worker
1716
+ */
1717
+ async runBenchmarkWorkerLocal() {
1718
+ const benchmarkFile = join(this.projectRoot, '.claude-flow', 'metrics', 'benchmark.json');
1719
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1720
+ if (!existsSync(metricsDir)) {
1721
+ mkdirSync(metricsDir, { recursive: true });
1722
+ }
1723
+ const result = {
1724
+ timestamp: new Date().toISOString(),
1725
+ mode: 'local',
1726
+ benchmarks: {
1727
+ memoryUsage: process.memoryUsage(),
1728
+ cpuUsage: process.cpuUsage(),
1729
+ uptime: process.uptime(),
1730
+ },
1731
+ };
1732
+ writeFileSync(benchmarkFile, JSON.stringify(result, null, 2));
1733
+ return result;
1734
+ }
1735
+ /**
1736
+ * Local preload worker
1737
+ */
1738
+ async runPreloadWorkerLocal() {
1739
+ return {
1740
+ timestamp: new Date().toISOString(),
1741
+ mode: 'local',
1742
+ resourcesPreloaded: 0,
1743
+ cacheStatus: 'active',
1744
+ };
1745
+ }
1746
+ /**
1747
+ * Manually trigger a worker
1748
+ */
1749
+ async triggerWorker(type) {
1750
+ const workerConfig = this.config.workers.find(w => w.type === type);
1751
+ if (!workerConfig) {
1752
+ throw new Error(`Unknown worker type: ${type}`);
1753
+ }
1754
+ // #2251 — wait for headless probe to settle before running. Without
1755
+ // this, on-demand `daemon trigger -w <worker>` races the constructor's
1756
+ // fire-and-forget init and ALWAYS falls through to local mode even
1757
+ // when `claude` is on PATH and scheduled fires of the same worker
1758
+ // use headless correctly. Scheduled fires already wait long enough
1759
+ // (timer + offset) that this is a no-op for them.
1760
+ await this.headlessInitPromise;
1761
+ // #2661 root-fix — an explicit manual trigger bypasses the repository-
1762
+ // supervisor gate (still budget/dedup-gated) — see runWorkerLogic()'s
1763
+ // doc comment.
1764
+ return this.executeWorker(workerConfig, { manualTrigger: true });
1765
+ }
1766
+ /**
1767
+ * Enable/disable a worker
1768
+ */
1769
+ setWorkerEnabled(type, enabled) {
1770
+ const workerConfig = this.config.workers.find(w => w.type === type);
1771
+ if (workerConfig) {
1772
+ workerConfig.enabled = enabled;
1773
+ if (enabled && this.running) {
1774
+ this.scheduleWorker(workerConfig);
1775
+ }
1776
+ else if (!enabled) {
1777
+ const timer = this.timers.get(type);
1778
+ if (timer) {
1779
+ clearTimeout(timer);
1780
+ this.timers.delete(type);
1781
+ }
1782
+ }
1783
+ this.saveState();
1784
+ }
1785
+ }
1786
+ /**
1787
+ * Save daemon state to file
1788
+ */
1789
+ saveState() {
1790
+ const state = {
1791
+ running: this.running,
1792
+ startedAt: this.startedAt?.toISOString(),
1793
+ workers: Object.fromEntries(Array.from(this.workers.entries()).map(([type, state]) => [
1794
+ type,
1795
+ {
1796
+ ...state,
1797
+ lastRun: state.lastRun?.toISOString(),
1798
+ lastStartedAt: state.lastStartedAt?.toISOString(),
1799
+ nextRun: state.nextRun?.toISOString(),
1800
+ }
1801
+ ])),
1802
+ config: {
1803
+ ...this.config,
1804
+ workers: this.config.workers.map(w => ({ ...w })),
1805
+ },
1806
+ savedAt: new Date().toISOString(),
1807
+ };
1808
+ try {
1809
+ const tmpFile = this.config.stateFile + '.tmp';
1810
+ writeFileSync(tmpFile, JSON.stringify(state, null, 2));
1811
+ renameSync(tmpFile, this.config.stateFile);
1812
+ }
1813
+ catch (error) {
1814
+ this.log('error', `Failed to save state: ${error}`);
1815
+ }
1816
+ }
1817
+ /**
1818
+ * Log message
1819
+ */
1820
+ log(level, message) {
1821
+ const timestamp = new Date().toISOString();
1822
+ const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
1823
+ this.emit('log', { level, message, timestamp });
1824
+ // Also write to log file
1825
+ try {
1826
+ const logFile = join(this.config.logDir, 'daemon.log');
1827
+ appendFileSync(logFile, logMessage + '\n');
1828
+ }
1829
+ catch {
1830
+ // Ignore log write errors
1831
+ }
1832
+ }
1833
+ }
1834
+ // Singleton instance for global access
1835
+ let daemonInstance = null;
1836
+ /**
1837
+ * Get or create daemon instance
1838
+ */
1839
+ export function getDaemon(projectRoot, config) {
1840
+ if (!daemonInstance && projectRoot) {
1841
+ daemonInstance = new WorkerDaemon(projectRoot, config);
1842
+ }
1843
+ if (!daemonInstance) {
1844
+ throw new Error('Daemon not initialized. Provide projectRoot on first call.');
1845
+ }
1846
+ return daemonInstance;
1847
+ }
1848
+ /**
1849
+ * Start daemon (for use in session-start hook)
1850
+ */
1851
+ export async function startDaemon(projectRoot, config) {
1852
+ const daemon = getDaemon(projectRoot, config);
1853
+ await daemon.start();
1854
+ return daemon;
1855
+ }
1856
+ /**
1857
+ * Stop daemon
1858
+ */
1859
+ export async function stopDaemon() {
1860
+ if (daemonInstance) {
1861
+ await daemonInstance.stop();
1862
+ }
1863
+ }
1864
+ export default WorkerDaemon;
1865
+ //# sourceMappingURL=worker-daemon.js.map