@ashlr/hub 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/CHANGELOG.md +1158 -0
  2. package/LICENSE +21 -0
  3. package/README.md +833 -0
  4. package/bin/ashlr +8 -0
  5. package/dist/api/core.d.ts +28 -0
  6. package/dist/api/core.js +42 -0
  7. package/dist/api/core.js.map +1 -0
  8. package/dist/api/index.d.ts +9 -0
  9. package/dist/api/index.js +9 -0
  10. package/dist/api/index.js.map +1 -0
  11. package/dist/api/plugin.d.ts +12 -0
  12. package/dist/api/plugin.js +13 -0
  13. package/dist/api/plugin.js.map +1 -0
  14. package/dist/api/types.d.ts +9 -0
  15. package/dist/api/types.js +9 -0
  16. package/dist/api/types.js.map +1 -0
  17. package/dist/cli/args.d.ts +20 -0
  18. package/dist/cli/args.js +23 -0
  19. package/dist/cli/args.js.map +1 -0
  20. package/dist/cli/ask.d.ts +22 -0
  21. package/dist/cli/ask.js +240 -0
  22. package/dist/cli/ask.js.map +1 -0
  23. package/dist/cli/audit.d.ts +26 -0
  24. package/dist/cli/audit.js +199 -0
  25. package/dist/cli/audit.js.map +1 -0
  26. package/dist/cli/backlog.d.ts +26 -0
  27. package/dist/cli/backlog.js +322 -0
  28. package/dist/cli/backlog.js.map +1 -0
  29. package/dist/cli/completions.d.ts +18 -0
  30. package/dist/cli/completions.js +156 -0
  31. package/dist/cli/completions.js.map +1 -0
  32. package/dist/cli/daemon.d.ts +31 -0
  33. package/dist/cli/daemon.js +334 -0
  34. package/dist/cli/daemon.js.map +1 -0
  35. package/dist/cli/demo-sandbox.d.ts +78 -0
  36. package/dist/cli/demo-sandbox.js +227 -0
  37. package/dist/cli/demo-sandbox.js.map +1 -0
  38. package/dist/cli/demo.d.ts +76 -0
  39. package/dist/cli/demo.js +285 -0
  40. package/dist/cli/demo.js.map +1 -0
  41. package/dist/cli/digest.d.ts +46 -0
  42. package/dist/cli/digest.js +352 -0
  43. package/dist/cli/digest.js.map +1 -0
  44. package/dist/cli/doctor-init.d.ts +28 -0
  45. package/dist/cli/doctor-init.js +335 -0
  46. package/dist/cli/doctor-init.js.map +1 -0
  47. package/dist/cli/genome.d.ts +62 -0
  48. package/dist/cli/genome.js +1061 -0
  49. package/dist/cli/genome.js.map +1 -0
  50. package/dist/cli/gh.d.ts +27 -0
  51. package/dist/cli/gh.js +322 -0
  52. package/dist/cli/gh.js.map +1 -0
  53. package/dist/cli/goals.d.ts +55 -0
  54. package/dist/cli/goals.js +667 -0
  55. package/dist/cli/goals.js.map +1 -0
  56. package/dist/cli/health.d.ts +56 -0
  57. package/dist/cli/health.js +407 -0
  58. package/dist/cli/health.js.map +1 -0
  59. package/dist/cli/help.d.ts +51 -0
  60. package/dist/cli/help.js +436 -0
  61. package/dist/cli/help.js.map +1 -0
  62. package/dist/cli/inbox.d.ts +28 -0
  63. package/dist/cli/inbox.js +589 -0
  64. package/dist/cli/inbox.js.map +1 -0
  65. package/dist/cli/index.d.ts +41 -0
  66. package/dist/cli/index.js +1208 -0
  67. package/dist/cli/index.js.map +1 -0
  68. package/dist/cli/knowledge.d.ts +33 -0
  69. package/dist/cli/knowledge.js +575 -0
  70. package/dist/cli/knowledge.js.map +1 -0
  71. package/dist/cli/mcp.d.ts +26 -0
  72. package/dist/cli/mcp.js +487 -0
  73. package/dist/cli/mcp.js.map +1 -0
  74. package/dist/cli/models.d.ts +26 -0
  75. package/dist/cli/models.js +435 -0
  76. package/dist/cli/models.js.map +1 -0
  77. package/dist/cli/new.d.ts +32 -0
  78. package/dist/cli/new.js +565 -0
  79. package/dist/cli/new.js.map +1 -0
  80. package/dist/cli/notify.d.ts +20 -0
  81. package/dist/cli/notify.js +188 -0
  82. package/dist/cli/notify.js.map +1 -0
  83. package/dist/cli/onboard.d.ts +85 -0
  84. package/dist/cli/onboard.js +305 -0
  85. package/dist/cli/onboard.js.map +1 -0
  86. package/dist/cli/open.d.ts +38 -0
  87. package/dist/cli/open.js +86 -0
  88. package/dist/cli/open.js.map +1 -0
  89. package/dist/cli/orient.d.ts +14 -0
  90. package/dist/cli/orient.js +110 -0
  91. package/dist/cli/orient.js.map +1 -0
  92. package/dist/cli/picker.d.ts +21 -0
  93. package/dist/cli/picker.js +177 -0
  94. package/dist/cli/picker.js.map +1 -0
  95. package/dist/cli/plugins.d.ts +21 -0
  96. package/dist/cli/plugins.js +394 -0
  97. package/dist/cli/plugins.js.map +1 -0
  98. package/dist/cli/preflight.d.ts +29 -0
  99. package/dist/cli/preflight.js +131 -0
  100. package/dist/cli/preflight.js.map +1 -0
  101. package/dist/cli/pulse.d.ts +26 -0
  102. package/dist/cli/pulse.js +482 -0
  103. package/dist/cli/pulse.js.map +1 -0
  104. package/dist/cli/reflect.d.ts +42 -0
  105. package/dist/cli/reflect.js +381 -0
  106. package/dist/cli/reflect.js.map +1 -0
  107. package/dist/cli/run.d.ts +29 -0
  108. package/dist/cli/run.js +687 -0
  109. package/dist/cli/run.js.map +1 -0
  110. package/dist/cli/sandbox.d.ts +20 -0
  111. package/dist/cli/sandbox.js +345 -0
  112. package/dist/cli/sandbox.js.map +1 -0
  113. package/dist/cli/seams.d.ts +41 -0
  114. package/dist/cli/seams.js +167 -0
  115. package/dist/cli/seams.js.map +1 -0
  116. package/dist/cli/serve.d.ts +25 -0
  117. package/dist/cli/serve.js +249 -0
  118. package/dist/cli/serve.js.map +1 -0
  119. package/dist/cli/ship.d.ts +33 -0
  120. package/dist/cli/ship.js +433 -0
  121. package/dist/cli/ship.js.map +1 -0
  122. package/dist/cli/spec.d.ts +19 -0
  123. package/dist/cli/spec.js +478 -0
  124. package/dist/cli/spec.js.map +1 -0
  125. package/dist/cli/swarm.d.ts +42 -0
  126. package/dist/cli/swarm.js +1365 -0
  127. package/dist/cli/swarm.js.map +1 -0
  128. package/dist/cli/telemetry.d.ts +26 -0
  129. package/dist/cli/telemetry.js +364 -0
  130. package/dist/cli/telemetry.js.map +1 -0
  131. package/dist/cli/tui.d.ts +17 -0
  132. package/dist/cli/tui.js +52 -0
  133. package/dist/cli/tui.js.map +1 -0
  134. package/dist/cli/ui.d.ts +53 -0
  135. package/dist/cli/ui.js +66 -0
  136. package/dist/cli/ui.js.map +1 -0
  137. package/dist/cli/update.d.ts +38 -0
  138. package/dist/cli/update.js +682 -0
  139. package/dist/cli/update.js.map +1 -0
  140. package/dist/cli/vercel.d.ts +20 -0
  141. package/dist/cli/vercel.js +193 -0
  142. package/dist/cli/vercel.js.map +1 -0
  143. package/dist/cli/verify-safety.d.ts +96 -0
  144. package/dist/cli/verify-safety.js +509 -0
  145. package/dist/cli/verify-safety.js.map +1 -0
  146. package/dist/cli/wire.d.ts +22 -0
  147. package/dist/cli/wire.js +205 -0
  148. package/dist/cli/wire.js.map +1 -0
  149. package/dist/core/classify.d.ts +51 -0
  150. package/dist/core/classify.js +450 -0
  151. package/dist/core/classify.js.map +1 -0
  152. package/dist/core/config.d.ts +63 -0
  153. package/dist/core/config.js +474 -0
  154. package/dist/core/config.js.map +1 -0
  155. package/dist/core/daemon/loop.d.ts +74 -0
  156. package/dist/core/daemon/loop.js +618 -0
  157. package/dist/core/daemon/loop.js.map +1 -0
  158. package/dist/core/daemon/state.d.ts +66 -0
  159. package/dist/core/daemon/state.js +197 -0
  160. package/dist/core/daemon/state.js.map +1 -0
  161. package/dist/core/dashboard.d.ts +40 -0
  162. package/dist/core/dashboard.js +463 -0
  163. package/dist/core/dashboard.js.map +1 -0
  164. package/dist/core/digest/build.d.ts +51 -0
  165. package/dist/core/digest/build.js +230 -0
  166. package/dist/core/digest/build.js.map +1 -0
  167. package/dist/core/digest/deliver.d.ts +47 -0
  168. package/dist/core/digest/deliver.js +230 -0
  169. package/dist/core/digest/deliver.js.map +1 -0
  170. package/dist/core/digest/store.d.ts +57 -0
  171. package/dist/core/digest/store.js +223 -0
  172. package/dist/core/digest/store.js.map +1 -0
  173. package/dist/core/doctor-fix.d.ts +21 -0
  174. package/dist/core/doctor-fix.js +440 -0
  175. package/dist/core/doctor-fix.js.map +1 -0
  176. package/dist/core/doctor.d.ts +18 -0
  177. package/dist/core/doctor.js +806 -0
  178. package/dist/core/doctor.js.map +1 -0
  179. package/dist/core/env-bridge.d.ts +64 -0
  180. package/dist/core/env-bridge.js +103 -0
  181. package/dist/core/env-bridge.js.map +1 -0
  182. package/dist/core/genome/capture.d.ts +49 -0
  183. package/dist/core/genome/capture.js +352 -0
  184. package/dist/core/genome/capture.js.map +1 -0
  185. package/dist/core/genome/consolidate.d.ts +38 -0
  186. package/dist/core/genome/consolidate.js +426 -0
  187. package/dist/core/genome/consolidate.js.map +1 -0
  188. package/dist/core/genome/export.d.ts +29 -0
  189. package/dist/core/genome/export.js +102 -0
  190. package/dist/core/genome/export.js.map +1 -0
  191. package/dist/core/genome/playbook.d.ts +33 -0
  192. package/dist/core/genome/playbook.js +320 -0
  193. package/dist/core/genome/playbook.js.map +1 -0
  194. package/dist/core/genome/recall.d.ts +45 -0
  195. package/dist/core/genome/recall.js +293 -0
  196. package/dist/core/genome/recall.js.map +1 -0
  197. package/dist/core/genome/store.d.ts +62 -0
  198. package/dist/core/genome/store.js +711 -0
  199. package/dist/core/genome/store.js.map +1 -0
  200. package/dist/core/git.d.ts +34 -0
  201. package/dist/core/git.js +115 -0
  202. package/dist/core/git.js.map +1 -0
  203. package/dist/core/goals/advance.d.ts +82 -0
  204. package/dist/core/goals/advance.js +263 -0
  205. package/dist/core/goals/advance.js.map +1 -0
  206. package/dist/core/goals/planner.d.ts +58 -0
  207. package/dist/core/goals/planner.js +233 -0
  208. package/dist/core/goals/planner.js.map +1 -0
  209. package/dist/core/goals/store.d.ts +128 -0
  210. package/dist/core/goals/store.js +442 -0
  211. package/dist/core/goals/store.js.map +1 -0
  212. package/dist/core/inbox/apply.d.ts +36 -0
  213. package/dist/core/inbox/apply.js +407 -0
  214. package/dist/core/inbox/apply.js.map +1 -0
  215. package/dist/core/inbox/notify-proposal.d.ts +13 -0
  216. package/dist/core/inbox/notify-proposal.js +21 -0
  217. package/dist/core/inbox/notify-proposal.js.map +1 -0
  218. package/dist/core/inbox/store.d.ts +67 -0
  219. package/dist/core/inbox/store.js +258 -0
  220. package/dist/core/inbox/store.js.map +1 -0
  221. package/dist/core/index-engine.d.ts +62 -0
  222. package/dist/core/index-engine.js +486 -0
  223. package/dist/core/index-engine.js.map +1 -0
  224. package/dist/core/integrations/desktop-notify.d.ts +18 -0
  225. package/dist/core/integrations/desktop-notify.js +42 -0
  226. package/dist/core/integrations/desktop-notify.js.map +1 -0
  227. package/dist/core/integrations/editors.d.ts +50 -0
  228. package/dist/core/integrations/editors.js +216 -0
  229. package/dist/core/integrations/editors.js.map +1 -0
  230. package/dist/core/integrations/github.d.ts +66 -0
  231. package/dist/core/integrations/github.js +332 -0
  232. package/dist/core/integrations/github.js.map +1 -0
  233. package/dist/core/integrations/identity.d.ts +29 -0
  234. package/dist/core/integrations/identity.js +359 -0
  235. package/dist/core/integrations/identity.js.map +1 -0
  236. package/dist/core/integrations/notify.d.ts +23 -0
  237. package/dist/core/integrations/notify.js +96 -0
  238. package/dist/core/integrations/notify.js.map +1 -0
  239. package/dist/core/integrations/vercel.d.ts +37 -0
  240. package/dist/core/integrations/vercel.js +192 -0
  241. package/dist/core/integrations/vercel.js.map +1 -0
  242. package/dist/core/knowledge/ask.d.ts +34 -0
  243. package/dist/core/knowledge/ask.js +325 -0
  244. package/dist/core/knowledge/ask.js.map +1 -0
  245. package/dist/core/knowledge/graph.d.ts +51 -0
  246. package/dist/core/knowledge/graph.js +564 -0
  247. package/dist/core/knowledge/graph.js.map +1 -0
  248. package/dist/core/knowledge/index.d.ts +74 -0
  249. package/dist/core/knowledge/index.js +586 -0
  250. package/dist/core/knowledge/index.js.map +1 -0
  251. package/dist/core/learn/playbooks.d.ts +84 -0
  252. package/dist/core/learn/playbooks.js +241 -0
  253. package/dist/core/learn/playbooks.js.map +1 -0
  254. package/dist/core/learn/reflect.d.ts +87 -0
  255. package/dist/core/learn/reflect.js +435 -0
  256. package/dist/core/learn/reflect.js.map +1 -0
  257. package/dist/core/learn/store.d.ts +51 -0
  258. package/dist/core/learn/store.js +165 -0
  259. package/dist/core/learn/store.js.map +1 -0
  260. package/dist/core/learn/tuning.d.ts +48 -0
  261. package/dist/core/learn/tuning.js +201 -0
  262. package/dist/core/learn/tuning.js.map +1 -0
  263. package/dist/core/lifecycle/scaffold.d.ts +43 -0
  264. package/dist/core/lifecycle/scaffold.js +260 -0
  265. package/dist/core/lifecycle/scaffold.js.map +1 -0
  266. package/dist/core/lifecycle/ship.d.ts +48 -0
  267. package/dist/core/lifecycle/ship.js +513 -0
  268. package/dist/core/lifecycle/ship.js.map +1 -0
  269. package/dist/core/lifecycle/templates.d.ts +20 -0
  270. package/dist/core/lifecycle/templates.js +605 -0
  271. package/dist/core/lifecycle/templates.js.map +1 -0
  272. package/dist/core/mcp-gateway.d.ts +59 -0
  273. package/dist/core/mcp-gateway.js +385 -0
  274. package/dist/core/mcp-gateway.js.map +1 -0
  275. package/dist/core/mcp-native.d.ts +53 -0
  276. package/dist/core/mcp-native.js +507 -0
  277. package/dist/core/mcp-native.js.map +1 -0
  278. package/dist/core/mcp-registry.d.ts +36 -0
  279. package/dist/core/mcp-registry.js +180 -0
  280. package/dist/core/mcp-registry.js.map +1 -0
  281. package/dist/core/observability/budget-alert.d.ts +18 -0
  282. package/dist/core/observability/budget-alert.js +90 -0
  283. package/dist/core/observability/budget-alert.js.map +1 -0
  284. package/dist/core/observability/estimate.d.ts +27 -0
  285. package/dist/core/observability/estimate.js +188 -0
  286. package/dist/core/observability/estimate.js.map +1 -0
  287. package/dist/core/observability/forecast.d.ts +19 -0
  288. package/dist/core/observability/forecast.js +101 -0
  289. package/dist/core/observability/forecast.js.map +1 -0
  290. package/dist/core/observability/governance.d.ts +28 -0
  291. package/dist/core/observability/governance.js +101 -0
  292. package/dist/core/observability/governance.js.map +1 -0
  293. package/dist/core/observability/otlp.d.ts +88 -0
  294. package/dist/core/observability/otlp.js +217 -0
  295. package/dist/core/observability/otlp.js.map +1 -0
  296. package/dist/core/observability/rollup.d.ts +35 -0
  297. package/dist/core/observability/rollup.js +311 -0
  298. package/dist/core/observability/rollup.js.map +1 -0
  299. package/dist/core/observability/telemetry-sink.d.ts +63 -0
  300. package/dist/core/observability/telemetry-sink.js +350 -0
  301. package/dist/core/observability/telemetry-sink.js.map +1 -0
  302. package/dist/core/observability/usage-source.d.ts +60 -0
  303. package/dist/core/observability/usage-source.js +347 -0
  304. package/dist/core/observability/usage-source.js.map +1 -0
  305. package/dist/core/onboard.d.ts +27 -0
  306. package/dist/core/onboard.js +288 -0
  307. package/dist/core/onboard.js.map +1 -0
  308. package/dist/core/orient.d.ts +23 -0
  309. package/dist/core/orient.js +135 -0
  310. package/dist/core/orient.js.map +1 -0
  311. package/dist/core/phantom.d.ts +23 -0
  312. package/dist/core/phantom.js +279 -0
  313. package/dist/core/phantom.js.map +1 -0
  314. package/dist/core/plugins/host-api.d.ts +29 -0
  315. package/dist/core/plugins/host-api.js +113 -0
  316. package/dist/core/plugins/host-api.js.map +1 -0
  317. package/dist/core/plugins/integrity.d.ts +36 -0
  318. package/dist/core/plugins/integrity.js +73 -0
  319. package/dist/core/plugins/integrity.js.map +1 -0
  320. package/dist/core/plugins/manifest.d.ts +31 -0
  321. package/dist/core/plugins/manifest.js +316 -0
  322. package/dist/core/plugins/manifest.js.map +1 -0
  323. package/dist/core/plugins/registry.d.ts +87 -0
  324. package/dist/core/plugins/registry.js +415 -0
  325. package/dist/core/plugins/registry.js.map +1 -0
  326. package/dist/core/plugins/types.d.ts +182 -0
  327. package/dist/core/plugins/types.js +40 -0
  328. package/dist/core/plugins/types.js.map +1 -0
  329. package/dist/core/plugins/wrappers.d.ts +40 -0
  330. package/dist/core/plugins/wrappers.js +229 -0
  331. package/dist/core/plugins/wrappers.js.map +1 -0
  332. package/dist/core/portfolio/backlog.d.ts +40 -0
  333. package/dist/core/portfolio/backlog.js +177 -0
  334. package/dist/core/portfolio/backlog.js.map +1 -0
  335. package/dist/core/portfolio/scanners.d.ts +21 -0
  336. package/dist/core/portfolio/scanners.js +600 -0
  337. package/dist/core/portfolio/scanners.js.map +1 -0
  338. package/dist/core/providers.d.ts +34 -0
  339. package/dist/core/providers.js +250 -0
  340. package/dist/core/providers.js.map +1 -0
  341. package/dist/core/quality/conventions.d.ts +35 -0
  342. package/dist/core/quality/conventions.js +267 -0
  343. package/dist/core/quality/conventions.js.map +1 -0
  344. package/dist/core/quality/fixes.d.ts +56 -0
  345. package/dist/core/quality/fixes.js +209 -0
  346. package/dist/core/quality/fixes.js.map +1 -0
  347. package/dist/core/quality/health.d.ts +69 -0
  348. package/dist/core/quality/health.js +350 -0
  349. package/dist/core/quality/health.js.map +1 -0
  350. package/dist/core/quality/store.d.ts +56 -0
  351. package/dist/core/quality/store.js +195 -0
  352. package/dist/core/quality/store.js.map +1 -0
  353. package/dist/core/readiness.d.ts +112 -0
  354. package/dist/core/readiness.js +431 -0
  355. package/dist/core/readiness.js.map +1 -0
  356. package/dist/core/run/agent-loop.d.ts +40 -0
  357. package/dist/core/run/agent-loop.js +291 -0
  358. package/dist/core/run/agent-loop.js.map +1 -0
  359. package/dist/core/run/budget.d.ts +47 -0
  360. package/dist/core/run/budget.js +113 -0
  361. package/dist/core/run/budget.js.map +1 -0
  362. package/dist/core/run/engines.d.ts +75 -0
  363. package/dist/core/run/engines.js +199 -0
  364. package/dist/core/run/engines.js.map +1 -0
  365. package/dist/core/run/model-manager.d.ts +64 -0
  366. package/dist/core/run/model-manager.js +339 -0
  367. package/dist/core/run/model-manager.js.map +1 -0
  368. package/dist/core/run/orchestrator.d.ts +100 -0
  369. package/dist/core/run/orchestrator.js +1515 -0
  370. package/dist/core/run/orchestrator.js.map +1 -0
  371. package/dist/core/run/provider-client.d.ts +46 -0
  372. package/dist/core/run/provider-client.js +796 -0
  373. package/dist/core/run/provider-client.js.map +1 -0
  374. package/dist/core/run/retry.d.ts +19 -0
  375. package/dist/core/run/retry.js +68 -0
  376. package/dist/core/run/retry.js.map +1 -0
  377. package/dist/core/run/router.d.ts +50 -0
  378. package/dist/core/run/router.js +257 -0
  379. package/dist/core/run/router.js.map +1 -0
  380. package/dist/core/run/self-heal.d.ts +52 -0
  381. package/dist/core/run/self-heal.js +181 -0
  382. package/dist/core/run/self-heal.js.map +1 -0
  383. package/dist/core/run/streaming.d.ts +31 -0
  384. package/dist/core/run/streaming.js +122 -0
  385. package/dist/core/run/streaming.js.map +1 -0
  386. package/dist/core/run/verify.d.ts +30 -0
  387. package/dist/core/run/verify.js +204 -0
  388. package/dist/core/run/verify.js.map +1 -0
  389. package/dist/core/sandbox/audit.d.ts +31 -0
  390. package/dist/core/sandbox/audit.js +169 -0
  391. package/dist/core/sandbox/audit.js.map +1 -0
  392. package/dist/core/sandbox/policy.d.ts +61 -0
  393. package/dist/core/sandbox/policy.js +211 -0
  394. package/dist/core/sandbox/policy.js.map +1 -0
  395. package/dist/core/sandbox/worktree.d.ts +175 -0
  396. package/dist/core/sandbox/worktree.js +673 -0
  397. package/dist/core/sandbox/worktree.js.map +1 -0
  398. package/dist/core/seams/backlog.d.ts +47 -0
  399. package/dist/core/seams/backlog.js +47 -0
  400. package/dist/core/seams/backlog.js.map +1 -0
  401. package/dist/core/seams/daemon-coordinator.d.ts +72 -0
  402. package/dist/core/seams/daemon-coordinator.js +76 -0
  403. package/dist/core/seams/daemon-coordinator.js.map +1 -0
  404. package/dist/core/seams/genome.d.ts +45 -0
  405. package/dist/core/seams/genome.js +53 -0
  406. package/dist/core/seams/genome.js.map +1 -0
  407. package/dist/core/seams/identity.d.ts +40 -0
  408. package/dist/core/seams/identity.js +44 -0
  409. package/dist/core/seams/identity.js.map +1 -0
  410. package/dist/core/seams/inbox.d.ts +60 -0
  411. package/dist/core/seams/inbox.js +66 -0
  412. package/dist/core/seams/inbox.js.map +1 -0
  413. package/dist/core/seams/index.d.ts +20 -0
  414. package/dist/core/seams/index.js +21 -0
  415. package/dist/core/seams/index.js.map +1 -0
  416. package/dist/core/seams/portfolio.d.ts +50 -0
  417. package/dist/core/seams/portfolio.js +61 -0
  418. package/dist/core/seams/portfolio.js.map +1 -0
  419. package/dist/core/seams/registry.d.ts +42 -0
  420. package/dist/core/seams/registry.js +128 -0
  421. package/dist/core/seams/registry.js.map +1 -0
  422. package/dist/core/seams/run-swarm.d.ts +66 -0
  423. package/dist/core/seams/run-swarm.js +74 -0
  424. package/dist/core/seams/run-swarm.js.map +1 -0
  425. package/dist/core/seams/types.d.ts +123 -0
  426. package/dist/core/seams/types.js +35 -0
  427. package/dist/core/seams/types.js.map +1 -0
  428. package/dist/core/spec/spec-store.d.ts +62 -0
  429. package/dist/core/spec/spec-store.js +359 -0
  430. package/dist/core/spec/spec-store.js.map +1 -0
  431. package/dist/core/swarm/gate.d.ts +45 -0
  432. package/dist/core/swarm/gate.js +114 -0
  433. package/dist/core/swarm/gate.js.map +1 -0
  434. package/dist/core/swarm/planner.d.ts +32 -0
  435. package/dist/core/swarm/planner.js +293 -0
  436. package/dist/core/swarm/planner.js.map +1 -0
  437. package/dist/core/swarm/rollback.d.ts +56 -0
  438. package/dist/core/swarm/rollback.js +266 -0
  439. package/dist/core/swarm/rollback.js.map +1 -0
  440. package/dist/core/swarm/runner.d.ts +62 -0
  441. package/dist/core/swarm/runner.js +1263 -0
  442. package/dist/core/swarm/runner.js.map +1 -0
  443. package/dist/core/swarm/sign.d.ts +71 -0
  444. package/dist/core/swarm/sign.js +362 -0
  445. package/dist/core/swarm/sign.js.map +1 -0
  446. package/dist/core/swarm/store.d.ts +52 -0
  447. package/dist/core/swarm/store.js +195 -0
  448. package/dist/core/swarm/store.js.map +1 -0
  449. package/dist/core/tidy.d.ts +32 -0
  450. package/dist/core/tidy.js +354 -0
  451. package/dist/core/tidy.js.map +1 -0
  452. package/dist/core/tools-registry.d.ts +16 -0
  453. package/dist/core/tools-registry.js +308 -0
  454. package/dist/core/tools-registry.js.map +1 -0
  455. package/dist/core/types.d.ts +2545 -0
  456. package/dist/core/types.js +9 -0
  457. package/dist/core/types.js.map +1 -0
  458. package/dist/core/web/api.d.ts +53 -0
  459. package/dist/core/web/api.js +698 -0
  460. package/dist/core/web/api.js.map +1 -0
  461. package/dist/core/web/public/app.js +1906 -0
  462. package/dist/core/web/public/index.html +721 -0
  463. package/dist/core/web/public/styles.css +2007 -0
  464. package/dist/core/web/server.d.ts +18 -0
  465. package/dist/core/web/server.js +122 -0
  466. package/dist/core/web/server.js.map +1 -0
  467. package/dist/core/web/static.d.ts +18 -0
  468. package/dist/core/web/static.js +122 -0
  469. package/dist/core/web/static.js.map +1 -0
  470. package/dist/tui/app.d.ts +33 -0
  471. package/dist/tui/app.js +350 -0
  472. package/dist/tui/app.js.map +1 -0
  473. package/dist/tui/render.d.ts +20 -0
  474. package/dist/tui/render.js +558 -0
  475. package/dist/tui/render.js.map +1 -0
  476. package/package.json +80 -0
  477. package/schema/config.schema.json +223 -0
