@ag-eco/agentplate-cli 0.13.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 (455) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +462 -0
  3. package/agents/ap-co-creation.md +90 -0
  4. package/agents/builder.md +144 -0
  5. package/agents/coordinator.md +377 -0
  6. package/agents/lead.md +435 -0
  7. package/agents/merger.md +164 -0
  8. package/agents/monitor.md +214 -0
  9. package/agents/orchestrator.md +239 -0
  10. package/agents/reviewer.md +140 -0
  11. package/agents/scout.md +125 -0
  12. package/agents/supervisor.md +427 -0
  13. package/package.json +66 -0
  14. package/src/agents/capabilities.test.ts +85 -0
  15. package/src/agents/capabilities.ts +125 -0
  16. package/src/agents/checkpoint.test.ts +88 -0
  17. package/src/agents/checkpoint.ts +101 -0
  18. package/src/agents/copilot-hooks-deployer.test.ts +162 -0
  19. package/src/agents/copilot-hooks-deployer.ts +93 -0
  20. package/src/agents/guard-rules.test.ts +372 -0
  21. package/src/agents/guard-rules.ts +97 -0
  22. package/src/agents/headless-mail-injector.test.ts +709 -0
  23. package/src/agents/headless-mail-injector.ts +377 -0
  24. package/src/agents/headless-prompt.test.ts +102 -0
  25. package/src/agents/headless-prompt.ts +68 -0
  26. package/src/agents/hooks-deployer.test.ts +3119 -0
  27. package/src/agents/hooks-deployer.ts +804 -0
  28. package/src/agents/identity.test.ts +604 -0
  29. package/src/agents/identity.ts +384 -0
  30. package/src/agents/lifecycle.test.ts +196 -0
  31. package/src/agents/lifecycle.ts +183 -0
  32. package/src/agents/mail-poll-detect.test.ts +153 -0
  33. package/src/agents/mail-poll-detect.ts +73 -0
  34. package/src/agents/manifest.test.ts +1026 -0
  35. package/src/agents/manifest.ts +376 -0
  36. package/src/agents/overlay.test.ts +1058 -0
  37. package/src/agents/overlay.ts +490 -0
  38. package/src/agents/scope-detect.test.ts +190 -0
  39. package/src/agents/scope-detect.ts +146 -0
  40. package/src/agents/turn-lock.test.ts +181 -0
  41. package/src/agents/turn-lock.ts +235 -0
  42. package/src/agents/turn-runner-dispatch.test.ts +182 -0
  43. package/src/agents/turn-runner-dispatch.ts +105 -0
  44. package/src/agents/turn-runner.test.ts +2312 -0
  45. package/src/agents/turn-runner.ts +1383 -0
  46. package/src/beads/client.test.ts +217 -0
  47. package/src/beads/client.ts +230 -0
  48. package/src/beads/molecules.test.ts +338 -0
  49. package/src/beads/molecules.ts +198 -0
  50. package/src/commands/agents.test.ts +328 -0
  51. package/src/commands/agents.ts +299 -0
  52. package/src/commands/clean.test.ts +797 -0
  53. package/src/commands/clean.ts +791 -0
  54. package/src/commands/completions.test.ts +348 -0
  55. package/src/commands/completions.ts +981 -0
  56. package/src/commands/coordinator.test.ts +2975 -0
  57. package/src/commands/coordinator.ts +1841 -0
  58. package/src/commands/costs.test.ts +1183 -0
  59. package/src/commands/costs.ts +599 -0
  60. package/src/commands/dashboard.test.ts +954 -0
  61. package/src/commands/dashboard.ts +1212 -0
  62. package/src/commands/discover.test.ts +288 -0
  63. package/src/commands/discover.ts +202 -0
  64. package/src/commands/doctor.test.ts +303 -0
  65. package/src/commands/doctor.ts +311 -0
  66. package/src/commands/ecosystem.test.ts +226 -0
  67. package/src/commands/ecosystem.ts +248 -0
  68. package/src/commands/errors.test.ts +654 -0
  69. package/src/commands/errors.ts +197 -0
  70. package/src/commands/feed.test.ts +709 -0
  71. package/src/commands/feed.ts +260 -0
  72. package/src/commands/group.test.ts +475 -0
  73. package/src/commands/group.ts +546 -0
  74. package/src/commands/hooks.test.ts +458 -0
  75. package/src/commands/hooks.ts +263 -0
  76. package/src/commands/init.test.ts +1011 -0
  77. package/src/commands/init.ts +967 -0
  78. package/src/commands/inspect.test.ts +1239 -0
  79. package/src/commands/inspect.ts +648 -0
  80. package/src/commands/log.test.ts +1913 -0
  81. package/src/commands/log.ts +958 -0
  82. package/src/commands/logs.test.ts +801 -0
  83. package/src/commands/logs.ts +483 -0
  84. package/src/commands/mail.test.ts +1501 -0
  85. package/src/commands/mail.ts +848 -0
  86. package/src/commands/merge.test.ts +864 -0
  87. package/src/commands/merge.ts +381 -0
  88. package/src/commands/metrics.test.ts +458 -0
  89. package/src/commands/metrics.ts +129 -0
  90. package/src/commands/monitor.test.ts +191 -0
  91. package/src/commands/monitor.ts +409 -0
  92. package/src/commands/nudge.test.ts +579 -0
  93. package/src/commands/nudge.ts +646 -0
  94. package/src/commands/orchestrator.ts +42 -0
  95. package/src/commands/prime.test.ts +612 -0
  96. package/src/commands/prime.ts +359 -0
  97. package/src/commands/replay.test.ts +757 -0
  98. package/src/commands/replay.ts +231 -0
  99. package/src/commands/run.test.ts +469 -0
  100. package/src/commands/run.ts +353 -0
  101. package/src/commands/serve/agent-actions.test.ts +210 -0
  102. package/src/commands/serve/agent-actions.ts +192 -0
  103. package/src/commands/serve/build.test.ts +202 -0
  104. package/src/commands/serve/build.ts +206 -0
  105. package/src/commands/serve/coordinator-actions.test.ts +339 -0
  106. package/src/commands/serve/coordinator-actions.ts +410 -0
  107. package/src/commands/serve/dev.test.ts +168 -0
  108. package/src/commands/serve/dev.ts +117 -0
  109. package/src/commands/serve/mail-actions.test.ts +312 -0
  110. package/src/commands/serve/mail-actions.ts +167 -0
  111. package/src/commands/serve/rest.test.ts +1680 -0
  112. package/src/commands/serve/rest.ts +1130 -0
  113. package/src/commands/serve/static.ts +51 -0
  114. package/src/commands/serve/ws.test.ts +361 -0
  115. package/src/commands/serve/ws.ts +332 -0
  116. package/src/commands/serve.test.ts +459 -0
  117. package/src/commands/serve.ts +654 -0
  118. package/src/commands/sling.test.ts +1583 -0
  119. package/src/commands/sling.ts +1351 -0
  120. package/src/commands/spec.test.ts +179 -0
  121. package/src/commands/spec.ts +105 -0
  122. package/src/commands/status.test.ts +614 -0
  123. package/src/commands/status.ts +403 -0
  124. package/src/commands/stop.test.ts +964 -0
  125. package/src/commands/stop.ts +319 -0
  126. package/src/commands/supervisor.test.ts +185 -0
  127. package/src/commands/supervisor.ts +537 -0
  128. package/src/commands/trace.test.ts +762 -0
  129. package/src/commands/trace.ts +205 -0
  130. package/src/commands/update.test.ts +466 -0
  131. package/src/commands/update.ts +263 -0
  132. package/src/commands/upgrade.test.ts +48 -0
  133. package/src/commands/upgrade.ts +240 -0
  134. package/src/commands/watch.test.ts +257 -0
  135. package/src/commands/watch.ts +308 -0
  136. package/src/commands/worktree.test.ts +1297 -0
  137. package/src/commands/worktree.ts +451 -0
  138. package/src/config.test.ts +1535 -0
  139. package/src/config.ts +1064 -0
  140. package/src/doctor/agents.test.ts +523 -0
  141. package/src/doctor/agents.ts +399 -0
  142. package/src/doctor/config-check.test.ts +191 -0
  143. package/src/doctor/config-check.ts +183 -0
  144. package/src/doctor/consistency.test.ts +807 -0
  145. package/src/doctor/consistency.ts +347 -0
  146. package/src/doctor/databases.test.ts +350 -0
  147. package/src/doctor/databases.ts +243 -0
  148. package/src/doctor/dependencies.test.ts +296 -0
  149. package/src/doctor/dependencies.ts +272 -0
  150. package/src/doctor/ecosystem.test.ts +308 -0
  151. package/src/doctor/ecosystem.ts +156 -0
  152. package/src/doctor/logs.test.ts +253 -0
  153. package/src/doctor/logs.ts +295 -0
  154. package/src/doctor/merge-queue.test.ts +315 -0
  155. package/src/doctor/merge-queue.ts +167 -0
  156. package/src/doctor/providers.test.ts +409 -0
  157. package/src/doctor/providers.ts +250 -0
  158. package/src/doctor/serve.test.ts +95 -0
  159. package/src/doctor/serve.ts +86 -0
  160. package/src/doctor/structure.test.ts +423 -0
  161. package/src/doctor/structure.ts +285 -0
  162. package/src/doctor/types.ts +43 -0
  163. package/src/doctor/version.test.ts +241 -0
  164. package/src/doctor/version.ts +132 -0
  165. package/src/doctor/watchdog.test.ts +167 -0
  166. package/src/doctor/watchdog.ts +214 -0
  167. package/src/e2e/init-sling-lifecycle.test.ts +283 -0
  168. package/src/errors.test.ts +350 -0
  169. package/src/errors.ts +217 -0
  170. package/src/events/store.test.ts +660 -0
  171. package/src/events/store.ts +369 -0
  172. package/src/events/tailer.test.ts +719 -0
  173. package/src/events/tailer.ts +332 -0
  174. package/src/events/tool-filter.test.ts +330 -0
  175. package/src/events/tool-filter.ts +126 -0
  176. package/src/index.ts +533 -0
  177. package/src/insights/analyzer.test.ts +466 -0
  178. package/src/insights/analyzer.ts +203 -0
  179. package/src/insights/quality-gates.test.ts +141 -0
  180. package/src/insights/quality-gates.ts +156 -0
  181. package/src/json.test.ts +72 -0
  182. package/src/json.ts +53 -0
  183. package/src/loam/client.test.ts +752 -0
  184. package/src/loam/client.ts +664 -0
  185. package/src/logging/color.test.ts +252 -0
  186. package/src/logging/color.ts +105 -0
  187. package/src/logging/format.test.ts +110 -0
  188. package/src/logging/format.ts +255 -0
  189. package/src/logging/logger.test.ts +814 -0
  190. package/src/logging/logger.ts +266 -0
  191. package/src/logging/reporter.test.ts +259 -0
  192. package/src/logging/reporter.ts +110 -0
  193. package/src/logging/sanitizer.test.ts +190 -0
  194. package/src/logging/sanitizer.ts +57 -0
  195. package/src/logging/theme.ts +140 -0
  196. package/src/mail/broadcast.test.ts +204 -0
  197. package/src/mail/broadcast.ts +92 -0
  198. package/src/mail/client.test.ts +774 -0
  199. package/src/mail/client.ts +236 -0
  200. package/src/mail/store.test.ts +898 -0
  201. package/src/mail/store.ts +425 -0
  202. package/src/merge/lock.test.ts +149 -0
  203. package/src/merge/lock.ts +140 -0
  204. package/src/merge/predict.test.ts +387 -0
  205. package/src/merge/predict.ts +249 -0
  206. package/src/merge/queue.test.ts +426 -0
  207. package/src/merge/queue.ts +246 -0
  208. package/src/merge/resolver.test.ts +1993 -0
  209. package/src/merge/resolver.ts +926 -0
  210. package/src/metrics/pricing.test.ts +258 -0
  211. package/src/metrics/pricing.ts +135 -0
  212. package/src/metrics/store.test.ts +978 -0
  213. package/src/metrics/store.ts +501 -0
  214. package/src/metrics/summary.test.ts +398 -0
  215. package/src/metrics/summary.ts +178 -0
  216. package/src/metrics/transcript.test.ts +483 -0
  217. package/src/metrics/transcript.ts +114 -0
  218. package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
  219. package/src/runtimes/aider.test.ts +124 -0
  220. package/src/runtimes/aider.ts +147 -0
  221. package/src/runtimes/amp.test.ts +164 -0
  222. package/src/runtimes/amp.ts +154 -0
  223. package/src/runtimes/claude.test.ts +1474 -0
  224. package/src/runtimes/claude.ts +579 -0
  225. package/src/runtimes/codex.test.ts +805 -0
  226. package/src/runtimes/codex.ts +273 -0
  227. package/src/runtimes/connections.test.ts +214 -0
  228. package/src/runtimes/connections.ts +103 -0
  229. package/src/runtimes/copilot.test.ts +707 -0
  230. package/src/runtimes/copilot.ts +316 -0
  231. package/src/runtimes/cursor.test.ts +497 -0
  232. package/src/runtimes/cursor.ts +205 -0
  233. package/src/runtimes/gemini.test.ts +537 -0
  234. package/src/runtimes/gemini.ts +243 -0
  235. package/src/runtimes/goose.test.ts +133 -0
  236. package/src/runtimes/goose.ts +157 -0
  237. package/src/runtimes/headless-connection.test.ts +264 -0
  238. package/src/runtimes/headless-connection.ts +158 -0
  239. package/src/runtimes/opencode.test.ts +325 -0
  240. package/src/runtimes/opencode.ts +188 -0
  241. package/src/runtimes/pi-guards.test.ts +486 -0
  242. package/src/runtimes/pi-guards.ts +367 -0
  243. package/src/runtimes/pi.test.ts +789 -0
  244. package/src/runtimes/pi.ts +305 -0
  245. package/src/runtimes/registry.test.ts +196 -0
  246. package/src/runtimes/registry.ts +99 -0
  247. package/src/runtimes/sapling.test.ts +1267 -0
  248. package/src/runtimes/sapling.ts +710 -0
  249. package/src/runtimes/types.ts +266 -0
  250. package/src/schema-consistency.test.ts +246 -0
  251. package/src/sessions/compat.test.ts +281 -0
  252. package/src/sessions/compat.ts +105 -0
  253. package/src/sessions/store.test.ts +1748 -0
  254. package/src/sessions/store.ts +858 -0
  255. package/src/test-helpers.test.ts +124 -0
  256. package/src/test-helpers.ts +145 -0
  257. package/src/test-setup.test.ts +31 -0
  258. package/src/test-setup.ts +28 -0
  259. package/src/tools/loam/api.ts +368 -0
  260. package/src/tools/loam/cli.ts +278 -0
  261. package/src/tools/loam/commands/add.ts +52 -0
  262. package/src/tools/loam/commands/archive.ts +214 -0
  263. package/src/tools/loam/commands/audit.ts +276 -0
  264. package/src/tools/loam/commands/compact.ts +1062 -0
  265. package/src/tools/loam/commands/completions.ts +79 -0
  266. package/src/tools/loam/commands/config.ts +381 -0
  267. package/src/tools/loam/commands/delete-domain.ts +121 -0
  268. package/src/tools/loam/commands/delete.ts +316 -0
  269. package/src/tools/loam/commands/diff.ts +200 -0
  270. package/src/tools/loam/commands/doctor.ts +1113 -0
  271. package/src/tools/loam/commands/edit.ts +226 -0
  272. package/src/tools/loam/commands/init.ts +31 -0
  273. package/src/tools/loam/commands/learn.ts +179 -0
  274. package/src/tools/loam/commands/move.ts +323 -0
  275. package/src/tools/loam/commands/onboard.ts +374 -0
  276. package/src/tools/loam/commands/outcome.ts +185 -0
  277. package/src/tools/loam/commands/prime.ts +688 -0
  278. package/src/tools/loam/commands/prune.ts +614 -0
  279. package/src/tools/loam/commands/query.ts +218 -0
  280. package/src/tools/loam/commands/rank.ts +180 -0
  281. package/src/tools/loam/commands/ready.ts +189 -0
  282. package/src/tools/loam/commands/record.ts +1210 -0
  283. package/src/tools/loam/commands/restore.ts +166 -0
  284. package/src/tools/loam/commands/search.ts +327 -0
  285. package/src/tools/loam/commands/setup.ts +887 -0
  286. package/src/tools/loam/commands/status.ts +103 -0
  287. package/src/tools/loam/commands/sync.ts +298 -0
  288. package/src/tools/loam/commands/update.ts +19 -0
  289. package/src/tools/loam/commands/upgrade.ts +93 -0
  290. package/src/tools/loam/commands/validate.ts +190 -0
  291. package/src/tools/loam/index.ts +62 -0
  292. package/src/tools/loam/log.ts +127 -0
  293. package/src/tools/loam/registry/builtins.ts +409 -0
  294. package/src/tools/loam/registry/custom.ts +431 -0
  295. package/src/tools/loam/registry/init.ts +55 -0
  296. package/src/tools/loam/registry/template.ts +40 -0
  297. package/src/tools/loam/registry/type-registry.ts +113 -0
  298. package/src/tools/loam/schemas/config-schema.ts +489 -0
  299. package/src/tools/loam/schemas/config.ts +245 -0
  300. package/src/tools/loam/schemas/index.ts +18 -0
  301. package/src/tools/loam/schemas/record-schema.ts +191 -0
  302. package/src/tools/loam/schemas/record.ts +115 -0
  303. package/src/tools/loam/utils/active-work.ts +205 -0
  304. package/src/tools/loam/utils/anchor-validity.ts +80 -0
  305. package/src/tools/loam/utils/archive.ts +146 -0
  306. package/src/tools/loam/utils/audit.ts +667 -0
  307. package/src/tools/loam/utils/bm25.ts +238 -0
  308. package/src/tools/loam/utils/budget.ts +142 -0
  309. package/src/tools/loam/utils/config.ts +344 -0
  310. package/src/tools/loam/utils/dir-anchors.ts +62 -0
  311. package/src/tools/loam/utils/domain-rules.ts +114 -0
  312. package/src/tools/loam/utils/expertise.ts +393 -0
  313. package/src/tools/loam/utils/format-helpers.ts +96 -0
  314. package/src/tools/loam/utils/format.ts +1234 -0
  315. package/src/tools/loam/utils/git-context.ts +50 -0
  316. package/src/tools/loam/utils/git.ts +183 -0
  317. package/src/tools/loam/utils/hooks.ts +299 -0
  318. package/src/tools/loam/utils/index.ts +52 -0
  319. package/src/tools/loam/utils/json-output.ts +13 -0
  320. package/src/tools/loam/utils/lock.ts +76 -0
  321. package/src/tools/loam/utils/markers.ts +48 -0
  322. package/src/tools/loam/utils/numeric-flags.ts +20 -0
  323. package/src/tools/loam/utils/palette.ts +44 -0
  324. package/src/tools/loam/utils/prime-ranking.ts +135 -0
  325. package/src/tools/loam/utils/recipe-discovery.ts +195 -0
  326. package/src/tools/loam/utils/runtime-flags.ts +28 -0
  327. package/src/tools/loam/utils/scoring.ts +94 -0
  328. package/src/tools/loam/utils/version.ts +116 -0
  329. package/src/tools/sprout/commands/block.ts +64 -0
  330. package/src/tools/sprout/commands/blocked.ts +86 -0
  331. package/src/tools/sprout/commands/close.ts +129 -0
  332. package/src/tools/sprout/commands/completions.ts +198 -0
  333. package/src/tools/sprout/commands/config.ts +238 -0
  334. package/src/tools/sprout/commands/create.ts +164 -0
  335. package/src/tools/sprout/commands/dep.ts +148 -0
  336. package/src/tools/sprout/commands/doctor.ts +979 -0
  337. package/src/tools/sprout/commands/init.ts +83 -0
  338. package/src/tools/sprout/commands/label.ts +178 -0
  339. package/src/tools/sprout/commands/list.ts +210 -0
  340. package/src/tools/sprout/commands/migrate.ts +133 -0
  341. package/src/tools/sprout/commands/onboard.ts +207 -0
  342. package/src/tools/sprout/commands/plan-show.ts +278 -0
  343. package/src/tools/sprout/commands/plan.ts +2526 -0
  344. package/src/tools/sprout/commands/prime.ts +399 -0
  345. package/src/tools/sprout/commands/ready.ts +245 -0
  346. package/src/tools/sprout/commands/search.ts +221 -0
  347. package/src/tools/sprout/commands/show.ts +277 -0
  348. package/src/tools/sprout/commands/stats.ts +146 -0
  349. package/src/tools/sprout/commands/sync.ts +134 -0
  350. package/src/tools/sprout/commands/tpl.ts +364 -0
  351. package/src/tools/sprout/commands/unblock.ts +115 -0
  352. package/src/tools/sprout/commands/update.ts +257 -0
  353. package/src/tools/sprout/commands/upgrade.ts +91 -0
  354. package/src/tools/sprout/config-schema.ts +152 -0
  355. package/src/tools/sprout/config.ts +355 -0
  356. package/src/tools/sprout/filter.ts +107 -0
  357. package/src/tools/sprout/format.ts +43 -0
  358. package/src/tools/sprout/id.ts +22 -0
  359. package/src/tools/sprout/index.ts +204 -0
  360. package/src/tools/sprout/log.ts +76 -0
  361. package/src/tools/sprout/markers.ts +22 -0
  362. package/src/tools/sprout/output.ts +121 -0
  363. package/src/tools/sprout/plan-backref.ts +93 -0
  364. package/src/tools/sprout/plan-context.ts +81 -0
  365. package/src/tools/sprout/plan-domain.ts +139 -0
  366. package/src/tools/sprout/plan-lifecycle.ts +65 -0
  367. package/src/tools/sprout/plan-loam.ts +207 -0
  368. package/src/tools/sprout/plan-schema.ts +209 -0
  369. package/src/tools/sprout/sort.ts +31 -0
  370. package/src/tools/sprout/store.ts +172 -0
  371. package/src/tools/sprout/types.ts +118 -0
  372. package/src/tools/sprout/validation.ts +119 -0
  373. package/src/tools/sprout/version.ts +1 -0
  374. package/src/tools/sprout/yaml.ts +387 -0
  375. package/src/tools/trellis/commands/archive.ts +87 -0
  376. package/src/tools/trellis/commands/completions.ts +610 -0
  377. package/src/tools/trellis/commands/config.ts +382 -0
  378. package/src/tools/trellis/commands/create.ts +252 -0
  379. package/src/tools/trellis/commands/diff.ts +150 -0
  380. package/src/tools/trellis/commands/doctor.ts +771 -0
  381. package/src/tools/trellis/commands/emit.ts +365 -0
  382. package/src/tools/trellis/commands/history.ts +83 -0
  383. package/src/tools/trellis/commands/import.ts +198 -0
  384. package/src/tools/trellis/commands/init.ts +81 -0
  385. package/src/tools/trellis/commands/list.ts +103 -0
  386. package/src/tools/trellis/commands/onboard.ts +156 -0
  387. package/src/tools/trellis/commands/pin.ts +172 -0
  388. package/src/tools/trellis/commands/prime.ts +193 -0
  389. package/src/tools/trellis/commands/render.ts +122 -0
  390. package/src/tools/trellis/commands/schema.ts +353 -0
  391. package/src/tools/trellis/commands/show.ts +115 -0
  392. package/src/tools/trellis/commands/stats.ts +65 -0
  393. package/src/tools/trellis/commands/sync.ts +112 -0
  394. package/src/tools/trellis/commands/tree.ts +123 -0
  395. package/src/tools/trellis/commands/update.ts +330 -0
  396. package/src/tools/trellis/commands/upgrade.ts +95 -0
  397. package/src/tools/trellis/commands/validate.ts +166 -0
  398. package/src/tools/trellis/config-schema.ts +81 -0
  399. package/src/tools/trellis/config.ts +108 -0
  400. package/src/tools/trellis/frontmatter.ts +348 -0
  401. package/src/tools/trellis/id.ts +24 -0
  402. package/src/tools/trellis/index.ts +209 -0
  403. package/src/tools/trellis/markers.ts +28 -0
  404. package/src/tools/trellis/output.ts +84 -0
  405. package/src/tools/trellis/render.ts +212 -0
  406. package/src/tools/trellis/store.ts +144 -0
  407. package/src/tools/trellis/types.ts +82 -0
  408. package/src/tools/trellis/validate.ts +199 -0
  409. package/src/tools/trellis/yaml.ts +309 -0
  410. package/src/tracker/beads.test.ts +454 -0
  411. package/src/tracker/beads.ts +56 -0
  412. package/src/tracker/factory.test.ts +90 -0
  413. package/src/tracker/factory.ts +65 -0
  414. package/src/tracker/sprout.test.ts +461 -0
  415. package/src/tracker/sprout.ts +182 -0
  416. package/src/tracker/types.ts +52 -0
  417. package/src/trellis/client.test.ts +107 -0
  418. package/src/trellis/client.ts +179 -0
  419. package/src/types.ts +970 -0
  420. package/src/utils/bin.test.ts +10 -0
  421. package/src/utils/bin.ts +37 -0
  422. package/src/utils/browser.test.ts +49 -0
  423. package/src/utils/browser.ts +48 -0
  424. package/src/utils/fs.test.ts +119 -0
  425. package/src/utils/fs.ts +62 -0
  426. package/src/utils/pid.test.ts +152 -0
  427. package/src/utils/pid.ts +130 -0
  428. package/src/utils/process-scan.test.ts +53 -0
  429. package/src/utils/process-scan.ts +76 -0
  430. package/src/utils/time.test.ts +43 -0
  431. package/src/utils/time.ts +37 -0
  432. package/src/utils/version.test.ts +33 -0
  433. package/src/utils/version.ts +70 -0
  434. package/src/version.ts +5 -0
  435. package/src/watchdog/daemon.test.ts +3721 -0
  436. package/src/watchdog/daemon.ts +1257 -0
  437. package/src/watchdog/health.test.ts +830 -0
  438. package/src/watchdog/health.ts +434 -0
  439. package/src/watchdog/triage.test.ts +205 -0
  440. package/src/watchdog/triage.ts +205 -0
  441. package/src/worktree/manager.test.ts +720 -0
  442. package/src/worktree/manager.ts +405 -0
  443. package/src/worktree/process.test.ts +172 -0
  444. package/src/worktree/process.ts +131 -0
  445. package/src/worktree/tmux.test.ts +1616 -0
  446. package/src/worktree/tmux.ts +721 -0
  447. package/templates/CLAUDE.md.tmpl +100 -0
  448. package/templates/copilot-hooks.json.tmpl +13 -0
  449. package/templates/hooks.json.tmpl +109 -0
  450. package/templates/overlay.md.tmpl +88 -0
  451. package/ui/dist/apple-touch-icon-bdy6teep.png +0 -0
  452. package/ui/dist/chunk-8s31f05k.css +1 -0
  453. package/ui/dist/chunk-vm5rz679.js +300 -0
  454. package/ui/dist/favicon-nzb39vza.svg +4 -0
  455. package/ui/dist/index.html +17 -0
