@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,454 @@
1
+ /**
2
+ * Beads tracker adapter tests.
3
+ *
4
+ * Uses Bun.spawn mocks — legitimate exception to "never mock what you can use for real".
5
+ * The `bd` CLI may not be installed in all environments and would modify real tracker
6
+ * state (creating/closing actual beads) if invoked directly in tests.
7
+ */
8
+
9
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
10
+ import { AgentError } from "../errors.ts";
11
+ import { createBeadsTracker } from "./beads.ts";
12
+
13
+ /**
14
+ * Helper to create a mock Bun.spawn return value.
15
+ *
16
+ * The actual code reads stdout/stderr via `new Response(proc.stdout).text()`
17
+ * and `new Response(proc.stderr).text()`, so we need ReadableStreams.
18
+ */
19
+ function mockSpawnResult(
20
+ stdout: string,
21
+ stderr: string,
22
+ exitCode: number,
23
+ ): {
24
+ stdout: ReadableStream<Uint8Array>;
25
+ stderr: ReadableStream<Uint8Array>;
26
+ exited: Promise<number>;
27
+ pid: number;
28
+ } {
29
+ return {
30
+ stdout: new Response(stdout).body as ReadableStream<Uint8Array>,
31
+ stderr: new Response(stderr).body as ReadableStream<Uint8Array>,
32
+ exited: Promise.resolve(exitCode),
33
+ pid: 12345,
34
+ };
35
+ }
36
+
37
+ const TEST_CWD = "/test/repo";
38
+
39
+ describe("createBeadsTracker — ready()", () => {
40
+ let spawnSpy: ReturnType<typeof spyOn>;
41
+
42
+ beforeEach(() => {
43
+ spawnSpy = spyOn(Bun, "spawn");
44
+ });
45
+
46
+ afterEach(() => {
47
+ spawnSpy.mockRestore();
48
+ });
49
+
50
+ test("returns normalized TrackerIssue[] with issue_type → type mapping", async () => {
51
+ const raw = [
52
+ {
53
+ id: "bd-1",
54
+ title: "Fix login",
55
+ status: "open",
56
+ priority: 1,
57
+ issue_type: "bug",
58
+ },
59
+ {
60
+ id: "bd-2",
61
+ title: "Add auth",
62
+ status: "open",
63
+ priority: 2,
64
+ issue_type: "feature",
65
+ assignee: "bob",
66
+ },
67
+ ];
68
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
69
+
70
+ const tracker = createBeadsTracker(TEST_CWD);
71
+ const issues = await tracker.ready();
72
+
73
+ expect(issues).toHaveLength(2);
74
+ expect(issues[0]).toMatchObject({ id: "bd-1", title: "Fix login", type: "bug" });
75
+ expect(issues[1]).toMatchObject({ id: "bd-2", type: "feature", assignee: "bob" });
76
+ });
77
+
78
+ test("verifies CLI args: [bd, ready, --json]", async () => {
79
+ spawnSpy.mockImplementation(() => mockSpawnResult("[]", "", 0));
80
+
81
+ const tracker = createBeadsTracker(TEST_CWD);
82
+ await tracker.ready();
83
+
84
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
85
+ const cmd = callArgs[0] as string[];
86
+ expect(cmd).toEqual(["bd", "ready", "--json"]);
87
+ });
88
+
89
+ test("throws AgentError on non-zero exit code", async () => {
90
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "bd: command not found", 1));
91
+
92
+ const tracker = createBeadsTracker(TEST_CWD);
93
+ await expect(tracker.ready()).rejects.toThrow(AgentError);
94
+ });
95
+ });
96
+
97
+ describe("createBeadsTracker — show()", () => {
98
+ let spawnSpy: ReturnType<typeof spyOn>;
99
+
100
+ beforeEach(() => {
101
+ spawnSpy = spyOn(Bun, "spawn");
102
+ });
103
+
104
+ afterEach(() => {
105
+ spawnSpy.mockRestore();
106
+ });
107
+
108
+ test("returns normalized TrackerIssue from bd array response", async () => {
109
+ // bd show --json returns an array with a single element
110
+ const raw = [
111
+ {
112
+ id: "bd-42",
113
+ title: "Critical bug",
114
+ status: "open",
115
+ priority: 1,
116
+ issue_type: "bug",
117
+ description: "Crashes on startup",
118
+ blocks: ["bd-50"],
119
+ },
120
+ ];
121
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
122
+
123
+ const tracker = createBeadsTracker(TEST_CWD);
124
+ const issue = await tracker.show("bd-42");
125
+
126
+ expect(issue).toMatchObject({
127
+ id: "bd-42",
128
+ title: "Critical bug",
129
+ type: "bug",
130
+ description: "Crashes on startup",
131
+ blocks: ["bd-50"],
132
+ });
133
+ });
134
+
135
+ test("throws AgentError when bd returns empty array", async () => {
136
+ spawnSpy.mockImplementation(() => mockSpawnResult("[]", "", 0));
137
+
138
+ const tracker = createBeadsTracker(TEST_CWD);
139
+ await expect(tracker.show("bd-99")).rejects.toThrow(AgentError);
140
+ });
141
+
142
+ test("verifies CLI args: [bd, show, <id>, --json]", async () => {
143
+ const raw = [{ id: "bd-1", title: "t", status: "open", priority: 1, issue_type: "task" }];
144
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
145
+
146
+ const tracker = createBeadsTracker(TEST_CWD);
147
+ await tracker.show("bd-1");
148
+
149
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
150
+ const cmd = callArgs[0] as string[];
151
+ expect(cmd).toEqual(["bd", "show", "bd-1", "--json"]);
152
+ });
153
+ });
154
+
155
+ describe("createBeadsTracker — create()", () => {
156
+ let spawnSpy: ReturnType<typeof spyOn>;
157
+
158
+ beforeEach(() => {
159
+ spawnSpy = spyOn(Bun, "spawn");
160
+ });
161
+
162
+ afterEach(() => {
163
+ spawnSpy.mockRestore();
164
+ });
165
+
166
+ test("returns new issue ID from { id: '...' } response", async () => {
167
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify({ id: "bd-101" }), "", 0));
168
+
169
+ const tracker = createBeadsTracker(TEST_CWD);
170
+ const id = await tracker.create("New feature");
171
+
172
+ expect(id).toBe("bd-101");
173
+ });
174
+
175
+ test("verifies CLI args: [bd, create, <title>, --json]", async () => {
176
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify({ id: "bd-1" }), "", 0));
177
+
178
+ const tracker = createBeadsTracker(TEST_CWD);
179
+ await tracker.create("My Issue");
180
+
181
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
182
+ const cmd = callArgs[0] as string[];
183
+ expect(cmd[0]).toBe("bd");
184
+ expect(cmd[1]).toBe("create");
185
+ expect(cmd[2]).toBe("My Issue");
186
+ expect(cmd).toContain("--json");
187
+ });
188
+
189
+ test("passes optional --type, --priority, --description args", async () => {
190
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify({ id: "bd-200" }), "", 0));
191
+
192
+ const tracker = createBeadsTracker(TEST_CWD);
193
+ await tracker.create("My task", {
194
+ type: "feature",
195
+ priority: 2,
196
+ description: "A detailed description",
197
+ });
198
+
199
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
200
+ const cmd = callArgs[0] as string[];
201
+ expect(cmd).toContain("--type");
202
+ expect(cmd).toContain("feature");
203
+ expect(cmd).toContain("--priority");
204
+ expect(cmd).toContain("2");
205
+ expect(cmd).toContain("--description");
206
+ expect(cmd).toContain("A detailed description");
207
+ });
208
+ });
209
+
210
+ describe("createBeadsTracker — claim()", () => {
211
+ let spawnSpy: ReturnType<typeof spyOn>;
212
+
213
+ beforeEach(() => {
214
+ spawnSpy = spyOn(Bun, "spawn");
215
+ });
216
+
217
+ afterEach(() => {
218
+ spawnSpy.mockRestore();
219
+ });
220
+
221
+ test("calls [bd, update, <id>, --status, in_progress]", async () => {
222
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "", 0));
223
+
224
+ const tracker = createBeadsTracker(TEST_CWD);
225
+ await tracker.claim("bd-7");
226
+
227
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
228
+ const cmd = callArgs[0] as string[];
229
+ expect(cmd).toEqual(["bd", "update", "bd-7", "--status", "in_progress"]);
230
+ });
231
+ });
232
+
233
+ describe("createBeadsTracker — close()", () => {
234
+ let spawnSpy: ReturnType<typeof spyOn>;
235
+
236
+ beforeEach(() => {
237
+ spawnSpy = spyOn(Bun, "spawn");
238
+ });
239
+
240
+ afterEach(() => {
241
+ spawnSpy.mockRestore();
242
+ });
243
+
244
+ test("calls [bd, close, <id>] without reason", async () => {
245
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "", 0));
246
+
247
+ const tracker = createBeadsTracker(TEST_CWD);
248
+ await tracker.close("bd-10");
249
+
250
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
251
+ const cmd = callArgs[0] as string[];
252
+ expect(cmd).toEqual(["bd", "close", "bd-10"]);
253
+ });
254
+
255
+ test("calls [bd, close, <id>, --reason, ...] with reason", async () => {
256
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "", 0));
257
+
258
+ const tracker = createBeadsTracker(TEST_CWD);
259
+ await tracker.close("bd-10", "Completed implementation");
260
+
261
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
262
+ const cmd = callArgs[0] as string[];
263
+ expect(cmd).toEqual(["bd", "close", "bd-10", "--reason", "Completed implementation"]);
264
+ });
265
+ });
266
+
267
+ describe("createBeadsTracker — list()", () => {
268
+ let spawnSpy: ReturnType<typeof spyOn>;
269
+
270
+ beforeEach(() => {
271
+ spawnSpy = spyOn(Bun, "spawn");
272
+ });
273
+
274
+ afterEach(() => {
275
+ spawnSpy.mockRestore();
276
+ });
277
+
278
+ test("returns normalized issues from bd array response", async () => {
279
+ const raw = [
280
+ { id: "bd-1", title: "Task A", status: "open", priority: 1, issue_type: "task" },
281
+ {
282
+ id: "bd-2",
283
+ title: "Bug B",
284
+ status: "in_progress",
285
+ priority: 2,
286
+ issue_type: "bug",
287
+ },
288
+ ];
289
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
290
+
291
+ const tracker = createBeadsTracker(TEST_CWD);
292
+ const issues = await tracker.list();
293
+
294
+ expect(issues).toHaveLength(2);
295
+ expect(issues[0]).toMatchObject({ id: "bd-1", type: "task" });
296
+ expect(issues[1]).toMatchObject({ id: "bd-2", type: "bug", status: "in_progress" });
297
+ });
298
+
299
+ test("verifies CLI args: [bd, list, --json]", async () => {
300
+ spawnSpy.mockImplementation(() => mockSpawnResult("[]", "", 0));
301
+
302
+ const tracker = createBeadsTracker(TEST_CWD);
303
+ await tracker.list();
304
+
305
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
306
+ const cmd = callArgs[0] as string[];
307
+ expect(cmd[0]).toBe("bd");
308
+ expect(cmd[1]).toBe("list");
309
+ expect(cmd).toContain("--json");
310
+ });
311
+
312
+ test("passes --status and --limit options", async () => {
313
+ spawnSpy.mockImplementation(() => mockSpawnResult("[]", "", 0));
314
+
315
+ const tracker = createBeadsTracker(TEST_CWD);
316
+ await tracker.list({ status: "open", limit: 5 });
317
+
318
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
319
+ const cmd = callArgs[0] as string[];
320
+ expect(cmd).toContain("--status");
321
+ expect(cmd).toContain("open");
322
+ expect(cmd).toContain("--limit");
323
+ expect(cmd).toContain("5");
324
+ });
325
+ });
326
+
327
+ describe("createBeadsTracker — sync()", () => {
328
+ let spawnSpy: ReturnType<typeof spyOn>;
329
+
330
+ beforeEach(() => {
331
+ spawnSpy = spyOn(Bun, "spawn");
332
+ });
333
+
334
+ afterEach(() => {
335
+ spawnSpy.mockRestore();
336
+ });
337
+
338
+ test("calls [bd, sync] directly (not via beads client)", async () => {
339
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "", 0));
340
+
341
+ const tracker = createBeadsTracker(TEST_CWD);
342
+ await tracker.sync();
343
+
344
+ // sync() calls Bun.spawn directly in beads.ts, not via the beads client
345
+ expect(spawnSpy).toHaveBeenCalledTimes(1);
346
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
347
+ const cmd = callArgs[0] as string[];
348
+ expect(cmd).toEqual(["bd", "sync"]);
349
+ });
350
+
351
+ test("throws AgentError on failure", async () => {
352
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "bd sync failed", 1));
353
+
354
+ const tracker = createBeadsTracker(TEST_CWD);
355
+ await expect(tracker.sync()).rejects.toThrow(AgentError);
356
+ });
357
+ });
358
+
359
+ describe("createBeadsTracker — issue_type normalization", () => {
360
+ let spawnSpy: ReturnType<typeof spyOn>;
361
+
362
+ beforeEach(() => {
363
+ spawnSpy = spyOn(Bun, "spawn");
364
+ });
365
+
366
+ afterEach(() => {
367
+ spawnSpy.mockRestore();
368
+ });
369
+
370
+ test("maps issue_type to type field", async () => {
371
+ const raw = [{ id: "bd-1", title: "t", status: "open", priority: 1, issue_type: "bug" }];
372
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
373
+
374
+ const tracker = createBeadsTracker(TEST_CWD);
375
+ const issues = await tracker.ready();
376
+
377
+ expect(issues[0]?.type).toBe("bug");
378
+ });
379
+
380
+ test("falls back to type when issue_type absent", async () => {
381
+ const raw = [{ id: "bd-1", title: "t", status: "open", priority: 1, type: "feature" }];
382
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
383
+
384
+ const tracker = createBeadsTracker(TEST_CWD);
385
+ const issues = await tracker.ready();
386
+
387
+ expect(issues[0]?.type).toBe("feature");
388
+ });
389
+
390
+ test("defaults to 'unknown' when neither issue_type nor type present", async () => {
391
+ const raw = [{ id: "bd-1", title: "t", status: "open", priority: 1 }];
392
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
393
+
394
+ const tracker = createBeadsTracker(TEST_CWD);
395
+ const issues = await tracker.ready();
396
+
397
+ expect(issues[0]?.type).toBe("unknown");
398
+ });
399
+
400
+ test("prefers issue_type over type when both present", async () => {
401
+ const raw = [
402
+ {
403
+ id: "bd-1",
404
+ title: "t",
405
+ status: "open",
406
+ priority: 1,
407
+ issue_type: "bug",
408
+ type: "feature",
409
+ },
410
+ ];
411
+ spawnSpy.mockImplementation(() => mockSpawnResult(JSON.stringify(raw), "", 0));
412
+
413
+ const tracker = createBeadsTracker(TEST_CWD);
414
+ const issues = await tracker.ready();
415
+
416
+ expect(issues[0]?.type).toBe("bug");
417
+ });
418
+ });
419
+
420
+ describe("createBeadsTracker — cwd propagation", () => {
421
+ let spawnSpy: ReturnType<typeof spyOn>;
422
+
423
+ beforeEach(() => {
424
+ spawnSpy = spyOn(Bun, "spawn");
425
+ });
426
+
427
+ afterEach(() => {
428
+ spawnSpy.mockRestore();
429
+ });
430
+
431
+ test("propagates cwd to Bun.spawn for ready()", async () => {
432
+ spawnSpy.mockImplementation(() => mockSpawnResult("[]", "", 0));
433
+
434
+ const customCwd = "/my/project/root";
435
+ const tracker = createBeadsTracker(customCwd);
436
+ await tracker.ready();
437
+
438
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
439
+ const opts = callArgs[1] as { cwd: string };
440
+ expect(opts.cwd).toBe(customCwd);
441
+ });
442
+
443
+ test("propagates cwd to Bun.spawn for sync()", async () => {
444
+ spawnSpy.mockImplementation(() => mockSpawnResult("", "", 0));
445
+
446
+ const customCwd = "/my/project/root";
447
+ const tracker = createBeadsTracker(customCwd);
448
+ await tracker.sync();
449
+
450
+ const callArgs = spawnSpy.mock.calls[0] as unknown[];
451
+ const opts = callArgs[1] as { cwd: string };
452
+ expect(opts.cwd).toBe(customCwd);
453
+ });
454
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Beads tracker adapter.
3
+ *
4
+ * Wraps src/beads/client.ts to implement the unified TrackerClient interface.
5
+ */
6
+
7
+ import { createBeadsClient } from "../beads/client.ts";
8
+ import { AgentError } from "../errors.ts";
9
+ import type { TrackerClient, TrackerIssue } from "./types.ts";
10
+
11
+ /**
12
+ * Create a TrackerClient backed by the beads (bd) CLI.
13
+ *
14
+ * @param cwd - Working directory for bd commands
15
+ */
16
+ export function createBeadsTracker(cwd: string): TrackerClient {
17
+ const client = createBeadsClient(cwd);
18
+
19
+ return {
20
+ async ready() {
21
+ const issues = await client.ready();
22
+ return issues as TrackerIssue[];
23
+ },
24
+
25
+ async show(id) {
26
+ const issue = await client.show(id);
27
+ return issue as TrackerIssue;
28
+ },
29
+
30
+ async create(title, options) {
31
+ return client.create(title, options);
32
+ },
33
+
34
+ async claim(id) {
35
+ return client.claim(id);
36
+ },
37
+
38
+ async close(id, reason) {
39
+ return client.close(id, reason);
40
+ },
41
+
42
+ async list(options) {
43
+ const issues = await client.list(options);
44
+ return issues as TrackerIssue[];
45
+ },
46
+
47
+ async sync() {
48
+ const proc = Bun.spawn(["bd", "sync"], { cwd, stdout: "pipe", stderr: "pipe" });
49
+ const exitCode = await proc.exited;
50
+ if (exitCode !== 0) {
51
+ const stderr = await new Response(proc.stderr).text();
52
+ throw new AgentError(`bd sync failed (exit ${exitCode}): ${stderr.trim()}`);
53
+ }
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,90 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createTrackerClient, resolveBackend, trackerCliName } from "./factory.ts";
6
+
7
+ describe("createTrackerClient", () => {
8
+ test("creates beads tracker for beads backend", () => {
9
+ const client = createTrackerClient("beads", "/tmp");
10
+ expect(client).toBeDefined();
11
+ expect(client.ready).toBeTypeOf("function");
12
+ expect(client.show).toBeTypeOf("function");
13
+ expect(client.create).toBeTypeOf("function");
14
+ expect(client.claim).toBeTypeOf("function");
15
+ expect(client.close).toBeTypeOf("function");
16
+ expect(client.list).toBeTypeOf("function");
17
+ expect(client.sync).toBeTypeOf("function");
18
+ });
19
+
20
+ test("creates sprout tracker for sprout backend", () => {
21
+ const client = createTrackerClient("sprout", "/tmp");
22
+ expect(client).toBeDefined();
23
+ expect(client.ready).toBeTypeOf("function");
24
+ expect(client.show).toBeTypeOf("function");
25
+ expect(client.create).toBeTypeOf("function");
26
+ expect(client.claim).toBeTypeOf("function");
27
+ expect(client.close).toBeTypeOf("function");
28
+ expect(client.list).toBeTypeOf("function");
29
+ expect(client.sync).toBeTypeOf("function");
30
+ });
31
+
32
+ test("throws for invalid backend", () => {
33
+ // @ts-expect-error - intentionally testing runtime guard
34
+ expect(() => createTrackerClient("invalid", "/tmp")).toThrow();
35
+ });
36
+ });
37
+
38
+ describe("resolveBackend", () => {
39
+ test("returns beads for beads backend", async () => {
40
+ expect(await resolveBackend("beads", "/tmp")).toBe("beads");
41
+ });
42
+ test("returns sprout for sprout backend", async () => {
43
+ expect(await resolveBackend("sprout", "/tmp")).toBe("sprout");
44
+ });
45
+ test("returns sprout for auto when no tracker dirs exist", async () => {
46
+ const tempDir = await mkdtemp(join(tmpdir(), "tracker-test-"));
47
+ try {
48
+ expect(await resolveBackend("auto", tempDir)).toBe("sprout");
49
+ } finally {
50
+ await rm(tempDir, { recursive: true });
51
+ }
52
+ });
53
+ test("returns sprout for auto when .sprout/ exists", async () => {
54
+ const tempDir = await mkdtemp(join(tmpdir(), "tracker-test-"));
55
+ try {
56
+ await mkdir(join(tempDir, ".sprout"));
57
+ expect(await resolveBackend("auto", tempDir)).toBe("sprout");
58
+ } finally {
59
+ await rm(tempDir, { recursive: true });
60
+ }
61
+ });
62
+ test("returns beads for auto when .beads/ exists", async () => {
63
+ const tempDir = await mkdtemp(join(tmpdir(), "tracker-test-"));
64
+ try {
65
+ await mkdir(join(tempDir, ".beads"));
66
+ expect(await resolveBackend("auto", tempDir)).toBe("beads");
67
+ } finally {
68
+ await rm(tempDir, { recursive: true });
69
+ }
70
+ });
71
+ test("returns beads for auto when both .sprout/ and .beads/ exist", async () => {
72
+ const tempDir = await mkdtemp(join(tmpdir(), "tracker-test-"));
73
+ try {
74
+ await mkdir(join(tempDir, ".beads"));
75
+ await mkdir(join(tempDir, ".sprout"));
76
+ expect(await resolveBackend("auto", tempDir)).toBe("beads");
77
+ } finally {
78
+ await rm(tempDir, { recursive: true });
79
+ }
80
+ });
81
+ });
82
+
83
+ describe("trackerCliName", () => {
84
+ test("returns bd for beads", () => {
85
+ expect(trackerCliName("beads")).toBe("bd");
86
+ });
87
+ test("returns sr for sprout", () => {
88
+ expect(trackerCliName("sprout")).toBe("sr");
89
+ });
90
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Tracker factory — creates the right backend client based on configuration.
3
+ */
4
+
5
+ import { stat } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import type { TaskTrackerBackend } from "../types.ts";
8
+ import { createBeadsTracker } from "./beads.ts";
9
+ import { createSproutTracker } from "./sprout.ts";
10
+ import type { TrackerBackend, TrackerClient } from "./types.ts";
11
+
12
+ /**
13
+ * Create a tracker client for the specified backend.
14
+ *
15
+ * @param backend - Which backend to use ("beads" or "sprout")
16
+ * @param cwd - Working directory for CLI commands
17
+ */
18
+ export function createTrackerClient(backend: TrackerBackend, cwd: string): TrackerClient {
19
+ switch (backend) {
20
+ case "beads":
21
+ return createBeadsTracker(cwd);
22
+ case "sprout":
23
+ return createSproutTracker(cwd);
24
+ default: {
25
+ const _exhaustive: never = backend;
26
+ throw new Error(`Unknown tracker backend: ${_exhaustive}`);
27
+ }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Resolve "auto" to a concrete backend by probing the filesystem.
33
+ * Explicit "beads" or "sprout" values pass through unchanged.
34
+ */
35
+ export async function resolveBackend(
36
+ configBackend: TaskTrackerBackend,
37
+ cwd: string,
38
+ ): Promise<TrackerBackend> {
39
+ if (configBackend === "beads") return "beads";
40
+ if (configBackend === "sprout") return "sprout";
41
+ // "auto" detection: check for .beads/ first (never auto-scaffolded by ap init,
42
+ // so its presence signals explicit user setup), then .sprout/.
43
+ const dirExists = async (path: string): Promise<boolean> => {
44
+ try {
45
+ const s = await stat(path);
46
+ return s.isDirectory();
47
+ } catch {
48
+ return false;
49
+ }
50
+ };
51
+ if (await dirExists(join(cwd, ".beads"))) return "beads";
52
+ if (await dirExists(join(cwd, ".sprout"))) return "sprout";
53
+ // Default fallback — sprout is the preferred tracker
54
+ return "sprout";
55
+ }
56
+
57
+ /**
58
+ * Return the CLI tool name for a resolved backend.
59
+ */
60
+ export function trackerCliName(backend: TrackerBackend): string {
61
+ return backend === "sprout" ? "sr" : "bd";
62
+ }
63
+
64
+ // Re-export types for convenience
65
+ export type { TrackerBackend, TrackerClient, TrackerIssue } from "./types.ts";