@@ -0,0 +1,1365 @@
1
+ /**
2
+ * `ashlr swarm` and `ashlr swarms` CLI commands.
3
+ *
4
+ * `ashlr swarm "<goal>" | <specId> [--budget N] [--parallel N] [--background]
5
+ * [--resume <id>] [--dry-run] [--allow-cloud] [--project <path>] [--json] [--no-capture]`
6
+ *
7
+ * Subcommands:
8
+ * `ashlr swarm show <id>` — print a saved swarm in detail.
9
+ * `ashlr swarm verify <id>` — verify all task output signatures (M17).
10
+ * `ashlr swarm approve <id>` — explicit human resume of a needs-approval swarm (M17).
11
+ * `ashlr swarm rollback <id>` — restore a project to its pre-swarm git state (M17).
12
+ *
13
+ * `ashlr swarms [--json]` — list persisted swarms.
14
+ *
15
+ * Exit codes:
16
+ * 0 success
17
+ * 1 swarm failed / aborted / not-found / verify-fail / rollback-refused
18
+ * 2 bad usage
19
+ * 3 recursion guard (ASHLR_IN_SWARM is set)
20
+ */
21
+ import * as readline from 'node:readline';
22
+ import { pad, makeColors, isTty, isStderrTty } from './ui.js';
23
+ import { parsePositiveInt } from './args.js';
24
+ // ---------------------------------------------------------------------------
25
+ // ANSI helpers (non-TTY safe)
26
+ // ---------------------------------------------------------------------------
27
+ const { bold, dim, red, green, yellow, cyan, gray } = makeColors(isTty());
28
+ const seCol = makeColors(isStderrTty());
29
+ // ---------------------------------------------------------------------------
30
+ // Lazy imports — core/swarm modules built by other M12/M17 agents
31
+ // ---------------------------------------------------------------------------
32
+ async function importConfig() {
33
+ return import('../core/config.js');
34
+ }
35
+ async function importRunner() {
36
+ return import('../core/swarm/runner.js');
37
+ }
38
+ async function importStore() {
39
+ return import('../core/swarm/store.js');
40
+ }
41
+ async function importSpecStore() {
42
+ return import('../core/spec/spec-store.js');
43
+ }
44
+ /**
45
+ * Lazily import streaming.ts (M11). Falls back gracefully to stubs if not
46
+ * present so the CLI can run without the streaming module.
47
+ */
48
+ async function importStreaming() {
49
+ try {
50
+ return await import('../core/run/streaming.js');
51
+ }
52
+ catch {
53
+ const noop = () => (_e) => { };
54
+ return { nullSink: noop, makeCliSink: noop };
55
+ }
56
+ }
57
+ async function importSign() {
58
+ try {
59
+ return await import('../core/swarm/sign.js');
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function importRollback() {
66
+ try {
67
+ return await import('../core/swarm/rollback.js');
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ function parseSwarmArgs(args) {
74
+ // Handle M17 subcommands: verify, approve, rollback
75
+ if (args[0] === 'verify') {
76
+ const subId = args[1];
77
+ if (!subId) {
78
+ return {
79
+ subcommand: 'run',
80
+ background: false,
81
+ dryRun: false,
82
+ allowCloud: false,
83
+ json: false,
84
+ noCapture: false,
85
+ yes: false,
86
+ force: false,
87
+ usageError: 'Usage: ashlr swarm verify <id>',
88
+ };
89
+ }
90
+ return {
91
+ subcommand: 'verify',
92
+ subId,
93
+ background: false,
94
+ dryRun: false,
95
+ allowCloud: false,
96
+ json: false,
97
+ noCapture: false,
98
+ yes: false,
99
+ force: false,
100
+ };
101
+ }
102
+ if (args[0] === 'approve') {
103
+ const subId = args[1];
104
+ if (!subId) {
105
+ return {
106
+ subcommand: 'run',
107
+ background: false,
108
+ dryRun: false,
109
+ allowCloud: false,
110
+ json: false,
111
+ noCapture: false,
112
+ yes: false,
113
+ force: false,
114
+ usageError: 'Usage: ashlr swarm approve <id>',
115
+ };
116
+ }
117
+ return {
118
+ subcommand: 'approve',
119
+ subId,
120
+ background: false,
121
+ dryRun: false,
122
+ allowCloud: false,
123
+ json: false,
124
+ noCapture: false,
125
+ yes: false,
126
+ force: false,
127
+ };
128
+ }
129
+ if (args[0] === 'rollback') {
130
+ const subId = args[1];
131
+ if (!subId) {
132
+ return {
133
+ subcommand: 'run',
134
+ background: false,
135
+ dryRun: false,
136
+ allowCloud: false,
137
+ json: false,
138
+ noCapture: false,
139
+ yes: false,
140
+ force: false,
141
+ usageError: 'Usage: ashlr swarm rollback <id> [--yes] [--force]',
142
+ };
143
+ }
144
+ const rest = args.slice(2);
145
+ const yes = rest.includes('--yes');
146
+ const force = rest.includes('--force');
147
+ const unknown = rest.find(a => a !== '--yes' && a !== '--force');
148
+ if (unknown) {
149
+ return {
150
+ subcommand: 'run',
151
+ background: false,
152
+ dryRun: false,
153
+ allowCloud: false,
154
+ json: false,
155
+ noCapture: false,
156
+ yes: false,
157
+ force: false,
158
+ usageError: `unknown flag for rollback: ${unknown}`,
159
+ };
160
+ }
161
+ return {
162
+ subcommand: 'rollback',
163
+ subId,
164
+ background: false,
165
+ dryRun: false,
166
+ allowCloud: false,
167
+ json: false,
168
+ noCapture: false,
169
+ yes,
170
+ force,
171
+ };
172
+ }
173
+ // Handle `swarm show <id>` subcommand
174
+ if (args[0] === 'show') {
175
+ const showId = args[1];
176
+ if (!showId) {
177
+ return {
178
+ subcommand: 'run',
179
+ background: false,
180
+ dryRun: false,
181
+ allowCloud: false,
182
+ json: false,
183
+ noCapture: false,
184
+ yes: false,
185
+ force: false,
186
+ usageError: 'Usage: ashlr swarm show <id>',
187
+ };
188
+ }
189
+ return {
190
+ subcommand: 'show',
191
+ showId,
192
+ background: false,
193
+ dryRun: false,
194
+ allowCloud: false,
195
+ json: false,
196
+ noCapture: false,
197
+ yes: false,
198
+ force: false,
199
+ };
200
+ }
201
+ const result = {
202
+ subcommand: 'run',
203
+ background: false,
204
+ dryRun: false,
205
+ allowCloud: false,
206
+ json: false,
207
+ noCapture: false,
208
+ yes: false,
209
+ force: false,
210
+ };
211
+ let i = 0;
212
+ while (i < args.length) {
213
+ const arg = args[i];
214
+ if (arg === '--background' || arg === '--bg') {
215
+ result.background = true;
216
+ i++;
217
+ }
218
+ else if (arg === '--dry-run') {
219
+ result.dryRun = true;
220
+ i++;
221
+ }
222
+ else if (arg === '--estimate') {
223
+ result.estimate = true;
224
+ i++;
225
+ }
226
+ else if (arg === '--allow-cloud') {
227
+ result.allowCloud = true;
228
+ i++;
229
+ }
230
+ else if (arg === '--json') {
231
+ result.json = true;
232
+ i++;
233
+ }
234
+ else if (arg === '--budget') {
235
+ const parsed = parsePositiveInt('budget', args[++i]);
236
+ if ('error' in parsed) {
237
+ result.usageError = parsed.error;
238
+ return result;
239
+ }
240
+ result.budget = parsed.n;
241
+ i++;
242
+ }
243
+ else if (arg === '--parallel') {
244
+ const parsed = parsePositiveInt('parallel', args[++i]);
245
+ if ('error' in parsed) {
246
+ result.usageError = parsed.error;
247
+ return result;
248
+ }
249
+ result.parallel = parsed.n;
250
+ i++;
251
+ }
252
+ else if (arg === '--resume') {
253
+ const val = args[++i];
254
+ if (!val) {
255
+ result.usageError = '--resume requires a swarm id';
256
+ return result;
257
+ }
258
+ result.resumeId = val;
259
+ i++;
260
+ }
261
+ else if (arg === '--no-capture') {
262
+ result.noCapture = true;
263
+ i++;
264
+ }
265
+ else if (arg === '--over-budget') {
266
+ // M19 spend-governance escape hatch — proceed even when over the period cap.
267
+ result.overBudget = true;
268
+ i++;
269
+ }
270
+ else if (arg === '--project') {
271
+ const val = args[++i];
272
+ if (!val) {
273
+ result.usageError = '--project requires a path';
274
+ return result;
275
+ }
276
+ result.project = val;
277
+ i++;
278
+ }
279
+ else if (!arg.startsWith('--')) {
280
+ // positional = goal or specId
281
+ if (result.goalOrSpecId !== undefined) {
282
+ result.usageError = `unexpected positional argument: ${arg}`;
283
+ return result;
284
+ }
285
+ result.goalOrSpecId = arg;
286
+ i++;
287
+ }
288
+ else {
289
+ result.usageError = `unknown flag: ${arg}`;
290
+ return result;
291
+ }
292
+ }
293
+ // goal/specId is required unless --resume is set
294
+ if (!result.goalOrSpecId && !result.resumeId) {
295
+ result.usageError =
296
+ 'Usage: ashlr swarm "<goal>" | <specId> [--budget N] [--parallel N]\n' +
297
+ ' [--background] [--resume <id>] [--dry-run] [--allow-cloud]\n' +
298
+ ' [--project <path>] [--json] [--no-capture]\n' +
299
+ ' ashlr swarm show <id>\n' +
300
+ ' ashlr swarm verify <id>\n' +
301
+ ' ashlr swarm approve <id>\n' +
302
+ ' ashlr swarm rollback <id> [--yes] [--force]';
303
+ }
304
+ return result;
305
+ }
306
+ // ---------------------------------------------------------------------------
307
+ // Formatting helpers
308
+ // ---------------------------------------------------------------------------
309
+ function relativeTime(iso) {
310
+ const ms = Date.now() - new Date(iso).getTime();
311
+ const s = Math.floor(ms / 1000);
312
+ const m = Math.floor(s / 60);
313
+ const h = Math.floor(m / 60);
314
+ const d = Math.floor(h / 24);
315
+ if (d > 0)
316
+ return `${d}d ago`;
317
+ if (h > 0)
318
+ return `${h}h ago`;
319
+ if (m > 0)
320
+ return `${m}m ago`;
321
+ return `${s}s ago`;
322
+ }
323
+ function swarmStatusColor(status) {
324
+ switch (status) {
325
+ case 'done': return green(status);
326
+ case 'running': return cyan(status);
327
+ case 'planning': return cyan(status);
328
+ case 'aborted': return yellow(status);
329
+ case 'failed': return red(status);
330
+ case 'needs-approval': return yellow('needs-approval');
331
+ default: return String(status);
332
+ }
333
+ }
334
+ function taskStatusColor(status) {
335
+ switch (status) {
336
+ case 'done': return green(status);
337
+ case 'running': return cyan(status);
338
+ case 'failed': return red(status);
339
+ case 'pending': return gray(status);
340
+ case 'skipped': return dim(status);
341
+ default: return String(status);
342
+ }
343
+ }
344
+ function formatDuration(createdAt, updatedAt) {
345
+ const ms = new Date(updatedAt).getTime() - new Date(createdAt).getTime();
346
+ if (ms < 1000)
347
+ return `${ms}ms`;
348
+ if (ms < 60_000)
349
+ return `${(ms / 1000).toFixed(1)}s`;
350
+ const mins = Math.floor(ms / 60_000);
351
+ const secs = Math.floor((ms % 60_000) / 1000);
352
+ return `${mins}m ${secs}s`;
353
+ }
354
+ // ---------------------------------------------------------------------------
355
+ // Swarm summary renderer
356
+ // ---------------------------------------------------------------------------
357
+ function printSwarmSummary(swarm) {
358
+ const { id, goal, status, usage, budget, tasks, result, createdAt, updatedAt, parallel, specId, project, escalations, rollback, } = swarm;
359
+ const duration = formatDuration(createdAt, updatedAt);
360
+ const totalTokens = usage.tokensIn + usage.tokensOut;
361
+ const doneTasks = tasks.filter(t => t.status === 'done').length;
362
+ const failedTasks = tasks.filter(t => t.status === 'failed').length;
363
+ const isLocal = usage.estCostUsd === 0;
364
+ console.log('');
365
+ console.log(bold(' ashlr swarm') + gray(` — ${id}`));
366
+ console.log('');
367
+ console.log(` ${bold('Goal:')} ${goal}`);
368
+ if (specId)
369
+ console.log(` ${bold('Spec ID:')} ${dim(specId)}`);
370
+ if (project)
371
+ console.log(` ${bold('Project:')} ${dim(project)}`);
372
+ console.log(` ${bold('Status:')} ${swarmStatusColor(status)}` +
373
+ (status === 'aborted' ? yellow(' — budget/limit reached') : '') +
374
+ (status === 'needs-approval'
375
+ ? yellow(' — paused at escalation gate; run: ashlr swarm approve ' + id)
376
+ : ''));
377
+ console.log(` ${bold('Parallel:')} ${parallel} ${dim('tasks concurrently (BUILD phase)')}`);
378
+ console.log(` ${bold('Duration:')} ${duration}`);
379
+ console.log(` ${bold('Progress:')} ${doneTasks}/${tasks.length} tasks done` +
380
+ (failedTasks > 0 ? ` ${red(`${failedTasks} failed`)}` : ''));
381
+ console.log('');
382
+ // ── Escalations (M17) ────────────────────────────────────────────────────
383
+ if (escalations && escalations.length > 0) {
384
+ console.log(` ${bold(yellow('Escalations:'))} ${escalations.length} gate trip(s)`);
385
+ for (const esc of escalations) {
386
+ const who = esc.taskId ? dim(`task ${esc.taskId}`) : dim('swarm-level');
387
+ console.log(` ${yellow('!')} ${bold(esc.kind)} ${who} ${esc.detail}`);
388
+ console.log(` ${dim(esc.ts)}`);
389
+ }
390
+ console.log('');
391
+ }
392
+ // ── Rollback snapshot (M17) ──────────────────────────────────────────────
393
+ if (rollback) {
394
+ const snapLabel = rollback.isRepo
395
+ ? `${dim(rollback.head ?? 'unknown')}${rollback.dirty
396
+ ? yellow(' (dirty — stash: ' + (rollback.stashRef ?? 'none') + ')')
397
+ : ''}`
398
+ : dim('(not a git repo)');
399
+ console.log(` ${bold('Rollback:')} ${snapLabel}`);
400
+ if (rollback.isRepo) {
401
+ console.log(` ${dim('restore with: ashlr swarm rollback ' + id)}`);
402
+ }
403
+ console.log('');
404
+ }
405
+ // ── Plan / tasks table ───────────────────────────────────────────────────
406
+ if (tasks.length > 0) {
407
+ const phases = ['scaffold', 'build', 'integrate', 'verify', 'review'];
408
+ for (const phase of phases) {
409
+ const phaseTasks = tasks.filter(t => t.phase === phase);
410
+ if (phaseTasks.length === 0)
411
+ continue;
412
+ const idW = Math.max(4, ...phaseTasks.map(t => t.id.length));
413
+ const statusW = 8;
414
+ const tokW = 7;
415
+ const sigW = 4; // sig/----
416
+ const goalW = 40;
417
+ console.log(` ${bold(phase.toUpperCase())} ${dim(`(${phaseTasks.length} task${phaseTasks.length !== 1 ? 's' : ''})`)}`);
418
+ console.log('');
419
+ console.log(` ${bold(pad('ID', idW))} ${bold(pad('Status', statusW))} ` +
420
+ `${bold(pad('Tokens', tokW, 'right'))} ${bold(pad('Sig', sigW))} ${bold('Goal')}`);
421
+ console.log(` ${'─'.repeat(idW)} ${'─'.repeat(statusW)} ${'─'.repeat(tokW)} ${'─'.repeat(sigW)} ${'─'.repeat(goalW)}`);
422
+ for (const t of phaseTasks) {
423
+ const tok = t.usage ? String(t.usage.tokensIn + t.usage.tokensOut) : '—';
424
+ // Find matching spec goal via plan
425
+ const specTask = swarm.plan.tasks.find(s => s.id === t.id);
426
+ const taskGoal = specTask?.goal ?? '';
427
+ const goalTrunc = taskGoal.length > goalW ? taskGoal.slice(0, goalW - 1) + '…' : taskGoal;
428
+ const errorNote = t.error ? red(` ✗ ${t.error.slice(0, 40)}`) : '';
429
+ // Signature indicator
430
+ const sigIndicator = t.signature ? dim('sig') : dim('----');
431
+ console.log(` ${pad(dim(t.id), idW)} ${pad(taskStatusColor(t.status), statusW)} ` +
432
+ `${pad(tok, tokW, 'right')} ${pad(sigIndicator, sigW)} ${goalTrunc}${errorNote}`);
433
+ }
434
+ console.log('');
435
+ }
436
+ }
437
+ // ── Result ───────────────────────────────────────────────────────────────
438
+ if (result) {
439
+ console.log(` ${bold('Result:')}`);
440
+ for (const line of result.split('\n')) {
441
+ console.log(` ${line}`);
442
+ }
443
+ console.log('');
444
+ }
445
+ // ── Usage / cost summary ─────────────────────────────────────────────────
446
+ const cloudNote = isLocal ? green('local — $0.00') : yellow('cloud provider');
447
+ console.log(` ${bold('Usage:')}`);
448
+ console.log(` Tokens in: ${usage.tokensIn.toLocaleString()}`);
449
+ console.log(` Tokens out: ${usage.tokensOut.toLocaleString()}`);
450
+ console.log(` Total: ${totalTokens.toLocaleString()} / ${budget.maxTokens.toLocaleString()} max`);
451
+ console.log(` Steps: ${usage.steps}`);
452
+ console.log(` Est. cost: ${usage.estCostUsd > 0 ? '$' + usage.estCostUsd.toFixed(6) : '$0.00'} ${dim('·')} ${cloudNote}`);
453
+ console.log('');
454
+ // ── Resume / approve hint ─────────────────────────────────────────────────
455
+ if (status === 'aborted' || status === 'failed') {
456
+ console.log(` ${yellow('Tip:')} resume this swarm with ${bold(`ashlr swarm --resume ${id}`)}`);
457
+ console.log('');
458
+ }
459
+ if (status === 'needs-approval') {
460
+ console.log(` ${yellow('Action required:')} this swarm is paused at an escalation gate.`);
461
+ console.log(` Review the escalations above, then run ${bold(`ashlr swarm approve ${id}`)} to resume.`);
462
+ console.log('');
463
+ }
464
+ // ── Show hint ────────────────────────────────────────────────────────────
465
+ console.log(dim(` Swarm ID: ${id} | ashlr swarm show ${id}`));
466
+ console.log('');
467
+ }
468
+ // ---------------------------------------------------------------------------
469
+ // Dry-run plan printer
470
+ // ---------------------------------------------------------------------------
471
+ function printDryRunPlan(plan, opts) {
472
+ if (opts.json) {
473
+ process.stdout.write(JSON.stringify(plan, null, 2) + '\n');
474
+ return;
475
+ }
476
+ const phases = ['scaffold', 'build', 'integrate', 'verify', 'review'];
477
+ const totalTasks = plan.tasks.length;
478
+ console.log('');
479
+ console.log(bold(' ashlr swarm') + gray(' — dry-run plan (no execution)'));
480
+ console.log('');
481
+ console.log(` ${bold('Goal:')} ${plan.goal}`);
482
+ if (plan.specId)
483
+ console.log(` ${bold('Spec:')} ${dim(plan.specId)}`);
484
+ console.log(` ${bold('Total tasks:')} ${totalTasks}`);
485
+ console.log('');
486
+ for (const phase of phases) {
487
+ const phaseTasks = plan.tasks.filter(t => t.phase === phase);
488
+ if (phaseTasks.length === 0)
489
+ continue;
490
+ console.log(` ${bold(cyan(phase.toUpperCase()))} ${dim(`(${phaseTasks.length})`)}`);
491
+ for (const t of phaseTasks) {
492
+ const depStr = t.deps.length > 0 ? gray(` [deps: ${t.deps.join(', ')}]`) : '';
493
+ const goalTrunc = t.goal.length > 70 ? t.goal.slice(0, 69) + '…' : t.goal;
494
+ console.log(` ${dim(t.id)} ${goalTrunc}${depStr}`);
495
+ }
496
+ console.log('');
497
+ }
498
+ console.log(dim(' Run without --dry-run to execute this plan.'));
499
+ console.log('');
500
+ }
501
+ // ---------------------------------------------------------------------------
502
+ // `swarm show <id>`
503
+ // ---------------------------------------------------------------------------
504
+ async function cmdSwarmShow(id, jsonMode) {
505
+ let store;
506
+ try {
507
+ store = await importStore();
508
+ }
509
+ catch (err) {
510
+ process.stderr.write(red('error: ') + 'Failed to load swarm store (M12 module not yet built): ' +
511
+ (err instanceof Error ? err.message : String(err)) + '\n');
512
+ return 1;
513
+ }
514
+ const swarm = store.loadSwarm(id);
515
+ if (!swarm) {
516
+ const msg = `Swarm not found: ${id}`;
517
+ if (jsonMode) {
518
+ process.stdout.write(JSON.stringify({ error: msg }) + '\n');
519
+ }
520
+ else {
521
+ process.stderr.write(red('error: ') + msg + '\n');
522
+ }
523
+ return 1;
524
+ }
525
+ if (jsonMode) {
526
+ process.stdout.write(JSON.stringify(swarm, null, 2) + '\n');
527
+ return 0;
528
+ }
529
+ printSwarmSummary(swarm);
530
+ return 0;
531
+ }
532
+ // ---------------------------------------------------------------------------
533
+ // M17: `swarm verify <id>`
534
+ // ---------------------------------------------------------------------------
535
+ /**
536
+ * Verify all task output signatures in a swarm.
537
+ *
538
+ * For each task that has a signature, calls verifyOutput and prints a
539
+ * per-task PASS/FAIL table. Exit 0 if all signatures valid, 1 if any
540
+ * fail or the swarm is not found.
541
+ */
542
+ async function cmdSwarmVerify(id) {
543
+ // Load store
544
+ let store;
545
+ try {
546
+ store = await importStore();
547
+ }
548
+ catch (err) {
549
+ process.stderr.write(red('error: ') + 'Failed to load swarm store: ' +
550
+ (err instanceof Error ? err.message : String(err)) + '\n');
551
+ return 1;
552
+ }
553
+ const swarm = store.loadSwarm(id);
554
+ if (!swarm) {
555
+ process.stderr.write(red('error: ') + `Swarm not found: ${id}\n`);
556
+ return 1;
557
+ }
558
+ // Load config (needed for verifyOutput key resolution)
559
+ let cfg;
560
+ try {
561
+ const { loadConfig } = await importConfig();
562
+ cfg = loadConfig();
563
+ }
564
+ catch (err) {
565
+ process.stderr.write(red('error: ') + 'Failed to load config: ' +
566
+ (err instanceof Error ? err.message : String(err)) + '\n');
567
+ return 1;
568
+ }
569
+ // Load sign module (M17 — may not yet be built)
570
+ const signMod = await importSign();
571
+ const signedTasks = swarm.tasks.filter(t => t.signature);
572
+ const unsignedTasks = swarm.tasks.filter(t => !t.signature && t.status === 'done');
573
+ console.log('');
574
+ console.log(bold(' ashlr swarm verify') + gray(` — ${id}`));
575
+ console.log('');
576
+ console.log(` ${bold('Goal:')} ${swarm.goal}`);
577
+ console.log(` ${bold('Status:')} ${swarmStatusColor(swarm.status)}`);
578
+ console.log(` ${bold('Tasks:')} ${swarm.tasks.length} total, ` +
579
+ `${signedTasks.length} signed, ${unsignedTasks.length} done-but-unsigned`);
580
+ console.log('');
581
+ if (swarm.tasks.length === 0) {
582
+ console.log(` ${dim('No tasks in this swarm.')}`);
583
+ console.log('');
584
+ return 0;
585
+ }
586
+ if (!signMod) {
587
+ // M17 sign module not yet built — report all as unverifiable
588
+ process.stderr.write(yellow('warn: ') +
589
+ 'sign module (core/swarm/sign.ts) not available — cannot verify signatures.\n' +
590
+ ' All tasks are reported as UNSIGNED.\n');
591
+ }
592
+ const idW = Math.max(6, ...swarm.tasks.map(t => t.id.length));
593
+ const statusW = 8;
594
+ const verW = 6; // PASS /FAIL /SKIP /UNSIGN
595
+ const algW = 12;
596
+ console.log(` ${bold(pad('Task ID', idW))} ${bold(pad('Status', statusW))} ` +
597
+ `${bold(pad('Verify', verW))} ${bold(pad('Algorithm', algW))} ${bold('Detail')}`);
598
+ console.log(` ${'─'.repeat(idW)} ${'─'.repeat(statusW)} ${'─'.repeat(verW)} ${'─'.repeat(algW)} ${'─'.repeat(40)}`);
599
+ let anyFail = false;
600
+ for (const task of swarm.tasks) {
601
+ const taskStatus = taskStatusColor(task.status);
602
+ if (!task.signature) {
603
+ // No signature — not signed (pre-M17 swarm or non-done task)
604
+ const verLabel = task.status === 'done' ? yellow('UNSIGN') : dim(' N/A ');
605
+ console.log(` ${pad(dim(task.id), idW)} ${pad(taskStatus, statusW)} ` +
606
+ `${pad(verLabel, verW)} ${pad(dim('—'), algW)} ${dim('no signature recorded')}`);
607
+ continue;
608
+ }
609
+ const sig = task.signature;
610
+ if (!signMod) {
611
+ // Module unavailable — cannot verify
612
+ console.log(` ${pad(dim(task.id), idW)} ${pad(taskStatus, statusW)} ` +
613
+ `${pad(yellow('SKIP '), verW)} ${pad(dim(sig.alg), algW)} ${dim('sign module unavailable')}`);
614
+ continue;
615
+ }
616
+ // verifyOutput requires the result content
617
+ const content = task.result ?? '';
618
+ let valid = false;
619
+ let detail = '';
620
+ try {
621
+ valid = signMod.verifyOutput(content, sig, cfg);
622
+ detail = valid
623
+ ? `signer: ${sig.signer} ts: ${sig.ts}`
624
+ : 'TAMPER DETECTED — signature mismatch';
625
+ }
626
+ catch {
627
+ valid = false;
628
+ detail = 'verification threw unexpectedly';
629
+ }
630
+ if (!valid)
631
+ anyFail = true;
632
+ const verLabel = valid ? green('PASS ') : red('FAIL ');
633
+ const detailColored = valid ? dim(detail) : red(detail);
634
+ console.log(` ${pad(dim(task.id), idW)} ${pad(taskStatus, statusW)} ` +
635
+ `${pad(verLabel, verW)} ${pad(dim(sig.alg), algW)} ${detailColored}`);
636
+ }
637
+ console.log('');
638
+ if (anyFail) {
639
+ console.log(` ${red('Verification FAILED.')} One or more task signatures do not match.`);
640
+ console.log(` ${red('This swarm may have been tampered with after completion.')}`);
641
+ console.log('');
642
+ return 1;
643
+ }
644
+ if (signedTasks.length === 0) {
645
+ console.log(` ${yellow('No signatures to verify.')} This swarm was not signed (pre-M17 or unsigned).`);
646
+ }
647
+ else {
648
+ console.log(` ${green('All signatures valid.')} ${signedTasks.length} task(s) verified.`);
649
+ }
650
+ console.log('');
651
+ return 0;
652
+ }
653
+ // ---------------------------------------------------------------------------
654
+ // M17: `swarm approve <id>`
655
+ // ---------------------------------------------------------------------------
656
+ /**
657
+ * Explicit human approval to resume a needs-approval swarm.
658
+ *
659
+ * Only valid when status === 'needs-approval'. Clears the gate and resumes
660
+ * runSwarm via the existing --resume path. No auto-approval exists anywhere.
661
+ * Exit 0 on successful resume, 1 if not found or not awaiting approval.
662
+ */
663
+ async function cmdSwarmApprove(id) {
664
+ // Load store
665
+ let store;
666
+ try {
667
+ store = await importStore();
668
+ }
669
+ catch (err) {
670
+ process.stderr.write(red('error: ') + 'Failed to load swarm store: ' +
671
+ (err instanceof Error ? err.message : String(err)) + '\n');
672
+ return 1;
673
+ }
674
+ const swarm = store.loadSwarm(id);
675
+ if (!swarm) {
676
+ process.stderr.write(red('error: ') + `Swarm not found: ${id}\n`);
677
+ return 1;
678
+ }
679
+ if (swarm.status !== 'needs-approval') {
680
+ // Nothing to approve
681
+ const hint = swarm.status === 'done' ? 'This swarm has already completed.' :
682
+ swarm.status === 'aborted' ? 'This swarm was aborted (budget). Use --resume to retry.' :
683
+ swarm.status === 'failed' ? 'This swarm failed. Use --resume to retry.' :
684
+ swarm.status === 'running' ? 'This swarm is currently running.' :
685
+ swarm.status === 'planning' ? 'This swarm is still planning.' :
686
+ `Swarm status: ${swarm.status}`;
687
+ process.stderr.write(yellow('info: ') + `Nothing to approve for swarm ${id}.\n` +
688
+ ` ${hint}\n`);
689
+ return 1;
690
+ }
691
+ // Print escalation context before resuming
692
+ console.log('');
693
+ console.log(bold(' ashlr swarm approve') + gray(` — ${id}`));
694
+ console.log('');
695
+ console.log(` ${bold('Goal:')} ${swarm.goal}`);
696
+ if (swarm.project)
697
+ console.log(` ${bold('Project:')} ${dim(swarm.project)}`);
698
+ console.log('');
699
+ if (swarm.escalations && swarm.escalations.length > 0) {
700
+ const last = swarm.escalations[swarm.escalations.length - 1];
701
+ console.log(` ${bold(yellow('Escalation that paused this swarm:'))}`);
702
+ console.log(` Kind: ${bold(last.kind)}`);
703
+ console.log(` Detail: ${last.detail}`);
704
+ if (last.taskId)
705
+ console.log(` Task: ${dim(last.taskId)}`);
706
+ console.log(` Time: ${dim(last.ts)}`);
707
+ console.log('');
708
+ }
709
+ // Mark every task named by an escalation event as risk-acknowledged BEFORE
710
+ // resuming. This is the load-bearing fix for the goal-risk approve loop: a
711
+ // pre-execution goal-risk gate trips before the offending task runs (it stays
712
+ // 'pending'), so without an explicit per-task approval signal the resumed run
713
+ // would re-scan the identical static goal text and re-escalate instantly. By
714
+ // flagging taskRun.approved=true here (and passing approved:true to runSwarm),
715
+ // the runner SKIPS the goal/result risk re-scan for these specific tasks and
716
+ // can advance past them. Only the human-approved tasks are exempted.
717
+ if (swarm.escalations) {
718
+ for (const ev of swarm.escalations) {
719
+ if (ev.taskId) {
720
+ const tr = swarm.tasks.find((t) => t.id === ev.taskId);
721
+ if (tr)
722
+ tr.approved = true;
723
+ }
724
+ }
725
+ }
726
+ // Leave status as 'needs-approval' on disk so the runner's needs-approval
727
+ // block is entered (it is gated on existing.status === 'needs-approval'). The
728
+ // runner clears the pause to 'running' itself only when opts.approved is set —
729
+ // this keeps approval an explicit, threaded signal rather than a silent flip.
730
+ swarm.updatedAt = new Date().toISOString();
731
+ try {
732
+ store.saveSwarm(swarm);
733
+ }
734
+ catch {
735
+ // best-effort; runner will re-persist after first step
736
+ }
737
+ console.log(` ${cyan('Resuming swarm…')}`);
738
+ console.log('');
739
+ // Load config
740
+ let cfg;
741
+ try {
742
+ const { loadConfig } = await importConfig();
743
+ cfg = loadConfig();
744
+ }
745
+ catch (err) {
746
+ process.stderr.write(red('error: ') + 'Failed to load config: ' +
747
+ (err instanceof Error ? err.message : String(err)) + '\n');
748
+ return 1;
749
+ }
750
+ // Load runner
751
+ let runner;
752
+ try {
753
+ runner = await importRunner();
754
+ }
755
+ catch (err) {
756
+ process.stderr.write(red('error: ') + 'Failed to load swarm runner: ' +
757
+ (err instanceof Error ? err.message : String(err)) + '\n');
758
+ return 1;
759
+ }
760
+ // Build sink
761
+ const streaming = await importStreaming();
762
+ const sink = streaming.makeCliSink({ json: false });
763
+ // Resume via existing --resume path
764
+ const resumedSwarm = await runner.runSwarm({ goal: swarm.goal, specId: swarm.specId ?? undefined }, cfg, { resumeId: id, approved: true }, sink);
765
+ printSwarmSummary(resumedSwarm);
766
+ if (resumedSwarm.status === 'done')
767
+ return 0;
768
+ if (resumedSwarm.status === 'aborted')
769
+ return 1;
770
+ if (resumedSwarm.status === 'failed')
771
+ return 1;
772
+ // Still needs-approval (another gate tripped mid-resume)
773
+ if (resumedSwarm.status === 'needs-approval')
774
+ return 1;
775
+ return 0;
776
+ }
777
+ // ---------------------------------------------------------------------------
778
+ // M17: `swarm rollback <id> [--yes] [--force]`
779
+ // ---------------------------------------------------------------------------
780
+ /**
781
+ * Restore a project to its pre-swarm git snapshot.
782
+ *
783
+ * GUARDRAILS (top priority — this is the only potentially-destructive op):
784
+ * - ALWAYS prints exactly what will be restored before acting.
785
+ * - NEVER runs automatically; requires explicit `ashlr swarm rollback <id>`.
786
+ * - NEVER proceeds without `--yes` or an interactive "y" confirmation.
787
+ * - NEVER force-resets a dirty tree without `--force`.
788
+ * - NEVER runs `git push --force`, NEVER deletes branches.
789
+ * - Refuses clearly on non-repo, missing snapshot, or ambiguous state.
790
+ * - Exit 0 on success, 1 on refusal/failure/not-found.
791
+ */
792
+ async function cmdSwarmRollback(id, yes, force) {
793
+ // Load store
794
+ let store;
795
+ try {
796
+ store = await importStore();
797
+ }
798
+ catch (err) {
799
+ process.stderr.write(red('error: ') + 'Failed to load swarm store: ' +
800
+ (err instanceof Error ? err.message : String(err)) + '\n');
801
+ return 1;
802
+ }
803
+ const swarm = store.loadSwarm(id);
804
+ if (!swarm) {
805
+ process.stderr.write(red('error: ') + `Swarm not found: ${id}\n`);
806
+ return 1;
807
+ }
808
+ const snap = swarm.rollback;
809
+ if (!snap) {
810
+ process.stderr.write(red('error: ') +
811
+ `Swarm ${id} has no rollback snapshot.\n` +
812
+ ` This swarm was either created before M17, had no project dir, ` +
813
+ `or the project was not a git repo.\n`);
814
+ return 1;
815
+ }
816
+ if (!snap.isRepo) {
817
+ process.stderr.write(red('error: ') +
818
+ `Cannot rollback: the project directory is not a git repository.\n` +
819
+ ` Project: ${snap.project ?? '(none)'}\n` +
820
+ ` Snapshot taken: ${snap.ts}\n`);
821
+ return 1;
822
+ }
823
+ if (!snap.head) {
824
+ process.stderr.write(red('error: ') +
825
+ `Cannot rollback: snapshot has no HEAD commit recorded (detached or empty repo).\n` +
826
+ ` Snapshot taken: ${snap.ts}\n`);
827
+ return 1;
828
+ }
829
+ // ── Print EXACTLY what will be restored ──────────────────────────────────
830
+ console.log('');
831
+ console.log(bold(' ashlr swarm rollback') + gray(` — ${id}`));
832
+ console.log('');
833
+ console.log(` ${bold('Swarm goal:')} ${swarm.goal}`);
834
+ console.log(` ${bold('Project:')} ${snap.project ?? dim('(none)')}`);
835
+ console.log(` ${bold('Snapshot taken:')} ${snap.ts}`);
836
+ console.log('');
837
+ console.log(` ${bold('Will restore to:')} HEAD ${bold(snap.head)}`);
838
+ if (snap.dirty && snap.stashRef) {
839
+ console.log(` ${bold('Dirty-tree stash:')} ${snap.stashRef} ` +
840
+ yellow('(working tree changes from snapshot time will be re-applied)'));
841
+ }
842
+ else if (snap.dirty && !snap.stashRef) {
843
+ console.log(` ${yellow('Note:')} working tree was dirty at snapshot time but no stash ref was recorded.`);
844
+ console.log(` ${dim('The working tree will be reset to HEAD; those changes cannot be restored.')}`);
845
+ }
846
+ else {
847
+ console.log(` ${dim('Working tree was clean at snapshot time.')}`);
848
+ }
849
+ console.log('');
850
+ console.log(` ${red('WARNING:')} This will ${bold('reset')} the working tree in ` +
851
+ `${snap.project ?? 'the project directory'}.`);
852
+ if (force) {
853
+ console.log(` ${red('WARNING:')} ${bold('--force')} uses ${bold('git reset --hard')}. In addition to ` +
854
+ `discarding uncommitted changes, this ${bold('also discards any commits made AFTER the snapshot')} ` +
855
+ `(${snap.head ? `commits ahead of ${dim(snap.head)}` : 'commits ahead of the snapshot'}).`);
856
+ console.log(` ${dim('Such commits become unreferenced and are recoverable only via `git reflog` until garbage-collected.')}`);
857
+ }
858
+ console.log(` ${dim('It will NOT push to remote, will NOT delete branches, and will NOT run git push --force.')}`);
859
+ console.log('');
860
+ // ── Confirm gate ─────────────────────────────────────────────────────────
861
+ // If --yes was NOT passed, require interactive confirmation.
862
+ if (!yes) {
863
+ const confirmed = await promptConfirm(' Proceed with rollback? [y/N] ');
864
+ if (!confirmed) {
865
+ console.log(` ${yellow('Rollback cancelled.')}`);
866
+ console.log('');
867
+ return 1;
868
+ }
869
+ }
870
+ else {
871
+ console.log(` ${dim('--yes flag set; skipping confirmation prompt.')}`);
872
+ console.log('');
873
+ }
874
+ // ── Load rollback module (M17) ────────────────────────────────────────────
875
+ const rollbackMod = await importRollback();
876
+ if (!rollbackMod) {
877
+ process.stderr.write(red('error: ') +
878
+ 'rollback module (core/swarm/rollback.ts) is not available.\n' +
879
+ ' It may not have been built yet (M17 agent not complete).\n');
880
+ return 1;
881
+ }
882
+ // ── Execute ───────────────────────────────────────────────────────────────
883
+ console.log(` ${cyan('Restoring…')}`);
884
+ let rollbackResult;
885
+ try {
886
+ rollbackResult = await rollbackMod.rollbackTo(snap, { force });
887
+ }
888
+ catch (err) {
889
+ process.stderr.write(red('error: ') + 'rollbackTo threw unexpectedly: ' +
890
+ (err instanceof Error ? err.message : String(err)) + '\n');
891
+ return 1;
892
+ }
893
+ if (!rollbackResult.ok) {
894
+ process.stderr.write(red('error: ') + `Rollback refused: ${rollbackResult.detail}\n`);
895
+ // Provide guidance for the two most common refusals
896
+ if (/dirty/i.test(rollbackResult.detail)) {
897
+ process.stderr.write(` ${yellow('Hint:')} the working tree has uncommitted changes.\n` +
898
+ ` Pass ${bold('--force')} to discard them and restore the snapshot.\n` +
899
+ ` Example: ashlr swarm rollback ${id} --yes --force\n`);
900
+ }
901
+ else if (/not a (git )?repo/i.test(rollbackResult.detail)) {
902
+ process.stderr.write(` ${yellow('Hint:')} the project directory is not a git repository — cannot roll back.\n`);
903
+ }
904
+ console.log('');
905
+ return 1;
906
+ }
907
+ console.log(` ${green('Rollback complete.')} ${rollbackResult.detail}`);
908
+ console.log('');
909
+ return 0;
910
+ }
911
+ // ---------------------------------------------------------------------------
912
+ // Interactive confirm helper (readline)
913
+ // ---------------------------------------------------------------------------
914
+ /**
915
+ * Prompt the user for a yes/no answer. Returns true on "y" / "yes"
916
+ * (case-insensitive), false on anything else (empty = default N).
917
+ *
918
+ * If stdin is not a TTY (piped/redirected), refuses by default and instructs
919
+ * the caller to use --yes. Never throws.
920
+ */
921
+ async function promptConfirm(question) {
922
+ return new Promise((resolve) => {
923
+ try {
924
+ // Non-TTY stdin: refuse by default to prevent accidental pipeline triggers.
925
+ if (!process.stdin.isTTY) {
926
+ process.stderr.write(yellow('warn: ') +
927
+ 'stdin is not a TTY — defaulting to N (rollback refused).\n' +
928
+ ' Pass --yes to skip the interactive prompt in non-TTY contexts.\n');
929
+ resolve(false);
930
+ return;
931
+ }
932
+ const rl = readline.createInterface({
933
+ input: process.stdin,
934
+ output: process.stdout,
935
+ terminal: true,
936
+ });
937
+ rl.question(question, (answer) => {
938
+ rl.close();
939
+ const a = answer.trim().toLowerCase();
940
+ resolve(a === 'y' || a === 'yes');
941
+ });
942
+ }
943
+ catch {
944
+ resolve(false);
945
+ }
946
+ });
947
+ }
948
+ // ---------------------------------------------------------------------------
949
+ // `ashlr swarm` — main
950
+ // ---------------------------------------------------------------------------
951
+ /**
952
+ * `ashlr swarm "<goal>" | <specId> [--budget N] [--parallel N] [--background]
953
+ * [--resume <id>] [--dry-run] [--allow-cloud] [--project <path>] [--json] [--no-capture]`
954
+ *
955
+ * Also handles:
956
+ * `ashlr swarm show <id>`
957
+ * `ashlr swarm verify <id>`
958
+ * `ashlr swarm approve <id>`
959
+ * `ashlr swarm rollback <id> [--yes] [--force]`
960
+ *
961
+ * GUARDRAILS:
962
+ * - REFUSES to start when ASHLR_IN_SWARM env var is set (recursion guard).
963
+ * - LOCAL-FIRST by default; --allow-cloud opts in to cloud providers.
964
+ * - --dry-run plans only — no agent execution.
965
+ * - --background spawns a detached worker and returns the swarm ID immediately.
966
+ * - rollback is the ONLY potentially-destructive op; ALWAYS confirm-gated.
967
+ */
968
+ export async function cmdSwarm(args) {
969
+ // Help shortcircuit
970
+ if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
971
+ printSwarmHelp();
972
+ return 0;
973
+ }
974
+ // ── RECURSION GUARD ──────────────────────────────────────────────────────
975
+ // A swarm task MUST NOT spawn `ashlr swarm`. If ASHLR_IN_SWARM is set, this
976
+ // process is a swarm subtask — refuse with a clear message.
977
+ if (process.env['ASHLR_IN_SWARM']) {
978
+ process.stderr.write(red('error: ') +
979
+ 'Nested swarms are not permitted (ASHLR_IN_SWARM is set).\n' +
980
+ ' ashlr swarm refuses to start inside another swarm task to prevent fork bombs.\n' +
981
+ ' If this is intentional, unset ASHLR_IN_SWARM before proceeding.\n');
982
+ return 3;
983
+ }
984
+ const parsed = parseSwarmArgs(args);
985
+ if (parsed.usageError) {
986
+ process.stderr.write(red('error: ') + parsed.usageError + '\n');
987
+ return 2;
988
+ }
989
+ // ── M17 subcommands ───────────────────────────────────────────────────────
990
+ // These are read-only (verify) or explicitly confirm-gated (rollback); the
991
+ // recursion guard above covers all entry points uniformly.
992
+ if (parsed.subcommand === 'verify' && parsed.subId) {
993
+ return cmdSwarmVerify(parsed.subId);
994
+ }
995
+ if (parsed.subcommand === 'approve' && parsed.subId) {
996
+ return cmdSwarmApprove(parsed.subId);
997
+ }
998
+ if (parsed.subcommand === 'rollback' && parsed.subId) {
999
+ return cmdSwarmRollback(parsed.subId, parsed.yes, parsed.force);
1000
+ }
1001
+ // Subcommand: show
1002
+ if (parsed.subcommand === 'show' && parsed.showId) {
1003
+ return cmdSwarmShow(parsed.showId, parsed.json);
1004
+ }
1005
+ // Load config
1006
+ let cfg;
1007
+ try {
1008
+ const { loadConfig } = await importConfig();
1009
+ cfg = loadConfig();
1010
+ }
1011
+ catch (err) {
1012
+ process.stderr.write(red('error: ') + 'Failed to load config: ' +
1013
+ (err instanceof Error ? err.message : String(err)) + '\n');
1014
+ return 1;
1015
+ }
1016
+ // Resolve goal and specId from the positional arg.
1017
+ // If goalOrSpecId looks like a spec id (no spaces, no quotes), try to load
1018
+ // it as a spec first; fall back to treating it as a goal string.
1019
+ let goal = parsed.goalOrSpecId ?? '';
1020
+ let specId;
1021
+ // If --resume is set without a goal, load the goal from the stored swarm.
1022
+ if (!goal && parsed.resumeId) {
1023
+ try {
1024
+ const store = await importStore();
1025
+ const prior = store.loadSwarm(parsed.resumeId);
1026
+ if (!prior) {
1027
+ process.stderr.write(red('error: ') + `Cannot resume: swarm not found: ${parsed.resumeId}\n`);
1028
+ return 1;
1029
+ }
1030
+ goal = prior.goal;
1031
+ specId = prior.specId ?? undefined;
1032
+ }
1033
+ catch (err) {
1034
+ process.stderr.write(red('error: ') + `Failed to load swarm ${parsed.resumeId}: ` +
1035
+ (err instanceof Error ? err.message : String(err)) + '\n');
1036
+ return 1;
1037
+ }
1038
+ }
1039
+ // If the positional looks like a potential spec id (no whitespace), try spec store.
1040
+ if (goal && !/\s/.test(goal) && !parsed.resumeId) {
1041
+ try {
1042
+ const specStore = await importSpecStore();
1043
+ const spec = specStore.loadSpec(goal);
1044
+ if (spec) {
1045
+ specId = goal;
1046
+ // Use the spec's goal as the swarm goal
1047
+ goal = spec.meta.goal;
1048
+ }
1049
+ }
1050
+ catch {
1051
+ // spec-store not yet built or id not found — treat positional as a plain goal
1052
+ }
1053
+ }
1054
+ // Build SwarmOptions
1055
+ const swarmOpts = {
1056
+ allowCloud: parsed.allowCloud,
1057
+ parallel: parsed.parallel,
1058
+ background: parsed.background,
1059
+ resumeId: parsed.resumeId,
1060
+ dryRun: parsed.dryRun,
1061
+ project: parsed.project,
1062
+ };
1063
+ if (parsed.budget !== undefined || parsed.allowCloud !== undefined) {
1064
+ swarmOpts.budget = {};
1065
+ if (parsed.budget !== undefined)
1066
+ swarmOpts.budget.maxTokens = parsed.budget;
1067
+ if (parsed.allowCloud !== undefined)
1068
+ swarmOpts.budget.allowCloud = parsed.allowCloud;
1069
+ }
1070
+ // Pass --no-capture so captureFromSwarm skips genome auto-capture for this swarm.
1071
+ if (parsed.noCapture) {
1072
+ swarmOpts.noCapture = true;
1073
+ }
1074
+ // Pass --over-budget so checkGovernanceSwarm lets an over-cap swarm proceed (M19).
1075
+ if (parsed.overBudget) {
1076
+ swarmOpts.overBudget = true;
1077
+ }
1078
+ // M32: --estimate — pre-flight cost prediction; prints and exits 0 (no swarm).
1079
+ if (parsed.estimate) {
1080
+ const { loadConfig } = await import('../core/config.js');
1081
+ const { estimateSwarm, renderEstimate } = await import('../core/observability/estimate.js');
1082
+ const est = await estimateSwarm(goal, { maxTokens: parsed.budget, allowCloud: parsed.allowCloud }, loadConfig());
1083
+ if (parsed.json) {
1084
+ process.stdout.write(JSON.stringify(est, null, 2) + '\n');
1085
+ }
1086
+ else {
1087
+ process.stdout.write(renderEstimate(est) + '\n');
1088
+ }
1089
+ return 0;
1090
+ }
1091
+ // Print launch banner (non-JSON)
1092
+ if (!parsed.json) {
1093
+ console.log('');
1094
+ console.log(bold(' ashlr swarm') + gray(' — launching'));
1095
+ console.log(` ${dim('Goal:')} ${goal}`);
1096
+ if (specId)
1097
+ console.log(` ${dim('Spec:')} ${specId}`);
1098
+ if (parsed.resumeId)
1099
+ console.log(` ${dim('Resuming:')} ${parsed.resumeId}`);
1100
+ if (parsed.dryRun)
1101
+ console.log(` ${dim('Mode:')} dry-run (plan only, no execution)`);
1102
+ if (parsed.allowCloud)
1103
+ console.log(` ${dim('Cloud:')} allowed (--allow-cloud)`);
1104
+ if (parsed.background)
1105
+ console.log(` ${dim('Mode:')} background (detached worker)`);
1106
+ console.log('');
1107
+ }
1108
+ else {
1109
+ process.stderr.write(`[ashlr swarm] goal: ${goal}` +
1110
+ (parsed.resumeId ? ` (resuming ${parsed.resumeId})` : '') +
1111
+ (parsed.dryRun ? ' [dry-run]' : '') + '\n');
1112
+ }
1113
+ // Load runner
1114
+ let runner;
1115
+ try {
1116
+ runner = await importRunner();
1117
+ }
1118
+ catch (err) {
1119
+ process.stderr.write(red('error: ') + 'Failed to load swarm runner (M12 module not yet built): ' +
1120
+ (err instanceof Error ? err.message : String(err)) + '\n');
1121
+ return 1;
1122
+ }
1123
+ const streaming = await importStreaming();
1124
+ const sink = parsed.json
1125
+ ? streaming.makeCliSink({ json: true })
1126
+ : streaming.makeCliSink({ json: false });
1127
+ // Run the swarm
1128
+ let swarm;
1129
+ try {
1130
+ swarm = await runner.runSwarm({ goal, specId }, cfg, swarmOpts, sink);
1131
+ }
1132
+ catch (err) {
1133
+ const msg = err instanceof Error ? err.message : String(err);
1134
+ // Surface cloud refusal clearly
1135
+ if (/local.first|cloud.provider|allow.cloud|no local/i.test(msg)) {
1136
+ const lines = [
1137
+ '',
1138
+ ` ${red('Local-first refusal:')} ${msg}`,
1139
+ '',
1140
+ ` ashlr swarm only uses LOCAL providers (Ollama / LM Studio) by default.`,
1141
+ ` To allow a cloud provider, pass ${bold('--allow-cloud')} and ensure the API key is set.`,
1142
+ '',
1143
+ ` Example: ashlr swarm "<goal>" --allow-cloud`,
1144
+ '',
1145
+ ].join('\n');
1146
+ if (parsed.json) {
1147
+ process.stderr.write(lines + '\n');
1148
+ }
1149
+ else {
1150
+ process.stdout.write(lines + '\n');
1151
+ }
1152
+ return 3;
1153
+ }
1154
+ process.stderr.write(red('error: ') + msg + '\n');
1155
+ return 1;
1156
+ }
1157
+ // ── BACKGROUND launch: the runner persisted a skeleton, spawned a detached
1158
+ // worker (ashlr swarm --resume <id> --_worker with ASHLR_IN_SWARM cleared),
1159
+ // and returned immediately. Report the id; the worker drives the swarm. ─────
1160
+ if (parsed.background) {
1161
+ if (parsed.json) {
1162
+ process.stdout.write(JSON.stringify({ swarmId: swarm.id, background: true }) + '\n');
1163
+ }
1164
+ else {
1165
+ console.log(` ${green('Swarm started in background.')} ID: ${bold(swarm.id)}`);
1166
+ console.log(` ${dim('Watch progress with:')} ashlr swarm show ${swarm.id}`);
1167
+ console.log('');
1168
+ }
1169
+ return 0;
1170
+ }
1171
+ // ── DRY-RUN output: plan only ────────────────────────────────────────────
1172
+ if (parsed.dryRun) {
1173
+ printDryRunPlan(swarm.plan, { json: parsed.json });
1174
+ // M32: a dry-run is the natural place to preview cost — append the estimate.
1175
+ if (!parsed.json) {
1176
+ try {
1177
+ const { loadConfig } = await import('../core/config.js');
1178
+ const { estimateSwarm, renderEstimate } = await import('../core/observability/estimate.js');
1179
+ const est = await estimateSwarm(goal, { maxTokens: parsed.budget, allowCloud: parsed.allowCloud }, loadConfig());
1180
+ console.log('');
1181
+ console.log(renderEstimate(est));
1182
+ }
1183
+ catch { /* estimate is best-effort — dry-run output stands alone */ }
1184
+ }
1185
+ return 0;
1186
+ }
1187
+ // ── Normal output ────────────────────────────────────────────────────────
1188
+ if (parsed.json) {
1189
+ process.stdout.write(JSON.stringify(swarm, null, 2) + '\n');
1190
+ }
1191
+ else {
1192
+ printSwarmSummary(swarm);
1193
+ }
1194
+ // Exit codes
1195
+ if (swarm.status === 'done')
1196
+ return 0;
1197
+ if (swarm.status === 'aborted')
1198
+ return 1;
1199
+ if (swarm.status === 'failed')
1200
+ return 1;
1201
+ // M17: needs-approval means the swarm paused at an escalation gate
1202
+ if (swarm.status === 'needs-approval')
1203
+ return 1;
1204
+ return 0;
1205
+ }
1206
+ // ---------------------------------------------------------------------------
1207
+ // `ashlr swarms` — list persisted swarms
1208
+ // ---------------------------------------------------------------------------
1209
+ /**
1210
+ * `ashlr swarms [--json]` — list all persisted swarms (newest first).
1211
+ */
1212
+ export async function cmdSwarms(args) {
1213
+ const jsonMode = args.includes('--json');
1214
+ let store;
1215
+ try {
1216
+ store = await importStore();
1217
+ }
1218
+ catch (err) {
1219
+ process.stderr.write(red('error: ') + 'Failed to load swarm store (M12 module not yet built): ' +
1220
+ (err instanceof Error ? err.message : String(err)) + '\n');
1221
+ return 1;
1222
+ }
1223
+ // `swarms show <id>` is also routed here via index.ts dispatch
1224
+ if (args[0] === 'show') {
1225
+ const id = args[1];
1226
+ if (!id) {
1227
+ process.stderr.write(red('error: ') + 'Usage: ashlr swarms show <id>\n');
1228
+ return 2;
1229
+ }
1230
+ return cmdSwarmShow(id, jsonMode);
1231
+ }
1232
+ const swarms = store.listSwarms();
1233
+ if (jsonMode) {
1234
+ process.stdout.write(JSON.stringify(swarms, null, 2) + '\n');
1235
+ return 0;
1236
+ }
1237
+ if (swarms.length === 0) {
1238
+ console.log('');
1239
+ console.log(` ${dim('No swarms found.')} Start one with ${bold('ashlr swarm "<goal>"')}.`);
1240
+ console.log('');
1241
+ return 0;
1242
+ }
1243
+ const idW = Math.max(4, ...swarms.map(s => s.id.length));
1244
+ const statusW = 14; // wider to accommodate 'needs-approval'
1245
+ const progressW = 10;
1246
+ const tokW = 8;
1247
+ const timeW = 8;
1248
+ const goalW = 46;
1249
+ console.log('');
1250
+ console.log(bold(' ashlr swarms') + gray(` — ${swarms.length} swarm(s)`));
1251
+ console.log('');
1252
+ console.log(` ${bold(pad('ID', idW))} ${bold(pad('Status', statusW))} ` +
1253
+ `${bold(pad('Progress', progressW))} ${bold(pad('Tokens', tokW, 'right'))} ` +
1254
+ `${bold(pad('When', timeW))} ${bold('Goal')}`);
1255
+ console.log(` ${'─'.repeat(idW)} ${'─'.repeat(statusW)} ${'─'.repeat(progressW)} ` +
1256
+ `${'─'.repeat(tokW)} ${'─'.repeat(timeW)} ${'─'.repeat(goalW)}`);
1257
+ for (const s of swarms) {
1258
+ const totalTokens = s.usage.tokensIn + s.usage.tokensOut;
1259
+ const doneTasks = s.tasks.filter(t => t.status === 'done').length;
1260
+ const totalTasks = s.tasks.length;
1261
+ const progressStr = `${doneTasks}/${totalTasks}`;
1262
+ const goalTrunc = s.goal.length > goalW ? s.goal.slice(0, goalW - 1) + '…' : s.goal;
1263
+ const when = relativeTime(s.createdAt);
1264
+ const isLocal = s.usage.estCostUsd === 0;
1265
+ void isLocal; // used implicitly via cloudNote in summary; not shown in list
1266
+ console.log(` ${pad(dim(s.id), idW)} ${pad(swarmStatusColor(s.status), statusW)} ` +
1267
+ `${pad(seCol.cyan(progressStr), progressW)} ` +
1268
+ `${pad(totalTokens.toLocaleString(), tokW, 'right')} ` +
1269
+ `${pad(seCol.gray(when), timeW)} ` +
1270
+ goalTrunc);
1271
+ }
1272
+ console.log('');
1273
+ console.log(dim(` Use 'ashlr swarm show <id>' to see details.`));
1274
+ console.log('');
1275
+ return 0;
1276
+ }
1277
+ // ---------------------------------------------------------------------------
1278
+ // Help
1279
+ // ---------------------------------------------------------------------------
1280
+ function printSwarmHelp() {
1281
+ console.log('');
1282
+ console.log(bold(' ashlr swarm') + dim(' — contracts-first multi-agent fleet runner'));
1283
+ console.log('');
1284
+ console.log(' ' + bold('Usage:'));
1285
+ console.log('');
1286
+ console.log(` ashlr swarm ${cyan('"<goal>"')} [options]`);
1287
+ console.log(` ashlr swarm ${cyan('<specId>')} [options]`);
1288
+ console.log(` ashlr swarm show ${cyan('<id>')}`);
1289
+ console.log(` ashlr swarm verify ${cyan('<id>')} ${dim('# M17: verify task output signatures')}`);
1290
+ console.log(` ashlr swarm approve ${cyan('<id>')} ${dim('# M17: resume a paused (needs-approval) swarm')}`);
1291
+ console.log(` ashlr swarm rollback ${cyan('<id>')} [--yes] [--force] ${dim('# M17: restore pre-swarm git state')}`);
1292
+ console.log(` ashlr swarms [--json]`);
1293
+ console.log('');
1294
+ console.log(' ' + bold('Options:'));
1295
+ console.log('');
1296
+ const opts = [
1297
+ ['--budget N', 'Hard total token ceiling across ALL tasks (aborts cleanly when exceeded).'],
1298
+ ['--parallel N', 'Max tasks to execute concurrently in the BUILD phase (default: 3, max: 8).'],
1299
+ ['--background', 'Launch a detached worker; return the swarm ID immediately.'],
1300
+ ['--resume <id>', 'Resume a previously paused/aborted swarm.'],
1301
+ ['--dry-run', 'Plan only — print the phases/tasks without executing any agent.'],
1302
+ ['--allow-cloud', 'Allow cloud providers for tasks (default: local-first Ollama/LM Studio).'],
1303
+ ['--project <path>', 'Absolute path to the target project directory.'],
1304
+ ['--json', 'Emit SwarmRun JSON on stdout; progress goes to stderr.'],
1305
+ ['--no-capture', 'Skip auto-capture of this swarm to the genome (M16 playbook).'],
1306
+ ];
1307
+ const optW = Math.max(...opts.map(([o]) => o.length));
1308
+ for (const [opt, desc] of opts) {
1309
+ console.log(` ${cyan(pad(opt, optW))} ${desc}`);
1310
+ }
1311
+ console.log('');
1312
+ console.log(' ' + bold('M17 subcommand options:'));
1313
+ console.log('');
1314
+ const m17opts = [
1315
+ ['rollback --yes', 'Skip the interactive confirmation prompt.'],
1316
+ ['rollback --force', 'Allow rollback even when the working tree is dirty (discards changes).'],
1317
+ ];
1318
+ const m17W = Math.max(...m17opts.map(([o]) => o.length));
1319
+ for (const [opt, desc] of m17opts) {
1320
+ console.log(` ${cyan(pad(opt, m17W))} ${desc}`);
1321
+ }
1322
+ console.log('');
1323
+ console.log(' ' + bold('Examples:'));
1324
+ console.log('');
1325
+ console.log(` ${gray('# Plan a swarm without executing (safe preview)')}`);
1326
+ console.log(` ashlr swarm "Add end-to-end tests for the auth module" --dry-run`);
1327
+ console.log('');
1328
+ console.log(` ${gray('# Run a swarm with a tight budget')}`);
1329
+ console.log(` ashlr swarm "Refactor config parsing" --budget 40000 --parallel 2`);
1330
+ console.log('');
1331
+ console.log(` ${gray('# Run from a spec artifact')}`);
1332
+ console.log(` ashlr swarm my-spec-id --project ~/Desktop/github/my-project`);
1333
+ console.log('');
1334
+ console.log(` ${gray('# Launch in background and watch later')}`);
1335
+ console.log(` ashlr swarm "Implement feature X" --background --budget 80000`);
1336
+ console.log(` ashlr swarm show <swarm-id>`);
1337
+ console.log('');
1338
+ console.log(` ${gray('# List all past swarms')}`);
1339
+ console.log(` ashlr swarms`);
1340
+ console.log('');
1341
+ console.log(` ${gray('# M17: verify task signatures after a completed swarm')}`);
1342
+ console.log(` ashlr swarm verify swarm-xyz789`);
1343
+ console.log('');
1344
+ console.log(` ${gray('# M17: resume a swarm paused at an escalation gate')}`);
1345
+ console.log(` ashlr swarm approve swarm-xyz789`);
1346
+ console.log('');
1347
+ console.log(` ${gray('# M17: roll back a project to its pre-swarm git state')}`);
1348
+ console.log(` ashlr swarm rollback swarm-xyz789`);
1349
+ console.log(` ashlr swarm rollback swarm-xyz789 --yes --force ${dim('# non-interactive + dirty-ok')}`);
1350
+ console.log('');
1351
+ console.log(' ' + bold('Safety:'));
1352
+ console.log('');
1353
+ console.log(` ${dim('• LOCAL-FIRST: only Ollama / LM Studio by default.')}`);
1354
+ console.log(` ${dim('• HARD total budget across the whole swarm — never runaway.')}`);
1355
+ console.log(` ${dim('• Bounded parallelism: default 3, max 8 concurrent tasks.')}`);
1356
+ console.log(` ${dim('• Nested swarms refused (ASHLR_IN_SWARM guard).')}`);
1357
+ console.log(` ${dim('• No outward/destructive actions (push/deploy) by default.')}`);
1358
+ console.log(` ${dim('• All state persists to ~/.ashlr/swarms/<id>.json (resumable).')}`);
1359
+ console.log(` ${dim('• --no-capture disables genome auto-capture for this swarm.')}`);
1360
+ console.log(` ${dim('• M17: task outputs signed; downstream tasks verify before consuming.')}`);
1361
+ console.log(` ${dim('• M17: escalation gates PAUSE (needs-approval) — never auto-proceed.')}`);
1362
+ console.log(` ${dim('• M17: rollback is confirm-gated, never automatic, never force-pushes.')}`);
1363
+ console.log('');
1364
+ }
1365
+ //# sourceMappingURL=swarm.js.map