@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,167 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { utimes } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import type { AgentplateConfig } from "../types.ts";
7
+ import { checkWatchdog } from "./watchdog.ts";
8
+
9
+ describe("checkWatchdog", () => {
10
+ let tempDir: string;
11
+ let mockConfig: AgentplateConfig;
12
+
13
+ beforeEach(() => {
14
+ tempDir = mkdtempSync(join(tmpdir(), "agentplate-watchdog-test-"));
15
+ mockConfig = {
16
+ project: { name: "test", root: tempDir, canonicalBranch: "main" },
17
+ agents: {
18
+ manifestPath: "",
19
+ baseDir: "",
20
+ maxConcurrent: 5,
21
+ staggerDelayMs: 100,
22
+ maxDepth: 2,
23
+ maxSessionsPerRun: 0,
24
+ maxAgentsPerLead: 5,
25
+ },
26
+ worktrees: { baseDir: "" },
27
+ taskTracker: { backend: "auto", enabled: true },
28
+ loam: { enabled: true, domains: [], primeFormat: "markdown" },
29
+ merge: { aiResolveEnabled: false, reimagineEnabled: false },
30
+ providers: {
31
+ anthropic: { type: "native" },
32
+ },
33
+ watchdog: {
34
+ tier0Enabled: true,
35
+ tier0IntervalMs: 30000,
36
+ tier1Enabled: false,
37
+ tier2Enabled: false,
38
+ staleThresholdMs: 300000,
39
+ zombieThresholdMs: 600000,
40
+ nudgeIntervalMs: 60000,
41
+ },
42
+ models: {},
43
+ logging: { verbose: false, redactSecrets: true },
44
+ };
45
+ });
46
+
47
+ afterEach(() => {
48
+ rmSync(tempDir, { recursive: true, force: true });
49
+ });
50
+
51
+ test("all checks skip when tier0Enabled is false — returns single pass check", async () => {
52
+ mockConfig.watchdog.tier0Enabled = false;
53
+ const checks = await checkWatchdog(mockConfig, tempDir);
54
+
55
+ expect(checks).toHaveLength(1);
56
+ expect(checks[0]?.status).toBe("pass");
57
+ expect(checks[0]?.message).toContain("disabled");
58
+ });
59
+
60
+ test("PID file missing — returns warn about daemon not running", async () => {
61
+ const checks = await checkWatchdog(mockConfig, tempDir);
62
+
63
+ const pidCheck = checks.find((c) => c.name === "watchdog pid file");
64
+ expect(pidCheck).toBeDefined();
65
+ expect(pidCheck?.status).toBe("warn");
66
+ expect(pidCheck?.message).toContain("PID file not found");
67
+ });
68
+
69
+ test("PID file corrupted — returns fail with fixable", async () => {
70
+ writeFileSync(join(tempDir, "watchdog.pid"), "not-a-pid");
71
+ const checks = await checkWatchdog(mockConfig, tempDir);
72
+
73
+ const integrityCheck = checks.find((c) => c.name === "watchdog pid integrity");
74
+ expect(integrityCheck).toBeDefined();
75
+ expect(integrityCheck?.status).toBe("fail");
76
+ expect(integrityCheck?.fixable).toBe(true);
77
+ expect(integrityCheck?.details?.some((d) => d.includes("not-a-pid"))).toBe(true);
78
+ });
79
+
80
+ test("PID file with valid PID but process not running — returns warn (stale PID)", async () => {
81
+ // PID 999999999 is extremely unlikely to exist
82
+ writeFileSync(join(tempDir, "watchdog.pid"), "999999999");
83
+ const checks = await checkWatchdog(mockConfig, tempDir);
84
+
85
+ const processCheck = checks.find((c) => c.name === "watchdog process");
86
+ expect(processCheck).toBeDefined();
87
+ expect(processCheck?.status).toBe("warn");
88
+ expect(processCheck?.message).toContain("stale PID file");
89
+ expect(processCheck?.fixable).toBe(true);
90
+ });
91
+
92
+ test("PID file with current process PID — returns pass", async () => {
93
+ writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
94
+ const checks = await checkWatchdog(mockConfig, tempDir);
95
+
96
+ const processCheck = checks.find((c) => c.name === "watchdog process");
97
+ expect(processCheck).toBeDefined();
98
+ expect(processCheck?.status).toBe("pass");
99
+ expect(processCheck?.message).toContain("running");
100
+ });
101
+
102
+ test("PID file older than 24 hours — returns staleness warn", async () => {
103
+ const pidFile = join(tempDir, "watchdog.pid");
104
+ writeFileSync(pidFile, String(process.pid));
105
+
106
+ // Set mtime 25 hours ago
107
+ const twentyFiveHoursAgo = new Date(Date.now() - 25 * 60 * 60 * 1000);
108
+ await utimes(pidFile, twentyFiveHoursAgo, twentyFiveHoursAgo);
109
+
110
+ const checks = await checkWatchdog(mockConfig, tempDir);
111
+
112
+ const stalenessCheck = checks.find((c) => c.name === "watchdog pid staleness");
113
+ expect(stalenessCheck).toBeDefined();
114
+ expect(stalenessCheck?.status).toBe("warn");
115
+ expect(stalenessCheck?.message).toContain("older than 24 hours");
116
+ expect(stalenessCheck?.details?.some((d) => d.includes("hours"))).toBe(true);
117
+ });
118
+
119
+ test("Tier 2 monitor check skipped when tier2Enabled=false — no monitor check in results", async () => {
120
+ mockConfig.watchdog.tier2Enabled = false;
121
+ writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
122
+ const checks = await checkWatchdog(mockConfig, tempDir);
123
+
124
+ const monitorCheck = checks.find((c) => c.name === "tier2 monitor");
125
+ expect(monitorCheck).toBeUndefined();
126
+ });
127
+
128
+ test("Tier 1 triage check skipped when tier1Enabled=false — no triage check in results", async () => {
129
+ mockConfig.watchdog.tier1Enabled = false;
130
+ writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
131
+ const checks = await checkWatchdog(mockConfig, tempDir);
132
+
133
+ const triageCheck = checks.find((c) => c.name === "tier1 triage");
134
+ expect(triageCheck).toBeUndefined();
135
+ });
136
+
137
+ test("Tier 2 monitor check warns when no monitor session found", async () => {
138
+ mockConfig.watchdog.tier2Enabled = true;
139
+ writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
140
+ // No sessions.db or sessions.json — openSessionStore creates empty DB
141
+ const checks = await checkWatchdog(mockConfig, tempDir);
142
+
143
+ const monitorCheck = checks.find((c) => c.name === "tier2 monitor");
144
+ expect(monitorCheck).toBeDefined();
145
+ // Either warns about not running or store unavailable — both are acceptable
146
+ expect(monitorCheck?.status).toBe("warn");
147
+ });
148
+
149
+ test("Tier 1 triage check does not crash when enabled", async () => {
150
+ mockConfig.watchdog.tier1Enabled = true;
151
+ writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
152
+ // getRuntime will succeed (defaults to "claude" which is always registered)
153
+ let checks: Awaited<ReturnType<typeof checkWatchdog>>;
154
+ try {
155
+ checks = await checkWatchdog(mockConfig, tempDir);
156
+ } catch {
157
+ // Should not throw
158
+ expect(false).toBe(true);
159
+ return;
160
+ }
161
+
162
+ const triageCheck = checks.find((c) => c.name === "tier1 triage");
163
+ expect(triageCheck).toBeDefined();
164
+ // Either pass or warn — depending on environment; it should not throw
165
+ expect(triageCheck?.status === "pass" || triageCheck?.status === "warn").toBe(true);
166
+ });
167
+ });
@@ -0,0 +1,214 @@
1
+ import { existsSync } from "node:fs";
2
+ import { stat, unlink } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { getRuntime } from "../runtimes/registry.ts";
5
+ import { openSessionStore } from "../sessions/compat.ts";
6
+ import { findRunningWatchdogProcesses } from "../utils/process-scan.ts";
7
+ import { isProcessRunning } from "../watchdog/health.ts";
8
+ import type { DoctorCheck, DoctorCheckFn } from "./types.ts";
9
+
10
+ /**
11
+ * Watchdog subsystem health checks.
12
+ * Validates PID file integrity, process liveness, and tier availability.
13
+ */
14
+ export const checkWatchdog: DoctorCheckFn = async (
15
+ config,
16
+ agentplateDir,
17
+ ): Promise<DoctorCheck[]> => {
18
+ const checks: DoctorCheck[] = [];
19
+
20
+ // If tier0 is disabled, skip all checks with a single pass result
21
+ if (!config.watchdog.tier0Enabled) {
22
+ checks.push({
23
+ name: "watchdog disabled",
24
+ category: "watchdog",
25
+ status: "pass",
26
+ message: "Watchdog daemon is disabled (tier0Enabled: false)",
27
+ });
28
+ return checks;
29
+ }
30
+
31
+ const pidFilePath = join(agentplateDir, "watchdog.pid");
32
+
33
+ // Check 1: PID file exists and is readable
34
+ if (!existsSync(pidFilePath)) {
35
+ checks.push({
36
+ name: "watchdog pid file",
37
+ category: "watchdog",
38
+ status: "warn",
39
+ message: "Watchdog PID file not found — daemon may not be running",
40
+ });
41
+ } else {
42
+ // Check 2: PID file not corrupted
43
+ const pidText = await Bun.file(pidFilePath).text();
44
+ const pid = Number.parseInt(pidText.trim(), 10);
45
+
46
+ if (Number.isNaN(pid) || pid <= 0) {
47
+ checks.push({
48
+ name: "watchdog pid integrity",
49
+ category: "watchdog",
50
+ status: "fail",
51
+ message: "Watchdog PID file is corrupted",
52
+ details: [`Raw content: ${pidText.trim()}`],
53
+ fixable: true,
54
+ fix: async () => {
55
+ await unlink(pidFilePath);
56
+ return ["Removed corrupted watchdog PID file"];
57
+ },
58
+ });
59
+ } else {
60
+ // Check 3: PID alive via isProcessRunning()
61
+ const alive = isProcessRunning(pid);
62
+ if (!alive) {
63
+ checks.push({
64
+ name: "watchdog process",
65
+ category: "watchdog",
66
+ status: "warn",
67
+ message: "Watchdog process is not running (stale PID file)",
68
+ details: [`PID: ${pid}`],
69
+ fixable: true,
70
+ fix: async () => {
71
+ await unlink(pidFilePath);
72
+ return ["Removed stale watchdog PID file"];
73
+ },
74
+ });
75
+ } else {
76
+ checks.push({
77
+ name: "watchdog process",
78
+ category: "watchdog",
79
+ status: "pass",
80
+ message: "Watchdog daemon is running",
81
+ });
82
+ }
83
+
84
+ // Check 4: PID file staleness > 24h
85
+ const fileStat = await stat(pidFilePath);
86
+ const ageMs = Date.now() - fileStat.mtimeMs;
87
+ const twentyFourHoursMs = 24 * 60 * 60 * 1000;
88
+ if (ageMs > twentyFourHoursMs) {
89
+ const ageHours = Math.round(ageMs / (1000 * 60 * 60));
90
+ checks.push({
91
+ name: "watchdog pid staleness",
92
+ category: "watchdog",
93
+ status: "warn",
94
+ message: "Watchdog PID file is older than 24 hours",
95
+ details: [`File age: ${ageHours} hours`],
96
+ });
97
+ }
98
+ }
99
+ }
100
+
101
+ // Check 5: Tier 2 monitor running if tier2Enabled
102
+ if (config.watchdog.tier2Enabled) {
103
+ try {
104
+ const { store } = openSessionStore(agentplateDir);
105
+ try {
106
+ const sessions = store.getAll();
107
+ const monitorActive = sessions.some(
108
+ (s) => s.capability === "monitor" && s.state !== "completed" && s.state !== "zombie",
109
+ );
110
+ if (!monitorActive) {
111
+ checks.push({
112
+ name: "tier2 monitor",
113
+ category: "watchdog",
114
+ status: "warn",
115
+ message: "Tier 2 monitor is enabled but not running",
116
+ });
117
+ } else {
118
+ checks.push({
119
+ name: "tier2 monitor",
120
+ category: "watchdog",
121
+ status: "pass",
122
+ message: "Tier 2 monitor agent is active",
123
+ });
124
+ }
125
+ } finally {
126
+ store.close();
127
+ }
128
+ } catch {
129
+ checks.push({
130
+ name: "tier2 monitor",
131
+ category: "watchdog",
132
+ status: "warn",
133
+ message: "Tier 2 monitor check skipped — session store unavailable",
134
+ });
135
+ }
136
+ }
137
+
138
+ // Check 6: multi-daemon detection (agentplate-8ef6).
139
+ // Earlier releases had no exclusion lock, so multiple `ap watch` daemons
140
+ // could run simultaneously. We scan the process table for `ap watch`
141
+ // processes and flag any case with more than one. This is observational —
142
+ // even with the lock now in place, a corrupted/missing PID file could
143
+ // still let a foreign daemon slip past, and we want doctor to catch it.
144
+ try {
145
+ const watchProcs = await findRunningWatchdogProcesses();
146
+ if (watchProcs.length > 1) {
147
+ const lockOwner = existsSync(pidFilePath)
148
+ ? Number.parseInt((await Bun.file(pidFilePath).text()).trim(), 10)
149
+ : Number.NaN;
150
+ const lockOwnerLabel = Number.isFinite(lockOwner) ? `${lockOwner}` : "(none)";
151
+ const pidList = watchProcs.map((p) => p.pid).join(", ");
152
+ checks.push({
153
+ name: "watchdog multi-daemon",
154
+ category: "watchdog",
155
+ status: "fail",
156
+ message: `${watchProcs.length} 'ap watch' daemons running concurrently — only one should be live`,
157
+ details: [
158
+ `Live PIDs: ${pidList}`,
159
+ `PID-file owner: ${lockOwnerLabel}`,
160
+ "Run 'ap watch --kill-others' to terminate the foreign daemons.",
161
+ ],
162
+ fixable: true,
163
+ fix: async () => {
164
+ const ownerPid = Number.isFinite(lockOwner) ? lockOwner : null;
165
+ const messages: string[] = [];
166
+ for (const proc of watchProcs) {
167
+ if (proc.pid === ownerPid) continue;
168
+ try {
169
+ process.kill(proc.pid, "SIGTERM");
170
+ messages.push(`Killed foreign watchdog PID ${proc.pid}`);
171
+ } catch {
172
+ messages.push(`PID ${proc.pid} already gone`);
173
+ }
174
+ }
175
+ if (messages.length === 0) {
176
+ messages.push("No foreign watchdogs to kill — fix is a no-op");
177
+ }
178
+ return messages;
179
+ },
180
+ });
181
+ }
182
+ } catch {
183
+ // Process scan failure is non-fatal — leave a soft warning instead of
184
+ // failing the whole doctor run.
185
+ checks.push({
186
+ name: "watchdog multi-daemon",
187
+ category: "watchdog",
188
+ status: "warn",
189
+ message: "Could not scan process table for foreign 'ap watch' daemons",
190
+ });
191
+ }
192
+
193
+ // Check 7: Tier 1 triage available if tier1Enabled
194
+ if (config.watchdog.tier1Enabled) {
195
+ try {
196
+ getRuntime(config?.runtime?.printCommand ?? config?.runtime?.default, config);
197
+ checks.push({
198
+ name: "tier1 triage",
199
+ category: "watchdog",
200
+ status: "pass",
201
+ message: "Tier 1 triage runtime is available",
202
+ });
203
+ } catch {
204
+ checks.push({
205
+ name: "tier1 triage",
206
+ category: "watchdog",
207
+ status: "warn",
208
+ message: "Tier 1 triage is enabled but runtime is not available",
209
+ });
210
+ }
211
+ }
212
+
213
+ return checks;
214
+ };
@@ -0,0 +1,283 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { readdir, stat } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { createManifestLoader } from "../agents/manifest.ts";
5
+ import { writeOverlay } from "../agents/overlay.ts";
6
+ import type { Spawner } from "../commands/init.ts";
7
+ import { initCommand } from "../commands/init.ts";
8
+ import { loadConfig } from "../config.ts";
9
+ import { cleanupTempDir, createTempGitRepo } from "../test-helpers.ts";
10
+ import type { OverlayConfig } from "../types.ts";
11
+
12
+ /**
13
+ * E2E test: init→sling lifecycle on a throwaway external project.
14
+ *
15
+ * Validates the "project-agnostic" promise by running agentplate init on a
16
+ * fresh temp git repo (NOT the agentplate repo itself), then verifying all
17
+ * artifacts, loading config + manifest via real APIs, and generating an overlay.
18
+ *
19
+ * Uses real filesystem and real git repos.
20
+ * Uses a no-op spawner so ecosystem CLIs (lm/sr/tl) don't need to be installed in CI.
21
+ * Suppresses stdout because initCommand prints status lines.
22
+ */
23
+
24
+ /** No-op spawner that treats all ecosystem tools as "not installed". */
25
+ const noopSpawner: Spawner = async () => ({ exitCode: 1, stdout: "", stderr: "not found" });
26
+
27
+ const EXPECTED_AGENT_DEFS = [
28
+ "builder.md",
29
+ "coordinator.md",
30
+ "lead.md",
31
+ "merger.md",
32
+ "monitor.md",
33
+ "orchestrator.md",
34
+ "ap-co-creation.md",
35
+ "reviewer.md",
36
+ "scout.md",
37
+ ];
38
+
39
+ describe("E2E: init→sling lifecycle on external project", () => {
40
+ let tempDir: string;
41
+ let originalCwd: string;
42
+ let originalWrite: typeof process.stdout.write;
43
+
44
+ beforeEach(async () => {
45
+ tempDir = await createTempGitRepo();
46
+ originalCwd = process.cwd();
47
+ process.chdir(tempDir);
48
+
49
+ // Suppress stdout noise from initCommand
50
+ originalWrite = process.stdout.write;
51
+ process.stdout.write = (() => true) as typeof process.stdout.write;
52
+ });
53
+
54
+ afterEach(async () => {
55
+ process.chdir(originalCwd);
56
+ process.stdout.write = originalWrite;
57
+ await cleanupTempDir(tempDir);
58
+ });
59
+
60
+ test("init creates all expected artifacts", async () => {
61
+ await initCommand({ _spawner: noopSpawner });
62
+
63
+ const agentplateDir = join(tempDir, ".agentplate");
64
+
65
+ // config.yaml exists
66
+ const configFile = Bun.file(join(agentplateDir, "config.yaml"));
67
+ expect(await configFile.exists()).toBe(true);
68
+
69
+ // agent-manifest.json exists and is valid JSON
70
+ const manifestFile = Bun.file(join(agentplateDir, "agent-manifest.json"));
71
+ expect(await manifestFile.exists()).toBe(true);
72
+ const manifestText = await manifestFile.text();
73
+ const manifestJson = JSON.parse(manifestText);
74
+ expect(manifestJson).toBeDefined();
75
+ expect(manifestJson.version).toBe("1.0");
76
+ expect(typeof manifestJson.agents).toBe("object");
77
+
78
+ // hooks.json exists
79
+ const hooksFile = Bun.file(join(agentplateDir, "hooks.json"));
80
+ expect(await hooksFile.exists()).toBe(true);
81
+
82
+ // .gitignore exists
83
+ const gitignoreFile = Bun.file(join(agentplateDir, ".gitignore"));
84
+ expect(await gitignoreFile.exists()).toBe(true);
85
+
86
+ // agent-defs/ contains all 9 agent definition files (supervisor deprecated)
87
+ const agentDefsDir = join(agentplateDir, "agent-defs");
88
+ const agentDefFiles = (await readdir(agentDefsDir)).filter((f) => f.endsWith(".md")).sort();
89
+ expect(agentDefFiles).toEqual(EXPECTED_AGENT_DEFS);
90
+
91
+ // Required subdirectories exist
92
+ const expectedDirs = ["agents", "worktrees", "specs", "logs"];
93
+ for (const dirName of expectedDirs) {
94
+ const dirPath = join(agentplateDir, dirName);
95
+ const dirStat = await stat(dirPath);
96
+ expect(dirStat.isDirectory()).toBe(true);
97
+ }
98
+ });
99
+
100
+ test("loadConfig returns valid config pointing to temp dir", async () => {
101
+ await initCommand({ _spawner: noopSpawner });
102
+
103
+ const config = await loadConfig(tempDir);
104
+
105
+ // project.root should point to the temp directory
106
+ expect(config.project.root).toBe(tempDir);
107
+
108
+ // agents.baseDir should be the relative path to agent-defs
109
+ expect(config.agents.baseDir).toBe(".agentplate/agent-defs");
110
+
111
+ // canonicalBranch should be detected (main for our test repos)
112
+ expect(config.project.canonicalBranch).toBeTruthy();
113
+
114
+ // name should be set (from dir basename or git remote)
115
+ expect(config.project.name).toBeTruthy();
116
+ });
117
+
118
+ test("manifest loads successfully with all 8 agents (supervisor deprecated)", async () => {
119
+ await initCommand({ _spawner: noopSpawner });
120
+
121
+ const manifestPath = join(tempDir, ".agentplate", "agent-manifest.json");
122
+ const agentDefsDir = join(tempDir, ".agentplate", "agent-defs");
123
+ const loader = createManifestLoader(manifestPath, agentDefsDir);
124
+
125
+ const manifest = await loader.load();
126
+
127
+ // All 8 agents present (supervisor removed: deprecated, use lead instead)
128
+ const agentNames = Object.keys(manifest.agents).sort();
129
+ expect(agentNames).toEqual([
130
+ "builder",
131
+ "coordinator",
132
+ "lead",
133
+ "merger",
134
+ "monitor",
135
+ "orchestrator",
136
+ "reviewer",
137
+ "scout",
138
+ ]);
139
+
140
+ // Each agent has a valid file reference
141
+ for (const [_name, def] of Object.entries(manifest.agents)) {
142
+ expect(def.file).toEndWith(".md");
143
+ // Verify the referenced .md file actually exists
144
+ const mdFile = Bun.file(join(agentDefsDir, def.file));
145
+ expect(await mdFile.exists()).toBe(true);
146
+ }
147
+
148
+ // Validation returns no errors
149
+ const errors = loader.validate();
150
+ expect(errors).toEqual([]);
151
+ });
152
+
153
+ test("manifest capability index is consistent", async () => {
154
+ await initCommand({ _spawner: noopSpawner });
155
+
156
+ const manifestPath = join(tempDir, ".agentplate", "agent-manifest.json");
157
+ const agentDefsDir = join(tempDir, ".agentplate", "agent-defs");
158
+ const loader = createManifestLoader(manifestPath, agentDefsDir);
159
+
160
+ const manifest = await loader.load();
161
+
162
+ // capabilityIndex should map capabilities to agent names
163
+ expect(Object.keys(manifest.capabilityIndex).length).toBeGreaterThan(0);
164
+
165
+ // Each capability in the index should reference agents that declare it
166
+ for (const [cap, names] of Object.entries(manifest.capabilityIndex)) {
167
+ for (const name of names) {
168
+ const agent = manifest.agents[name];
169
+ expect(agent).toBeDefined();
170
+ expect(agent?.capabilities).toContain(cap);
171
+ }
172
+ }
173
+ });
174
+
175
+ test("overlay generation works for external project", async () => {
176
+ await initCommand({ _spawner: noopSpawner });
177
+
178
+ const agentDefsDir = join(tempDir, ".agentplate", "agent-defs");
179
+ const baseDefinition = await Bun.file(join(agentDefsDir, "builder.md")).text();
180
+
181
+ const overlayConfig: OverlayConfig = {
182
+ agentName: "test-agent",
183
+ taskId: "test-bead-001",
184
+ specPath: null,
185
+ branchName: "agentplate/test-agent/test-bead-001",
186
+ worktreePath: join(tempDir, ".agentplate", "worktrees", "test-agent"),
187
+ fileScope: [],
188
+ loamDomains: [],
189
+ parentAgent: null,
190
+ depth: 0,
191
+ canSpawn: false,
192
+ capability: "builder",
193
+ baseDefinition,
194
+ };
195
+
196
+ // Write the overlay into a subdirectory of the temp dir (simulating a worktree)
197
+ const worktreePath = join(tempDir, ".agentplate", "worktrees", "test-agent");
198
+ const { mkdir } = await import("node:fs/promises");
199
+ await mkdir(worktreePath, { recursive: true });
200
+
201
+ await writeOverlay(worktreePath, overlayConfig, tempDir);
202
+
203
+ // Verify the overlay was written
204
+ const overlayPath = join(worktreePath, ".claude", "CLAUDE.md");
205
+ const overlayFile = Bun.file(overlayPath);
206
+ expect(await overlayFile.exists()).toBe(true);
207
+
208
+ const content = await overlayFile.text();
209
+
210
+ // Verify template placeholders were replaced
211
+ expect(content).toContain("test-agent");
212
+ expect(content).toContain("test-bead-001");
213
+ expect(content).toContain("agentplate/test-agent/test-bead-001");
214
+ expect(content).not.toContain("{{AGENT_NAME}}");
215
+ expect(content).not.toContain("{{BEAD_ID}}");
216
+ expect(content).not.toContain("{{BRANCH_NAME}}");
217
+ });
218
+
219
+ test("full init→config→manifest→overlay pipeline succeeds", async () => {
220
+ // This test validates the entire lifecycle in sequence:
221
+ // init → load config → load manifest → generate overlay
222
+
223
+ // Step 1: Init
224
+ await initCommand({ _spawner: noopSpawner });
225
+
226
+ // Step 2: Load config
227
+ const config = await loadConfig(tempDir);
228
+ expect(config.project.root).toBe(tempDir);
229
+
230
+ // Step 3: Load manifest using config paths
231
+ const manifestPath = join(config.project.root, config.agents.manifestPath);
232
+ const agentDefsDir = join(config.project.root, config.agents.baseDir);
233
+ const loader = createManifestLoader(manifestPath, agentDefsDir);
234
+ await loader.load();
235
+
236
+ // Verify builder agent exists (the one we'll use for overlay)
237
+ const builder = loader.getAgent("builder");
238
+ expect(builder).toBeDefined();
239
+ expect(builder?.canSpawn).toBe(false);
240
+
241
+ // Verify lead agent can spawn
242
+ const lead = loader.getAgent("lead");
243
+ expect(lead).toBeDefined();
244
+ expect(lead?.canSpawn).toBe(true);
245
+
246
+ // Step 4: Generate overlay using a realistic config
247
+ const builderDef = await Bun.file(join(agentDefsDir, "builder.md")).text();
248
+ const overlayConfig: OverlayConfig = {
249
+ agentName: "lifecycle-builder",
250
+ taskId: "lifecycle-001",
251
+ specPath: join(tempDir, ".agentplate", "specs", "lifecycle-001.md"),
252
+ branchName: "agentplate/lifecycle-builder/lifecycle-001",
253
+ worktreePath: join(tempDir, ".agentplate", "worktrees", "lifecycle-builder"),
254
+ fileScope: ["src/main.ts", "src/utils.ts"],
255
+ loamDomains: ["typescript"],
256
+ parentAgent: "orchestrator",
257
+ depth: 0,
258
+ canSpawn: false,
259
+ capability: "builder",
260
+ baseDefinition: builderDef,
261
+ };
262
+
263
+ const worktreePath = join(tempDir, ".agentplate", "worktrees", "lifecycle-builder");
264
+ const { mkdir } = await import("node:fs/promises");
265
+ await mkdir(worktreePath, { recursive: true });
266
+
267
+ await writeOverlay(worktreePath, overlayConfig, tempDir);
268
+
269
+ const overlayContent = await Bun.file(join(worktreePath, ".claude", "CLAUDE.md")).text();
270
+
271
+ // Verify all overlay fields rendered correctly
272
+ expect(overlayContent).toContain("lifecycle-builder");
273
+ expect(overlayContent).toContain("lifecycle-001");
274
+ expect(overlayContent).toContain("agentplate/lifecycle-builder/lifecycle-001");
275
+ expect(overlayContent).toContain("orchestrator");
276
+ expect(overlayContent).toContain("`src/main.ts`");
277
+ expect(overlayContent).toContain("`src/utils.ts`");
278
+ expect(overlayContent).toContain("lm prime typescript");
279
+
280
+ // No unresolved placeholders
281
+ expect(overlayContent).not.toMatch(/\{\{[A-Z_]+\}\}/);
282
+ });
283
+ });