@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,614 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdir, mkdtemp } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { stripAnsi } from "../logging/color.ts";
6
+ import { createSessionStore } from "../sessions/store.ts";
7
+ import { cleanupTempDir, createTempGitRepo } from "../test-helpers.ts";
8
+ import type { AgentSession } from "../types.ts";
9
+ import {
10
+ gatherStatus,
11
+ invalidateStatusCache,
12
+ printStatus,
13
+ type StatusData,
14
+ statusCommand,
15
+ type VerboseAgentDetail,
16
+ } from "./status.ts";
17
+
18
+ /**
19
+ * Tests for the --verbose flag in agentplate status.
20
+ *
21
+ * printStatus is tested by capturing process.stdout.write output.
22
+ * We spy on stdout.write because printStatus uses it directly.
23
+ */
24
+
25
+ function makeAgent(overrides: Partial<AgentSession> = {}): AgentSession {
26
+ return {
27
+ id: "sess-001",
28
+ agentName: "test-builder",
29
+ capability: "builder",
30
+ worktreePath: "/tmp/worktrees/test-builder",
31
+ branchName: "agentplate/test-builder/task-1",
32
+ taskId: "task-1",
33
+ tmuxSession: "agentplate-test-builder",
34
+ state: "working",
35
+ pid: 12345,
36
+ parentAgent: null,
37
+ depth: 0,
38
+ runId: null,
39
+ startedAt: new Date(Date.now() - 60_000).toISOString(),
40
+ lastActivity: new Date().toISOString(),
41
+ escalationLevel: 0,
42
+ stalledSince: null,
43
+ transcriptPath: null,
44
+ ...overrides,
45
+ };
46
+ }
47
+
48
+ function makeStatusData(overrides: Partial<StatusData> = {}): StatusData {
49
+ return {
50
+ agents: [makeAgent()],
51
+ worktrees: [],
52
+ tmuxSessions: [{ name: "agentplate-test-builder", pid: 12345 }],
53
+ unreadMailCount: 0,
54
+ unreadMailScope: "orchestrator",
55
+ mergeQueueCount: 0,
56
+ recentMetricsCount: 0,
57
+ ...overrides,
58
+ };
59
+ }
60
+
61
+ describe("printStatus", () => {
62
+ let chunks: string[];
63
+ let originalWrite: typeof process.stdout.write;
64
+
65
+ beforeEach(() => {
66
+ chunks = [];
67
+ originalWrite = process.stdout.write;
68
+ process.stdout.write = ((chunk: string) => {
69
+ chunks.push(chunk);
70
+ return true;
71
+ }) as typeof process.stdout.write;
72
+ });
73
+
74
+ afterEach(() => {
75
+ process.stdout.write = originalWrite;
76
+ });
77
+
78
+ function output(): string {
79
+ return chunks.join("");
80
+ }
81
+
82
+ test("non-verbose: does not show worktree path or logs dir", () => {
83
+ const data = makeStatusData();
84
+ printStatus(data);
85
+ const out = output();
86
+
87
+ expect(out).toContain("test-builder");
88
+ expect(out).toContain("[builder]");
89
+ expect(out).not.toContain("Worktree:");
90
+ expect(out).not.toContain("Logs:");
91
+ expect(out).not.toContain("Mail sent:");
92
+ });
93
+
94
+ test("Mail line names the scope agent so per-agent scope is unambiguous", () => {
95
+ const data = makeStatusData({ unreadMailCount: 3, unreadMailScope: "lead-1" });
96
+ printStatus(data);
97
+ expect(stripAnsi(output())).toContain("Mail: 3 unread (to lead-1)");
98
+ });
99
+
100
+ test("verbose: shows worktree path, logs dir, and mail timestamps", () => {
101
+ const detail: VerboseAgentDetail = {
102
+ worktreePath: "/tmp/worktrees/test-builder",
103
+ logsDir: "/tmp/.agentplate/logs/test-builder",
104
+ lastMailSent: "2025-01-15T10:00:00.000Z",
105
+ lastMailReceived: "2025-01-15T10:05:00.000Z",
106
+ capability: "builder",
107
+ };
108
+
109
+ const data = makeStatusData({
110
+ verboseDetails: { "test-builder": detail },
111
+ });
112
+ printStatus(data);
113
+ const out = output();
114
+
115
+ expect(out).toContain("Worktree: /tmp/worktrees/test-builder");
116
+ expect(out).toContain("Logs: /tmp/.agentplate/logs/test-builder");
117
+ expect(out).toContain("Mail sent: 2025-01-15T10:00:00.000Z");
118
+ expect(out).toContain("received: 2025-01-15T10:05:00.000Z");
119
+ });
120
+
121
+ test("verbose: shows 'none' for null mail timestamps", () => {
122
+ const detail: VerboseAgentDetail = {
123
+ worktreePath: "/tmp/worktrees/test-builder",
124
+ logsDir: "/tmp/.agentplate/logs/test-builder",
125
+ lastMailSent: null,
126
+ lastMailReceived: null,
127
+ capability: "builder",
128
+ };
129
+
130
+ const data = makeStatusData({
131
+ verboseDetails: { "test-builder": detail },
132
+ });
133
+ printStatus(data);
134
+ const out = output();
135
+
136
+ expect(out).toContain("Mail sent: none");
137
+ expect(out).toContain("received: none");
138
+ });
139
+
140
+ test("verbose: zombie agents do not get verbose detail", () => {
141
+ const agent = makeAgent({ state: "zombie", agentName: "zombie-agent" });
142
+ const detail: VerboseAgentDetail = {
143
+ worktreePath: "/tmp/worktrees/zombie-agent",
144
+ logsDir: "/tmp/.agentplate/logs/zombie-agent",
145
+ lastMailSent: null,
146
+ lastMailReceived: null,
147
+ capability: "builder",
148
+ };
149
+
150
+ const data = makeStatusData({
151
+ agents: [agent],
152
+ verboseDetails: { "zombie-agent": detail },
153
+ });
154
+ printStatus(data);
155
+ const out = output();
156
+
157
+ // Zombie agents are filtered from the active list
158
+ expect(out).toContain("0 active");
159
+ expect(out).not.toContain("Worktree:");
160
+ });
161
+
162
+ test("verbose with multiple agents: each gets its own detail", () => {
163
+ const agent1 = makeAgent({ agentName: "builder-1", tmuxSession: "agentplate-builder-1" });
164
+ const agent2 = makeAgent({
165
+ agentName: "scout-1",
166
+ capability: "scout",
167
+ tmuxSession: "agentplate-scout-1",
168
+ });
169
+
170
+ const data = makeStatusData({
171
+ agents: [agent1, agent2],
172
+ tmuxSessions: [
173
+ { name: "agentplate-builder-1", pid: 100 },
174
+ { name: "agentplate-scout-1", pid: 200 },
175
+ ],
176
+ verboseDetails: {
177
+ "builder-1": {
178
+ worktreePath: "/tmp/wt/builder-1",
179
+ logsDir: "/tmp/logs/builder-1",
180
+ lastMailSent: "2025-01-15T10:00:00.000Z",
181
+ lastMailReceived: null,
182
+ capability: "builder",
183
+ },
184
+ "scout-1": {
185
+ worktreePath: "/tmp/wt/scout-1",
186
+ logsDir: "/tmp/logs/scout-1",
187
+ lastMailSent: null,
188
+ lastMailReceived: "2025-01-15T11:00:00.000Z",
189
+ capability: "scout",
190
+ },
191
+ },
192
+ });
193
+ printStatus(data);
194
+ const out = output();
195
+
196
+ expect(out).toContain("Worktree: /tmp/wt/builder-1");
197
+ expect(out).toContain("Worktree: /tmp/wt/scout-1");
198
+ expect(out).toContain("Logs: /tmp/logs/builder-1");
199
+ expect(out).toContain("Logs: /tmp/logs/scout-1");
200
+ });
201
+ });
202
+
203
+ describe("--verbose --json", () => {
204
+ test("verboseDetails is included in StatusData when present", () => {
205
+ const detail: VerboseAgentDetail = {
206
+ worktreePath: "/tmp/wt/agent",
207
+ logsDir: "/tmp/logs/agent",
208
+ lastMailSent: "2025-01-15T10:00:00.000Z",
209
+ lastMailReceived: null,
210
+ capability: "builder",
211
+ };
212
+
213
+ const data: StatusData = {
214
+ agents: [],
215
+ worktrees: [],
216
+ tmuxSessions: [],
217
+ unreadMailCount: 0,
218
+ unreadMailScope: "orchestrator",
219
+ mergeQueueCount: 0,
220
+ recentMetricsCount: 0,
221
+ verboseDetails: { agent: detail },
222
+ };
223
+
224
+ const json = JSON.parse(JSON.stringify(data)) as StatusData;
225
+ expect(json.verboseDetails).toBeDefined();
226
+ expect(json.verboseDetails?.agent?.worktreePath).toBe("/tmp/wt/agent");
227
+ expect(json.verboseDetails?.agent?.lastMailSent).toBe("2025-01-15T10:00:00.000Z");
228
+ expect(json.verboseDetails?.agent?.lastMailReceived).toBeNull();
229
+ });
230
+
231
+ test("verboseDetails is omitted from JSON when undefined", () => {
232
+ const data: StatusData = {
233
+ agents: [],
234
+ worktrees: [],
235
+ tmuxSessions: [],
236
+ unreadMailCount: 0,
237
+ unreadMailScope: "orchestrator",
238
+ mergeQueueCount: 0,
239
+ recentMetricsCount: 0,
240
+ };
241
+
242
+ const json = JSON.stringify(data);
243
+ expect(json).not.toContain("verboseDetails");
244
+ });
245
+ });
246
+
247
+ describe("run scoping", () => {
248
+ let chunks: string[];
249
+ let originalWrite: typeof process.stdout.write;
250
+
251
+ beforeEach(() => {
252
+ chunks = [];
253
+ originalWrite = process.stdout.write;
254
+ process.stdout.write = ((chunk: string) => {
255
+ chunks.push(chunk);
256
+ return true;
257
+ }) as typeof process.stdout.write;
258
+ });
259
+
260
+ afterEach(() => {
261
+ process.stdout.write = originalWrite;
262
+ invalidateStatusCache();
263
+ });
264
+
265
+ function output(): string {
266
+ return chunks.join("");
267
+ }
268
+
269
+ test("printStatus shows run ID when currentRunId is set", () => {
270
+ const data = makeStatusData({ currentRunId: "run-123" });
271
+ printStatus(data);
272
+ expect(stripAnsi(output())).toContain("Run: run-123");
273
+ });
274
+
275
+ test("printStatus does not show run line when currentRunId is undefined", () => {
276
+ const data = makeStatusData();
277
+ printStatus(data);
278
+ expect(output()).not.toContain("Run:");
279
+ });
280
+
281
+ test("printStatus does not show run line when currentRunId is null", () => {
282
+ const data = makeStatusData({ currentRunId: null });
283
+ printStatus(data);
284
+ expect(output()).not.toContain("Run:");
285
+ });
286
+
287
+ test("help text includes --all", async () => {
288
+ const helpChunks: string[] = [];
289
+ const origWrite = process.stdout.write;
290
+ process.stdout.write = ((chunk: string) => {
291
+ helpChunks.push(chunk);
292
+ return true;
293
+ }) as typeof process.stdout.write;
294
+
295
+ try {
296
+ await statusCommand(["--help"]);
297
+ } finally {
298
+ process.stdout.write = origWrite;
299
+ }
300
+
301
+ const out = helpChunks.join("");
302
+ expect(out).toContain("--all");
303
+ });
304
+
305
+ test("gatherStatus includes null-runId sessions when run-scoped", async () => {
306
+ // Use a real git repo so listWorktrees() doesn't throw
307
+ const tempDir = await createTempGitRepo();
308
+ const agentplateDir = join(tempDir, ".agentplate");
309
+ await mkdir(agentplateDir, { recursive: true });
310
+
311
+ // Seed sessions.db with three sessions:
312
+ // coordinator: runId=null
313
+ // builder-1: runId="run-001" (in-scope)
314
+ // builder-2: runId="run-002" (out-of-scope)
315
+ const store = createSessionStore(join(agentplateDir, "sessions.db"));
316
+ const now = new Date().toISOString();
317
+ for (const session of [
318
+ makeAgent({
319
+ agentName: "coordinator",
320
+ capability: "coordinator",
321
+ runId: null,
322
+ tmuxSession: "agentplate-fake-coordinator",
323
+ }),
324
+ makeAgent({
325
+ id: "sess-002",
326
+ agentName: "builder-1",
327
+ capability: "builder",
328
+ runId: "run-001",
329
+ tmuxSession: "agentplate-fake-builder-1",
330
+ }),
331
+ makeAgent({
332
+ id: "sess-003",
333
+ agentName: "builder-2",
334
+ capability: "builder",
335
+ runId: "run-002",
336
+ tmuxSession: "agentplate-fake-builder-2",
337
+ }),
338
+ ] as AgentSession[]) {
339
+ session.startedAt = now;
340
+ session.lastActivity = now;
341
+ store.upsert(session);
342
+ }
343
+ store.close();
344
+
345
+ try {
346
+ const result = await gatherStatus(tempDir, "orchestrator", false, "run-001");
347
+ const names = result.agents.map((a) => a.agentName);
348
+
349
+ // coordinator (null runId) must appear
350
+ expect(names).toContain("coordinator");
351
+ // in-scope builder must appear
352
+ expect(names).toContain("builder-1");
353
+ // out-of-scope builder must NOT appear
354
+ expect(names).not.toContain("builder-2");
355
+ } finally {
356
+ await cleanupTempDir(tempDir);
357
+ }
358
+ });
359
+ });
360
+
361
+ describe("headless agent alive markers", () => {
362
+ let chunks: string[];
363
+ let originalWrite: typeof process.stdout.write;
364
+
365
+ beforeEach(() => {
366
+ chunks = [];
367
+ originalWrite = process.stdout.write;
368
+ process.stdout.write = ((chunk: string) => {
369
+ chunks.push(chunk);
370
+ return true;
371
+ }) as typeof process.stdout.write;
372
+ });
373
+
374
+ afterEach(() => {
375
+ process.stdout.write = originalWrite;
376
+ });
377
+
378
+ function output(): string {
379
+ return chunks.join("");
380
+ }
381
+
382
+ test("printStatus shows green marker for headless agent with alive PID", () => {
383
+ // Use own process PID — guaranteed alive
384
+ const alivePid = process.pid;
385
+ const agent = makeAgent({
386
+ agentName: "headless-builder",
387
+ tmuxSession: "", // headless: no tmux
388
+ pid: alivePid,
389
+ state: "working",
390
+ });
391
+ const data = makeStatusData({
392
+ agents: [agent],
393
+ tmuxSessions: [], // no tmux sessions
394
+ });
395
+ printStatus(data);
396
+ const out = output();
397
+ // Green marker is ">" — check it appears in the output
398
+ expect(out).toContain("headless-builder");
399
+ expect(out).toContain(">");
400
+ });
401
+
402
+ test("printStatus shows red marker for headless agent with dead PID", () => {
403
+ const deadPid = 2_147_483_647; // max int, virtually guaranteed non-existent
404
+ const agent = makeAgent({
405
+ agentName: "dead-headless-builder",
406
+ tmuxSession: "", // headless: no tmux
407
+ pid: deadPid,
408
+ state: "working",
409
+ });
410
+ const data = makeStatusData({
411
+ agents: [agent],
412
+ tmuxSessions: [],
413
+ });
414
+ printStatus(data);
415
+ const out = output();
416
+ expect(out).toContain("dead-headless-builder");
417
+ expect(out).toContain("x");
418
+ });
419
+
420
+ test("printStatus uses tmux check (not PID) for tmux-based agents", () => {
421
+ const agent = makeAgent({
422
+ agentName: "tmux-builder",
423
+ tmuxSession: "agentplate-test-builder",
424
+ pid: process.pid, // alive PID, but should use tmux check
425
+ state: "working",
426
+ });
427
+ // tmuxSessions empty → tmux dead → red marker
428
+ const data = makeStatusData({
429
+ agents: [agent],
430
+ tmuxSessions: [],
431
+ });
432
+ printStatus(data);
433
+ const out = output();
434
+ expect(out).toContain("x");
435
+ });
436
+ });
437
+
438
+ describe("--watch deprecation", () => {
439
+ test("help text marks --watch as deprecated", async () => {
440
+ const chunks: string[] = [];
441
+ const originalWrite = process.stdout.write;
442
+ process.stdout.write = ((chunk: string) => {
443
+ chunks.push(chunk);
444
+ return true;
445
+ }) as typeof process.stdout.write;
446
+
447
+ try {
448
+ await statusCommand(["--help"]);
449
+ } finally {
450
+ process.stdout.write = originalWrite;
451
+ }
452
+
453
+ const out = chunks.join("");
454
+ expect(out).toContain("deprecated");
455
+ expect(out).toContain("ap dashboard");
456
+ });
457
+
458
+ test("--watch writes deprecation notice to stderr", async () => {
459
+ const stderrChunks: string[] = [];
460
+ const originalStderr = process.stderr.write;
461
+ process.stderr.write = ((chunk: string) => {
462
+ stderrChunks.push(chunk);
463
+ return true;
464
+ }) as typeof process.stderr.write;
465
+
466
+ // statusCommand with --watch will fail at loadConfig (no .agentplate/)
467
+ // but the deprecation notice is written before that. We just verify
468
+ // the notice was emitted.
469
+ const tmpDir = await mkdtemp(join(tmpdir(), "status-deprecation-"));
470
+ const originalCwd = process.cwd();
471
+ process.chdir(tmpDir);
472
+
473
+ try {
474
+ await statusCommand(["--watch"]);
475
+ } catch {
476
+ // Expected: loadConfig fails without .agentplate/
477
+ } finally {
478
+ process.stderr.write = originalStderr;
479
+ process.chdir(originalCwd);
480
+ await cleanupTempDir(tmpDir);
481
+ }
482
+
483
+ const err = stderrChunks.join("");
484
+ expect(err).toContain("--watch is deprecated");
485
+ expect(err).toContain("ap dashboard");
486
+ });
487
+ });
488
+
489
+ describe("gatherStatus reconciliation", () => {
490
+ afterEach(() => {
491
+ invalidateStatusCache();
492
+ });
493
+
494
+ test("booting agent with dead tmux becomes zombie", async () => {
495
+ const tempDir = await createTempGitRepo();
496
+ const agentplateDir = join(tempDir, ".agentplate");
497
+ await mkdir(agentplateDir, { recursive: true });
498
+
499
+ const store = createSessionStore(join(agentplateDir, "sessions.db"));
500
+ const now = new Date().toISOString();
501
+ const session = makeAgent({
502
+ agentName: "boot-builder",
503
+ capability: "builder",
504
+ state: "booting",
505
+ tmuxSession: "agentplate-boot-builder",
506
+ runId: null,
507
+ });
508
+ session.startedAt = now;
509
+ session.lastActivity = now;
510
+ store.upsert(session);
511
+ store.close();
512
+
513
+ try {
514
+ // No real tmux running, so getCachedTmuxSessions() returns empty array
515
+ // evaluateHealth ZFC Rule 1: tmux dead → zombie
516
+ const result = await gatherStatus(tempDir, "orchestrator", false, undefined);
517
+ const agent = result.agents.find((a) => a.agentName === "boot-builder");
518
+ expect(agent).toBeDefined();
519
+ expect(agent?.state).toBe("zombie");
520
+ } finally {
521
+ await cleanupTempDir(tempDir);
522
+ }
523
+ });
524
+
525
+ test("completed agents skip reconciliation", async () => {
526
+ const tempDir = await createTempGitRepo();
527
+ const agentplateDir = join(tempDir, ".agentplate");
528
+ await mkdir(agentplateDir, { recursive: true });
529
+
530
+ const store = createSessionStore(join(agentplateDir, "sessions.db"));
531
+ const now = new Date().toISOString();
532
+ const session = makeAgent({
533
+ agentName: "done-builder",
534
+ capability: "builder",
535
+ state: "completed",
536
+ tmuxSession: "agentplate-done-builder",
537
+ runId: null,
538
+ });
539
+ session.startedAt = now;
540
+ session.lastActivity = now;
541
+ store.upsert(session);
542
+ store.close();
543
+
544
+ try {
545
+ const result = await gatherStatus(tempDir, "orchestrator", false, undefined);
546
+ const agent = result.agents.find((a) => a.agentName === "done-builder");
547
+ expect(agent).toBeDefined();
548
+ expect(agent?.state).toBe("completed");
549
+ } finally {
550
+ await cleanupTempDir(tempDir);
551
+ }
552
+ });
553
+
554
+ test("working agent with dead tmux becomes zombie", async () => {
555
+ const tempDir = await createTempGitRepo();
556
+ const agentplateDir = join(tempDir, ".agentplate");
557
+ await mkdir(agentplateDir, { recursive: true });
558
+
559
+ const store = createSessionStore(join(agentplateDir, "sessions.db"));
560
+ const now = new Date().toISOString();
561
+ const session = makeAgent({
562
+ agentName: "working-builder",
563
+ capability: "builder",
564
+ state: "working",
565
+ tmuxSession: "agentplate-working-builder",
566
+ runId: null,
567
+ });
568
+ session.startedAt = now;
569
+ session.lastActivity = now;
570
+ store.upsert(session);
571
+ store.close();
572
+
573
+ try {
574
+ // No real tmux session → tmux dead → zombie via evaluateHealth ZFC Rule 1
575
+ const result = await gatherStatus(tempDir, "orchestrator", false, undefined);
576
+ const agent = result.agents.find((a) => a.agentName === "working-builder");
577
+ expect(agent).toBeDefined();
578
+ expect(agent?.state).toBe("zombie");
579
+ } finally {
580
+ await cleanupTempDir(tempDir);
581
+ }
582
+ });
583
+ });
584
+
585
+ describe("subprocess caching (invalidateStatusCache)", () => {
586
+ afterEach(() => {
587
+ invalidateStatusCache();
588
+ });
589
+
590
+ test("invalidateStatusCache is exported and callable", () => {
591
+ // Should not throw
592
+ invalidateStatusCache();
593
+ });
594
+
595
+ test("invalidateStatusCache resets cache so gatherStatus re-fetches on next call", async () => {
596
+ const tempDir = await createTempGitRepo();
597
+ const agentplateDir = join(tempDir, ".agentplate");
598
+ await mkdir(agentplateDir, { recursive: true });
599
+
600
+ try {
601
+ // First call populates the cache
602
+ const result1 = await gatherStatus(tempDir, "orchestrator", false, undefined);
603
+ // Invalidate cache
604
+ invalidateStatusCache();
605
+ // Second call must succeed (re-fetches, no stale cache issues)
606
+ const result2 = await gatherStatus(tempDir, "orchestrator", false, undefined);
607
+ // Both results should have the same structure
608
+ expect(Array.isArray(result1.worktrees)).toBe(true);
609
+ expect(Array.isArray(result2.worktrees)).toBe(true);
610
+ } finally {
611
+ await cleanupTempDir(tempDir);
612
+ }
613
+ });
614
+ });