@@ -0,0 +1,791 @@
1
+ /**
2
+ * CLI command: ap clean [--all] [--mail] [--sessions] [--metrics]
3
+ * [--logs] [--worktrees] [--branches] [--agents] [--specs]
4
+ *
5
+ * Nuclear cleanup of agentplate runtime state.
6
+ * --all does everything. Individual flags allow selective cleanup.
7
+ *
8
+ * Execution order for --all (processes → filesystem → databases):
9
+ * 0. Run loam health checks (informational, non-destructive):
10
+ * - Check domains approaching governance limits
11
+ * - Run loam prune --dry-run (report stale record counts)
12
+ * - Run loam doctor (report health issues)
13
+ * 1. Kill all agentplate tmux sessions
14
+ * 2. Remove all worktrees
15
+ * 3. Delete orphaned agentplate/* branches
16
+ * 4. Delete SQLite databases (mail.db, metrics.db)
17
+ * 5. Wipe sessions.db, merge-queue.db
18
+ * 6. Clear directory contents (logs/, agents/, specs/)
19
+ * 7. Delete nudge-state.json
20
+ */
21
+
22
+ import { existsSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import { loadConfig } from "../config.ts";
25
+ import { AgentError, ValidationError } from "../errors.ts";
26
+ import { createEventStore } from "../events/store.ts";
27
+ import { jsonOutput } from "../json.ts";
28
+ import { createLoamClient } from "../loam/client.ts";
29
+ import { printHint, printSuccess } from "../logging/color.ts";
30
+ import { openSessionStore } from "../sessions/compat.ts";
31
+ import type { AgentSession, LoamDoctorResult, LoamPruneResult, LoamStatus } from "../types.ts";
32
+ import { clearDirectory, deleteFile, resetJsonFile, wipeSqliteDb } from "../utils/fs.ts";
33
+ import { listWorktrees, removeWorktree } from "../worktree/manager.ts";
34
+ import {
35
+ isProcessAlive,
36
+ isSessionAlive,
37
+ killProcessTree,
38
+ killSession,
39
+ listSessions,
40
+ sanitizeTmuxName,
41
+ } from "../worktree/tmux.ts";
42
+
43
+ export interface CleanOptions {
44
+ agent?: string;
45
+ all?: boolean;
46
+ mail?: boolean;
47
+ sessions?: boolean;
48
+ metrics?: boolean;
49
+ logs?: boolean;
50
+ worktrees?: boolean;
51
+ branches?: boolean;
52
+ agents?: boolean;
53
+ specs?: boolean;
54
+ json?: boolean;
55
+ }
56
+
57
+ /**
58
+ * Load active agent sessions from SessionStore for session-end event logging.
59
+ * Returns sessions that are in an active state (booting, working, stalled).
60
+ *
61
+ * Checks for sessions.db or sessions.json existence first to avoid creating
62
+ * an empty database file as a side effect (which would interfere with
63
+ * the "Nothing to clean" detection later in the pipeline).
64
+ */
65
+ function loadActiveSessions(agentplateDir: string): AgentSession[] {
66
+ try {
67
+ const dbPath = join(agentplateDir, "sessions.db");
68
+ const jsonPath = join(agentplateDir, "sessions.json");
69
+ if (!existsSync(dbPath) && !existsSync(jsonPath)) {
70
+ return [];
71
+ }
72
+ const { store } = openSessionStore(agentplateDir);
73
+ try {
74
+ return store.getActive();
75
+ } finally {
76
+ store.close();
77
+ }
78
+ } catch {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Log synthetic session-end events for all active agents before killing tmux sessions.
85
+ *
86
+ * When clean --all or --worktrees kills tmux sessions, the Stop hook never fires
87
+ * because the process is killed externally. This function writes session_end events
88
+ * to the EventStore with reason='clean' so observability records are complete.
89
+ */
90
+ async function logSyntheticSessionEndEvents(agentplateDir: string): Promise<number> {
91
+ let logged = 0;
92
+ try {
93
+ const activeSessions = loadActiveSessions(agentplateDir);
94
+ if (activeSessions.length === 0) {
95
+ return 0;
96
+ }
97
+
98
+ const eventsDbPath = join(agentplateDir, "events.db");
99
+ const eventStore = createEventStore(eventsDbPath);
100
+ try {
101
+ for (const session of activeSessions) {
102
+ eventStore.insert({
103
+ runId: session.runId,
104
+ agentName: session.agentName,
105
+ sessionId: session.id,
106
+ eventType: "session_end",
107
+ toolName: null,
108
+ toolArgs: null,
109
+ toolDurationMs: null,
110
+ level: "info",
111
+ data: JSON.stringify({ reason: "clean", capability: session.capability }),
112
+ });
113
+ logged++;
114
+ }
115
+ } finally {
116
+ eventStore.close();
117
+ }
118
+ } catch {
119
+ // Best effort: event logging should not block cleanup
120
+ }
121
+ return logged;
122
+ }
123
+
124
+ interface CleanResult {
125
+ sessionEndEventsLogged: number;
126
+ tmuxKilled: number;
127
+ orphanPidsReaped: number;
128
+ worktreesCleaned: number;
129
+ branchesDeleted: number;
130
+ mailWiped: boolean;
131
+ sessionsCleared: boolean;
132
+ mergeQueueCleared: boolean;
133
+ metricsWiped: boolean;
134
+ logsCleared: boolean;
135
+ agentsCleared: boolean;
136
+ specsCleared: boolean;
137
+ nudgeStateCleared: boolean;
138
+ currentRunCleared: boolean;
139
+ loamHealth: {
140
+ checked: boolean;
141
+ domainsNearLimit: Array<{ domain: string; recordCount: number; warnThreshold: number }>;
142
+ stalePruneCandidates: number;
143
+ doctorIssues: number;
144
+ doctorWarnings: number;
145
+ } | null;
146
+ }
147
+
148
+ /**
149
+ * Kill agentplate tmux sessions registered in THIS project's SessionStore.
150
+ *
151
+ * Project-scoped: only kills tmux sessions whose names appear in the
152
+ * project's sessions.db (or sessions.json). This prevents cross-project
153
+ * kills during dogfooding, where `bun test` might run inside a live swarm.
154
+ *
155
+ * Falls back to killing all "agentplate-{projectName}-" prefixed tmux sessions
156
+ * only if the SessionStore is unavailable (graceful degradation for broken state).
157
+ */
158
+ async function killAllTmuxSessions(agentplateDir: string, projectName: string): Promise<number> {
159
+ let killed = 0;
160
+ const projectPrefix = `agentplate-${sanitizeTmuxName(projectName)}-`;
161
+ try {
162
+ const tmuxSessions = await listSessions();
163
+ const agentPlateSessions = tmuxSessions.filter((s) => s.name.startsWith(projectPrefix));
164
+ if (agentPlateSessions.length === 0) {
165
+ return 0;
166
+ }
167
+
168
+ // Build a set of tmux session names registered in this project's SessionStore.
169
+ const registeredNames = loadRegisteredTmuxNames(agentplateDir);
170
+
171
+ // If we got registered names, only kill those. Otherwise fall back to all
172
+ // agentplate-{projectName}-* sessions.
173
+ const toKill =
174
+ registeredNames !== null
175
+ ? agentPlateSessions.filter((s) => registeredNames.has(s.name))
176
+ : agentPlateSessions;
177
+
178
+ for (const session of toKill) {
179
+ try {
180
+ await killSession(session.name);
181
+ killed++;
182
+ } catch {
183
+ // Best effort
184
+ }
185
+ }
186
+ } catch {
187
+ // tmux not available or no server running
188
+ }
189
+ return killed;
190
+ }
191
+
192
+ /**
193
+ * Load the set of tmux session names registered in this project's SessionStore.
194
+ *
195
+ * Returns null if the SessionStore cannot be opened (signals the caller to
196
+ * fall back to the legacy "kill all agentplate-*" behavior).
197
+ */
198
+ function loadRegisteredTmuxNames(agentplateDir: string): Set<string> | null {
199
+ try {
200
+ const dbPath = join(agentplateDir, "sessions.db");
201
+ const jsonPath = join(agentplateDir, "sessions.json");
202
+ if (!existsSync(dbPath) && !existsSync(jsonPath)) {
203
+ // No session data at all -- return empty set (not null).
204
+ // This is distinct from "store unavailable": it means the project
205
+ // has no registered sessions, so nothing should be killed.
206
+ return new Set();
207
+ }
208
+ const { store } = openSessionStore(agentplateDir);
209
+ try {
210
+ const allSessions = store.getAll();
211
+ return new Set(allSessions.map((s) => s.tmuxSession));
212
+ } finally {
213
+ store.close();
214
+ }
215
+ } catch {
216
+ // SessionStore is broken -- fall back to legacy behavior
217
+ return null;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Reap any spawn PIDs in sessions.db that survived tmux teardown.
223
+ *
224
+ * `killAllTmuxSessions` walks descendants of the live tmux pane PID and is
225
+ * sufficient for sessions whose tmux container is still up. This handles the
226
+ * leftover case: a stored pid that is still alive but its tmux session is
227
+ * gone (claude was reparented to init when its bash wrapper got SIGHUP) or
228
+ * the session is in a terminal state but the spawn never exited. Best-effort.
229
+ * (agentplate-505d)
230
+ */
231
+ async function reapOrphanedPids(agentplateDir: string): Promise<number> {
232
+ let reaped = 0;
233
+ try {
234
+ const dbPath = join(agentplateDir, "sessions.db");
235
+ const jsonPath = join(agentplateDir, "sessions.json");
236
+ if (!existsSync(dbPath) && !existsSync(jsonPath)) {
237
+ return 0;
238
+ }
239
+ const { store } = openSessionStore(agentplateDir);
240
+ try {
241
+ for (const session of store.getAll()) {
242
+ if (session.pid === null) continue;
243
+ if (!isProcessAlive(session.pid)) continue;
244
+ try {
245
+ await killProcessTree(session.pid);
246
+ reaped++;
247
+ } catch {
248
+ // Best effort
249
+ }
250
+ }
251
+ } finally {
252
+ store.close();
253
+ }
254
+ } catch {
255
+ // Best effort
256
+ }
257
+ return reaped;
258
+ }
259
+
260
+ /**
261
+ * Remove all agentplate worktrees (force remove with branch deletion).
262
+ */
263
+ async function cleanAllWorktrees(root: string): Promise<number> {
264
+ let cleaned = 0;
265
+ try {
266
+ const worktrees = await listWorktrees(root);
267
+ const agentplateWts = worktrees.filter((wt) => wt.branch.startsWith("agentplate/"));
268
+ for (const wt of agentplateWts) {
269
+ try {
270
+ await removeWorktree(root, wt.path, { force: true, forceBranch: true });
271
+ cleaned++;
272
+ } catch {
273
+ // Best effort
274
+ }
275
+ }
276
+ } catch {
277
+ // No worktrees or git error
278
+ }
279
+ return cleaned;
280
+ }
281
+
282
+ /**
283
+ * Delete orphaned agentplate/* branch refs not tied to a worktree.
284
+ */
285
+ async function deleteOrphanedBranches(root: string): Promise<number> {
286
+ let deleted = 0;
287
+ try {
288
+ const proc = Bun.spawn(
289
+ ["git", "for-each-ref", "refs/heads/agentplate/", "--format=%(refname:short)"],
290
+ { cwd: root, stdout: "pipe", stderr: "pipe" },
291
+ );
292
+ const stdout = await new Response(proc.stdout).text();
293
+ await proc.exited;
294
+
295
+ const branches = stdout
296
+ .trim()
297
+ .split("\n")
298
+ .filter((b) => b.length > 0);
299
+ for (const branch of branches) {
300
+ try {
301
+ const del = Bun.spawn(["git", "branch", "-D", branch], {
302
+ cwd: root,
303
+ stdout: "pipe",
304
+ stderr: "pipe",
305
+ });
306
+ const exitCode = await del.exited;
307
+ if (exitCode === 0) deleted++;
308
+ } catch {
309
+ // Best effort
310
+ }
311
+ }
312
+ } catch {
313
+ // Git error
314
+ }
315
+ return deleted;
316
+ }
317
+
318
+ /**
319
+ * Check loam repository health and return diagnostic information.
320
+ *
321
+ * Governance limits warn threshold (based on loam defaults):
322
+ * - Max records per domain: 500 (warn at 400 = 80%)
323
+ *
324
+ * This is informational only — no data is modified.
325
+ */
326
+ async function checkLoamHealth(repoRoot: string): Promise<{
327
+ domainsNearLimit: Array<{ domain: string; recordCount: number; warnThreshold: number }>;
328
+ stalePruneCandidates: number;
329
+ doctorIssues: number;
330
+ doctorWarnings: number;
331
+ } | null> {
332
+ try {
333
+ const loam = createLoamClient(repoRoot);
334
+
335
+ // 1. Check domain sizes against governance limits
336
+ let status: LoamStatus;
337
+ try {
338
+ status = await loam.status();
339
+ } catch {
340
+ // Loam not available or no .loam directory
341
+ return null;
342
+ }
343
+
344
+ const warnThreshold = 400; // 80% of 500 max
345
+ const domainsNearLimit = status.domains
346
+ .filter((d) => d.recordCount >= warnThreshold)
347
+ .map((d) => ({ domain: d.name, recordCount: d.recordCount, warnThreshold }));
348
+
349
+ // 2. Run prune --dry-run to count stale records
350
+ let pruneResult: LoamPruneResult;
351
+ try {
352
+ pruneResult = await loam.prune({ dryRun: true });
353
+ } catch {
354
+ // Prune failed — skip this check
355
+ pruneResult = { success: false, command: "prune", dryRun: true, totalPruned: 0, results: [] };
356
+ }
357
+
358
+ const stalePruneCandidates = pruneResult.totalPruned;
359
+
360
+ // 3. Run doctor to check repository health
361
+ let doctorResult: LoamDoctorResult;
362
+ try {
363
+ doctorResult = await loam.doctor({ fix: false });
364
+ } catch {
365
+ // Doctor failed — skip this check
366
+ doctorResult = {
367
+ success: false,
368
+ command: "doctor",
369
+ checks: [],
370
+ summary: { pass: 0, warn: 0, fail: 0 },
371
+ };
372
+ }
373
+
374
+ const doctorIssues = doctorResult.summary.fail;
375
+ const doctorWarnings = doctorResult.summary.warn;
376
+
377
+ return {
378
+ domainsNearLimit,
379
+ stalePruneCandidates,
380
+ doctorIssues,
381
+ doctorWarnings,
382
+ };
383
+ } catch {
384
+ // Loam not available or other error — skip health checks
385
+ return null;
386
+ }
387
+ }
388
+
389
+ interface AgentCleanResult {
390
+ agentName: string;
391
+ tmuxKilled: boolean;
392
+ pidKilled: boolean;
393
+ worktreeRemoved: boolean;
394
+ branchDeleted: boolean;
395
+ agentDirCleared: boolean;
396
+ logsDirCleared: boolean;
397
+ sessionEndEventLogged: boolean;
398
+ markedCompleted: boolean;
399
+ }
400
+
401
+ /**
402
+ * Delete a git branch (best-effort).
403
+ */
404
+ async function deleteBranch(repoRoot: string, branch: string): Promise<boolean> {
405
+ try {
406
+ const proc = Bun.spawn(["git", "branch", "-D", branch], {
407
+ cwd: repoRoot,
408
+ stdout: "pipe",
409
+ stderr: "pipe",
410
+ });
411
+ const exitCode = await proc.exited;
412
+ return exitCode === 0;
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Perform targeted cleanup of a single agent.
420
+ *
421
+ * Kills its tmux session or process, removes its worktree, deletes its branch,
422
+ * clears its agent and log directories, logs a synthetic session-end event,
423
+ * and marks the session as completed.
424
+ */
425
+ async function cleanSingleAgent(
426
+ agentName: string,
427
+ agentplateDir: string,
428
+ projectRoot: string,
429
+ ): Promise<AgentCleanResult> {
430
+ const result: AgentCleanResult = {
431
+ agentName,
432
+ tmuxKilled: false,
433
+ pidKilled: false,
434
+ worktreeRemoved: false,
435
+ branchDeleted: false,
436
+ agentDirCleared: false,
437
+ logsDirCleared: false,
438
+ sessionEndEventLogged: false,
439
+ markedCompleted: false,
440
+ };
441
+
442
+ const { store } = openSessionStore(agentplateDir);
443
+ let session: AgentSession | undefined;
444
+ try {
445
+ const found = store.getByName(agentName);
446
+ if (!found) {
447
+ throw new AgentError(`Agent "${agentName}" not found`, { agentName });
448
+ }
449
+ session = found;
450
+
451
+ // Log synthetic session-end event for non-completed agents
452
+ if (session.state !== "completed") {
453
+ try {
454
+ const eventsDbPath = join(agentplateDir, "events.db");
455
+ const eventStore = createEventStore(eventsDbPath);
456
+ try {
457
+ eventStore.insert({
458
+ runId: session.runId,
459
+ agentName: session.agentName,
460
+ sessionId: session.id,
461
+ eventType: "session_end",
462
+ toolName: null,
463
+ toolArgs: null,
464
+ toolDurationMs: null,
465
+ level: "info",
466
+ data: JSON.stringify({ reason: "clean --agent", capability: session.capability }),
467
+ });
468
+ result.sessionEndEventLogged = true;
469
+ } finally {
470
+ eventStore.close();
471
+ }
472
+ } catch {
473
+ // Best effort
474
+ }
475
+ }
476
+
477
+ const isHeadless = session.tmuxSession === "" && session.pid !== null;
478
+
479
+ // Kill tmux session or process
480
+ if (isHeadless && session.pid !== null) {
481
+ try {
482
+ if (isProcessAlive(session.pid)) {
483
+ await killProcessTree(session.pid);
484
+ result.pidKilled = true;
485
+ }
486
+ } catch {
487
+ // Best effort
488
+ }
489
+ } else if (session.tmuxSession) {
490
+ try {
491
+ if (await isSessionAlive(session.tmuxSession)) {
492
+ await killSession(session.tmuxSession);
493
+ result.tmuxKilled = true;
494
+ }
495
+ } catch {
496
+ // Best effort
497
+ }
498
+ }
499
+
500
+ // Remove worktree (force)
501
+ if (session.worktreePath) {
502
+ try {
503
+ await removeWorktree(projectRoot, session.worktreePath, {
504
+ force: true,
505
+ forceBranch: false,
506
+ });
507
+ result.worktreeRemoved = true;
508
+ } catch {
509
+ // Best effort
510
+ }
511
+ }
512
+
513
+ // Delete branch
514
+ if (session.branchName) {
515
+ result.branchDeleted = await deleteBranch(projectRoot, session.branchName);
516
+ }
517
+
518
+ // Mark completed
519
+ if (session.state !== "completed") {
520
+ store.updateState(agentName, "completed");
521
+ store.updateLastActivity(agentName);
522
+ result.markedCompleted = true;
523
+ }
524
+ } finally {
525
+ store.close();
526
+ }
527
+
528
+ // Clear agent identity directory
529
+ if (session) {
530
+ const agentDir = join(agentplateDir, "agents", agentName);
531
+ result.agentDirCleared = await clearDirectory(agentDir);
532
+
533
+ // Clear agent logs directory
534
+ const logsDir = join(agentplateDir, "logs", agentName);
535
+ result.logsDirCleared = await clearDirectory(logsDir);
536
+ }
537
+
538
+ return result;
539
+ }
540
+
541
+ /**
542
+ * Entry point for `ap clean [flags]`.
543
+ *
544
+ * @param opts - Command options
545
+ */
546
+ export async function cleanCommand(opts: CleanOptions): Promise<void> {
547
+ const json = opts.json ?? false;
548
+ const all = opts.all ?? false;
549
+ const agentName = opts.agent;
550
+
551
+ // --agent and --all are mutually exclusive
552
+ if (agentName && all) {
553
+ throw new ValidationError(
554
+ "--agent and --all are mutually exclusive. Use --agent <name> for single-agent cleanup or --all for full cleanup.",
555
+ { field: "flags" },
556
+ );
557
+ }
558
+
559
+ const doWorktrees = all || (opts.worktrees ?? false);
560
+ const doBranches = all || (opts.branches ?? false);
561
+ const doMail = all || (opts.mail ?? false);
562
+ const doSessions = all || (opts.sessions ?? false);
563
+ const doMetrics = all || (opts.metrics ?? false);
564
+ const doLogs = all || (opts.logs ?? false);
565
+ const doAgents = all || (opts.agents ?? false);
566
+ const doSpecs = all || (opts.specs ?? false);
567
+
568
+ const anySelected =
569
+ agentName ||
570
+ doWorktrees ||
571
+ doBranches ||
572
+ doMail ||
573
+ doSessions ||
574
+ doMetrics ||
575
+ doLogs ||
576
+ doAgents ||
577
+ doSpecs;
578
+
579
+ if (!anySelected) {
580
+ throw new ValidationError(
581
+ "No cleanup targets specified. Use --all for full cleanup, --agent <name> for single-agent cleanup, or individual flags (--mail, --sessions, --metrics, --logs, --worktrees, --branches, --agents, --specs).",
582
+ { field: "flags" },
583
+ );
584
+ }
585
+
586
+ const config = await loadConfig(process.cwd());
587
+ const root = config.project.root;
588
+ const agentplateDir = join(root, ".agentplate");
589
+
590
+ // Per-agent cleanup: targeted single-agent cleanup
591
+ if (agentName) {
592
+ const agentResult = await cleanSingleAgent(agentName, agentplateDir, root);
593
+ if (json) {
594
+ jsonOutput("clean", { agent: agentResult });
595
+ } else {
596
+ printSuccess("Agent cleaned", agentName);
597
+ if (agentResult.tmuxKilled) process.stdout.write(` Tmux session killed\n`);
598
+ if (agentResult.pidKilled) process.stdout.write(` Process killed (PID)\n`);
599
+ if (agentResult.worktreeRemoved) process.stdout.write(` Worktree removed\n`);
600
+ if (agentResult.branchDeleted)
601
+ process.stdout.write(` Branch deleted: ${agentResult.agentName}\n`);
602
+ if (agentResult.agentDirCleared) process.stdout.write(` Cleared agents/${agentName}/\n`);
603
+ if (agentResult.logsDirCleared) process.stdout.write(` Cleared logs/${agentName}/\n`);
604
+ }
605
+ return;
606
+ }
607
+
608
+ const result: CleanResult = {
609
+ sessionEndEventsLogged: 0,
610
+ tmuxKilled: 0,
611
+ orphanPidsReaped: 0,
612
+ worktreesCleaned: 0,
613
+ branchesDeleted: 0,
614
+ mailWiped: false,
615
+ sessionsCleared: false,
616
+ mergeQueueCleared: false,
617
+ metricsWiped: false,
618
+ logsCleared: false,
619
+ agentsCleared: false,
620
+ specsCleared: false,
621
+ nudgeStateCleared: false,
622
+ currentRunCleared: false,
623
+ loamHealth: null,
624
+ };
625
+
626
+ // 0. Run loam health checks BEFORE cleanup operations (when --all is set).
627
+ // This is informational only — no data is modified.
628
+ if (all) {
629
+ const healthCheck = await checkLoamHealth(root);
630
+ if (healthCheck) {
631
+ result.loamHealth = {
632
+ checked: true,
633
+ domainsNearLimit: healthCheck.domainsNearLimit,
634
+ stalePruneCandidates: healthCheck.stalePruneCandidates,
635
+ doctorIssues: healthCheck.doctorIssues,
636
+ doctorWarnings: healthCheck.doctorWarnings,
637
+ };
638
+ }
639
+ }
640
+
641
+ // 1. Log synthetic session-end events BEFORE killing tmux sessions.
642
+ // When processes are killed externally, the Stop hook never fires,
643
+ // so session_end events would be lost without this step.
644
+ if (doWorktrees || all) {
645
+ result.sessionEndEventsLogged = await logSyntheticSessionEndEvents(agentplateDir);
646
+ }
647
+
648
+ // 2. Kill tmux sessions (must happen before worktree removal)
649
+ if (doWorktrees || all) {
650
+ result.tmuxKilled = await killAllTmuxSessions(agentplateDir, config.project.name);
651
+ }
652
+
653
+ // 2b. Reap any orphaned spawn PIDs that survived tmux teardown.
654
+ // Must run after killAllTmuxSessions (which collects descendants of live
655
+ // panes) but before sessions.db is wiped (we need pid records to find
656
+ // orphans). (agentplate-505d)
657
+ if (doWorktrees || all) {
658
+ result.orphanPidsReaped = await reapOrphanedPids(agentplateDir);
659
+ }
660
+
661
+ // 3. Remove worktrees
662
+ if (doWorktrees) {
663
+ result.worktreesCleaned = await cleanAllWorktrees(root);
664
+ }
665
+
666
+ // 4. Delete orphaned branches
667
+ if (doBranches) {
668
+ result.branchesDeleted = await deleteOrphanedBranches(root);
669
+ }
670
+
671
+ // 5. Wipe databases
672
+ if (doMail) {
673
+ result.mailWiped = await wipeSqliteDb(join(agentplateDir, "mail.db"));
674
+ }
675
+ if (doMetrics) {
676
+ result.metricsWiped = await wipeSqliteDb(join(agentplateDir, "metrics.db"));
677
+ }
678
+
679
+ // 6. Wipe sessions.db + legacy sessions.json
680
+ if (doSessions) {
681
+ result.sessionsCleared = await wipeSqliteDb(join(agentplateDir, "sessions.db"));
682
+ // Also clean legacy sessions.json if it still exists
683
+ await resetJsonFile(join(agentplateDir, "sessions.json"));
684
+ }
685
+ if (all) {
686
+ result.mergeQueueCleared = await wipeSqliteDb(join(agentplateDir, "merge-queue.db"));
687
+ }
688
+
689
+ // 7. Clear directories
690
+ if (doLogs) {
691
+ result.logsCleared = await clearDirectory(join(agentplateDir, "logs"));
692
+ }
693
+ if (doAgents) {
694
+ result.agentsCleared = await clearDirectory(join(agentplateDir, "agents"));
695
+ }
696
+ if (doSpecs) {
697
+ result.specsCleared = await clearDirectory(join(agentplateDir, "specs"));
698
+ }
699
+
700
+ // 8. Delete nudge state + pending nudge markers + current-run.txt
701
+ if (all) {
702
+ result.nudgeStateCleared = await deleteFile(join(agentplateDir, "nudge-state.json"));
703
+ await clearDirectory(join(agentplateDir, "pending-nudges"));
704
+ result.currentRunCleared = await deleteFile(join(agentplateDir, "current-run.txt"));
705
+ }
706
+
707
+ // Output
708
+ if (json) {
709
+ jsonOutput("clean", { ...result });
710
+ return;
711
+ }
712
+
713
+ const lines: string[] = [];
714
+ if (result.sessionEndEventsLogged > 0) {
715
+ lines.push(
716
+ `Logged ${result.sessionEndEventsLogged} synthetic session-end event${result.sessionEndEventsLogged === 1 ? "" : "s"}`,
717
+ );
718
+ }
719
+ if (result.tmuxKilled > 0) {
720
+ lines.push(`Killed ${result.tmuxKilled} tmux session${result.tmuxKilled === 1 ? "" : "s"}`);
721
+ }
722
+ if (result.orphanPidsReaped > 0) {
723
+ lines.push(
724
+ `Reaped ${result.orphanPidsReaped} orphaned spawn process${result.orphanPidsReaped === 1 ? "" : "es"}`,
725
+ );
726
+ }
727
+ if (result.worktreesCleaned > 0) {
728
+ lines.push(
729
+ `Removed ${result.worktreesCleaned} worktree${result.worktreesCleaned === 1 ? "" : "s"}`,
730
+ );
731
+ }
732
+ if (result.branchesDeleted > 0) {
733
+ lines.push(
734
+ `Deleted ${result.branchesDeleted} orphaned branch${result.branchesDeleted === 1 ? "" : "es"}`,
735
+ );
736
+ }
737
+ if (result.mailWiped) lines.push("Wiped mail.db");
738
+ if (result.metricsWiped) lines.push("Wiped metrics.db");
739
+ if (result.sessionsCleared) lines.push("Wiped sessions.db");
740
+ if (result.mergeQueueCleared) lines.push("Wiped merge-queue.db");
741
+ if (result.logsCleared) lines.push("Cleared logs/");
742
+ if (result.agentsCleared) lines.push("Cleared agents/");
743
+ if (result.specsCleared) lines.push("Cleared specs/");
744
+ if (result.nudgeStateCleared) lines.push("Cleared nudge-state.json");
745
+ if (result.currentRunCleared) lines.push("Cleared current-run.txt");
746
+
747
+ // Loam health diagnostics (shown before cleanup results)
748
+ if (result.loamHealth?.checked) {
749
+ const health = result.loamHealth;
750
+ const healthLines: string[] = [];
751
+
752
+ if (health.domainsNearLimit.length > 0) {
753
+ healthLines.push("\nWarning: Loam domains approaching governance limits:");
754
+ for (const d of health.domainsNearLimit) {
755
+ healthLines.push(
756
+ ` ${d.domain}: ${d.recordCount} records (warn threshold: ${d.warnThreshold})`,
757
+ );
758
+ }
759
+ }
760
+
761
+ if (health.stalePruneCandidates > 0) {
762
+ healthLines.push(
763
+ `\nStale records found: ${health.stalePruneCandidates} candidate${health.stalePruneCandidates === 1 ? "" : "s"} (run 'loam prune' to remove)`,
764
+ );
765
+ }
766
+
767
+ if (health.doctorWarnings > 0 || health.doctorIssues > 0) {
768
+ healthLines.push(
769
+ `\nLoam health check: ${health.doctorWarnings} warning${health.doctorWarnings === 1 ? "" : "s"}, ${health.doctorIssues} issue${health.doctorIssues === 1 ? "" : "s"} (run 'loam doctor' for details)`,
770
+ );
771
+ }
772
+
773
+ if (healthLines.length > 0) {
774
+ for (const line of healthLines) {
775
+ process.stdout.write(`${line}\n`);
776
+ }
777
+ }
778
+ }
779
+
780
+ if (lines.length === 0) {
781
+ printHint("Nothing to clean");
782
+ } else {
783
+ if (result.loamHealth?.checked) {
784
+ process.stdout.write("\n--- Cleanup Results ---\n");
785
+ }
786
+ for (const line of lines) {
787
+ process.stdout.write(`${line}\n`);
788
+ }
789
+ printSuccess("Clean complete");
790
+ }
791
+ }