@claude-flow/cli 3.32.3 → 3.32.5

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 +20 -0
  172. package/dist/src/commands/proxy-lifecycle.js +277 -0
  173. package/dist/src/commands/proxy.d.ts +21 -0
  174. package/dist/src/commands/proxy.js +403 -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,1902 @@
1
+ /**
2
+ * V3 CLI Doctor Command
3
+ * System diagnostics, dependency checks, config validation
4
+ *
5
+ * Created with ruv.io
6
+ */
7
+ import { output } from '../output.js';
8
+ import { existsSync, readFileSync, statSync } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { createHash } from 'crypto';
12
+ import { execSync, exec } from 'child_process';
13
+ import { promisify } from 'util';
14
+ import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
15
+ import { isEncryptedBlob } from '../encryption/vault.js';
16
+ import { resolveMemoryPackageFromProject, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
17
+ // Promisified exec with proper shell and env inheritance for cross-platform support
18
+ const execAsync = promisify(exec);
19
+ /**
20
+ * Execute command asynchronously with proper environment inheritance
21
+ * Critical for Windows where PATH may not be inherited properly
22
+ */
23
+ async function runCommand(command, timeoutMs = 5000) {
24
+ const { stdout } = await execAsync(command, {
25
+ encoding: 'utf8',
26
+ timeout: timeoutMs,
27
+ shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh', // Use proper shell per platform
28
+ env: { ...process.env }, // Explicitly inherit full environment
29
+ windowsHide: true, // Hide window on Windows
30
+ });
31
+ return stdout.trim();
32
+ }
33
+ // Check Node.js version
34
+ async function checkNodeVersion() {
35
+ const requiredMajor = 20;
36
+ const version = process.version;
37
+ const major = parseInt(version.slice(1).split('.')[0], 10);
38
+ if (major >= requiredMajor) {
39
+ return { name: 'Node.js Version', status: 'pass', message: `${version} (>= ${requiredMajor} required)` };
40
+ }
41
+ else if (major >= 18) {
42
+ return { name: 'Node.js Version', status: 'warn', message: `${version} (>= ${requiredMajor} recommended)`, fix: 'nvm install 20 && nvm use 20' };
43
+ }
44
+ else {
45
+ return { name: 'Node.js Version', status: 'fail', message: `${version} (>= ${requiredMajor} required)`, fix: 'nvm install 20 && nvm use 20' };
46
+ }
47
+ }
48
+ // Check npm version (async with proper env inheritance)
49
+ async function checkNpmVersion() {
50
+ try {
51
+ const version = await runCommand('npm --version');
52
+ const major = parseInt(version.split('.')[0], 10);
53
+ if (major >= 9) {
54
+ return { name: 'npm Version', status: 'pass', message: `v${version}` };
55
+ }
56
+ else {
57
+ return { name: 'npm Version', status: 'warn', message: `v${version} (>= 9 recommended)`, fix: 'npm install -g npm@latest' };
58
+ }
59
+ }
60
+ catch {
61
+ return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
62
+ }
63
+ }
64
+ // Check config file
65
+ async function checkConfigFile() {
66
+ // JSON configs (parse-validated). The first three are LEGACY shapes from
67
+ // pre-v3 init flows; v3 init writes only `.claude-flow/config.yaml`.
68
+ const jsonPaths = [
69
+ '.claude-flow/config.json',
70
+ 'claude-flow.config.json',
71
+ '.claude-flow.json'
72
+ ];
73
+ // YAML configs (existence-checked only — no heavy yaml parser dependency).
74
+ const yamlPaths = [
75
+ '.claude-flow/config.yaml',
76
+ '.claude-flow/config.yml',
77
+ 'claude-flow.config.yaml'
78
+ ];
79
+ // #1798 — collect ALL configs that exist instead of returning at the first
80
+ // hit. The previous early-return masked silent collisions: if both a v2
81
+ // JSON and a v3 YAML existed, doctor reported only the JSON while the
82
+ // daemon was actually reading from the YAML. Surfacing both lets the user
83
+ // see and resolve the disagreement.
84
+ const foundJson = [];
85
+ const invalidJson = [];
86
+ for (const configPath of jsonPaths) {
87
+ if (!existsSync(configPath))
88
+ continue;
89
+ try {
90
+ JSON.parse(readFileSync(configPath, 'utf8'));
91
+ foundJson.push(configPath);
92
+ }
93
+ catch {
94
+ invalidJson.push(configPath);
95
+ }
96
+ }
97
+ const foundYaml = yamlPaths.filter(p => existsSync(p));
98
+ // Hard failures first: malformed JSON wins.
99
+ if (invalidJson.length > 0) {
100
+ return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${invalidJson.join(', ')}`, fix: 'Fix JSON syntax in config file' };
101
+ }
102
+ // #1798 — collision: legacy JSON + new YAML both present. Subsystems can
103
+ // disagree on which to read; surface this as a warn with the recommended
104
+ // resolution (keep the YAML, archive the JSON).
105
+ if (foundJson.length > 0 && foundYaml.length > 0) {
106
+ return {
107
+ name: 'Config File',
108
+ status: 'warn',
109
+ message: `Config collision: legacy ${foundJson.join(', ')} + ${foundYaml.join(', ')} — subsystems may disagree silently`,
110
+ fix: `Archive the legacy JSON (mv ${foundJson[0]} ${foundJson[0]}.bak) and keep ${foundYaml[0]} as the canonical config`,
111
+ };
112
+ }
113
+ if (foundYaml.length > 0) {
114
+ return { name: 'Config File', status: 'pass', message: `Found: ${foundYaml[0]}` };
115
+ }
116
+ if (foundJson.length > 0) {
117
+ return { name: 'Config File', status: 'pass', message: `Found: ${foundJson[0]}` };
118
+ }
119
+ return { name: 'Config File', status: 'warn', message: 'No config file (using defaults)', fix: 'claude-flow config init' };
120
+ }
121
+ // Check daemon status
122
+ /**
123
+ * #2448 — Detect the runaway `npx @claude-flow/cli@latest` statusLine / hook
124
+ * commands left over in `.claude/settings.json` from pre-#2337 installs.
125
+ *
126
+ * These fire on every Claude Code event (statusLine refires every few hundred
127
+ * ms, hooks fire per tool-use), each spawning a cold Node process + npm
128
+ * registry round-trip. On the reporter's 48 GB macOS box this produced
129
+ * load average 49, jetsam, and a kernel watchdog panic two minutes after
130
+ * boot. Severity is CRITICAL when present; users who installed before #2337
131
+ * and never re-ran `ruflo init` still have it.
132
+ *
133
+ * Detection only — does not modify settings. Fix path is `ruflo init` (the
134
+ * executor's migration logic, also patched in #2448, will now regenerate
135
+ * the broken commands).
136
+ */
137
+ async function checkStaleSettingsNpx() {
138
+ // Same regex pattern the executor migration uses — kept in sync. Flag-list
139
+ // repetition bounded at 10 (CodeQL js/redos — unbounded `*` here is
140
+ // exponential-backtracking-prone on a crafted settings.json).
141
+ const BROKEN_RE = /npx\s+(?:--?\S+\s+){0,10}@?claude-flow\/cli@latest\s+hooks\s+(?:statusline|\S+)/;
142
+ // Look in both project-local and home-dir settings.
143
+ const candidates = [
144
+ join(process.cwd(), '.claude', 'settings.json'),
145
+ join(process.env.HOME ?? '', '.claude', 'settings.json'),
146
+ ].filter((p, i, a) => p && a.indexOf(p) === i);
147
+ const offenders = [];
148
+ for (const settingsPath of candidates) {
149
+ if (!existsSync(settingsPath))
150
+ continue;
151
+ let settings;
152
+ try {
153
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
154
+ }
155
+ catch {
156
+ continue; // checkConfigFile reports JSON errors separately
157
+ }
158
+ // statusLine.command
159
+ const sl = settings.statusLine;
160
+ if (sl?.command && BROKEN_RE.test(sl.command)) {
161
+ offenders.push({ path: settingsPath, where: 'statusLine' });
162
+ }
163
+ // hooks.<event>[].hooks[].command
164
+ const hooks = settings.hooks;
165
+ if (hooks) {
166
+ for (const [eventName, groups] of Object.entries(hooks)) {
167
+ if (!Array.isArray(groups))
168
+ continue;
169
+ for (const group of groups) {
170
+ if (!Array.isArray(group.hooks))
171
+ continue;
172
+ for (const h of group.hooks) {
173
+ if (typeof h?.command === 'string' && BROKEN_RE.test(h.command)) {
174
+ offenders.push({ path: settingsPath, where: `hooks.${eventName}` });
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ if (offenders.length === 0) {
182
+ return { name: 'Stale npx@latest in settings (#2448)', status: 'pass', message: 'no runaway commands detected' };
183
+ }
184
+ // Group by file for readable output
185
+ const byFile = offenders.reduce((acc, o) => {
186
+ (acc[o.path] ??= []).push(o.where);
187
+ return acc;
188
+ }, {});
189
+ const summary = Object.entries(byFile)
190
+ .map(([p, wheres]) => `${p} [${[...new Set(wheres)].join(', ')}]`)
191
+ .join('; ');
192
+ return {
193
+ name: 'Stale npx@latest in settings (#2448)',
194
+ status: 'fail',
195
+ message: `CRITICAL — runaway \`npx @claude-flow/cli@latest\` commands detected: ${summary}`,
196
+ fix: 'Re-run `npx ruflo init` to migrate (the v3.13.3+ init migrator regenerates these to local-helper form). On macOS this prevents the process-storm / kernel-panic class reported in #2448.',
197
+ };
198
+ }
199
+ async function checkDaemonStatus() {
200
+ try {
201
+ const pidFile = '.claude-flow/daemon.pid';
202
+ if (existsSync(pidFile)) {
203
+ const pid = readFileSync(pidFile, 'utf8').trim();
204
+ try {
205
+ process.kill(parseInt(pid, 10), 0); // Check if process exists
206
+ return { name: 'Daemon Status', status: 'pass', message: `Running (PID: ${pid})` };
207
+ }
208
+ catch {
209
+ return { name: 'Daemon Status', status: 'warn', message: 'Stale PID file', fix: 'rm .claude-flow/daemon.pid && claude-flow daemon start' };
210
+ }
211
+ }
212
+ return { name: 'Daemon Status', status: 'warn', message: 'Not running', fix: 'claude-flow daemon start' };
213
+ }
214
+ catch {
215
+ return { name: 'Daemon Status', status: 'warn', message: 'Unable to check', fix: 'claude-flow daemon status' };
216
+ }
217
+ }
218
+ // Check memory database
219
+ async function checkMemoryDatabase() {
220
+ // Authoritative path comes from `getMemoryRoot()` (honors
221
+ // `CLAUDE_FLOW_MEMORY_PATH`, claude-flow.config.json's `memory.persistPath`,
222
+ // then defaults to `.swarm/`). #1946: the previous hard-coded list missed
223
+ // `data/memory/memory.db` (a common config) and ignored the env var
224
+ // entirely, so doctor reported "Not initialized" on perfectly-init'd DBs.
225
+ // Try the configured path first, then fall back to the historic candidates.
226
+ const candidates = [];
227
+ try {
228
+ const { getMemoryRoot } = await import('../memory/memory-initializer.js');
229
+ candidates.push(join(getMemoryRoot(), 'memory.db'));
230
+ }
231
+ catch {
232
+ /* memory-initializer not available — fall through to legacy candidates */
233
+ }
234
+ candidates.push('.swarm/memory.db', '.claude-flow/memory.db', 'data/memory/memory.db', // matches `CLAUDE_FLOW_MEMORY_PATH=data/memory`
235
+ 'data/memory.db');
236
+ for (const dbPath of candidates) {
237
+ if (existsSync(dbPath)) {
238
+ try {
239
+ const stats = statSync(dbPath);
240
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
241
+ return { name: 'Memory Database', status: 'pass', message: `${dbPath} (${sizeMB} MB)` };
242
+ }
243
+ catch {
244
+ return { name: 'Memory Database', status: 'warn', message: `${dbPath} (unable to stat)` };
245
+ }
246
+ }
247
+ }
248
+ return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'claude-flow memory configure --backend hybrid' };
249
+ }
250
+ // ═══════════════════════════════════════════════════════════════════════════
251
+ // #2677 — memory doctor: functional checks (stuinfla)
252
+ //
253
+ // The existing `checkMemoryDatabase` above asserts existence + statability
254
+ // only, so it CANNOT distinguish a healthy DB from a 99.97%-empty or
255
+ // SQLite-malformed one. Stuinfla reported both cases live (81-store fleet).
256
+ // The three checks below layer functional assertions on top, ordered so
257
+ // the earliest chain-break is always the first red the user sees:
258
+ // 1. Integrity — can sql.js open it AND does PRAGMA integrity_check pass?
259
+ // 2. Content — do most memory_entries rows carry non-empty content?
260
+ // 3. Embedding coverage — do most rows have a vector? (unembedded rows are
261
+ // both unrecallable AND undistillable per ADR-174)
262
+ // Ordering matters: content ratio is meaningless on a DB that can't open;
263
+ // embedding coverage is meaningless on rows with no content. First red wins.
264
+ //
265
+ // Recall probe (stuinfla check 4) requires actual write+search+delete round
266
+ // trips through the CLI's own memory pipeline — deferred to a follow-up PR
267
+ // to keep this one purely additive and safe.
268
+ //
269
+ // Design rules (also from stuinfla's report):
270
+ // - "A check that cannot fail protects nothing" — every check has a
271
+ // demonstrable red state.
272
+ // - "UNKNOWN is never PASS" — if the check can't RUN, report warn/fail,
273
+ // never a reassuring pass. Encrypted-DB case gets warn with the caveat
274
+ // spelled out; corruption gets fail.
275
+ // - "Print the measurement, not a checkmark" — messages include the
276
+ // ratios so operators see the shape of the problem.
277
+ /** Resolve the same DB path checkMemoryDatabase above uses. Returns null
278
+ * if no candidate exists (in which case none of these checks should
279
+ * fire — checkMemoryDatabase will already have surfaced the missing DB). */
280
+ async function resolveMemoryDbPath() {
281
+ const candidates = [];
282
+ try {
283
+ const { getMemoryRoot } = await import('../memory/memory-initializer.js');
284
+ candidates.push(join(getMemoryRoot(), 'memory.db'));
285
+ }
286
+ catch { /* fall through to legacy candidates */ }
287
+ candidates.push('.swarm/memory.db', '.claude-flow/memory.db', 'data/memory/memory.db', 'data/memory.db');
288
+ for (const p of candidates)
289
+ if (existsSync(p))
290
+ return p;
291
+ return null;
292
+ }
293
+ /** Open a sql.js Database over an on-disk file, returning null when the
294
+ * file can't be opened as a SQLite database (encrypted / corrupted / not
295
+ * a database). Callers decide whether that's warn or fail. */
296
+ async function tryOpenSqlJs(dbPath) {
297
+ try {
298
+ const initSqlJs = (await import('sql.js')).default ?? (await import('sql.js'));
299
+ const SQL = await (typeof initSqlJs === 'function' ? initSqlJs() : initSqlJs.default());
300
+ const { readFileSync } = await import('fs');
301
+ const buf = readFileSync(dbPath);
302
+ return new SQL.Database(new Uint8Array(buf));
303
+ }
304
+ catch {
305
+ return null;
306
+ }
307
+ }
308
+ // Check 1 — sql.js can open it AND PRAGMA integrity_check returns 'ok'.
309
+ // Two fail modes handled distinctly per "UNKNOWN is never PASS":
310
+ // - Open fails: warn ("cannot open; encrypted DB or corrupt — doctor
311
+ // can't distinguish from this side")
312
+ // - Open succeeds but pragma != 'ok': fail (definite corruption)
313
+ async function checkMemoryIntegrity() {
314
+ const dbPath = await resolveMemoryDbPath();
315
+ if (!dbPath)
316
+ return { name: 'Memory Integrity', status: 'warn', message: 'no memory.db found (see Memory Database check above)' };
317
+ const db = await tryOpenSqlJs(dbPath);
318
+ if (!db) {
319
+ return {
320
+ name: 'Memory Integrity',
321
+ status: 'warn',
322
+ message: `${dbPath} — sql.js can't open (encrypted DB or corrupt; doctor can't tell which from outside)`,
323
+ fix: 'if encrypted: expected. if not: back up + `claude-flow memory init --force` to rebuild',
324
+ };
325
+ }
326
+ try {
327
+ const res = db.exec('PRAGMA integrity_check');
328
+ const rows = res[0]?.values?.map((v) => String(v[0])) ?? [];
329
+ if (rows.length === 1 && rows[0] === 'ok') {
330
+ return { name: 'Memory Integrity', status: 'pass', message: `${dbPath} — PRAGMA integrity_check: ok` };
331
+ }
332
+ return {
333
+ name: 'Memory Integrity',
334
+ status: 'fail',
335
+ message: `${dbPath} — PRAGMA integrity_check: ${rows.slice(0, 3).join('; ')}${rows.length > 3 ? ` (+${rows.length - 3} more)` : ''}`,
336
+ fix: 'back up .swarm/memory.db then `claude-flow memory init --force`',
337
+ };
338
+ }
339
+ catch (e) {
340
+ const msg = e.message || String(e);
341
+ const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
342
+ return {
343
+ name: 'Memory Integrity',
344
+ status: 'warn',
345
+ message: encryptedOrCorrupt
346
+ ? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
347
+ : `${dbPath} — probe threw: ${msg}`,
348
+ };
349
+ }
350
+ finally {
351
+ try {
352
+ db.close();
353
+ }
354
+ catch { /* best-effort */ }
355
+ }
356
+ }
357
+ // Check 2 — memory_entries rows should mostly carry non-empty content.
358
+ // Stuinfla's live case: 11,133 rows, 3 with content (0.03%). Threshold 95%
359
+ // is the number he proposed. Values below → fail with the exact ratio in
360
+ // the message ("Print the measurement, not a checkmark").
361
+ async function checkMemoryContent() {
362
+ const dbPath = await resolveMemoryDbPath();
363
+ if (!dbPath)
364
+ return { name: 'Memory Content', status: 'warn', message: 'no memory.db found' };
365
+ const db = await tryOpenSqlJs(dbPath);
366
+ if (!db)
367
+ return { name: 'Memory Content', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
368
+ try {
369
+ const tables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries'")[0]?.values?.map((v) => String(v[0])) ?? [];
370
+ if (tables.length === 0)
371
+ return { name: 'Memory Content', status: 'warn', message: 'no memory_entries table in DB — schema mismatch or empty init' };
372
+ const r = db.exec("SELECT count(*), sum(CASE WHEN length(trim(coalesce(content,'')))>0 THEN 1 ELSE 0 END) FROM memory_entries");
373
+ const total = Number(r[0]?.values?.[0]?.[0] ?? 0);
374
+ const populated = Number(r[0]?.values?.[0]?.[1] ?? 0);
375
+ if (total === 0)
376
+ return { name: 'Memory Content', status: 'pass', message: `${dbPath} — 0 rows (fresh DB, expected)` };
377
+ const ratio = populated / total;
378
+ const pct = (ratio * 100).toFixed(2);
379
+ const detail = `content ${populated}/${total} (${pct}%)`;
380
+ if (ratio < 0.95) {
381
+ return {
382
+ name: 'Memory Content',
383
+ status: 'fail',
384
+ message: `${dbPath} — ${detail} below 95% floor`,
385
+ fix: 'schema drift likely (rename of value→content or similar). check migration state via `claude-flow migrate status`',
386
+ };
387
+ }
388
+ return { name: 'Memory Content', status: 'pass', message: `${dbPath} — ${detail}` };
389
+ }
390
+ catch (e) {
391
+ const msg = e.message || String(e);
392
+ const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
393
+ return {
394
+ name: 'Memory Content',
395
+ status: 'warn',
396
+ message: encryptedOrCorrupt
397
+ ? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
398
+ : `${dbPath} — probe threw: ${msg}`,
399
+ };
400
+ }
401
+ finally {
402
+ try {
403
+ db.close();
404
+ }
405
+ catch { /* best-effort */ }
406
+ }
407
+ }
408
+ // Check 3 — most memory_entries with content should also carry an embedding
409
+ // vector. Rows without a vector are BOTH unrecallable (no similarity search
410
+ // can find them) AND undistillable (ADR-174's distill skips rows with no
411
+ // parseable vector). Same 95% threshold + fail-with-ratio pattern as check 2.
412
+ //
413
+ // Embedding storage varies across ruflo installs (agentdb migrations, HNSW
414
+ // index vs inline vector column). Discovers the shape via schema probes,
415
+ // falls back to warn if the schema is unrecognized (better than a false
416
+ // pass, per "UNKNOWN is never PASS").
417
+ async function checkMemoryEmbeddingCoverage() {
418
+ const dbPath = await resolveMemoryDbPath();
419
+ if (!dbPath)
420
+ return { name: 'Memory Embedding Coverage', status: 'warn', message: 'no memory.db found' };
421
+ const db = await tryOpenSqlJs(dbPath);
422
+ if (!db)
423
+ return { name: 'Memory Embedding Coverage', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
424
+ try {
425
+ // Schema-shape discovery. Three candidate columns / tables we've seen
426
+ // across the agentdb / ruvector history — first match wins.
427
+ const cols = db.exec("PRAGMA table_info(memory_entries)");
428
+ const colNames = new Set((cols[0]?.values ?? []).map((v) => String(v[1])));
429
+ let vectorPredicate = null;
430
+ if (colNames.has('embedding'))
431
+ vectorPredicate = "embedding IS NOT NULL AND length(embedding) > 0";
432
+ else if (colNames.has('vector'))
433
+ vectorPredicate = "vector IS NOT NULL AND length(vector) > 0";
434
+ if (!vectorPredicate) {
435
+ const otherTables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('vector_indexes','embeddings','memory_embeddings')")[0]?.values?.map((v) => String(v[0])) ?? [];
436
+ if (otherTables.length === 0) {
437
+ return {
438
+ name: 'Memory Embedding Coverage',
439
+ status: 'warn',
440
+ message: 'no embedding column/table recognized (agentdb schema mismatch or older format) — doctor cannot measure',
441
+ };
442
+ }
443
+ const tbl = otherTables[0];
444
+ const r = db.exec(`SELECT count(*) FROM memory_entries m WHERE EXISTS (SELECT 1 FROM ${tbl} e WHERE e.memory_id = m.id OR e.entry_id = m.id OR e.id = m.id)`);
445
+ const withEmbedding = Number(r[0]?.values?.[0]?.[0] ?? 0);
446
+ const total = Number(db.exec("SELECT count(*) FROM memory_entries WHERE length(trim(coalesce(content,'')))>0")[0]?.values?.[0]?.[0] ?? 0);
447
+ if (total === 0)
448
+ return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — 0 content rows (nothing to embed)` };
449
+ const ratio = withEmbedding / total;
450
+ const pct = (ratio * 100).toFixed(2);
451
+ const detail = `embedded ${withEmbedding}/${total} (${pct}%) via ${tbl}`;
452
+ if (ratio < 0.95)
453
+ return { name: 'Memory Embedding Coverage', status: 'fail', message: `${dbPath} — ${detail} below 95% floor`, fix: 'unembedded rows are unrecallable + undistillable — re-run `claude-flow memory embed --namespace <name>` for populated namespaces' };
454
+ return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
455
+ }
456
+ // Inline embedding column
457
+ const r = db.exec(`SELECT count(*), sum(CASE WHEN ${vectorPredicate} THEN 1 ELSE 0 END) FROM memory_entries WHERE length(trim(coalesce(content,'')))>0`);
458
+ const total = Number(r[0]?.values?.[0]?.[0] ?? 0);
459
+ const withEmb = Number(r[0]?.values?.[0]?.[1] ?? 0);
460
+ if (total === 0)
461
+ return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — 0 content rows (nothing to embed)` };
462
+ const ratio = withEmb / total;
463
+ const pct = (ratio * 100).toFixed(2);
464
+ const detail = `embedded ${withEmb}/${total} (${pct}%)`;
465
+ if (ratio < 0.95) {
466
+ return { name: 'Memory Embedding Coverage', status: 'fail', message: `${dbPath} — ${detail} below 95% floor`, fix: 'unembedded rows are unrecallable + undistillable — re-run `claude-flow memory embed --namespace <name>` for populated namespaces' };
467
+ }
468
+ return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
469
+ }
470
+ catch (e) {
471
+ const msg = e.message || String(e);
472
+ const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
473
+ return {
474
+ name: 'Memory Embedding Coverage',
475
+ status: 'warn',
476
+ message: encryptedOrCorrupt
477
+ ? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
478
+ : `${dbPath} — probe threw: ${msg}`,
479
+ };
480
+ }
481
+ finally {
482
+ try {
483
+ db.close();
484
+ }
485
+ catch { /* best-effort */ }
486
+ }
487
+ }
488
+ // #2545: Check that the self-learning bridge can actually load @claude-flow/memory
489
+ // the SAME way the SessionStart auto-memory hook does. On the documented `npx ruflo`
490
+ // path the package lands in the npx cache — unreachable from the project — so the
491
+ // hook silently no-op'd with no signal anywhere. This surfaces it.
492
+ async function checkLearningBridge() {
493
+ const cwd = process.cwd();
494
+ const hookPath = join(cwd, '.claude', 'helpers', 'auto-memory-hook.mjs');
495
+ // Only relevant once init has deployed the hook; otherwise stay quiet.
496
+ if (!existsSync(hookPath)) {
497
+ return {
498
+ name: 'Learning Bridge',
499
+ status: 'pass',
500
+ message: 'auto-memory hook not installed (run: npx ruflo@latest init)',
501
+ };
502
+ }
503
+ const distPath = resolveMemoryPackageFromProject(cwd);
504
+ if (distPath) {
505
+ const version = readMemoryPackageVersion(distPath);
506
+ return {
507
+ name: 'Learning Bridge',
508
+ status: 'pass',
509
+ message: `@claude-flow/memory resolvable${version ? ` (v${version})` : ''}`,
510
+ };
511
+ }
512
+ // #2599: Self-heal on plain `doctor` (not just --fix). The project-side resolver
513
+ // fails when the sidecar is stale or missing (e.g. npx cache generation rotated),
514
+ // but the CLI process itself can resolve @claude-flow/memory from its own module
515
+ // context. Write the sidecar automatically instead of hard-failing the check.
516
+ const record = recordMemoryPackagePath(cwd, 'doctor-auto');
517
+ if (record) {
518
+ const version = readMemoryPackageVersion(record.distPath);
519
+ return {
520
+ name: 'Learning Bridge',
521
+ status: 'pass',
522
+ message: `@claude-flow/memory resolvable via auto-recorded sidecar${version ? ` (v${version})` : ''}`,
523
+ };
524
+ }
525
+ return {
526
+ name: 'Learning Bridge',
527
+ status: 'fail',
528
+ message: '@claude-flow/memory NOT resolvable — SessionStart self-learning imports are a silent no-op',
529
+ fix: 'npm i -D @claude-flow/memory (optional dep appears absent — likely --omit=optional install)',
530
+ };
531
+ }
532
+ // Check API keys
533
+ async function checkApiKeys() {
534
+ const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
535
+ const found = [];
536
+ for (const key of keys) {
537
+ if (process.env[key]) {
538
+ found.push(key);
539
+ }
540
+ }
541
+ // Detect Claude Code environment — API keys are managed internally
542
+ const inClaudeCode = !!(process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
543
+ if (found.includes('ANTHROPIC_API_KEY') || found.includes('CLAUDE_API_KEY')) {
544
+ return { name: 'API Keys', status: 'pass', message: `Found: ${found.join(', ')}` };
545
+ }
546
+ else if (inClaudeCode) {
547
+ return { name: 'API Keys', status: 'pass', message: 'Claude Code (managed internally)' };
548
+ }
549
+ else if (found.length > 0) {
550
+ return { name: 'API Keys', status: 'warn', message: `Found: ${found.join(', ')} (no Claude key)`, fix: 'export ANTHROPIC_API_KEY=your_key' };
551
+ }
552
+ else {
553
+ return { name: 'API Keys', status: 'warn', message: 'No API keys found', fix: 'export ANTHROPIC_API_KEY=your_key' };
554
+ }
555
+ }
556
+ // Check git (async with proper env inheritance)
557
+ async function checkGit() {
558
+ try {
559
+ const version = await runCommand('git --version');
560
+ return { name: 'Git', status: 'pass', message: version.replace('git version ', 'v') };
561
+ }
562
+ catch {
563
+ return { name: 'Git', status: 'warn', message: 'Not installed', fix: 'Install git from https://git-scm.com' };
564
+ }
565
+ }
566
+ // Check if in git repo (async with proper env inheritance)
567
+ //
568
+ // #1791.7 — `git rev-parse` was reported as failing on hosts where `.git`
569
+ // clearly exists in cwd (linux-arm64 daemon contexts). Treat the git binary
570
+ // as authoritative when it succeeds, but fall back to a `.git` walk-up so a
571
+ // present repository is recognized even when the git invocation fails for
572
+ // environment reasons (PATH, broken global config, EBADCWD, etc.).
573
+ async function checkGitRepo() {
574
+ try {
575
+ await runCommand('git rev-parse --is-inside-work-tree');
576
+ return { name: 'Git Repository', status: 'pass', message: 'In a git repository' };
577
+ }
578
+ catch {
579
+ // Walk parents of cwd for a .git directory before reporting "not a repo"
580
+ let dir = process.cwd();
581
+ while (true) {
582
+ if (existsSync(join(dir, '.git'))) {
583
+ return {
584
+ name: 'Git Repository',
585
+ status: 'warn',
586
+ message: `Repo detected on disk (${join(dir, '.git')}) but \`git rev-parse\` failed — check git installation and PATH`,
587
+ fix: 'Verify git is on PATH (try `git --version`) and that the working tree is not corrupted',
588
+ };
589
+ }
590
+ const parent = dirname(dir);
591
+ if (parent === dir)
592
+ break;
593
+ dir = parent;
594
+ }
595
+ return { name: 'Git Repository', status: 'warn', message: 'Not a git repository', fix: 'git init' };
596
+ }
597
+ }
598
+ // Check AIDefence package availability (#1807)
599
+ //
600
+ // `aidefence_*` MCP tools (scan, analyze, has_pii, stats, learn) require
601
+ // `@claude-flow/aidefence` to be installed and loadable. The package is an
602
+ // optional dependency — present in some installs (project-local) but
603
+ // missing in others (npm-global of `claude-flow`). Without it, every
604
+ // aidefence MCP call fails at runtime with "Cannot find module".
605
+ //
606
+ // Surface that state in `doctor` so operators know BEFORE they rely on
607
+ // AI-defence scanning. The probe is the same dynamic `import()` the MCP
608
+ // tool's handler uses, so a `pass` here means the actual tools will work.
609
+ async function checkAIDefence() {
610
+ try {
611
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
612
+ await import('@claude-flow/aidefence');
613
+ return {
614
+ name: 'AIDefence',
615
+ status: 'pass',
616
+ message: '@claude-flow/aidefence loadable — aidefence_* MCP tools functional',
617
+ };
618
+ }
619
+ catch {
620
+ return {
621
+ name: 'AIDefence',
622
+ status: 'warn',
623
+ message: '@claude-flow/aidefence not loadable — aidefence_* MCP tools will fail (optional package)',
624
+ fix: 'npm install --save @claude-flow/aidefence (in your project), or run `claude-flow mcp start` from a directory that has it installed',
625
+ };
626
+ }
627
+ }
628
+ /**
629
+ * ADR-097 Phase 4: federation peer-state surface for doctor.
630
+ *
631
+ * Probes the federation plugin loadability + asserts the breaker entity
632
+ * layer is present in the installed version. Without the plugin
633
+ * installed this is a "not configured" pass — federation is opt-in.
634
+ *
635
+ * Live coordinator state (per-peer counts) requires a running MCP server
636
+ * with `federation_init` called; operators inspect that via the
637
+ * `federation_breaker_status` MCP tool, not the doctor (which is a
638
+ * one-shot CLI process with no coordinator session).
639
+ */
640
+ async function checkFederationBreaker() {
641
+ try {
642
+ // Optional plugin — not a hard dep of @claude-flow/cli. Build the
643
+ // module specifier dynamically so TypeScript cannot statically
644
+ // resolve it (which would emit TS2307); at runtime the import
645
+ // either resolves (plugin installed) or throws (handled below).
646
+ const specifier = ['@claude-flow', 'plugin-agent-federation'].join('/');
647
+ const mod = await import(specifier);
648
+ if (!mod.FederationNodeState) {
649
+ return {
650
+ name: 'Federation Breaker',
651
+ status: 'warn',
652
+ message: '@claude-flow/plugin-agent-federation loaded but FederationNodeState export missing — version older than ADR-097 Phase 2',
653
+ fix: 'Upgrade: npm install @claude-flow/plugin-agent-federation@alpha',
654
+ };
655
+ }
656
+ return {
657
+ name: 'Federation Breaker',
658
+ status: 'pass',
659
+ message: 'ADR-097 breaker loadable — federation_breaker_status / federation_evict / federation_reactivate MCP tools available',
660
+ };
661
+ }
662
+ catch {
663
+ return {
664
+ name: 'Federation Breaker',
665
+ status: 'pass',
666
+ message: 'Federation plugin not installed (optional) — install only if you need cross-installation peering',
667
+ fix: 'npm install --save @claude-flow/plugin-agent-federation@alpha',
668
+ };
669
+ }
670
+ }
671
+ // Check MCP servers
672
+ async function checkMcpServers() {
673
+ const home = process.env.HOME || process.env.USERPROFILE || '';
674
+ // #1842: ~/.claude.json holds project-scoped registrations under
675
+ // parsed.projects[<projectPath>].mcpServers.ruflo, in addition to any
676
+ // top-level mcpServers. Check both shapes plus the legacy desktop and
677
+ // local .mcp.json paths.
678
+ const mcpConfigPaths = [
679
+ join(home, '.claude.json'),
680
+ join(home, '.claude/claude_desktop_config.json'),
681
+ join(home, '.config/claude/mcp.json'),
682
+ '.mcp.json',
683
+ ];
684
+ const isRufloKey = (k) => k === 'ruflo' || k === 'ruflo_alpha' || k === 'claude-flow' || k === 'claude-flow_alpha';
685
+ // Canonical MCP-server key is `claude-flow` — matches the `mcp__claude-flow__*`
686
+ // prefix that ~166 plugin tool references depend on (#2206). A `ruflo`-keyed
687
+ // entry pointing at the same binary is the legacy duplicate created by
688
+ // pre-rename setup docs that #2612 tracks; doctor treats it as removable.
689
+ const isCurrentRufloKey = (k) => k === 'claude-flow' || k === 'claude-flow_alpha';
690
+ const isLegacyRufloKey = (k) => k === 'ruflo' || k === 'ruflo_alpha';
691
+ const isRufloServer = (server) => {
692
+ if (!server || typeof server !== 'object')
693
+ return false;
694
+ const entry = server;
695
+ const command = typeof entry.command === 'string' ? entry.command : '';
696
+ const args = Array.isArray(entry.args)
697
+ ? entry.args.filter((arg) => typeof arg === 'string')
698
+ : [];
699
+ const haystack = [command, ...args].join(' ');
700
+ return /\b(?:ruflo|claude-flow|@claude-flow\/cli)(?:@[\w.-]+)?\b/.test(haystack);
701
+ };
702
+ let totalServersSeen = 0;
703
+ const rufloLocations = [];
704
+ const duplicateLocations = [];
705
+ const legacyLocations = [];
706
+ const currentLocations = [];
707
+ const inspectServers = (servers, location) => {
708
+ if (!servers || typeof servers !== 'object')
709
+ return { total: 0, hasRuflo: false };
710
+ const entries = Object.entries(servers);
711
+ const activeRufloEntries = entries.filter(([key, server]) => isRufloKey(key) && isRufloServer(server));
712
+ const hasLegacy = activeRufloEntries.some(([key]) => isLegacyRufloKey(key));
713
+ const hasCurrent = activeRufloEntries.some(([key]) => isCurrentRufloKey(key));
714
+ if (activeRufloEntries.length > 0) {
715
+ rufloLocations.push(`${location}: ${activeRufloEntries.map(([key]) => key).join(', ')}`);
716
+ }
717
+ if (hasLegacy && hasCurrent) {
718
+ duplicateLocations.push(`${location}: ${activeRufloEntries.map(([key]) => key).join(' + ')}`);
719
+ }
720
+ for (const [key] of activeRufloEntries) {
721
+ if (isLegacyRufloKey(key))
722
+ legacyLocations.push(`${location}: ${key}`);
723
+ if (isCurrentRufloKey(key))
724
+ currentLocations.push(`${location}: ${key}`);
725
+ }
726
+ return { total: entries.length, hasRuflo: activeRufloEntries.length > 0 };
727
+ };
728
+ for (const configPath of mcpConfigPaths) {
729
+ if (!existsSync(configPath))
730
+ continue;
731
+ try {
732
+ const content = JSON.parse(readFileSync(configPath, 'utf8'));
733
+ // Top-level mcpServers (legacy / desktop form)
734
+ const topResult = inspectServers(content.mcpServers || content.servers || {}, `${configPath} top-level`);
735
+ totalServersSeen += topResult.total;
736
+ // Project-scoped (Claude Code shape): projects[*].mcpServers.ruflo
737
+ if (content.projects && typeof content.projects === 'object') {
738
+ for (const [projectKey, projectVal] of Object.entries(content.projects)) {
739
+ const pm = projectVal?.mcpServers;
740
+ if (pm && typeof pm === 'object') {
741
+ const projectResult = inspectServers(pm, `${configPath} projects[${projectKey}]`);
742
+ totalServersSeen += projectResult.total;
743
+ }
744
+ }
745
+ }
746
+ }
747
+ catch {
748
+ // continue to next path
749
+ }
750
+ }
751
+ if (duplicateLocations.length > 0 || (legacyLocations.length > 0 && currentLocations.length > 0)) {
752
+ const locations = duplicateLocations.length > 0
753
+ ? duplicateLocations.join('; ')
754
+ : `legacy ${legacyLocations.join('; ')} + current ${currentLocations.join('; ')}`;
755
+ return {
756
+ name: 'MCP Servers',
757
+ status: 'warn',
758
+ message: `Duplicate Ruflo MCP registrations found (${locations}) — Claude Code will start both tool schemas`,
759
+ fix: 'Remove the legacy `ruflo`-keyed MCP registration (pre-rename duplicate) and keep the canonical `claude-flow` entry: `claude mcp add claude-flow -- npx -y ruflo@latest mcp start`. The canonical key stays `claude-flow` so the ~166 `mcp__claude-flow__*` plugin tool references keep resolving (#2206).',
760
+ };
761
+ }
762
+ if (rufloLocations.length > 0) {
763
+ return {
764
+ name: 'MCP Servers',
765
+ status: 'pass',
766
+ message: `${totalServersSeen} servers (ruflo configured: ${rufloLocations.join('; ')})`,
767
+ };
768
+ }
769
+ if (totalServersSeen > 0) {
770
+ return {
771
+ name: 'MCP Servers',
772
+ status: 'warn',
773
+ message: `${totalServersSeen} servers (ruflo not found)`,
774
+ fix: 'claude mcp add claude-flow -- npx -y ruflo@latest mcp start',
775
+ };
776
+ }
777
+ return {
778
+ name: 'MCP Servers',
779
+ status: 'warn',
780
+ message: 'No MCP config found',
781
+ fix: 'claude mcp add ruflo -- npx -y ruflo@latest mcp start',
782
+ };
783
+ }
784
+ // Check disk space (async with proper env inheritance)
785
+ async function checkDiskSpace() {
786
+ try {
787
+ if (process.platform === 'win32') {
788
+ return { name: 'Disk Space', status: 'pass', message: 'Check skipped on Windows' };
789
+ }
790
+ // Use df -Ph for POSIX mode (guarantees single-line output even with long device names)
791
+ const output_str = await runCommand('df -Ph . | tail -1');
792
+ const parts = output_str.split(/\s+/);
793
+ // POSIX format: Filesystem Size Used Avail Capacity Mounted
794
+ const available = parts[3];
795
+ const usePercent = parseInt(parts[4]?.replace('%', '') || '0', 10);
796
+ if (isNaN(usePercent)) {
797
+ return { name: 'Disk Space', status: 'warn', message: `${available || 'unknown'} available (unable to parse usage)` };
798
+ }
799
+ if (usePercent > 90) {
800
+ return { name: 'Disk Space', status: 'fail', message: `${available} available (${usePercent}% used)`, fix: 'Free up disk space' };
801
+ }
802
+ else if (usePercent > 80) {
803
+ return { name: 'Disk Space', status: 'warn', message: `${available} available (${usePercent}% used)` };
804
+ }
805
+ return { name: 'Disk Space', status: 'pass', message: `${available} available` };
806
+ }
807
+ catch {
808
+ return { name: 'Disk Space', status: 'warn', message: 'Unable to check' };
809
+ }
810
+ }
811
+ // Check TypeScript/build (async with proper env inheritance)
812
+ async function checkBuildTools() {
813
+ try {
814
+ const tscVersion = await runCommand('npx tsc --version', 10000); // tsc can be slow
815
+ if (!tscVersion || tscVersion.includes('not found')) {
816
+ return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
817
+ }
818
+ return { name: 'TypeScript', status: 'pass', message: tscVersion.replace('Version ', 'v') };
819
+ }
820
+ catch {
821
+ return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
822
+ }
823
+ }
824
+ // Check for stale npx cache (version freshness)
825
+ async function checkVersionFreshness() {
826
+ try {
827
+ // Get current CLI version from package.json
828
+ // Use import.meta.url to reliably locate our own package.json,
829
+ // regardless of how deep the compiled file sits (e.g. dist/src/commands/).
830
+ let currentVersion = '0.0.0';
831
+ try {
832
+ const thisFile = fileURLToPath(import.meta.url);
833
+ let dir = dirname(thisFile);
834
+ // Walk up from the current file's directory until we find the
835
+ // package.json that belongs to @claude-flow/cli (or claude-flow/cli).
836
+ // Walk until dirname(dir) === dir (filesystem root on any platform).
837
+ for (;;) {
838
+ const candidate = join(dir, 'package.json');
839
+ try {
840
+ if (existsSync(candidate)) {
841
+ const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
842
+ if (pkg.version &&
843
+ typeof pkg.name === 'string' &&
844
+ (pkg.name === '@claude-flow/cli' || pkg.name === 'claude-flow' || pkg.name === 'ruflo')) {
845
+ currentVersion = pkg.version;
846
+ break;
847
+ }
848
+ }
849
+ }
850
+ catch {
851
+ // Unreadable/invalid JSON -- skip and keep walking up
852
+ }
853
+ const parent = dirname(dir);
854
+ if (parent === dir)
855
+ break; // reached root
856
+ dir = parent;
857
+ }
858
+ }
859
+ catch {
860
+ // Fall back to a default
861
+ currentVersion = '0.0.0';
862
+ }
863
+ // Check if running via npx (look for _npx in process path or argv)
864
+ const isNpx = process.argv[1]?.includes('_npx') ||
865
+ process.env.npm_execpath?.includes('npx') ||
866
+ process.cwd().includes('_npx');
867
+ // Query npm for latest version (using alpha tag since that's what we publish to)
868
+ let latestVersion = currentVersion;
869
+ try {
870
+ const npmInfo = await runCommand('npm view @claude-flow/cli@alpha version', 5000);
871
+ latestVersion = npmInfo.trim();
872
+ }
873
+ catch {
874
+ // Can't reach npm registry - skip check
875
+ return {
876
+ name: 'Version Freshness',
877
+ status: 'warn',
878
+ message: `v${currentVersion} (cannot check registry)`
879
+ };
880
+ }
881
+ // Parse version numbers for comparison (handle prerelease like 3.0.0-alpha.84)
882
+ const parseVersion = (v) => {
883
+ const match = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-[a-zA-Z]+\.(\d+))?/);
884
+ if (!match)
885
+ return { major: 0, minor: 0, patch: 0, prerelease: 0 };
886
+ return {
887
+ major: parseInt(match[1], 10) || 0,
888
+ minor: parseInt(match[2], 10) || 0,
889
+ patch: parseInt(match[3], 10) || 0,
890
+ prerelease: parseInt(match[4], 10) || 0
891
+ };
892
+ };
893
+ const current = parseVersion(currentVersion);
894
+ const latest = parseVersion(latestVersion);
895
+ // Compare versions (including prerelease number)
896
+ const isOutdated = (latest.major > current.major ||
897
+ (latest.major === current.major && latest.minor > current.minor) ||
898
+ (latest.major === current.major && latest.minor === current.minor && latest.patch > current.patch) ||
899
+ (latest.major === current.major && latest.minor === current.minor && latest.patch === current.patch && latest.prerelease > current.prerelease));
900
+ if (isOutdated) {
901
+ const fix = isNpx
902
+ ? 'rm -rf ~/.npm/_npx/* && npx -y @claude-flow/cli@latest'
903
+ : 'npm update @claude-flow/cli';
904
+ return {
905
+ name: 'Version Freshness',
906
+ status: 'warn',
907
+ message: `v${currentVersion} (latest: v${latestVersion})${isNpx ? ' [npx cache stale]' : ''}`,
908
+ fix
909
+ };
910
+ }
911
+ return {
912
+ name: 'Version Freshness',
913
+ status: 'pass',
914
+ message: `v${currentVersion} (up to date)`
915
+ };
916
+ }
917
+ catch (error) {
918
+ return {
919
+ name: 'Version Freshness',
920
+ status: 'warn',
921
+ message: 'Unable to check version freshness'
922
+ };
923
+ }
924
+ }
925
+ // Check Claude Code CLI (async with proper env inheritance)
926
+ // ADR-150 — surface MetaHarness availability + harnessFit score in
927
+ // the standard ruflo doctor flow. Graceful degradation: when metaharness
928
+ // is not installed (no network, optionalDep skipped), the check returns
929
+ // `warn` with a hint instead of `fail` — ruflo continues to function.
930
+ /**
931
+ * iter 45 — verify the ruflo-side MetaHarness integration is intact.
932
+ *
933
+ * The existing `checkMetaharness` verifies the UPSTREAM `metaharness`
934
+ * package is reachable (warn if missing — it's optional per ADR-150).
935
+ * This check verifies the INTEGRATION LAYER (plugin scripts, production
936
+ * module, subprocess bridge) is intact. Unlike upstream, the integration
937
+ * layer is shipped with ruflo — missing files mean ruflo's install is
938
+ * corrupted, not that an optional dep is absent.
939
+ *
940
+ * Status mapping:
941
+ * pass — all required files present + module loads + similarity() smoke OK
942
+ * fail — any required file missing OR module fails to import
943
+ * warn — files present but module import errored at runtime
944
+ *
945
+ * Verified files (iter 36-53 surfaces — full ADR-150 deep-integration set):
946
+ * - plugins/ruflo-metaharness/scripts/_harness.mjs (subprocess bridge)
947
+ * - plugins/ruflo-metaharness/scripts/_similarity.mjs (ADR-152 §3.1 module, iter 36)
948
+ * - plugins/ruflo-metaharness/scripts/similarity.mjs (CLI skill, iter 36)
949
+ * - plugins/ruflo-metaharness/scripts/_spike-similarity.mjs (regression anchor, iter 35)
950
+ * - plugins/ruflo-metaharness/scripts/drift-from-history.mjs (1-command primitive, iter 53)
951
+ * - plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md
952
+ * - plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md (iter 53)
953
+ */
954
+ /**
955
+ * ADR-305 — funnel state audit. Reports the effective enabled/disabled
956
+ * state and, critically for enterprise audit verification, WHICH
957
+ * precedence source decided it (env > enterprise > user > default).
958
+ * Informational: both states are `pass`; only a resolver failure warns.
959
+ */
960
+ async function checkFunnel() {
961
+ try {
962
+ const { resolveFunnelEnabled, getDisclosure } = await import('../funnel/index.js');
963
+ const decision = resolveFunnelEnabled();
964
+ const disclosure = getDisclosure();
965
+ return {
966
+ name: 'Funnel (ADR-305)',
967
+ status: 'pass',
968
+ message: `${decision.enabled ? 'enabled' : 'disabled'} (decided by: ${decision.decidedBy}; disclosure: ${disclosure.state})`,
969
+ };
970
+ }
971
+ catch (err) {
972
+ return {
973
+ name: 'Funnel (ADR-305)',
974
+ status: 'warn',
975
+ message: `state unreadable: ${err instanceof Error ? err.message : String(err)}`,
976
+ fix: 'ruflo funnel status',
977
+ };
978
+ }
979
+ }
980
+ /** Meta LLM Proxy — sponsored-downtime health (ADR-313). */
981
+ async function checkProxySponsoredConsent() {
982
+ try {
983
+ const { funnelStateDir, hasConsent, readRateLimitStatus, lastRecordedEvent } = await import('../funnel/index.js');
984
+ const dir = funnelStateDir();
985
+ const installed = existsSync(join(dir, 'proxy-token'));
986
+ const consented = hasConsent('sponsored-downtime');
987
+ const rateLimited = readRateLimitStatus();
988
+ const lastExhausted = lastRecordedEvent('sponsor_capacity_exhausted');
989
+ if (!installed) {
990
+ return {
991
+ name: 'Meta LLM Proxy (ADR-313)',
992
+ status: 'warn',
993
+ message: 'not installed — no proxy-token found; sponsored-downtime capacity is unavailable',
994
+ fix: 'See cognitum-one/meta-proxy (private) for install instructions',
995
+ };
996
+ }
997
+ const parts = [
998
+ `sponsored consent: ${consented ? 'granted' : 'not granted'}`,
999
+ `rate-limit flag: ${rateLimited.limited ? `set (since ${rateLimited.since})` : 'not set'}`,
1000
+ ];
1001
+ if (lastExhausted)
1002
+ parts.push(`last capacity-exhausted: ${lastExhausted}`);
1003
+ if (rateLimited.limited && !consented) {
1004
+ return {
1005
+ name: 'Meta LLM Proxy (ADR-313)',
1006
+ status: 'warn',
1007
+ message: `${parts.join('; ')} — flagged rate-limited but sponsored capacity isn't enabled`,
1008
+ fix: 'ruflo proxy sponsor-enable --yes',
1009
+ };
1010
+ }
1011
+ return { name: 'Meta LLM Proxy (ADR-313)', status: 'pass', message: parts.join('; ') };
1012
+ }
1013
+ catch (err) {
1014
+ return {
1015
+ name: 'Meta LLM Proxy (ADR-313)',
1016
+ status: 'warn',
1017
+ message: `state unreadable: ${err instanceof Error ? err.message : String(err)}`,
1018
+ fix: 'ruflo proxy sponsor-status',
1019
+ };
1020
+ }
1021
+ }
1022
+ /**
1023
+ * Binary presence + tamper check (ADR-307). Deliberately NEVER spawns the
1024
+ * binary to probe a version — confirmed empirically (2026-07-16) that
1025
+ * `meta-proxy` has no `--version`/`--help` flag and starts the live server
1026
+ * as a side effect of ANY invocation, which a doctor health check must never
1027
+ * do. Version info instead comes from install-manifest.json (written at
1028
+ * install time) and, once running, the proxy's own `/status` endpoint via
1029
+ * checkProxyProcess below.
1030
+ */
1031
+ async function checkProxyBinary() {
1032
+ const NAME = 'Meta LLM Proxy binary (ADR-307)';
1033
+ try {
1034
+ const { proxyBinaryPath, proxyInstallManifestPath } = await import('../proxy/paths.js');
1035
+ const binPath = proxyBinaryPath();
1036
+ if (!existsSync(binPath)) {
1037
+ return { name: NAME, status: 'warn', message: 'not installed', fix: 'ruflo proxy install' };
1038
+ }
1039
+ const manifestPath = proxyInstallManifestPath();
1040
+ if (!existsSync(manifestPath)) {
1041
+ return {
1042
+ name: NAME,
1043
+ status: 'warn',
1044
+ message: 'binary present but no install-manifest.json — provenance unknown (installed outside `ruflo proxy install`?)',
1045
+ fix: 'ruflo proxy update --release <x.y.z>',
1046
+ };
1047
+ }
1048
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
1049
+ const liveSha = createHash('sha256').update(readFileSync(binPath)).digest('hex');
1050
+ if (liveSha !== manifest.sha256) {
1051
+ return {
1052
+ name: NAME,
1053
+ status: 'fail',
1054
+ message: `binary sha256 does not match the recorded install manifest — possible tampering or a manual overwrite (expected ${manifest.sha256.slice(0, 12)}…, got ${liveSha.slice(0, 12)}…)`,
1055
+ fix: 'ruflo proxy update --release <x.y.z>',
1056
+ };
1057
+ }
1058
+ return { name: NAME, status: 'pass', message: `v${manifest.version}, signature-verified at install (${manifest.verifiedAt})` };
1059
+ }
1060
+ catch (err) {
1061
+ return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
1062
+ }
1063
+ }
1064
+ /** PID-file liveness (ADR-307) — mirrors daemon.ts's signal-0 probe pattern. */
1065
+ async function checkProxyProcess() {
1066
+ const NAME = 'Meta LLM Proxy process (ADR-307)';
1067
+ try {
1068
+ const { proxyPidFilePath } = await import('../proxy/paths.js');
1069
+ const pidPath = proxyPidFilePath();
1070
+ if (!existsSync(pidPath)) {
1071
+ return { name: NAME, status: 'warn', message: 'not running (no PID file)', fix: 'ruflo proxy start' };
1072
+ }
1073
+ const pidRaw = readFileSync(pidPath, 'utf-8').trim();
1074
+ const pid = parseInt(pidRaw, 10);
1075
+ if (!Number.isFinite(pid)) {
1076
+ return { name: NAME, status: 'warn', message: `PID file is malformed: ${JSON.stringify(pidRaw)}`, fix: 'ruflo proxy stop && ruflo proxy start' };
1077
+ }
1078
+ try {
1079
+ process.kill(pid, 0); // signal-0 liveness probe — throws if the process is dead
1080
+ }
1081
+ catch {
1082
+ return { name: NAME, status: 'warn', message: `PID file points at ${pid}, which is not running — stale PID file`, fix: 'ruflo proxy start' };
1083
+ }
1084
+ // Live version-compat + data-plane info, ONLY once PID liveness already
1085
+ // confirmed a process is running (never spawns anything — see
1086
+ // checkProxyBinary's comment on why probing via process launch is unsafe).
1087
+ // GET /status shape confirmed against the real v0.1.0 binary:
1088
+ // {"version","data_plane","bind","sponsored_available","proxy_token_valid"}.
1089
+ const { proxyConfigPath, proxyTokenPath, proxyInstallManifestPath } = await import('../proxy/paths.js');
1090
+ const bindMatch = existsSync(proxyConfigPath())
1091
+ ? readFileSync(proxyConfigPath(), 'utf-8').match(/^bind\s*=\s*"([^"]*)"/m)
1092
+ : null;
1093
+ const bind = bindMatch ? bindMatch[1] : '127.0.0.1:11435';
1094
+ let token;
1095
+ try {
1096
+ token = readFileSync(proxyTokenPath(), 'utf-8').trim();
1097
+ }
1098
+ catch {
1099
+ return { name: NAME, status: 'pass', message: `running (pid ${pid}); no proxy-token to query /status` };
1100
+ }
1101
+ try {
1102
+ const controller = new AbortController();
1103
+ const timer = setTimeout(() => controller.abort(), 2000);
1104
+ const resp = await fetch(`http://${bind}/status`, {
1105
+ headers: { authorization: `Bearer ${token}` },
1106
+ signal: controller.signal,
1107
+ });
1108
+ clearTimeout(timer);
1109
+ if (!resp.ok) {
1110
+ return { name: NAME, status: 'warn', message: `running (pid ${pid}); /status returned HTTP ${resp.status}` };
1111
+ }
1112
+ const body = (await resp.json());
1113
+ let versionNote = '';
1114
+ if (existsSync(proxyInstallManifestPath())) {
1115
+ const manifest = JSON.parse(readFileSync(proxyInstallManifestPath(), 'utf-8'));
1116
+ if (manifest.version && body.version && manifest.version !== body.version) {
1117
+ return {
1118
+ name: NAME,
1119
+ status: 'warn',
1120
+ message: `running (pid ${pid}) reports v${body.version}, but the installed binary is v${manifest.version} — a stale process from a previous version?`,
1121
+ fix: 'ruflo proxy stop && ruflo proxy start',
1122
+ };
1123
+ }
1124
+ versionNote = body.version ? ` v${body.version}` : '';
1125
+ }
1126
+ return {
1127
+ name: NAME,
1128
+ status: 'pass',
1129
+ message: `running (pid ${pid})${versionNote}; data plane: ${body.data_plane ?? 'unknown'}`,
1130
+ };
1131
+ }
1132
+ catch (err) {
1133
+ // Live process but /status unreachable (still starting up, or a
1134
+ // network hiccup) — not a failure, PID liveness already passed.
1135
+ return {
1136
+ name: NAME,
1137
+ status: 'pass',
1138
+ message: `running (pid ${pid}); /status unreachable (${err instanceof Error ? err.message : String(err)})`,
1139
+ };
1140
+ }
1141
+ }
1142
+ catch (err) {
1143
+ return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
1144
+ }
1145
+ }
1146
+ /** Non-loopback bind exposure warning (ADR-307's mandated startup warning, surfaced in doctor too). */
1147
+ async function checkProxyBindAddress() {
1148
+ const NAME = 'Meta LLM Proxy bind address (ADR-307)';
1149
+ try {
1150
+ const { proxyConfigPath, isLoopbackBind } = await import('../proxy/paths.js');
1151
+ const cfgPath = proxyConfigPath();
1152
+ if (!existsSync(cfgPath)) {
1153
+ return { name: NAME, status: 'pass', message: 'no config file yet — defaults to loopback-only (127.0.0.1:11435)' };
1154
+ }
1155
+ const raw = readFileSync(cfgPath, 'utf-8');
1156
+ const match = raw.match(/^bind\s*=\s*"([^"]*)"/m);
1157
+ const bind = match ? match[1] : '127.0.0.1:11435';
1158
+ if (!isLoopbackBind(bind)) {
1159
+ return {
1160
+ name: NAME,
1161
+ status: 'warn',
1162
+ message: `bound to non-loopback address ${bind} — this exposes the proxy to your network`,
1163
+ fix: 'Set bind back to 127.0.0.1:<port> in proxy-config.toml unless external exposure is intended',
1164
+ };
1165
+ }
1166
+ return { name: NAME, status: 'pass', message: `loopback-only (${bind})` };
1167
+ }
1168
+ catch (err) {
1169
+ return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
1170
+ }
1171
+ }
1172
+ /** `ruflo auth` health (ADR-306). Warn (never fail) on absence — auth is never required for core functionality. */
1173
+ async function checkAuth() {
1174
+ const NAME = 'Cognitum identity (ADR-306)';
1175
+ try {
1176
+ const { listProfiles } = await import('../auth/state.js');
1177
+ const { domainForScope } = await import('../auth/scopes.js');
1178
+ const { hasConsent } = await import('../funnel/index.js');
1179
+ const { profiles } = listProfiles();
1180
+ if (profiles.length === 0) {
1181
+ return { name: NAME, status: 'warn', message: 'not logged in', fix: 'ruflo auth login' };
1182
+ }
1183
+ let keychainAvailable = 'unknown';
1184
+ try {
1185
+ const sec = await import('@claude-flow/security');
1186
+ keychainAvailable = await (await sec.createKeychainAdapter()).isAvailable();
1187
+ }
1188
+ catch {
1189
+ keychainAvailable = 'unknown'; // security package unavailable — surfaced by checkProxyBinary's sibling concerns, not duplicated here
1190
+ }
1191
+ // Scope-vs-receipt consistency check (ADR-306: "fail-closed... reports").
1192
+ // Unlike every other check in this file, a violation here is a FAIL, not
1193
+ // a warn — a scope present without a matching consent receipt is exactly
1194
+ // the condition ADR-306 says must never silently pass.
1195
+ const violations = [];
1196
+ for (const p of profiles) {
1197
+ for (const scope of p.scopes) {
1198
+ const domain = domainForScope(scope);
1199
+ if (domain && !hasConsent(domain))
1200
+ violations.push(`${p.profile}: ${scope}`);
1201
+ }
1202
+ }
1203
+ if (violations.length > 0) {
1204
+ return {
1205
+ name: NAME,
1206
+ status: 'fail',
1207
+ message: `scope granted without a matching consent receipt: ${violations.join(', ')}`,
1208
+ fix: 'ruflo auth logout && ruflo auth login',
1209
+ };
1210
+ }
1211
+ const names = profiles.map((p) => p.profile).join(', ');
1212
+ const sessionOnly = profiles.filter((p) => !p.keychainRef).map((p) => p.profile);
1213
+ const parts = [`profiles: ${names}`];
1214
+ if (keychainAvailable === false)
1215
+ parts.push('keychain backend unreachable — falling back to session-only tokens');
1216
+ if (sessionOnly.length > 0)
1217
+ parts.push(`session-only (no persisted refresh token): ${sessionOnly.join(', ')}`);
1218
+ return { name: NAME, status: 'pass', message: parts.join('; ') };
1219
+ }
1220
+ catch (err) {
1221
+ return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
1222
+ }
1223
+ }
1224
+ async function checkMetaharnessIntegration() {
1225
+ // Locate plugins dir.
1226
+ //
1227
+ // Pre-#2437 fix this only walked up from `process.cwd()` + checked one
1228
+ // hard-coded `<cwd>/node_modules/@claude-flow/cli/...` candidate. That
1229
+ // missed the two cases users actually run from:
1230
+ // (a) `npx @claude-flow/cli@<tag>` → resolves to a per-version cache
1231
+ // under `~/.npm/_npx/<hash>/node_modules/@claude-flow/cli/...`
1232
+ // (b) `npm install -g @claude-flow/cli` → lives at
1233
+ // `$(npm prefix -g)/lib/node_modules/@claude-flow/cli/...`
1234
+ //
1235
+ // The bulletproof fix: resolve relative to THIS file's own location via
1236
+ // `import.meta.url`. The plugins dir is always a sibling of the package
1237
+ // root regardless of where the package was installed. Walk up from
1238
+ // `dist/src/commands/doctor.js` (built) or `src/commands/doctor.ts`
1239
+ // (dev) until we find a directory containing `plugins/ruflo-metaharness/`.
1240
+ const candidates = [];
1241
+ // Strategy 1: walk up from this module's own URL — covers npx + global install.
1242
+ try {
1243
+ const selfDir = dirname(fileURLToPath(import.meta.url));
1244
+ let q = selfDir;
1245
+ for (let i = 0; i < 8; i++) {
1246
+ candidates.push(join(q, 'plugins', 'ruflo-metaharness'));
1247
+ q = dirname(q);
1248
+ }
1249
+ }
1250
+ catch {
1251
+ // import.meta.url unavailable under some bundlers — fall through to cwd walk.
1252
+ }
1253
+ // Strategy 2: walk up from cwd — covers monorepo dev (running from a sub-package).
1254
+ let p = process.cwd();
1255
+ for (let i = 0; i < 8; i++) {
1256
+ candidates.push(join(p, 'plugins', 'ruflo-metaharness'));
1257
+ p = dirname(p);
1258
+ }
1259
+ // Strategy 3: explicit node_modules path relative to cwd — covers project-local install.
1260
+ candidates.push(join(process.cwd(), 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-metaharness'));
1261
+ let pluginDir = null;
1262
+ for (const c of candidates) {
1263
+ if (existsSync(join(c, 'scripts', '_harness.mjs'))) {
1264
+ pluginDir = c;
1265
+ break;
1266
+ }
1267
+ }
1268
+ if (!pluginDir) {
1269
+ // #2437: MetaHarness is documented as an optional dependency in
1270
+ // optionalDependencies (per ADR-150 architectural constraint #2 —
1271
+ // "Optional in package.json"). A genuinely-absent plugin therefore
1272
+ // warrants WARN, not FAIL — same posture as the runtime path which
1273
+ // returns {degraded: true, exit 0}. FAIL is reserved for misconfigured
1274
+ // installs where the plugin SHOULD be present but is broken.
1275
+ return {
1276
+ name: 'MetaHarness integration (ADR-150)',
1277
+ status: 'warn',
1278
+ message: 'plugins/ruflo-metaharness/ not found — MetaHarness skills will degrade gracefully',
1279
+ fix: 'Optional: install via `npm i -D @metaharness/darwin metaharness` or run `ruflo plugins install ruflo-metaharness`',
1280
+ };
1281
+ }
1282
+ // Required files (iter 36+44 surfaces, +ADR-153 darwin surfaces in v3.13.0)
1283
+ const required = [
1284
+ 'scripts/_harness.mjs',
1285
+ 'scripts/_similarity.mjs',
1286
+ 'scripts/similarity.mjs',
1287
+ 'scripts/_spike-similarity.mjs',
1288
+ // iter 53 surfaces — gated against silent deletion
1289
+ 'scripts/drift-from-history.mjs',
1290
+ 'skills/harness-similarity/SKILL.md',
1291
+ 'skills/harness-drift-from-history/SKILL.md',
1292
+ // ADR-153 Darwin Mode surfaces (v3.13.0) — added to gate against silent deletion
1293
+ 'scripts/_darwin.mjs',
1294
+ 'scripts/evolve.mjs',
1295
+ 'scripts/security-bench.mjs',
1296
+ 'scripts/bench.mjs',
1297
+ 'skills/harness-evolve/SKILL.md',
1298
+ 'skills/harness-security-bench/SKILL.md',
1299
+ 'skills/harness-bench/SKILL.md',
1300
+ ];
1301
+ const missing = required.filter((f) => !existsSync(join(pluginDir, f)));
1302
+ if (missing.length > 0) {
1303
+ return {
1304
+ name: 'MetaHarness integration (ADR-150)',
1305
+ status: 'fail',
1306
+ message: `Missing files: ${missing.join(', ')}`,
1307
+ fix: 'Reinstall ruflo or restore from git: `git checkout HEAD -- plugins/ruflo-metaharness/`',
1308
+ };
1309
+ }
1310
+ // Runtime smoke: import the similarity module and exercise it
1311
+ try {
1312
+ const modPath = join(pluginDir, 'scripts', '_similarity.mjs');
1313
+ const mod = await import(modPath);
1314
+ if (typeof mod.similarity !== 'function') {
1315
+ return {
1316
+ name: 'MetaHarness integration (ADR-150)',
1317
+ status: 'fail',
1318
+ message: '_similarity.mjs does not export similarity()',
1319
+ fix: 'Reinstall ruflo or restore the file from git',
1320
+ };
1321
+ }
1322
+ const result = mod.similarity({}, {});
1323
+ if (typeof result?.overall !== 'number') {
1324
+ return {
1325
+ name: 'MetaHarness integration (ADR-150)',
1326
+ status: 'warn',
1327
+ message: 'similarity() returned unexpected shape; module may be stale',
1328
+ };
1329
+ }
1330
+ // iter 52 — also verify the iter-50 mcp-scan text parser exports
1331
+ // correctly. parseMcpScanText is the shared util both mcp-scan.mjs
1332
+ // and oia-audit.mjs depend on; if it's missing the audit-trend
1333
+ // introduced/cleared diff silently degrades to dead code.
1334
+ //
1335
+ // iter 61 — additionally verify iter-56's async exports
1336
+ // (runHarnessAsync / runMetaharnessAsync). These are the
1337
+ // parallelization primitives oia-audit depends on; if they're
1338
+ // missing, oia-audit's import fails and the whole pipeline breaks.
1339
+ const harnessPath = join(pluginDir, 'scripts', '_harness.mjs');
1340
+ const harnessMod = await import(harnessPath);
1341
+ if (typeof harnessMod.parseMcpScanText !== 'function') {
1342
+ return {
1343
+ name: 'MetaHarness integration (ADR-150)',
1344
+ status: 'fail',
1345
+ message: '_harness.mjs does not export parseMcpScanText (iter 50 — needed by mcp-scan + oia-audit)',
1346
+ fix: 'Reinstall ruflo or restore _harness.mjs from git',
1347
+ };
1348
+ }
1349
+ if (typeof harnessMod.runHarnessAsync !== 'function' || typeof harnessMod.runMetaharnessAsync !== 'function') {
1350
+ return {
1351
+ name: 'MetaHarness integration (ADR-150)',
1352
+ status: 'fail',
1353
+ message: '_harness.mjs missing iter-56 async exports (runHarnessAsync / runMetaharnessAsync) — oia-audit parallelization will fail',
1354
+ fix: 'Reinstall ruflo or restore _harness.mjs from git',
1355
+ };
1356
+ }
1357
+ // Smoke: parser handles empty input gracefully
1358
+ const parsed = harnessMod.parseMcpScanText('');
1359
+ if (!Array.isArray(parsed?.findings)) {
1360
+ return {
1361
+ name: 'MetaHarness integration (ADR-150)',
1362
+ status: 'warn',
1363
+ message: 'parseMcpScanText returned unexpected shape on empty input',
1364
+ };
1365
+ }
1366
+ return {
1367
+ name: 'MetaHarness integration (ADR-150)',
1368
+ status: 'pass',
1369
+ message: 'plugin scripts intact, _similarity.mjs + parseMcpScanText load, smoke OK',
1370
+ };
1371
+ }
1372
+ catch (e) {
1373
+ return {
1374
+ name: 'MetaHarness integration (ADR-150)',
1375
+ status: 'warn',
1376
+ message: `Module import errored: ${e.message.slice(0, 60)}`,
1377
+ };
1378
+ }
1379
+ }
1380
+ async function checkMetaharness() {
1381
+ try {
1382
+ const version = await runCommand('npx -y metaharness@latest --version 2>&1', 15000);
1383
+ // metaharness emits multi-line stdout; parse a version-shaped line.
1384
+ const versionMatch = version.match(/(\d+\.\d+\.\d+)/);
1385
+ if (!versionMatch) {
1386
+ return {
1387
+ name: 'MetaHarness (ADR-150)',
1388
+ status: 'warn',
1389
+ message: 'Installed but version-string not parseable; integration may still work',
1390
+ };
1391
+ }
1392
+ return {
1393
+ name: 'MetaHarness (ADR-150)',
1394
+ status: 'pass',
1395
+ message: `v${versionMatch[1]} — run \`npx ruflo metaharness score\` for the full scorecard`,
1396
+ };
1397
+ }
1398
+ catch {
1399
+ return {
1400
+ name: 'MetaHarness (ADR-150)',
1401
+ status: 'warn',
1402
+ message: 'Not installed — `npx ruflo metaharness *` commands will degrade gracefully',
1403
+ fix: 'npm install --include=optional # to enable the metaharness optional dep',
1404
+ };
1405
+ }
1406
+ }
1407
+ async function checkClaudeCode() {
1408
+ try {
1409
+ const version = await runCommand('claude --version');
1410
+ // Parse version from output like "claude 1.0.0" or "Claude Code v1.0.0"
1411
+ const versionMatch = version.match(/v?(\d+\.\d+\.\d+)/);
1412
+ const versionStr = versionMatch ? `v${versionMatch[1]}` : version;
1413
+ return { name: 'Claude Code CLI', status: 'pass', message: versionStr };
1414
+ }
1415
+ catch {
1416
+ return {
1417
+ name: 'Claude Code CLI',
1418
+ status: 'warn',
1419
+ message: 'Not installed',
1420
+ fix: 'npm install -g @anthropic-ai/claude-code'
1421
+ };
1422
+ }
1423
+ }
1424
+ // Install Claude Code CLI
1425
+ async function installClaudeCode() {
1426
+ try {
1427
+ output.writeln();
1428
+ output.writeln(output.bold('Installing Claude Code CLI...'));
1429
+ execSync('npm install -g @anthropic-ai/claude-code', {
1430
+ encoding: 'utf8',
1431
+ stdio: 'inherit'
1432
+ });
1433
+ output.writeln(output.success('Claude Code CLI installed successfully!'));
1434
+ return true;
1435
+ }
1436
+ catch (error) {
1437
+ output.writeln(output.error('Failed to install Claude Code CLI'));
1438
+ if (error instanceof Error) {
1439
+ output.writeln(output.dim(error.message));
1440
+ }
1441
+ return false;
1442
+ }
1443
+ }
1444
+ // Check agentic-flow v3 integration (filesystem-based to avoid slow WASM/DB init)
1445
+ async function checkAgenticFlow() {
1446
+ try {
1447
+ // Walk common node_modules paths to find agentic-flow/package.json
1448
+ const candidates = [
1449
+ join(process.cwd(), 'node_modules', 'agentic-flow', 'package.json'),
1450
+ join(process.cwd(), '..', 'node_modules', 'agentic-flow', 'package.json'),
1451
+ ];
1452
+ let pkgJsonPath = null;
1453
+ for (const p of candidates) {
1454
+ if (existsSync(p)) {
1455
+ pkgJsonPath = p;
1456
+ break;
1457
+ }
1458
+ }
1459
+ if (!pkgJsonPath) {
1460
+ return {
1461
+ name: 'agentic-flow',
1462
+ status: 'warn',
1463
+ message: 'Not installed (optional — embeddings/routing will use fallbacks)',
1464
+ fix: 'npm install agentic-flow@latest'
1465
+ };
1466
+ }
1467
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
1468
+ const version = pkg.version || 'unknown';
1469
+ const exports = pkg.exports || {};
1470
+ const features = [
1471
+ exports['./reasoningbank'] ? 'ReasoningBank' : null,
1472
+ exports['./router'] ? 'Router' : null,
1473
+ exports['./transport/quic'] ? 'QUIC' : null,
1474
+ ].filter(Boolean);
1475
+ return {
1476
+ name: 'agentic-flow',
1477
+ status: 'pass',
1478
+ message: `v${version} (${features.join(', ')})`
1479
+ };
1480
+ }
1481
+ catch {
1482
+ return { name: 'agentic-flow', status: 'warn', message: 'Check failed' };
1483
+ }
1484
+ }
1485
+ // Check encryption-at-rest status (ADR-096 Phase 5)
1486
+ //
1487
+ // Reports four facets without disclosing the key itself:
1488
+ // 1. Gate status — is CLAUDE_FLOW_ENCRYPT_AT_REST set?
1489
+ // 2. Key resolution — does CLAUDE_FLOW_ENCRYPTION_KEY resolve to a valid
1490
+ // 32-byte key (env-var path only; keychain/passphrase are deferred)?
1491
+ // 3. Key fingerprint — first 16 hex chars of sha256(key) so users can
1492
+ // sanity-check across machines without ever logging the key bytes.
1493
+ // 4. High-tier store presence — for sessions/, terminals/, .swarm/memory.db
1494
+ // report whether on-disk bytes carry the RFE1 magic (encrypted) or not.
1495
+ async function checkEncryptionAtRest() {
1496
+ if (!isEncryptionEnabled()) {
1497
+ return {
1498
+ name: 'Encryption at Rest',
1499
+ status: 'warn',
1500
+ message: 'Off — session/terminal/memory stores are plaintext (mode 0600 only)',
1501
+ fix: 'export CLAUDE_FLOW_ENCRYPT_AT_REST=1 && export CLAUDE_FLOW_ENCRYPTION_KEY=<64-char-hex>',
1502
+ };
1503
+ }
1504
+ // Gate is on — try to resolve the key. Fail-closed if missing or malformed.
1505
+ const rawKey = process.env.CLAUDE_FLOW_ENCRYPTION_KEY;
1506
+ if (!rawKey) {
1507
+ return {
1508
+ name: 'Encryption at Rest',
1509
+ status: 'fail',
1510
+ message: 'Gate is on but CLAUDE_FLOW_ENCRYPTION_KEY is unset (fail-closed)',
1511
+ fix: 'Generate a key: openssl rand -hex 32 → export CLAUDE_FLOW_ENCRYPTION_KEY=<value>',
1512
+ };
1513
+ }
1514
+ let keyFingerprint;
1515
+ try {
1516
+ const key = decodeKey(rawKey);
1517
+ keyFingerprint = createHash('sha256').update(key).digest('hex').slice(0, 16);
1518
+ }
1519
+ catch (err) {
1520
+ return {
1521
+ name: 'Encryption at Rest',
1522
+ status: 'fail',
1523
+ message: `CLAUDE_FLOW_ENCRYPTION_KEY invalid: ${err instanceof Error ? err.message : String(err)}`,
1524
+ fix: 'Provide a 64-char hex or 44-char base64 key (32 bytes)',
1525
+ };
1526
+ }
1527
+ // Check the three high-tier store paths for RFE1 magic
1528
+ const cwd = process.cwd();
1529
+ const stores = [
1530
+ { label: 'sessions/', path: join(cwd, '.claude-flow', 'sessions') },
1531
+ { label: 'terminals', path: join(cwd, '.claude-flow', 'terminals', 'store.json') },
1532
+ { label: 'memory.db', path: join(cwd, '.swarm', 'memory.db') },
1533
+ ];
1534
+ const status = [];
1535
+ for (const s of stores) {
1536
+ if (!existsSync(s.path)) {
1537
+ status.push(`${s.label}=∅`);
1538
+ continue;
1539
+ }
1540
+ try {
1541
+ const stat = statSync(s.path);
1542
+ if (stat.isDirectory()) {
1543
+ // Sessions: probe the first .json file
1544
+ const { readdirSync } = await import('fs');
1545
+ const files = readdirSync(s.path).filter(f => f.endsWith('.json'));
1546
+ if (files.length === 0) {
1547
+ status.push(`${s.label}=∅`);
1548
+ continue;
1549
+ }
1550
+ const first = readFileSync(join(s.path, files[0]));
1551
+ status.push(`${s.label}=${isEncryptedBlob(first) ? 'enc' : 'plain'}`);
1552
+ }
1553
+ else {
1554
+ const buf = readFileSync(s.path);
1555
+ status.push(`${s.label}=${isEncryptedBlob(buf) ? 'enc' : 'plain'}`);
1556
+ }
1557
+ }
1558
+ catch {
1559
+ status.push(`${s.label}=err`);
1560
+ }
1561
+ }
1562
+ return {
1563
+ name: 'Encryption at Rest',
1564
+ status: 'pass',
1565
+ message: `On — key fp:${keyFingerprint}… (${status.join(' ')})`,
1566
+ };
1567
+ }
1568
+ // Format health check result
1569
+ function formatCheck(check) {
1570
+ const icon = check.status === 'pass' ? output.success('✓') :
1571
+ check.status === 'warn' ? output.warning('⚠') :
1572
+ output.error('✗');
1573
+ return `${icon} ${check.name}: ${check.message}`;
1574
+ }
1575
+ // Main doctor command
1576
+ export const doctorCommand = {
1577
+ name: 'doctor',
1578
+ description: 'System diagnostics and health checks',
1579
+ options: [
1580
+ {
1581
+ name: 'fix',
1582
+ short: 'f',
1583
+ // #1791.5 — flag name was misleading: it does NOT auto-apply fixes,
1584
+ // it only prints the suggested commands so the user can run them
1585
+ // themselves. Make that explicit in the help output.
1586
+ description: 'Print suggested fix commands (does not auto-apply — copy/paste them yourself)',
1587
+ type: 'boolean',
1588
+ default: false
1589
+ },
1590
+ {
1591
+ name: 'install',
1592
+ short: 'i',
1593
+ description: 'Auto-install missing dependencies (Claude Code CLI)',
1594
+ type: 'boolean',
1595
+ default: false
1596
+ },
1597
+ {
1598
+ name: 'component',
1599
+ short: 'c',
1600
+ description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, metaharness)',
1601
+ type: 'string'
1602
+ },
1603
+ {
1604
+ name: 'verbose',
1605
+ short: 'v',
1606
+ description: 'Verbose output',
1607
+ type: 'boolean',
1608
+ default: false
1609
+ },
1610
+ {
1611
+ name: 'fix-handles',
1612
+ // Windows-only mitigation for anthropics/claude-code#67888 — Claude Code's
1613
+ // Bash tool spawns cmd.exe/bash.exe without cleaning up child conhost.exe
1614
+ // handles, so a long session accumulates dozens (observed live: 26+
1615
+ // orphaned conhost.exe after a ~4h session). Each holds a kernel object
1616
+ // + ~1MB, and combined with memory pressure this measurably slows the
1617
+ // machine. This flag kills orphan conhost.exe (safe — Windows respawns
1618
+ // on demand). Deliberately does NOT touch cmd.exe/bash.exe — those can
1619
+ // be the invoking shell, and killing them 255's the caller.
1620
+ description: 'Windows only: kill orphaned conhost.exe processes leaked by Claude Code (mitigation for anthropics/claude-code#67888)',
1621
+ type: 'boolean',
1622
+ default: false
1623
+ }
1624
+ ],
1625
+ examples: [
1626
+ { command: 'claude-flow doctor', description: 'Run full health check' },
1627
+ { command: 'claude-flow doctor --fix', description: 'Print suggested fix commands (does not auto-apply)' },
1628
+ { command: 'claude-flow doctor --install', description: 'Auto-install missing dependencies' },
1629
+ { command: 'claude-flow doctor -c version', description: 'Check for stale npx cache' },
1630
+ { command: 'claude-flow doctor -c claude', description: 'Check Claude Code CLI only' },
1631
+ { command: 'claude-flow doctor --fix-handles', description: 'Windows: kill leaked conhost.exe from Claude Code sessions' }
1632
+ ],
1633
+ action: async (ctx) => {
1634
+ const showFix = ctx.flags.fix;
1635
+ const autoInstall = ctx.flags.install;
1636
+ const component = ctx.flags.component;
1637
+ const verbose = ctx.flags.verbose;
1638
+ // Parser camelCases kebab-case flag names — read via `fixHandles`, not `['fix-handles']`.
1639
+ const fixHandles = ctx.flags.fixHandles;
1640
+ // Early-return short-circuit: `--fix-handles` is a targeted mitigation, not
1641
+ // part of the health-check flow. Runs, reports, exits.
1642
+ if (fixHandles) {
1643
+ output.writeln();
1644
+ output.writeln(output.bold('RuFlo Doctor — fix-handles'));
1645
+ output.writeln(output.dim('─'.repeat(50)));
1646
+ output.writeln();
1647
+ if (process.platform !== 'win32') {
1648
+ output.printInfo('--fix-handles is a Windows-only mitigation. On this platform (' + process.platform + '), no action taken.');
1649
+ return { success: true };
1650
+ }
1651
+ const { spawnSync } = await import('child_process');
1652
+ // PowerShell one-liner: read before-count, kill conhost, read after-count, report delta.
1653
+ const psScript = [
1654
+ "$before = (Get-Process conhost -EA SilentlyContinue).Count",
1655
+ "$mem_before = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)",
1656
+ "$killed = 0",
1657
+ "Get-Process conhost -EA SilentlyContinue | ForEach-Object {",
1658
+ " try { Stop-Process -Id $_.Id -Force -EA Stop; $killed++ } catch {}",
1659
+ "}",
1660
+ "Start-Sleep -Seconds 1",
1661
+ "$after = (Get-Process conhost -EA SilentlyContinue).Count",
1662
+ "$mem_after = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)",
1663
+ "Write-Output ('BEFORE_COUNT=' + $before)",
1664
+ "Write-Output ('KILLED=' + $killed)",
1665
+ "Write-Output ('AFTER_COUNT=' + $after)",
1666
+ "Write-Output ('MEM_BEFORE_GB=' + $mem_before)",
1667
+ "Write-Output ('MEM_AFTER_GB=' + $mem_after)",
1668
+ ].join('; ');
1669
+ const res = spawnSync('powershell', ['-NoProfile', '-Command', psScript], {
1670
+ encoding: 'utf-8', timeout: 30000, windowsHide: true,
1671
+ });
1672
+ if (res.status !== 0) {
1673
+ output.printError('PowerShell exited with code ' + res.status);
1674
+ if (res.stderr)
1675
+ output.writeln(output.dim(res.stderr));
1676
+ return { success: false, exitCode: 1 };
1677
+ }
1678
+ const parse = (key) => {
1679
+ const line = (res.stdout || '').split('\n').find((l) => l.startsWith(key + '='));
1680
+ return line ? line.slice(key.length + 1).trim() : '?';
1681
+ };
1682
+ const before = parse('BEFORE_COUNT');
1683
+ const killed = parse('KILLED');
1684
+ const after = parse('AFTER_COUNT');
1685
+ const memBefore = parse('MEM_BEFORE_GB');
1686
+ const memAfter = parse('MEM_AFTER_GB');
1687
+ output.writeln('conhost.exe processes:');
1688
+ output.writeln(' before: ' + before);
1689
+ output.writeln(' killed: ' + output.success(killed));
1690
+ output.writeln(' after: ' + after);
1691
+ output.writeln('');
1692
+ output.writeln('Free RAM:');
1693
+ output.writeln(' before: ' + memBefore + ' GB');
1694
+ output.writeln(' after: ' + memAfter + ' GB');
1695
+ output.writeln('');
1696
+ output.writeln(output.dim('Note: does NOT touch cmd.exe/bash.exe/node.exe — those may be the invoking shell'));
1697
+ output.writeln(output.dim(' or an active MCP server. Kill them manually if you need to.'));
1698
+ output.writeln('');
1699
+ output.writeln(output.dim('Upstream tracking: https://github.com/anthropics/claude-code/issues/67888'));
1700
+ return { success: true };
1701
+ }
1702
+ output.writeln();
1703
+ output.writeln(output.bold('RuFlo Doctor'));
1704
+ output.writeln(output.dim('System diagnostics and health check'));
1705
+ output.writeln(output.dim('─'.repeat(50)));
1706
+ output.writeln();
1707
+ const allChecks = [
1708
+ checkVersionFreshness,
1709
+ checkNodeVersion,
1710
+ checkNpmVersion,
1711
+ checkClaudeCode,
1712
+ checkGit,
1713
+ checkGitRepo,
1714
+ checkConfigFile,
1715
+ checkStaleSettingsNpx, // #2448 — runaway `npx @latest` in statusLine/hooks
1716
+ checkDaemonStatus,
1717
+ checkMemoryDatabase,
1718
+ checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
1719
+ checkApiKeys,
1720
+ checkMcpServers,
1721
+ checkAIDefence, // #1807
1722
+ checkDiskSpace,
1723
+ checkBuildTools,
1724
+ checkAgenticFlow,
1725
+ checkEncryptionAtRest, // ADR-096 Phase 5
1726
+ checkFederationBreaker, // ADR-097 Phase 4
1727
+ checkMetaharness, // ADR-150 — MetaHarness upstream package
1728
+ checkMetaharnessIntegration, // iter 45 — ruflo-side integration layer
1729
+ checkFunnel, // ADR-305 — effective funnel state + deciding precedence source
1730
+ checkProxySponsoredConsent, // ADR-313 — Meta LLM Proxy sponsored-downtime health
1731
+ checkAuth, // ADR-306 — Cognitum identity (warn-only; never fails bare `ruflo doctor`)
1732
+ ];
1733
+ // #2677: `--component memory` now runs the whole memory-health suite,
1734
+ // not just the existence check. Values can be a single check or an
1735
+ // array — expanded at execution time. Stuinfla's report showed the
1736
+ // existence-only check reporting PASS on a 99.97%-empty and even a
1737
+ // SQLite-malformed DB; the array here layers integrity → content →
1738
+ // embedding coverage over the existing existence probe, ordered so
1739
+ // the earliest chain-break is always the first red the user sees.
1740
+ const componentMap = {
1741
+ 'version': checkVersionFreshness,
1742
+ 'freshness': checkVersionFreshness,
1743
+ 'node': checkNodeVersion,
1744
+ 'npm': checkNpmVersion,
1745
+ 'claude': checkClaudeCode,
1746
+ 'config': checkConfigFile,
1747
+ 'stale-settings': checkStaleSettingsNpx, // #2448
1748
+ 'daemon': checkDaemonStatus,
1749
+ 'memory': [
1750
+ checkMemoryDatabase, // existing: exists + statable (unchanged)
1751
+ checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
1752
+ checkMemoryContent, // #2677 check 2: memory_entries content coverage
1753
+ checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
1754
+ ],
1755
+ 'learning': checkLearningBridge, // #2545
1756
+ 'learning-bridge': checkLearningBridge, // #2545
1757
+ 'api': checkApiKeys,
1758
+ 'git': checkGit,
1759
+ 'mcp': checkMcpServers,
1760
+ 'aidefence': checkAIDefence, // #1807
1761
+ 'disk': checkDiskSpace,
1762
+ 'typescript': checkBuildTools,
1763
+ 'agentic-flow': checkAgenticFlow,
1764
+ 'encryption': checkEncryptionAtRest, // ADR-096 Phase 5
1765
+ 'federation': checkFederationBreaker, // ADR-097 Phase 4
1766
+ 'metaharness': checkMetaharness, // ADR-150 — upstream package
1767
+ 'metaharness-integration': checkMetaharnessIntegration, // iter 45 — ruflo-side
1768
+ 'funnel': checkFunnel, // ADR-305
1769
+ // ADR-307 — deep-dive array, same pattern as 'memory' above: the cheap
1770
+ // sponsored-consent check first, then binary/process/bind in the order
1771
+ // a user would actually debug them (is it installed? running? exposed?).
1772
+ 'proxy': [checkProxySponsoredConsent, checkProxyBinary, checkProxyProcess, checkProxyBindAddress],
1773
+ 'auth': checkAuth, // ADR-306
1774
+ };
1775
+ let checksToRun = allChecks;
1776
+ if (component && componentMap[component]) {
1777
+ const entry = componentMap[component];
1778
+ checksToRun = Array.isArray(entry) ? entry : [entry];
1779
+ }
1780
+ const results = [];
1781
+ const fixes = [];
1782
+ // OPTIMIZATION: Run all checks in parallel for 3-5x faster execution
1783
+ const spinner = output.createSpinner({ text: 'Running health checks in parallel...', spinner: 'dots' });
1784
+ spinner.start();
1785
+ try {
1786
+ // Execute all checks concurrently
1787
+ const checkResults = await Promise.allSettled(checksToRun.map(check => check()));
1788
+ spinner.stop();
1789
+ // Process results in order
1790
+ for (const settledResult of checkResults) {
1791
+ if (settledResult.status === 'fulfilled') {
1792
+ const result = settledResult.value;
1793
+ results.push(result);
1794
+ output.writeln(formatCheck(result));
1795
+ if (result.fix && (result.status === 'fail' || result.status === 'warn')) {
1796
+ fixes.push(`${result.name}: ${result.fix}`);
1797
+ }
1798
+ }
1799
+ else {
1800
+ const errorResult = {
1801
+ name: 'Check',
1802
+ status: 'fail',
1803
+ message: settledResult.reason?.message || 'Unknown error'
1804
+ };
1805
+ results.push(errorResult);
1806
+ output.writeln(formatCheck(errorResult));
1807
+ }
1808
+ }
1809
+ }
1810
+ catch (error) {
1811
+ spinner.stop();
1812
+ output.writeln(output.error('Failed to run health checks'));
1813
+ }
1814
+ // #2545: --fix / --install can actually repair the Learning Bridge by
1815
+ // recording the resolver sidecar. When doctor runs via `npx ruflo`, the CLI
1816
+ // CAN resolve its optional @claude-flow/memory dep (it is in the same npx
1817
+ // cache), so writing the sidecar makes the SessionStart hook find it.
1818
+ if ((showFix || autoInstall)) {
1819
+ const lbResult = results.find(r => r.name === 'Learning Bridge');
1820
+ if (lbResult && lbResult.status === 'fail') {
1821
+ const record = recordMemoryPackagePath(process.cwd(), 'doctor');
1822
+ if (record) {
1823
+ const newCheck = await checkLearningBridge();
1824
+ const idx = results.findIndex(r => r.name === 'Learning Bridge');
1825
+ if (idx !== -1)
1826
+ results[idx] = newCheck;
1827
+ const fixIdx = fixes.findIndex(f => f.startsWith('Learning Bridge:'));
1828
+ if (fixIdx !== -1 && newCheck.status === 'pass')
1829
+ fixes.splice(fixIdx, 1);
1830
+ output.writeln(output.success(`Repaired Learning Bridge — wrote .claude-flow/memory-package.json → ${record.distPath}`));
1831
+ output.writeln(formatCheck(newCheck));
1832
+ }
1833
+ }
1834
+ }
1835
+ // Auto-install missing dependencies if requested
1836
+ if (autoInstall) {
1837
+ const claudeCodeResult = results.find(r => r.name === 'Claude Code CLI');
1838
+ if (claudeCodeResult && claudeCodeResult.status !== 'pass') {
1839
+ const installed = await installClaudeCode();
1840
+ if (installed) {
1841
+ // Re-check Claude Code after installation
1842
+ const newCheck = await checkClaudeCode();
1843
+ const idx = results.findIndex(r => r.name === 'Claude Code CLI');
1844
+ if (idx !== -1) {
1845
+ results[idx] = newCheck;
1846
+ // Update fixes list
1847
+ const fixIdx = fixes.findIndex(f => f.startsWith('Claude Code CLI:'));
1848
+ if (fixIdx !== -1 && newCheck.status === 'pass') {
1849
+ fixes.splice(fixIdx, 1);
1850
+ }
1851
+ }
1852
+ output.writeln(formatCheck(newCheck));
1853
+ }
1854
+ }
1855
+ }
1856
+ // Summary
1857
+ const passed = results.filter(r => r.status === 'pass').length;
1858
+ const warnings = results.filter(r => r.status === 'warn').length;
1859
+ const failed = results.filter(r => r.status === 'fail').length;
1860
+ output.writeln();
1861
+ output.writeln(output.dim('─'.repeat(50)));
1862
+ output.writeln();
1863
+ const summaryParts = [
1864
+ output.success(`${passed} passed`),
1865
+ warnings > 0 ? output.warning(`${warnings} warnings`) : null,
1866
+ failed > 0 ? output.error(`${failed} failed`) : null
1867
+ ].filter(Boolean);
1868
+ output.writeln(`Summary: ${summaryParts.join(', ')}`);
1869
+ // Show fixes — #1791.5: header makes it explicit these are commands you
1870
+ // run yourself, not actions doctor took.
1871
+ if (showFix && fixes.length > 0) {
1872
+ output.writeln();
1873
+ output.writeln(output.bold('Suggested commands (run them yourself):'));
1874
+ output.writeln();
1875
+ for (const fix of fixes) {
1876
+ output.writeln(output.dim(` ${fix}`));
1877
+ }
1878
+ }
1879
+ else if (fixes.length > 0 && !showFix) {
1880
+ output.writeln();
1881
+ output.writeln(output.dim(`Run with --fix to see ${fixes.length} suggested command${fixes.length > 1 ? 's' : ''} (does not auto-apply)`));
1882
+ }
1883
+ // Overall result
1884
+ if (failed > 0) {
1885
+ output.writeln();
1886
+ output.writeln(output.error('Some checks failed. Please address the issues above.'));
1887
+ return { success: false, exitCode: 1, data: { passed, warnings, failed, results } };
1888
+ }
1889
+ else if (warnings > 0) {
1890
+ output.writeln();
1891
+ output.writeln(output.warning('All checks passed with some warnings.'));
1892
+ return { success: true, data: { passed, warnings, failed, results } };
1893
+ }
1894
+ else {
1895
+ output.writeln();
1896
+ output.writeln(output.success('All checks passed! System is healthy.'));
1897
+ return { success: true, data: { passed, warnings, failed, results } };
1898
+ }
1899
+ }
1900
+ };
1901
+ export default doctorCommand;
1902
+ //# sourceMappingURL=doctor.js.map