@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,409 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { AgentplateConfig } from "../types.ts";
5
+ import { checkProviders } from "./providers.ts";
6
+
7
+ /** Build a minimal valid AgentplateConfig for testing. */
8
+ function makeConfig(overrides: Partial<AgentplateConfig> = {}): AgentplateConfig {
9
+ const tmp = tmpdir();
10
+ return {
11
+ project: {
12
+ name: "test-project",
13
+ root: tmp,
14
+ canonicalBranch: "main",
15
+ },
16
+ agents: {
17
+ manifestPath: join(tmp, ".agentplate", "agent-manifest.json"),
18
+ baseDir: join(tmp, ".agentplate", "agents"),
19
+ maxConcurrent: 5,
20
+ staggerDelayMs: 1000,
21
+ maxDepth: 2,
22
+ maxSessionsPerRun: 0,
23
+ maxAgentsPerLead: 5,
24
+ },
25
+ worktrees: {
26
+ baseDir: join(tmp, ".agentplate", "worktrees"),
27
+ },
28
+ taskTracker: {
29
+ backend: "auto",
30
+ enabled: false,
31
+ },
32
+ loam: {
33
+ enabled: false,
34
+ domains: [],
35
+ primeFormat: "markdown",
36
+ },
37
+ merge: {
38
+ aiResolveEnabled: false,
39
+ reimagineEnabled: false,
40
+ },
41
+ providers: {
42
+ anthropic: { type: "native" },
43
+ },
44
+ watchdog: {
45
+ tier0Enabled: false,
46
+ tier0IntervalMs: 30000,
47
+ tier1Enabled: false,
48
+ tier2Enabled: false,
49
+ staleThresholdMs: 300000,
50
+ zombieThresholdMs: 600000,
51
+ nudgeIntervalMs: 60000,
52
+ },
53
+ models: {},
54
+ logging: {
55
+ verbose: false,
56
+ redactSecrets: true,
57
+ },
58
+ ...overrides,
59
+ };
60
+ }
61
+
62
+ // Dummy agentplateDir — provider checks don't use the filesystem
63
+ const AGENTPLATE_DIR = join(tmpdir(), ".agentplate");
64
+
65
+ describe("checkProviders", () => {
66
+ test("all checks have required DoctorCheck fields", async () => {
67
+ const config = makeConfig();
68
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
69
+
70
+ expect(checks).toBeArray();
71
+ for (const check of checks) {
72
+ expect(typeof check.name).toBe("string");
73
+ expect(check.category).toBe("providers");
74
+ expect(["pass", "warn", "fail"]).toContain(check.status);
75
+ expect(typeof check.message).toBe("string");
76
+ if (check.details !== undefined) {
77
+ expect(check.details).toBeArray();
78
+ }
79
+ }
80
+ });
81
+
82
+ describe("providers-configured check", () => {
83
+ test("native-only config (no gateway) returns pass for providers-configured", async () => {
84
+ const config = makeConfig({
85
+ providers: { anthropic: { type: "native" } },
86
+ });
87
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
88
+
89
+ const check = checks.find((c) => c.name === "providers-configured");
90
+ expect(check).toBeDefined();
91
+ expect(check?.status).toBe("pass");
92
+ });
93
+
94
+ test("empty providers returns warn for providers-configured", async () => {
95
+ const config = makeConfig({ providers: {} });
96
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
97
+
98
+ const check = checks.find((c) => c.name === "providers-configured");
99
+ expect(check).toBeDefined();
100
+ expect(check?.status).toBe("warn");
101
+ });
102
+
103
+ test("providers-configured details list provider names and types", async () => {
104
+ const config = makeConfig({
105
+ providers: {
106
+ anthropic: { type: "native" },
107
+ openrouter: { type: "gateway", baseUrl: "http://127.0.0.1:19873" },
108
+ },
109
+ });
110
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
111
+
112
+ const check = checks.find((c) => c.name === "providers-configured");
113
+ expect(check?.status).toBe("pass");
114
+ expect(check?.details).toBeDefined();
115
+ expect(check?.details?.some((d) => d.includes("anthropic"))).toBe(true);
116
+ expect(check?.details?.some((d) => d.includes("openrouter"))).toBe(true);
117
+ });
118
+ });
119
+
120
+ describe("provider-reachable-{name} check", () => {
121
+ test("gateway config triggers reachability check (warn path — no real server)", async () => {
122
+ const config = makeConfig({
123
+ providers: {
124
+ fake: {
125
+ type: "gateway",
126
+ // Use a port that is almost certainly not listening
127
+ baseUrl: "http://127.0.0.1:19873",
128
+ },
129
+ },
130
+ });
131
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
132
+
133
+ const check = checks.find((c) => c.name === "provider-reachable-fake");
134
+ expect(check).toBeDefined();
135
+ expect(check?.status).toBe("warn");
136
+ expect(check?.message).toContain("fake");
137
+ });
138
+
139
+ test("reachability pass path — local HTTP server", async () => {
140
+ // Start a minimal Bun HTTP server on an ephemeral port
141
+ const server = Bun.serve({
142
+ port: 0, // OS assigns a free port
143
+ fetch() {
144
+ return new Response("ok");
145
+ },
146
+ });
147
+
148
+ try {
149
+ const config = makeConfig({
150
+ providers: {
151
+ localtest: {
152
+ type: "gateway",
153
+ baseUrl: `http://127.0.0.1:${server.port}`,
154
+ },
155
+ },
156
+ });
157
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
158
+
159
+ const check = checks.find((c) => c.name === "provider-reachable-localtest");
160
+ expect(check).toBeDefined();
161
+ expect(check?.status).toBe("pass");
162
+ } finally {
163
+ await server.stop();
164
+ }
165
+ });
166
+
167
+ test("gateway without baseUrl skips reachability check", async () => {
168
+ const config = makeConfig({
169
+ providers: {
170
+ nourl: { type: "gateway" }, // no baseUrl
171
+ },
172
+ });
173
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
174
+
175
+ const reachCheck = checks.find((c) => c.name === "provider-reachable-nourl");
176
+ expect(reachCheck).toBeUndefined();
177
+ });
178
+ });
179
+
180
+ describe("provider-auth-token-{name} check", () => {
181
+ const ENV_KEY = "AGENTPLATE_TEST_FAKE_PROVIDER_TOKEN_XYZ";
182
+
183
+ beforeAll(() => {
184
+ // Ensure env var is unset before tests
185
+ delete process.env[ENV_KEY];
186
+ });
187
+
188
+ afterAll(() => {
189
+ delete process.env[ENV_KEY];
190
+ });
191
+
192
+ test("gateway with authTokenEnv warns when env var missing", async () => {
193
+ const config = makeConfig({
194
+ providers: {
195
+ testgateway: {
196
+ type: "gateway",
197
+ authTokenEnv: ENV_KEY,
198
+ },
199
+ },
200
+ });
201
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
202
+
203
+ const check = checks.find((c) => c.name === "provider-auth-token-testgateway");
204
+ expect(check).toBeDefined();
205
+ expect(check?.status).toBe("warn");
206
+ // Details must include the env var NAME, never a value
207
+ expect(check?.details?.some((d) => d.includes(ENV_KEY))).toBe(true);
208
+ });
209
+
210
+ test("gateway with authTokenEnv passes when env var is set", async () => {
211
+ process.env[ENV_KEY] = "test-token-value";
212
+
213
+ const config = makeConfig({
214
+ providers: {
215
+ testgateway: {
216
+ type: "gateway",
217
+ authTokenEnv: ENV_KEY,
218
+ },
219
+ },
220
+ });
221
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
222
+
223
+ const check = checks.find((c) => c.name === "provider-auth-token-testgateway");
224
+ expect(check).toBeDefined();
225
+ expect(check?.status).toBe("pass");
226
+ // Details include the var name, not the value
227
+ expect(check?.details?.some((d) => d.includes(ENV_KEY))).toBe(true);
228
+ // Value must NOT appear in details
229
+ expect(check?.details?.some((d) => d.includes("test-token-value"))).toBe(false);
230
+ });
231
+
232
+ test("native provider with no authTokenEnv skips auth-token check", async () => {
233
+ const config = makeConfig({
234
+ providers: { anthropic: { type: "native" } },
235
+ });
236
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
237
+
238
+ const authCheck = checks.find((c) => c.name?.startsWith("provider-auth-token-"));
239
+ expect(authCheck).toBeUndefined();
240
+ });
241
+ });
242
+
243
+ describe("tool-use-compat check", () => {
244
+ // Local HTTP server to avoid network calls to real openrouter.ai
245
+ let localServer: ReturnType<typeof Bun.serve>;
246
+ let localBaseUrl: string;
247
+
248
+ beforeAll(() => {
249
+ localServer = Bun.serve({
250
+ port: 0,
251
+ fetch() {
252
+ return new Response("ok");
253
+ },
254
+ });
255
+ localBaseUrl = `http://127.0.0.1:${localServer.port}`;
256
+ });
257
+
258
+ afterAll(async () => {
259
+ await localServer.stop();
260
+ });
261
+
262
+ test("tool-heavy role with provider-prefixed model warns", async () => {
263
+ const config = makeConfig({
264
+ models: { builder: "openrouter/openai/gpt-4o" },
265
+ providers: {
266
+ openrouter: { type: "gateway", baseUrl: localBaseUrl },
267
+ },
268
+ });
269
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
270
+
271
+ const check = checks.find((c) => c.name === "tool-use-compat" && c.status === "warn");
272
+ expect(check).toBeDefined();
273
+ expect(check?.message).toContain("builder");
274
+ });
275
+
276
+ test("non-tool-heavy role with provider-prefixed model does not warn", async () => {
277
+ // "lead" is not a tool-heavy role
278
+ const config = makeConfig({
279
+ models: { lead: "openrouter/openai/gpt-4o" },
280
+ providers: {
281
+ openrouter: { type: "gateway", baseUrl: localBaseUrl },
282
+ },
283
+ });
284
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
285
+
286
+ const warnChecks = checks.filter((c) => c.name === "tool-use-compat" && c.status === "warn");
287
+ expect(warnChecks.length).toBe(0);
288
+ });
289
+
290
+ test("no tool-heavy roles with prefixed models emits single pass", async () => {
291
+ const config = makeConfig({
292
+ models: { builder: "sonnet" }, // alias, not provider-prefixed
293
+ });
294
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
295
+
296
+ const passCheck = checks.find((c) => c.name === "tool-use-compat" && c.status === "pass");
297
+ expect(passCheck).toBeDefined();
298
+ });
299
+
300
+ test("all three tool-heavy roles can trigger separate warns", async () => {
301
+ const config = makeConfig({
302
+ models: {
303
+ builder: "openrouter/openai/gpt-4o",
304
+ scout: "openrouter/openai/gpt-4o",
305
+ merger: "openrouter/openai/gpt-4o",
306
+ },
307
+ providers: {
308
+ openrouter: { type: "gateway", baseUrl: localBaseUrl },
309
+ },
310
+ });
311
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
312
+
313
+ const warnChecks = checks.filter((c) => c.name === "tool-use-compat" && c.status === "warn");
314
+ expect(warnChecks.length).toBe(3);
315
+ });
316
+ });
317
+
318
+ describe("model-provider-ref(s) check", () => {
319
+ // Local HTTP server to avoid network calls to real openrouter.ai
320
+ let localServer: ReturnType<typeof Bun.serve>;
321
+ let localBaseUrl: string;
322
+
323
+ beforeAll(() => {
324
+ localServer = Bun.serve({
325
+ port: 0,
326
+ fetch() {
327
+ return new Response("ok");
328
+ },
329
+ });
330
+ localBaseUrl = `http://127.0.0.1:${localServer.port}`;
331
+ });
332
+
333
+ afterAll(async () => {
334
+ await localServer.stop();
335
+ });
336
+
337
+ test("model referencing unknown provider fails", async () => {
338
+ const config = makeConfig({
339
+ models: { builder: "unknownprovider/some-model" },
340
+ // unknownprovider not in providers
341
+ providers: { anthropic: { type: "native" } },
342
+ });
343
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
344
+
345
+ const check = checks.find((c) => c.name === "model-provider-ref" && c.status === "fail");
346
+ expect(check).toBeDefined();
347
+ expect(check?.message).toContain("unknownprovider");
348
+ });
349
+
350
+ test("model referencing defined provider passes", async () => {
351
+ const config = makeConfig({
352
+ models: { builder: "openrouter/openai/gpt-4o" },
353
+ providers: {
354
+ openrouter: { type: "gateway", baseUrl: localBaseUrl },
355
+ },
356
+ });
357
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
358
+
359
+ const check = checks.find((c) => c.name === "model-provider-ref" && c.status === "pass");
360
+ expect(check).toBeDefined();
361
+ });
362
+
363
+ test("no provider-prefixed models emits single pass named model-provider-refs", async () => {
364
+ const config = makeConfig({
365
+ models: { builder: "sonnet", scout: "haiku" },
366
+ });
367
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
368
+
369
+ const check = checks.find((c) => c.name === "model-provider-refs");
370
+ expect(check).toBeDefined();
371
+ expect(check?.status).toBe("pass");
372
+ });
373
+
374
+ test("empty models emits single pass named model-provider-refs", async () => {
375
+ const config = makeConfig({ models: {} });
376
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
377
+
378
+ const check = checks.find((c) => c.name === "model-provider-refs");
379
+ expect(check).toBeDefined();
380
+ expect(check?.status).toBe("pass");
381
+ });
382
+ });
383
+
384
+ describe("gateway-api-key-reminder check", () => {
385
+ test("gateway present triggers api-key reminder warn", async () => {
386
+ const config = makeConfig({
387
+ providers: {
388
+ openrouter: { type: "gateway", baseUrl: "http://127.0.0.1:19873" },
389
+ },
390
+ });
391
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
392
+
393
+ const check = checks.find((c) => c.name === "gateway-api-key-reminder");
394
+ expect(check).toBeDefined();
395
+ expect(check?.status).toBe("warn");
396
+ expect(check?.message).toContain("ANTHROPIC_API_KEY");
397
+ });
398
+
399
+ test("no gateway providers — reminder is absent", async () => {
400
+ const config = makeConfig({
401
+ providers: { anthropic: { type: "native" } },
402
+ });
403
+ const checks = await checkProviders(config, AGENTPLATE_DIR);
404
+
405
+ const check = checks.find((c) => c.name === "gateway-api-key-reminder");
406
+ expect(check).toBeUndefined();
407
+ });
408
+ });
409
+ });
@@ -0,0 +1,250 @@
1
+ import type { AgentplateConfig, ProviderConfig } from "../types.ts";
2
+ import type { DoctorCheck, DoctorCheckFn } from "./types.ts";
3
+
4
+ /** Roles that rely heavily on tool-use (function calling). */
5
+ const TOOL_HEAVY_ROLES = new Set(["builder", "scout", "merger"]);
6
+
7
+ /**
8
+ * Provider and multi-runtime configuration checks.
9
+ *
10
+ * Validates gateway provider reachability, auth tokens, model-provider references,
11
+ * and tool-use compatibility across configured runtimes.
12
+ */
13
+ export const checkProviders: DoctorCheckFn = async (
14
+ config,
15
+ _agentplateDir,
16
+ ): Promise<DoctorCheck[]> => {
17
+ const checks: DoctorCheck[] = [];
18
+
19
+ // Base check: at least one provider configured
20
+ checks.push(buildProvidersConfigured(config));
21
+
22
+ // Identify gateway providers
23
+ const gatewayEntries = Object.entries(config.providers).filter(([, p]) => p.type === "gateway");
24
+
25
+ // Check 1: provider-reachable-{name} — one per gateway provider with baseUrl
26
+ for (const [name, provider] of gatewayEntries) {
27
+ if (provider.baseUrl) {
28
+ checks.push(await checkProviderReachable(name, provider));
29
+ }
30
+ }
31
+
32
+ // Check 2: provider-auth-token-{name} — one per gateway provider with authTokenEnv
33
+ for (const [name, provider] of gatewayEntries) {
34
+ if (provider.authTokenEnv) {
35
+ checks.push(buildProviderAuthToken(name, provider));
36
+ }
37
+ }
38
+
39
+ // Check 3: tool-use-compat — one warn per tool-heavy role using a provider-prefixed model
40
+ checks.push(...buildToolUseCompat(config));
41
+
42
+ // Check 4: model-provider-ref(s) — one per provider-prefixed model, or single pass
43
+ checks.push(...buildModelProviderRefs(config));
44
+
45
+ // Check 5: gateway-api-key-reminder — only when gateway providers exist
46
+ if (gatewayEntries.length > 0) {
47
+ checks.push(buildGatewayApiKeyReminder());
48
+ }
49
+
50
+ return checks;
51
+ };
52
+
53
+ /**
54
+ * Base check: verifies at least one provider is configured.
55
+ */
56
+ function buildProvidersConfigured(config: AgentplateConfig): DoctorCheck {
57
+ const entries = Object.entries(config.providers);
58
+
59
+ if (entries.length > 0) {
60
+ return {
61
+ name: "providers-configured",
62
+ category: "providers",
63
+ status: "pass",
64
+ message: `${entries.length} provider${entries.length === 1 ? "" : "s"} configured`,
65
+ details: entries.map(([name, p]) => `${name} (${p.type})`),
66
+ };
67
+ }
68
+
69
+ return {
70
+ name: "providers-configured",
71
+ category: "providers",
72
+ status: "warn",
73
+ message: "No providers configured — add providers to config.yaml",
74
+ details: ["At least one native or gateway provider should be configured."],
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Check 1: HTTP reachability of a gateway provider's baseUrl.
80
+ *
81
+ * Uses fetch() with a 5-second timeout. Any HTTP response (any status code)
82
+ * counts as reachable — only network errors or timeouts produce a warn.
83
+ */
84
+ async function checkProviderReachable(
85
+ name: string,
86
+ provider: ProviderConfig,
87
+ ): Promise<DoctorCheck> {
88
+ const baseUrl = provider.baseUrl as string; // caller guards baseUrl is defined
89
+
90
+ try {
91
+ await fetch(baseUrl, {
92
+ method: "HEAD",
93
+ signal: AbortSignal.timeout(5000),
94
+ });
95
+
96
+ return {
97
+ name: `provider-reachable-${name}`,
98
+ category: "providers",
99
+ status: "pass",
100
+ message: `Gateway provider '${name}' is reachable`,
101
+ details: [baseUrl],
102
+ };
103
+ } catch (error) {
104
+ const errorMsg = error instanceof Error ? error.message : String(error);
105
+ return {
106
+ name: `provider-reachable-${name}`,
107
+ category: "providers",
108
+ status: "warn",
109
+ message: `Gateway provider '${name}' is unreachable`,
110
+ details: [baseUrl, errorMsg],
111
+ };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Check 2: Validate that the auth token env var for a gateway provider is set.
117
+ *
118
+ * Reports the env var NAME in details — never the value.
119
+ */
120
+ function buildProviderAuthToken(name: string, provider: ProviderConfig): DoctorCheck {
121
+ const envVar = provider.authTokenEnv as string; // caller guards authTokenEnv is defined
122
+ const value = process.env[envVar];
123
+
124
+ if (value && value.length > 0) {
125
+ return {
126
+ name: `provider-auth-token-${name}`,
127
+ category: "providers",
128
+ status: "pass",
129
+ message: `Auth token for provider '${name}' is set`,
130
+ details: [`Env var: ${envVar}`],
131
+ };
132
+ }
133
+
134
+ return {
135
+ name: `provider-auth-token-${name}`,
136
+ category: "providers",
137
+ status: "warn",
138
+ message: `Auth token for provider '${name}' is missing`,
139
+ details: [`Env var: ${envVar}`, `Set ${envVar} to authenticate with this provider.`],
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Check 3: Tool-use compatibility for tool-heavy roles using non-Anthropic models.
145
+ *
146
+ * Tool-heavy roles (builder, scout, merger) rely on structured tool-use (function
147
+ * calling). Non-Anthropic models accessed via gateway providers may have different
148
+ * tool-use behavior. Emits one warn per affected role, or a single pass if none.
149
+ */
150
+ function buildToolUseCompat(config: AgentplateConfig): DoctorCheck[] {
151
+ const checks: DoctorCheck[] = [];
152
+
153
+ for (const [role, model] of Object.entries(config.models)) {
154
+ if (!TOOL_HEAVY_ROLES.has(role)) continue;
155
+ if (model === undefined) continue;
156
+ if (!model.includes("/")) continue;
157
+
158
+ checks.push({
159
+ name: "tool-use-compat",
160
+ category: "providers",
161
+ status: "warn",
162
+ message: `models.${role} uses non-Anthropic model — tool-use compatibility not guaranteed`,
163
+ details: [
164
+ `Model: ${model}`,
165
+ "Tool use (function calling) behavior varies across providers.",
166
+ "Test agent behavior thoroughly before using in production.",
167
+ ],
168
+ });
169
+ }
170
+
171
+ if (checks.length === 0) {
172
+ checks.push({
173
+ name: "tool-use-compat",
174
+ category: "providers",
175
+ status: "pass",
176
+ message: "No tool-heavy roles use non-Anthropic models",
177
+ });
178
+ }
179
+
180
+ return checks;
181
+ }
182
+
183
+ /**
184
+ * Check 4: Validate that provider-prefixed model references point to configured providers.
185
+ *
186
+ * For each config.models entry containing '/' (provider-qualified), extracts the
187
+ * provider name and verifies it exists in config.providers. Emits one check per
188
+ * provider-prefixed model, or a single pass if no such models exist.
189
+ */
190
+ function buildModelProviderRefs(config: AgentplateConfig): DoctorCheck[] {
191
+ const checks: DoctorCheck[] = [];
192
+
193
+ for (const [role, model] of Object.entries(config.models)) {
194
+ if (model === undefined) continue;
195
+ if (!model.includes("/")) continue;
196
+
197
+ const providerName = model.split("/")[0];
198
+ if (!providerName) continue;
199
+
200
+ if (config.providers[providerName]) {
201
+ checks.push({
202
+ name: "model-provider-ref",
203
+ category: "providers",
204
+ status: "pass",
205
+ message: `models.${role} references defined provider '${providerName}'`,
206
+ details: [`Model: ${model}`],
207
+ });
208
+ } else {
209
+ checks.push({
210
+ name: "model-provider-ref",
211
+ category: "providers",
212
+ status: "fail",
213
+ message: `models.${role} references undefined provider '${providerName}'`,
214
+ details: [
215
+ `Model: ${model}`,
216
+ `Provider '${providerName}' is not defined in config.yaml providers section.`,
217
+ `Add it: providers:\n ${providerName}:\n type: gateway\n baseUrl: https://...`,
218
+ ],
219
+ });
220
+ }
221
+ }
222
+
223
+ if (checks.length === 0) {
224
+ checks.push({
225
+ name: "model-provider-refs",
226
+ category: "providers",
227
+ status: "pass",
228
+ message: "No provider-prefixed model references",
229
+ });
230
+ }
231
+
232
+ return checks;
233
+ }
234
+
235
+ /**
236
+ * Check 5: Reminder about ANTHROPIC_API_KEY when gateway providers are configured.
237
+ *
238
+ * Agents spawned via gateway routes receive ANTHROPIC_API_KEY="" so they use the
239
+ * gateway instead of Anthropic directly. Any direct Anthropic API calls (e.g.,
240
+ * from merge/resolver.ts) require a separate key.
241
+ */
242
+ function buildGatewayApiKeyReminder(): DoctorCheck {
243
+ return {
244
+ name: "gateway-api-key-reminder",
245
+ category: "providers",
246
+ status: "warn",
247
+ message:
248
+ "Gateway providers configured — agents using gateway routes will have ANTHROPIC_API_KEY set to empty string. Direct Anthropic API calls require a separate key.",
249
+ };
250
+ }