@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,967 @@
1
+ /**
2
+ * CLI command: ap init [--force] [--yes|-y] [--name <name>]
3
+ *
4
+ * Scaffolds the `.agentplate/` directory in the current project with:
5
+ * - config.yaml (serialized from DEFAULT_CONFIG)
6
+ * - agent-manifest.json (starter agent definitions)
7
+ * - hooks.json (central hooks config)
8
+ * - Required subdirectories (agents/, worktrees/, specs/, logs/)
9
+ * - .gitignore entries for transient files
10
+ */
11
+
12
+ import { Database } from "bun:sqlite";
13
+ import { mkdir, readdir, stat } from "node:fs/promises";
14
+ import { basename, join } from "node:path";
15
+ import { DEFAULT_CONFIG } from "../config.ts";
16
+ import { ValidationError } from "../errors.ts";
17
+ import { jsonOutput } from "../json.ts";
18
+ import { printHint, printSuccess, printWarning } from "../logging/color.ts";
19
+ import type { AgentManifest, AgentplateConfig } from "../types.ts";
20
+
21
+ const AGENTPLATE_DIR = ".agentplate";
22
+
23
+ // ---- Ecosystem Bootstrap ----
24
+
25
+ /**
26
+ * Spawner abstraction for testability.
27
+ * Wraps Bun.spawn for running sibling CLI tools.
28
+ */
29
+ export type Spawner = (
30
+ args: string[],
31
+ opts?: { cwd?: string },
32
+ ) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
33
+
34
+ const defaultSpawner: Spawner = async (args, opts) => {
35
+ try {
36
+ const proc = Bun.spawn(args, {
37
+ cwd: opts?.cwd,
38
+ stdout: "pipe",
39
+ stderr: "pipe",
40
+ });
41
+ const exitCode = await proc.exited;
42
+ const stdout = await new Response(proc.stdout).text();
43
+ const stderr = await new Response(proc.stderr).text();
44
+ return { exitCode, stdout, stderr };
45
+ } catch (err) {
46
+ // Binary not found (ENOENT) or other spawn failure — treat as non-zero exit
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ return { exitCode: 1, stdout: "", stderr: message };
49
+ }
50
+ };
51
+
52
+ interface SiblingTool {
53
+ name: string;
54
+ cli: string;
55
+ dotDir: string;
56
+ initCmd: string[];
57
+ onboardCmd: string[];
58
+ }
59
+
60
+ const SIBLING_TOOLS: SiblingTool[] = [
61
+ { name: "loam", cli: "lm", dotDir: ".loam", initCmd: ["init"], onboardCmd: ["onboard"] },
62
+ { name: "sprout", cli: "sr", dotDir: ".sprout", initCmd: ["init"], onboardCmd: ["onboard"] },
63
+ { name: "trellis", cli: "tl", dotDir: ".trellis", initCmd: ["init"], onboardCmd: ["onboard"] },
64
+ ];
65
+
66
+ type ToolStatus = "initialized" | "already_initialized" | "skipped";
67
+ type OnboardStatus = "appended" | "current";
68
+
69
+ /**
70
+ * Resolve the set of sibling tools to bootstrap.
71
+ *
72
+ * If opts.tools is set (comma-separated list of names), filter to those.
73
+ * Otherwise start with all three and remove any skipped via skip flags.
74
+ */
75
+ export function resolveToolSet(opts: InitOptions): SiblingTool[] {
76
+ if (opts.tools) {
77
+ const requested = opts.tools.split(",").map((t) => t.trim());
78
+ return SIBLING_TOOLS.filter((t) => requested.includes(t.name));
79
+ }
80
+ return SIBLING_TOOLS.filter((t) => {
81
+ if (t.name === "loam" && opts.skipLoam) return false;
82
+ if (t.name === "sprout" && opts.skipSprout) return false;
83
+ if (t.name === "trellis" && opts.skipTrellis) return false;
84
+ return true;
85
+ });
86
+ }
87
+
88
+ async function isToolInstalled(cli: string, spawner: Spawner): Promise<boolean> {
89
+ try {
90
+ const result = await spawner([cli, "--version"]);
91
+ return result.exitCode === 0;
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+
97
+ async function initSiblingTool(
98
+ tool: SiblingTool,
99
+ projectRoot: string,
100
+ spawner: Spawner,
101
+ ): Promise<ToolStatus> {
102
+ const installed = await isToolInstalled(tool.cli, spawner);
103
+ if (!installed) {
104
+ printWarning(
105
+ `${tool.name} not installed — skipping`,
106
+ `install: npm i -g @ag-eco/${tool.name}-cli`,
107
+ );
108
+ return "skipped";
109
+ }
110
+
111
+ let result: { exitCode: number; stdout: string; stderr: string };
112
+ try {
113
+ result = await spawner([tool.cli, ...tool.initCmd], { cwd: projectRoot });
114
+ } catch (err) {
115
+ // Spawn failure (e.g. ENOENT) — treat as not installed
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ printWarning(`${tool.name} init failed`, message);
118
+ return "skipped";
119
+ }
120
+ if (result.exitCode !== 0) {
121
+ // Check if dot directory already exists (already initialized)
122
+ try {
123
+ await stat(join(projectRoot, tool.dotDir));
124
+ return "already_initialized";
125
+ } catch {
126
+ // Directory doesn't exist — real failure
127
+ printWarning(`${tool.name} init failed`, result.stderr.trim() || result.stdout.trim());
128
+ return "skipped";
129
+ }
130
+ }
131
+
132
+ printSuccess(`Bootstrapped ${tool.name}`);
133
+ return "initialized";
134
+ }
135
+
136
+ async function onboardTool(
137
+ tool: SiblingTool,
138
+ projectRoot: string,
139
+ spawner: Spawner,
140
+ ): Promise<OnboardStatus> {
141
+ const installed = await isToolInstalled(tool.cli, spawner);
142
+ if (!installed) return "current";
143
+
144
+ try {
145
+ const result = await spawner([tool.cli, ...tool.onboardCmd], { cwd: projectRoot });
146
+ return result.exitCode === 0 ? "appended" : "current";
147
+ } catch {
148
+ return "current";
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Known runtime CLI candidates in detection priority order.
154
+ * First installed runtime wins.
155
+ */
156
+ const RUNTIME_CANDIDATES: Array<{ name: string; cli: string }> = [
157
+ { name: "claude", cli: "claude" },
158
+ { name: "copilot", cli: "copilot" },
159
+ { name: "gemini", cli: "gemini" },
160
+ { name: "opencode", cli: "opencode" },
161
+ { name: "sapling", cli: "sp" },
162
+ { name: "pi", cli: "pi" },
163
+ ];
164
+
165
+ /**
166
+ * Detect the default runtime by checking which coding agent CLIs are installed.
167
+ *
168
+ * Uses `which <cli>` via the spawner abstraction so detection is testable
169
+ * without real binaries on PATH. Returns the first installed runtime by
170
+ * priority order, or "claude" as the safe fallback.
171
+ *
172
+ * @param spawner - Spawner abstraction (defaults to Bun.spawn wrapper)
173
+ * @returns Runtime name suitable for config.runtime.default
174
+ */
175
+ export async function detectDefaultRuntime(spawner: Spawner): Promise<string> {
176
+ for (const { name, cli } of RUNTIME_CANDIDATES) {
177
+ const result = await spawner(["which", cli]);
178
+ if (result.exitCode === 0) {
179
+ return name;
180
+ }
181
+ }
182
+ return "claude";
183
+ }
184
+
185
+ /**
186
+ * Set up .gitattributes with merge=union entries for JSONL files.
187
+ *
188
+ * Only adds entries not already present. Returns true if file was modified.
189
+ */
190
+ async function setupGitattributes(projectRoot: string): Promise<boolean> {
191
+ const entries = [".loam/expertise/*.jsonl merge=union", ".sprout/issues.jsonl merge=union"];
192
+
193
+ const gitattrsPath = join(projectRoot, ".gitattributes");
194
+ let existing = "";
195
+
196
+ try {
197
+ existing = await Bun.file(gitattrsPath).text();
198
+ } catch {
199
+ // File doesn't exist yet — will be created
200
+ }
201
+
202
+ const missing = entries.filter((e) => !existing.includes(e));
203
+ if (missing.length === 0) return false;
204
+
205
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
206
+ await Bun.write(gitattrsPath, `${existing}${separator}${missing.join("\n")}\n`);
207
+ return true;
208
+ }
209
+
210
+ /**
211
+ * Detect the project name from git or fall back to directory name.
212
+ */
213
+ async function detectProjectName(root: string): Promise<string> {
214
+ // Try git remote origin
215
+ try {
216
+ const proc = Bun.spawn(["git", "remote", "get-url", "origin"], {
217
+ cwd: root,
218
+ stdout: "pipe",
219
+ stderr: "pipe",
220
+ });
221
+ const exitCode = await proc.exited;
222
+ if (exitCode === 0) {
223
+ const url = (await new Response(proc.stdout).text()).trim();
224
+ // Extract repo name from URL: git@host:user/repo.git or https://host/user/repo.git
225
+ const match = url.match(/\/([^/]+?)(?:\.git)?$/);
226
+ if (match?.[1]) {
227
+ return match[1];
228
+ }
229
+ }
230
+ } catch {
231
+ // Git not available or not a git repo
232
+ }
233
+
234
+ return basename(root);
235
+ }
236
+
237
+ /**
238
+ * Detect the canonical branch name from git.
239
+ */
240
+ async function detectCanonicalBranch(root: string): Promise<string> {
241
+ try {
242
+ const proc = Bun.spawn(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], {
243
+ cwd: root,
244
+ stdout: "pipe",
245
+ stderr: "pipe",
246
+ });
247
+ const exitCode = await proc.exited;
248
+ if (exitCode === 0) {
249
+ const ref = (await new Response(proc.stdout).text()).trim();
250
+ // refs/remotes/origin/main -> main
251
+ const branch = ref.split("/").pop();
252
+ if (branch) {
253
+ return branch;
254
+ }
255
+ }
256
+ } catch {
257
+ // Not available
258
+ }
259
+
260
+ // Fall back to checking current branch
261
+ try {
262
+ const proc = Bun.spawn(["git", "branch", "--show-current"], {
263
+ cwd: root,
264
+ stdout: "pipe",
265
+ stderr: "pipe",
266
+ });
267
+ const exitCode = await proc.exited;
268
+ if (exitCode === 0) {
269
+ const branch = (await new Response(proc.stdout).text()).trim();
270
+ if (branch) {
271
+ return branch;
272
+ }
273
+ }
274
+ } catch {
275
+ // Not available
276
+ }
277
+
278
+ return "main";
279
+ }
280
+
281
+ /**
282
+ * Serialize an AgentplateConfig to YAML format.
283
+ *
284
+ * Handles nested objects with indentation, scalar values,
285
+ * arrays with `- item` syntax, and empty arrays as `[]`.
286
+ */
287
+ function serializeConfigToYaml(config: AgentplateConfig): string {
288
+ const lines: string[] = [];
289
+ lines.push("# Agentplate configuration");
290
+ lines.push("# See: https://github.com/agentplate/agentplate");
291
+ lines.push("");
292
+
293
+ serializeObject(config as unknown as Record<string, unknown>, lines, 0);
294
+
295
+ return `${lines.join("\n")}\n`;
296
+ }
297
+
298
+ /**
299
+ * Recursively serialize an object to YAML lines.
300
+ */
301
+ function serializeObject(obj: Record<string, unknown>, lines: string[], depth: number): void {
302
+ const indent = " ".repeat(depth);
303
+
304
+ for (const [key, value] of Object.entries(obj)) {
305
+ if (value === null || value === undefined) {
306
+ lines.push(`${indent}${key}: null`);
307
+ } else if (typeof value === "object" && !Array.isArray(value)) {
308
+ lines.push(`${indent}${key}:`);
309
+ serializeObject(value as Record<string, unknown>, lines, depth + 1);
310
+ } else if (Array.isArray(value)) {
311
+ if (value.length === 0) {
312
+ lines.push(`${indent}${key}: []`);
313
+ } else {
314
+ lines.push(`${indent}${key}:`);
315
+ const itemIndent = " ".repeat(depth + 1);
316
+ const propIndent = " ".repeat(depth + 2);
317
+ for (const item of value) {
318
+ if (item !== null && typeof item === "object" && !Array.isArray(item)) {
319
+ // Object array item: "- firstKey: firstVal\n otherKey: otherVal"
320
+ const entries = Object.entries(item as Record<string, unknown>);
321
+ if (entries.length > 0) {
322
+ const [firstKey, firstVal] = entries[0] ?? [];
323
+ lines.push(`${itemIndent}- ${firstKey}: ${formatYamlValue(firstVal)}`);
324
+ for (let j = 1; j < entries.length; j++) {
325
+ const [k, v] = entries[j] ?? [];
326
+ lines.push(`${propIndent}${k}: ${formatYamlValue(v)}`);
327
+ }
328
+ }
329
+ } else {
330
+ lines.push(`${itemIndent}- ${formatYamlValue(item)}`);
331
+ }
332
+ }
333
+ }
334
+ } else {
335
+ lines.push(`${indent}${key}: ${formatYamlValue(value)}`);
336
+ }
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Format a scalar value for YAML output.
342
+ */
343
+ function formatYamlValue(value: unknown): string {
344
+ if (typeof value === "string") {
345
+ // Quote strings that could be misinterpreted
346
+ if (
347
+ value === "" ||
348
+ value === "true" ||
349
+ value === "false" ||
350
+ value === "null" ||
351
+ value.includes(":") ||
352
+ value.includes("#") ||
353
+ value.includes("'") ||
354
+ value.includes('"') ||
355
+ value.includes("\n") ||
356
+ /^\d/.test(value)
357
+ ) {
358
+ // Use double quotes, escaping inner double quotes
359
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
360
+ }
361
+ return value;
362
+ }
363
+
364
+ if (typeof value === "number") {
365
+ return String(value);
366
+ }
367
+
368
+ if (typeof value === "boolean") {
369
+ return value ? "true" : "false";
370
+ }
371
+
372
+ if (value === null || value === undefined) {
373
+ return "null";
374
+ }
375
+
376
+ return String(value);
377
+ }
378
+
379
+ /**
380
+ * Build the starter agent manifest.
381
+ */
382
+ export function buildAgentManifest(): AgentManifest {
383
+ const agents: AgentManifest["agents"] = {
384
+ scout: {
385
+ file: "scout.md",
386
+ model: "haiku",
387
+ tools: ["Read", "Glob", "Grep", "Bash"],
388
+ capabilities: ["explore", "research"],
389
+ canSpawn: false,
390
+ constraints: ["read-only"],
391
+ },
392
+ builder: {
393
+ file: "builder.md",
394
+ model: "sonnet",
395
+ tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
396
+ capabilities: ["implement", "refactor", "fix"],
397
+ canSpawn: false,
398
+ constraints: [],
399
+ },
400
+ reviewer: {
401
+ file: "reviewer.md",
402
+ model: "sonnet",
403
+ tools: ["Read", "Glob", "Grep", "Bash"],
404
+ capabilities: ["review", "validate"],
405
+ canSpawn: false,
406
+ constraints: ["read-only"],
407
+ },
408
+ lead: {
409
+ file: "lead.md",
410
+ model: "opus",
411
+ tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Task"],
412
+ capabilities: ["coordinate", "implement", "review"],
413
+ canSpawn: true,
414
+ constraints: [],
415
+ },
416
+ merger: {
417
+ file: "merger.md",
418
+ model: "sonnet",
419
+ tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
420
+ capabilities: ["merge", "resolve-conflicts"],
421
+ canSpawn: false,
422
+ constraints: [],
423
+ },
424
+ coordinator: {
425
+ file: "coordinator.md",
426
+ model: "opus",
427
+ tools: ["Read", "Glob", "Grep", "Bash"],
428
+ capabilities: ["coordinate", "dispatch", "escalate"],
429
+ canSpawn: true,
430
+ constraints: ["read-only", "no-worktree"],
431
+ },
432
+ orchestrator: {
433
+ file: "orchestrator.md",
434
+ model: "opus",
435
+ tools: ["Read", "Glob", "Grep", "Bash"],
436
+ capabilities: ["orchestrate", "coordinate", "dispatch", "escalate"],
437
+ canSpawn: true,
438
+ constraints: ["read-only", "no-worktree"],
439
+ },
440
+ monitor: {
441
+ file: "monitor.md",
442
+ model: "sonnet",
443
+ tools: ["Read", "Glob", "Grep", "Bash"],
444
+ capabilities: ["monitor", "patrol"],
445
+ canSpawn: false,
446
+ constraints: ["read-only", "no-worktree"],
447
+ },
448
+ };
449
+
450
+ // Build capability index: map each capability to agent names that declare it
451
+ const capabilityIndex: Record<string, string[]> = {};
452
+ for (const [name, def] of Object.entries(agents)) {
453
+ for (const cap of def.capabilities) {
454
+ const existing = capabilityIndex[cap];
455
+ if (existing) {
456
+ existing.push(name);
457
+ } else {
458
+ capabilityIndex[cap] = [name];
459
+ }
460
+ }
461
+ }
462
+
463
+ return { version: "1.0", agents, capabilityIndex };
464
+ }
465
+
466
+ /**
467
+ * Build the hooks.json content for the project orchestrator.
468
+ *
469
+ * Always generates from scratch (not from the agent template, which contains
470
+ * {{AGENT_NAME}} placeholders and space indentation). Uses tab indentation
471
+ * to match Biome formatting rules.
472
+ */
473
+ export function buildHooksJson(): string {
474
+ // Tool name extraction: reads hook stdin JSON and extracts tool_name field.
475
+ // Claude Code sends {"tool_name":"Bash","tool_input":{...}} on stdin for
476
+ // PreToolUse/PostToolUse hooks.
477
+ const toolNameExtract =
478
+ 'read -r INPUT; TOOL_NAME=$(echo "$INPUT" | sed \'s/.*"tool_name": *"\\([^"]*\\)".*/\\1/\');';
479
+
480
+ const hooks = {
481
+ hooks: {
482
+ SessionStart: [
483
+ {
484
+ matcher: "",
485
+ hooks: [
486
+ {
487
+ type: "command",
488
+ command: "ap prime --agent orchestrator",
489
+ },
490
+ ],
491
+ },
492
+ ],
493
+ UserPromptSubmit: [
494
+ {
495
+ matcher: "",
496
+ hooks: [
497
+ {
498
+ type: "command",
499
+ command: "ap mail check --inject --agent orchestrator",
500
+ },
501
+ ],
502
+ },
503
+ ],
504
+ PreToolUse: [
505
+ {
506
+ matcher: "Bash",
507
+ hooks: [
508
+ {
509
+ type: "command",
510
+ command:
511
+ 'read -r INPUT; CMD=$(echo "$INPUT" | sed \'s/.*"command": *"\\([^"]*\\)".*/\\1/\'); if echo "$CMD" | grep -qE \'\\bgit\\s+push\\b\'; then echo \'{"decision":"block","reason":"git push is blocked by agentplate — merge locally, push manually when ready"}\'; exit 0; fi;',
512
+ },
513
+ ],
514
+ },
515
+ {
516
+ matcher: "",
517
+ hooks: [
518
+ {
519
+ type: "command",
520
+ command: `${toolNameExtract} ap log tool-start --agent orchestrator --tool-name "$TOOL_NAME"`,
521
+ },
522
+ ],
523
+ },
524
+ ],
525
+ PostToolUse: [
526
+ {
527
+ matcher: "",
528
+ hooks: [
529
+ {
530
+ type: "command",
531
+ command: `${toolNameExtract} ap log tool-end --agent orchestrator --tool-name "$TOOL_NAME"`,
532
+ },
533
+ ],
534
+ },
535
+ {
536
+ matcher: "Bash",
537
+ hooks: [
538
+ {
539
+ type: "command",
540
+ command:
541
+ "read -r INPUT; if echo \"$INPUT\" | grep -q 'git commit'; then loam diff HEAD~1 2>/dev/null || true; fi",
542
+ },
543
+ ],
544
+ },
545
+ ],
546
+ Stop: [
547
+ {
548
+ matcher: "",
549
+ hooks: [
550
+ {
551
+ type: "command",
552
+ command: "ap log session-end --agent orchestrator",
553
+ },
554
+ {
555
+ type: "command",
556
+ command: "loam learn",
557
+ },
558
+ ],
559
+ },
560
+ ],
561
+ PreCompact: [
562
+ {
563
+ matcher: "",
564
+ hooks: [
565
+ {
566
+ type: "command",
567
+ command: "ap prime --agent orchestrator --compact",
568
+ },
569
+ ],
570
+ },
571
+ ],
572
+ },
573
+ };
574
+
575
+ return `${JSON.stringify(hooks, null, "\t")}\n`;
576
+ }
577
+
578
+ /**
579
+ * Migrate existing SQLite databases on --force reinit.
580
+ *
581
+ * Opens each DB, enables WAL mode, and re-runs CREATE TABLE/INDEX IF NOT EXISTS
582
+ * to apply any schema additions without losing existing data.
583
+ */
584
+ async function migrateExistingDatabases(agentplatePath: string): Promise<string[]> {
585
+ const migrated: string[] = [];
586
+
587
+ // Migrate mail.db
588
+ const mailDbPath = join(agentplatePath, "mail.db");
589
+ if (await Bun.file(mailDbPath).exists()) {
590
+ const db = new Database(mailDbPath);
591
+ db.exec("PRAGMA journal_mode = WAL");
592
+ db.exec("PRAGMA busy_timeout = 5000");
593
+ db.exec(`
594
+ CREATE TABLE IF NOT EXISTS messages (
595
+ id TEXT PRIMARY KEY,
596
+ from_agent TEXT NOT NULL,
597
+ to_agent TEXT NOT NULL,
598
+ subject TEXT NOT NULL,
599
+ body TEXT NOT NULL,
600
+ type TEXT NOT NULL DEFAULT 'status',
601
+ priority TEXT NOT NULL DEFAULT 'normal',
602
+ thread_id TEXT,
603
+ read INTEGER NOT NULL DEFAULT 0,
604
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
605
+ )`);
606
+ db.exec(`
607
+ CREATE INDEX IF NOT EXISTS idx_inbox ON messages(to_agent, read);
608
+ CREATE INDEX IF NOT EXISTS idx_thread ON messages(thread_id)`);
609
+ db.close();
610
+ migrated.push("mail.db");
611
+ }
612
+
613
+ // Migrate metrics.db
614
+ const metricsDbPath = join(agentplatePath, "metrics.db");
615
+ if (await Bun.file(metricsDbPath).exists()) {
616
+ const db = new Database(metricsDbPath);
617
+ db.exec("PRAGMA journal_mode = WAL");
618
+ db.exec("PRAGMA busy_timeout = 5000");
619
+ db.exec(`
620
+ CREATE TABLE IF NOT EXISTS sessions (
621
+ agent_name TEXT NOT NULL,
622
+ task_id TEXT NOT NULL,
623
+ capability TEXT NOT NULL,
624
+ started_at TEXT NOT NULL,
625
+ completed_at TEXT,
626
+ duration_ms INTEGER NOT NULL DEFAULT 0,
627
+ exit_code INTEGER,
628
+ merge_result TEXT,
629
+ parent_agent TEXT,
630
+ PRIMARY KEY (agent_name, task_id)
631
+ )`);
632
+ db.close();
633
+ migrated.push("metrics.db");
634
+ }
635
+
636
+ return migrated;
637
+ }
638
+
639
+ /**
640
+ * Content for .agentplate/.gitignore — runtime state that should not be tracked.
641
+ * Uses wildcard+whitelist pattern: ignore everything, whitelist tracked files.
642
+ * Auto-healed by ap prime on each session start.
643
+ * Config files (config.yaml, agent-manifest.json, hooks.json) remain tracked.
644
+ */
645
+ export const AGENTPLATE_GITIGNORE = `# Wildcard+whitelist: ignore everything, whitelist tracked files
646
+ # Auto-healed by ap prime on each session start
647
+ *
648
+ !.gitignore
649
+ !config.yaml
650
+ !agent-manifest.json
651
+ !hooks.json
652
+ !groups.json
653
+ !agent-defs/
654
+ !agent-defs/**
655
+ !README.md
656
+ `;
657
+
658
+ /**
659
+ * Content for .agentplate/README.md — explains the directory to contributors.
660
+ */
661
+ export const AGENTPLATE_README = `# .agentplate/
662
+
663
+ This directory is managed by [agentplate](https://github.com/hgoudat/agentplate) — a multi-agent orchestration system for Claude Code.
664
+
665
+ Agentplate turns a single Claude Code session into a multi-agent team by spawning worker agents in git worktrees via tmux, coordinating them through a custom SQLite mail system, and merging their work back with tiered conflict resolution.
666
+
667
+ ## Key Commands
668
+
669
+ - \`ap init\` — Initialize this directory
670
+ - \`ap status\` — Show active agents and state
671
+ - \`ap sling <id>\` — Spawn a worker agent
672
+ - \`ap mail check\` — Check agent messages
673
+ - \`ap merge\` — Merge agent work back
674
+ - \`ap dashboard\` — Live TUI monitoring
675
+ - \`ap doctor\` — Run health checks
676
+
677
+ ## Structure
678
+
679
+ - \`config.yaml\` — Project configuration
680
+ - \`agent-manifest.json\` — Agent registry
681
+ - \`hooks.json\` — Claude Code hooks config
682
+ - \`agent-defs/\` — Agent definition files (.md)
683
+ - \`specs/\` — Task specifications
684
+ - \`agents/\` — Per-agent state and identity
685
+ - \`worktrees/\` — Git worktrees (gitignored)
686
+ - \`logs/\` — Agent logs (gitignored)
687
+ `;
688
+
689
+ /**
690
+ * Write .agentplate/.gitignore for runtime state files.
691
+ * Always overwrites to support --force reinit and auto-healing via prime.
692
+ */
693
+ export async function writeAgentplateGitignore(agentplatePath: string): Promise<void> {
694
+ const gitignorePath = join(agentplatePath, ".gitignore");
695
+ await Bun.write(gitignorePath, AGENTPLATE_GITIGNORE);
696
+ }
697
+
698
+ /**
699
+ * Write .agentplate/README.md explaining the directory to contributors.
700
+ * Always overwrites to support --force reinit.
701
+ */
702
+ export async function writeAgentplateReadme(agentplatePath: string): Promise<void> {
703
+ const readmePath = join(agentplatePath, "README.md");
704
+ await Bun.write(readmePath, AGENTPLATE_README);
705
+ }
706
+
707
+ export interface InitOptions {
708
+ yes?: boolean;
709
+ name?: string;
710
+ force?: boolean;
711
+ /** Comma-separated list of ecosystem tools to bootstrap (e.g. "loam,sprout"). Default: all. */
712
+ tools?: string;
713
+ skipLoam?: boolean;
714
+ skipSprout?: boolean;
715
+ skipTrellis?: boolean;
716
+ /** Skip the onboard step (injecting CLAUDE.md sections for ecosystem tools). */
717
+ skipOnboard?: boolean;
718
+ /** Output final result as JSON envelope. */
719
+ json?: boolean;
720
+ /** Injectable spawner for testability. */
721
+ _spawner?: Spawner;
722
+ }
723
+
724
+ /**
725
+ * Print a success status line.
726
+ */
727
+ function printCreated(relativePath: string): void {
728
+ printSuccess("Created", relativePath);
729
+ }
730
+
731
+ /**
732
+ * Entry point for `ap init [--force] [--yes|-y] [--name <name>]`.
733
+ *
734
+ * Scaffolds the .agentplate/ directory structure in the current working directory.
735
+ *
736
+ * @param opts - Command options
737
+ */
738
+ export async function initCommand(opts: InitOptions): Promise<void> {
739
+ const force = opts.force ?? false;
740
+ const yes = opts.yes ?? false;
741
+ const projectRoot = process.cwd();
742
+ const spawner = opts._spawner ?? defaultSpawner;
743
+ const agentplatePath = join(projectRoot, AGENTPLATE_DIR);
744
+
745
+ // 0. Verify we're inside a git repository
746
+ const gitCheck = Bun.spawn(["git", "rev-parse", "--is-inside-work-tree"], {
747
+ cwd: projectRoot,
748
+ stdout: "pipe",
749
+ stderr: "pipe",
750
+ });
751
+ const gitCheckExit = await gitCheck.exited;
752
+ if (gitCheckExit !== 0) {
753
+ throw new ValidationError("agentplate requires a git repository. Run 'git init' first.", {
754
+ field: "git",
755
+ });
756
+ }
757
+
758
+ // 1. Check if .agentplate/ already exists
759
+ const existingDir = Bun.file(join(agentplatePath, "config.yaml"));
760
+ if (await existingDir.exists()) {
761
+ if (!force && !yes) {
762
+ process.stdout.write(
763
+ "Warning: .agentplate/ already initialized in this project.\n" +
764
+ "Use --force or --yes to reinitialize.\n",
765
+ );
766
+ return;
767
+ }
768
+ const flag = yes ? "--yes" : "--force";
769
+ process.stdout.write(`Reinitializing .agentplate/ (${flag})\n\n`);
770
+ }
771
+
772
+ // 2. Detect project info
773
+ const projectName = opts.name ?? (await detectProjectName(projectRoot));
774
+ const canonicalBranch = await detectCanonicalBranch(projectRoot);
775
+ let defaultRuntime = "claude";
776
+ try {
777
+ defaultRuntime = await detectDefaultRuntime(spawner);
778
+ } catch {
779
+ // Non-fatal: fall back to claude if runtime detection fails
780
+ }
781
+
782
+ process.stdout.write(`Initializing agentplate for "${projectName}"...\n\n`);
783
+
784
+ // 3. Create directory structure
785
+ const dirs = [
786
+ AGENTPLATE_DIR,
787
+ join(AGENTPLATE_DIR, "agents"),
788
+ join(AGENTPLATE_DIR, "agent-defs"),
789
+ join(AGENTPLATE_DIR, "worktrees"),
790
+ join(AGENTPLATE_DIR, "specs"),
791
+ join(AGENTPLATE_DIR, "logs"),
792
+ ];
793
+
794
+ for (const dir of dirs) {
795
+ await mkdir(join(projectRoot, dir), { recursive: true });
796
+ printCreated(`${dir}/`);
797
+ }
798
+
799
+ // 3b. Deploy agent definition .md files from agentplate install directory
800
+ const agentplateAgentsDir = join(import.meta.dir, "..", "..", "agents");
801
+ const agentDefsTarget = join(agentplatePath, "agent-defs");
802
+ const agentDefFiles = await readdir(agentplateAgentsDir);
803
+ for (const fileName of agentDefFiles) {
804
+ if (!fileName.endsWith(".md")) continue;
805
+ if (fileName === "supervisor.md") continue; // Deprecated: not deployed to new projects
806
+ const source = Bun.file(join(agentplateAgentsDir, fileName));
807
+ const content = await source.text();
808
+ await Bun.write(join(agentDefsTarget, fileName), content);
809
+ printCreated(`${AGENTPLATE_DIR}/agent-defs/${fileName}`);
810
+ }
811
+
812
+ // 4. Write config.yaml
813
+ const config = structuredClone(DEFAULT_CONFIG);
814
+ config.project.name = projectName;
815
+ config.project.root = projectRoot;
816
+ config.project.canonicalBranch = canonicalBranch;
817
+ if (config.runtime) {
818
+ config.runtime.default = defaultRuntime;
819
+ // New projects default to headless Claude spawns; the UI (`ap serve`) is the
820
+ // primary operator surface and tmux is opt-in via `--no-headless`. Existing
821
+ // projects keep tmux until they edit their config (agentplate-caec).
822
+ config.runtime.claudeHeadlessByDefault = true;
823
+ }
824
+
825
+ const configYaml = serializeConfigToYaml(config);
826
+ const configPath = join(agentplatePath, "config.yaml");
827
+ await Bun.write(configPath, configYaml);
828
+ printCreated(`${AGENTPLATE_DIR}/config.yaml`);
829
+
830
+ // 5. Write agent-manifest.json
831
+ const manifest = buildAgentManifest();
832
+ const manifestPath = join(agentplatePath, "agent-manifest.json");
833
+ await Bun.write(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
834
+ printCreated(`${AGENTPLATE_DIR}/agent-manifest.json`);
835
+
836
+ // 6. Write hooks.json
837
+ const hooksContent = buildHooksJson();
838
+ const hooksPath = join(agentplatePath, "hooks.json");
839
+ await Bun.write(hooksPath, hooksContent);
840
+ printCreated(`${AGENTPLATE_DIR}/hooks.json`);
841
+
842
+ // 7. Write .agentplate/.gitignore for runtime state
843
+ await writeAgentplateGitignore(agentplatePath);
844
+ printCreated(`${AGENTPLATE_DIR}/.gitignore`);
845
+
846
+ // 7b. Write .agentplate/README.md
847
+ await writeAgentplateReadme(agentplatePath);
848
+ printCreated(`${AGENTPLATE_DIR}/README.md`);
849
+
850
+ // 8. Migrate existing SQLite databases on --force reinit
851
+ if (force || yes) {
852
+ const migrated = await migrateExistingDatabases(agentplatePath);
853
+ for (const dbName of migrated) {
854
+ printSuccess("Migrated", dbName);
855
+ }
856
+ }
857
+
858
+ // 9. Bootstrap sibling ecosystem tools
859
+ const toolSet = resolveToolSet(opts);
860
+ const toolResults: Record<string, { status: ToolStatus; path: string }> = {
861
+ agentplate: { status: "initialized", path: agentplatePath },
862
+ };
863
+
864
+ if (toolSet.length > 0) {
865
+ process.stdout.write("\n");
866
+ process.stdout.write("Bootstrapping ecosystem tools...\n\n");
867
+ }
868
+
869
+ for (const tool of toolSet) {
870
+ const status = await initSiblingTool(tool, projectRoot, spawner);
871
+ toolResults[tool.name] = {
872
+ status,
873
+ path: join(projectRoot, tool.dotDir),
874
+ };
875
+ }
876
+
877
+ // 10. Set up .gitattributes with merge=union for JSONL files
878
+ const gitattrsUpdated = await setupGitattributes(projectRoot);
879
+ if (gitattrsUpdated) {
880
+ printCreated(".gitattributes");
881
+ }
882
+
883
+ // 11. Run onboard for each tool (inject CLAUDE.md sections)
884
+ const onboardResults: Record<string, OnboardStatus> = {};
885
+ if (!opts.skipOnboard) {
886
+ for (const tool of toolSet) {
887
+ if (toolResults[tool.name]?.status !== "skipped") {
888
+ const status = await onboardTool(tool, projectRoot, spawner);
889
+ onboardResults[tool.name] = status;
890
+ }
891
+ }
892
+ }
893
+
894
+ // 12. Auto-commit scaffold files so ecosystem dirs are tracked before agents create branches.
895
+ // Without this, agent branches that add files to .loam/.sprout/.trellis cause
896
+ // untracked-vs-tracked conflicts in ap merge (agentplate-fe42).
897
+ let scaffoldCommitted = false;
898
+ const pathsToAdd: string[] = [AGENTPLATE_DIR];
899
+
900
+ // Add .gitattributes if it exists
901
+ try {
902
+ await stat(join(projectRoot, ".gitattributes"));
903
+ pathsToAdd.push(".gitattributes");
904
+ } catch {
905
+ // not present — skip
906
+ }
907
+
908
+ // Add CLAUDE.md if it exists (may have been modified by onboard)
909
+ try {
910
+ await stat(join(projectRoot, "CLAUDE.md"));
911
+ pathsToAdd.push("CLAUDE.md");
912
+ } catch {
913
+ // not present — skip
914
+ }
915
+
916
+ // Add sibling tool dirs that were created
917
+ for (const tool of SIBLING_TOOLS) {
918
+ try {
919
+ await stat(join(projectRoot, tool.dotDir));
920
+ pathsToAdd.push(tool.dotDir);
921
+ } catch {
922
+ // not present — skip
923
+ }
924
+ }
925
+
926
+ const addResult = await spawner(["git", "add", ...pathsToAdd], { cwd: projectRoot });
927
+ if (addResult.exitCode !== 0) {
928
+ printWarning("Scaffold commit skipped", addResult.stderr.trim() || "git add failed");
929
+ } else {
930
+ // git diff --cached --quiet exits 0 if nothing staged, 1 if changes are staged
931
+ const diffResult = await spawner(["git", "diff", "--cached", "--quiet"], {
932
+ cwd: projectRoot,
933
+ });
934
+ if (diffResult.exitCode !== 0) {
935
+ // Changes are staged — commit them
936
+ const commitResult = await spawner(
937
+ ["git", "commit", "-m", "chore: initialize agentplate and ecosystem tools"],
938
+ { cwd: projectRoot },
939
+ );
940
+ if (commitResult.exitCode === 0) {
941
+ printSuccess("Committed", "scaffold files");
942
+ scaffoldCommitted = true;
943
+ } else {
944
+ printWarning("Scaffold commit failed", commitResult.stderr.trim() || "git commit failed");
945
+ }
946
+ }
947
+ }
948
+
949
+ // 13. Output final result
950
+ if (opts.json) {
951
+ jsonOutput("init", {
952
+ project: projectName,
953
+ tools: toolResults,
954
+ onboard: onboardResults,
955
+ gitattributes: gitattrsUpdated,
956
+ scaffoldCommitted,
957
+ });
958
+ return;
959
+ }
960
+
961
+ printSuccess("Initialized");
962
+ printHint("Next: run `ap hooks install` to enable Claude Code hooks.");
963
+ printHint("Then: `ap coordinator start` and `ap serve` — open http://localhost:7321");
964
+ printHint(
965
+ " (UI is the primary operator surface; pass `--no-headless` to ap sling for tmux attach)",
966
+ );
967
+ }