@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,771 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import chalk from "chalk";
4
+ import type { Command } from "commander";
5
+ import { VERSION } from "../../../version.ts";
6
+ import { loadConfig } from "../config.ts";
7
+ import { humanOut, jsonOut, palette } from "../output.ts";
8
+ import { resolvePrompt } from "../render.ts";
9
+ import { dedupById, dedupByIdLast, readJsonl } from "../store.ts";
10
+ import type { Prompt, Schema } from "../types.ts";
11
+ import { LOCK_STALE_MS } from "../types.ts";
12
+ import { validateLoam, validatePrompt } from "../validate.ts";
13
+ import { parseYaml } from "../yaml.ts";
14
+ import { resolveEmitDir } from "./emit.ts";
15
+
16
+ interface DoctorCheck {
17
+ name: string;
18
+ status: "pass" | "warn" | "fail";
19
+ message: string;
20
+ details: string[];
21
+ fixable: boolean;
22
+ }
23
+
24
+ interface RawLine {
25
+ lineNumber: number;
26
+ text: string;
27
+ parsed?: unknown;
28
+ error?: string;
29
+ }
30
+
31
+ function readRawLines(filePath: string): RawLine[] {
32
+ if (!existsSync(filePath)) return [];
33
+ const content = readFileSync(filePath, "utf8");
34
+ const lines: RawLine[] = [];
35
+ for (const [i, raw] of content.split("\n").entries()) {
36
+ const text = raw.trim();
37
+ if (!text) continue;
38
+ try {
39
+ lines.push({ lineNumber: i + 1, text, parsed: JSON.parse(text) });
40
+ } catch (err: unknown) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ lines.push({ lineNumber: i + 1, text, error: msg });
43
+ }
44
+ }
45
+ return lines;
46
+ }
47
+
48
+ // 1. config check
49
+ async function checkConfig(trellisDir: string): Promise<DoctorCheck> {
50
+ const configPath = join(trellisDir, "config.yaml");
51
+ if (!existsSync(trellisDir)) {
52
+ return {
53
+ name: "config",
54
+ status: "fail",
55
+ message: ".trellis/ directory not found",
56
+ details: [],
57
+ fixable: false,
58
+ };
59
+ }
60
+ if (!existsSync(configPath)) {
61
+ return {
62
+ name: "config",
63
+ status: "fail",
64
+ message: "config.yaml is missing",
65
+ details: [],
66
+ fixable: false,
67
+ };
68
+ }
69
+ try {
70
+ const cwd = trellisDir.replace(/\/.trellis$/, "");
71
+ const config = await loadConfig(cwd);
72
+ if (!config.project) {
73
+ return {
74
+ name: "config",
75
+ status: "fail",
76
+ message: "config.yaml missing required 'project' field",
77
+ details: [],
78
+ fixable: false,
79
+ };
80
+ }
81
+ return {
82
+ name: "config",
83
+ status: "pass",
84
+ message: "Config is valid",
85
+ details: [],
86
+ fixable: false,
87
+ };
88
+ } catch {
89
+ return {
90
+ name: "config",
91
+ status: "fail",
92
+ message: "config.yaml is unparseable",
93
+ details: [],
94
+ fixable: false,
95
+ };
96
+ }
97
+ }
98
+
99
+ // 2. prompts-integrity
100
+ function checkPromptsIntegrity(trellisDir: string): DoctorCheck {
101
+ const filePath = join(trellisDir, "prompts.jsonl");
102
+ if (!existsSync(filePath)) {
103
+ return {
104
+ name: "prompts-integrity",
105
+ status: "warn",
106
+ message: "prompts.jsonl not found",
107
+ details: [],
108
+ fixable: false,
109
+ };
110
+ }
111
+ const lines = readRawLines(filePath);
112
+ const badLines = lines.filter((l) => l.error);
113
+ if (badLines.length > 0) {
114
+ return {
115
+ name: "prompts-integrity",
116
+ status: "fail",
117
+ message: `${String(badLines.length)} malformed line(s) in prompts.jsonl`,
118
+ details: badLines.map((l) => `line ${String(l.lineNumber)}: ${l.error}`),
119
+ fixable: false,
120
+ };
121
+ }
122
+ const validCount = lines.filter((l) => l.parsed).length;
123
+ return {
124
+ name: "prompts-integrity",
125
+ status: "pass",
126
+ message: `${String(validCount)} records, all valid`,
127
+ details: [],
128
+ fixable: false,
129
+ };
130
+ }
131
+
132
+ // 3. schemas-integrity
133
+ function checkSchemasIntegrity(trellisDir: string): DoctorCheck {
134
+ const filePath = join(trellisDir, "schemas.jsonl");
135
+ if (!existsSync(filePath)) {
136
+ return {
137
+ name: "schemas-integrity",
138
+ status: "pass",
139
+ message: "No schemas.jsonl (optional)",
140
+ details: [],
141
+ fixable: false,
142
+ };
143
+ }
144
+ const lines = readRawLines(filePath);
145
+ const badLines = lines.filter((l) => l.error);
146
+ if (badLines.length > 0) {
147
+ return {
148
+ name: "schemas-integrity",
149
+ status: "fail",
150
+ message: `${String(badLines.length)} malformed line(s) in schemas.jsonl`,
151
+ details: badLines.map((l) => `line ${String(l.lineNumber)}: ${l.error}`),
152
+ fixable: false,
153
+ };
154
+ }
155
+ const validCount = lines.filter((l) => l.parsed).length;
156
+ return {
157
+ name: "schemas-integrity",
158
+ status: "pass",
159
+ message: `${String(validCount)} records, all valid`,
160
+ details: [],
161
+ fixable: false,
162
+ };
163
+ }
164
+
165
+ // 4. schema-validation
166
+ async function checkSchemaValidation(trellisDir: string): Promise<DoctorCheck> {
167
+ const promptsPath = join(trellisDir, "prompts.jsonl");
168
+ const schemasPath = join(trellisDir, "schemas.jsonl");
169
+ if (!existsSync(promptsPath)) {
170
+ return {
171
+ name: "schema-validation",
172
+ status: "pass",
173
+ message: "No prompts to validate",
174
+ details: [],
175
+ fixable: false,
176
+ };
177
+ }
178
+
179
+ const allRecords = await readJsonl<Prompt>(promptsPath);
180
+ const prompts = dedupById(allRecords);
181
+ const activeWithSchema = prompts.filter((p) => p.status === "active" && p.schema);
182
+
183
+ if (activeWithSchema.length === 0) {
184
+ return {
185
+ name: "schema-validation",
186
+ status: "pass",
187
+ message: "No prompts with schema declarations",
188
+ details: [],
189
+ fixable: false,
190
+ };
191
+ }
192
+
193
+ let schemas: Schema[] = [];
194
+ if (existsSync(schemasPath)) {
195
+ const schemaRecords = await readJsonl<Schema>(schemasPath);
196
+ schemas = dedupByIdLast(schemaRecords);
197
+ }
198
+
199
+ const schemaMap = new Map<string, Schema>();
200
+ for (const s of schemas) {
201
+ schemaMap.set(s.name, s);
202
+ }
203
+
204
+ const details: string[] = [];
205
+ for (const prompt of activeWithSchema) {
206
+ const schema = schemaMap.get(prompt.schema as string);
207
+ if (!schema) {
208
+ details.push(`${prompt.name}: schema "${prompt.schema}" not found`);
209
+ continue;
210
+ }
211
+ const result = validatePrompt(prompt, schema, prompts);
212
+ if (!result.valid) {
213
+ for (const err of result.errors) {
214
+ details.push(`${prompt.name}: ${err.message}`);
215
+ }
216
+ }
217
+ }
218
+
219
+ if (details.length > 0) {
220
+ return {
221
+ name: "schema-validation",
222
+ status: "warn",
223
+ message: `${String(details.length)} validation issue(s)`,
224
+ details,
225
+ fixable: false,
226
+ };
227
+ }
228
+ return {
229
+ name: "schema-validation",
230
+ status: "pass",
231
+ message: "All schema-declared prompts pass validation",
232
+ details: [],
233
+ fixable: false,
234
+ };
235
+ }
236
+
237
+ // 4b. loam-shape — structural validation of loam block + extends_loam flag
238
+ async function checkLoamShape(trellisDir: string): Promise<DoctorCheck> {
239
+ const promptsPath = join(trellisDir, "prompts.jsonl");
240
+ if (!existsSync(promptsPath)) {
241
+ return {
242
+ name: "loam-shape",
243
+ status: "pass",
244
+ message: "No prompts to check",
245
+ details: [],
246
+ fixable: false,
247
+ };
248
+ }
249
+
250
+ const allRecords = await readJsonl<Prompt>(promptsPath);
251
+ const prompts = dedupById(allRecords);
252
+
253
+ const details: string[] = [];
254
+ let checked = 0;
255
+ for (const prompt of prompts) {
256
+ if (prompt.status === "archived") continue;
257
+ const hasLoam = (prompt as { loam?: unknown }).loam !== undefined;
258
+ const hasExtendsLoam = (prompt as { extends_loam?: unknown }).extends_loam !== undefined;
259
+ if (!hasLoam && !hasExtendsLoam) continue;
260
+ checked++;
261
+ const errs = validateLoam(prompt);
262
+ for (const err of errs) {
263
+ details.push(`${prompt.name}: [${err.section}] ${err.message}`);
264
+ }
265
+ }
266
+
267
+ if (details.length > 0) {
268
+ return {
269
+ name: "loam-shape",
270
+ status: "fail",
271
+ message: `${String(details.length)} malformed loam declaration(s)`,
272
+ details,
273
+ fixable: false,
274
+ };
275
+ }
276
+ if (checked === 0) {
277
+ return {
278
+ name: "loam-shape",
279
+ status: "pass",
280
+ message: "No loam declarations to check",
281
+ details: [],
282
+ fixable: false,
283
+ };
284
+ }
285
+ return {
286
+ name: "loam-shape",
287
+ status: "pass",
288
+ message: `${String(checked)} loam declaration(s) well-formed`,
289
+ details: [],
290
+ fixable: false,
291
+ };
292
+ }
293
+
294
+ // 4c. loam-domains — optional check: validate referenced domain names against
295
+ // .loam/loam.config.yaml when present. Warns (does not fail) on unknowns and
296
+ // degrades to a pass/no-op when loam is absent. The result message labels
297
+ // which loam config was consulted to disambiguate multi-repo setups.
298
+ async function checkLoamDomains(trellisDir: string): Promise<DoctorCheck> {
299
+ const cwd = trellisDir.replace(/\/.trellis$/, "");
300
+ const loamConfigRel = ".loam/loam.config.yaml";
301
+ const loamConfigPath = join(cwd, loamConfigRel);
302
+
303
+ if (!existsSync(loamConfigPath)) {
304
+ return {
305
+ name: "loam-domains",
306
+ status: "pass",
307
+ message: `No ${loamConfigRel} found (skipped)`,
308
+ details: [],
309
+ fixable: false,
310
+ };
311
+ }
312
+
313
+ let validDomains: Set<string>;
314
+ try {
315
+ const parsed = parseYaml(readFileSync(loamConfigPath, "utf8"));
316
+ const domainsField = parsed.domains;
317
+ if (!Array.isArray(domainsField)) {
318
+ return {
319
+ name: "loam-domains",
320
+ status: "warn",
321
+ message: `Could not read 'domains' from ${loamConfigRel}`,
322
+ details: [],
323
+ fixable: false,
324
+ };
325
+ }
326
+ validDomains = new Set(domainsField);
327
+ } catch {
328
+ return {
329
+ name: "loam-domains",
330
+ status: "warn",
331
+ message: `Could not parse ${loamConfigRel}`,
332
+ details: [],
333
+ fixable: false,
334
+ };
335
+ }
336
+
337
+ const promptsPath = join(trellisDir, "prompts.jsonl");
338
+ if (!existsSync(promptsPath)) {
339
+ return {
340
+ name: "loam-domains",
341
+ status: "pass",
342
+ message: `No prompts to check (consulted ${loamConfigRel})`,
343
+ details: [],
344
+ fixable: false,
345
+ };
346
+ }
347
+
348
+ const allRecords = await readJsonl<Prompt>(promptsPath);
349
+ const prompts = dedupById(allRecords);
350
+ const details: string[] = [];
351
+ let checked = 0;
352
+
353
+ for (const prompt of prompts) {
354
+ if (prompt.status === "archived") continue;
355
+ const loam = (prompt as { loam?: { prime?: { domains?: unknown } } }).loam;
356
+ const domains = loam?.prime?.domains;
357
+ if (!Array.isArray(domains)) continue;
358
+ checked++;
359
+ for (const d of domains) {
360
+ if (typeof d !== "string") continue;
361
+ if (!validDomains.has(d)) {
362
+ details.push(`${prompt.name}: domain "${d}" not declared in ${loamConfigRel}`);
363
+ }
364
+ }
365
+ }
366
+
367
+ if (details.length > 0) {
368
+ return {
369
+ name: "loam-domains",
370
+ status: "warn",
371
+ message: `${String(details.length)} unknown domain reference(s) — consulted ${loamConfigRel}`,
372
+ details,
373
+ fixable: false,
374
+ };
375
+ }
376
+ if (checked === 0) {
377
+ return {
378
+ name: "loam-domains",
379
+ status: "pass",
380
+ message: `No loam domain declarations to check (consulted ${loamConfigRel})`,
381
+ details: [],
382
+ fixable: false,
383
+ };
384
+ }
385
+ return {
386
+ name: "loam-domains",
387
+ status: "pass",
388
+ message: `${String(checked)} prompt(s) reference known domains (consulted ${loamConfigRel})`,
389
+ details: [],
390
+ fixable: false,
391
+ };
392
+ }
393
+
394
+ // 5. inheritance
395
+ async function checkInheritance(trellisDir: string): Promise<DoctorCheck> {
396
+ const promptsPath = join(trellisDir, "prompts.jsonl");
397
+ if (!existsSync(promptsPath)) {
398
+ return {
399
+ name: "inheritance",
400
+ status: "pass",
401
+ message: "No prompts to check",
402
+ details: [],
403
+ fixable: false,
404
+ };
405
+ }
406
+
407
+ const allRecords = await readJsonl<Prompt>(promptsPath);
408
+ const prompts = dedupById(allRecords);
409
+ const details: string[] = [];
410
+
411
+ for (const prompt of prompts) {
412
+ if (!prompt.extends && (!prompt.mixins || prompt.mixins.length === 0)) continue;
413
+ try {
414
+ resolvePrompt(prompt.name, prompts);
415
+ } catch (err: unknown) {
416
+ const msg = err instanceof Error ? err.message : String(err);
417
+ details.push(`${prompt.name}: ${msg}`);
418
+ }
419
+ }
420
+
421
+ if (details.length > 0) {
422
+ return {
423
+ name: "inheritance",
424
+ status: "fail",
425
+ message: `${String(details.length)} broken inheritance chain(s)`,
426
+ details,
427
+ fixable: false,
428
+ };
429
+ }
430
+ return {
431
+ name: "inheritance",
432
+ status: "pass",
433
+ message: "No broken references",
434
+ details: [],
435
+ fixable: false,
436
+ };
437
+ }
438
+
439
+ // 6. emit-staleness
440
+ async function checkEmitStaleness(trellisDir: string): Promise<DoctorCheck> {
441
+ const promptsPath = join(trellisDir, "prompts.jsonl");
442
+ if (!existsSync(promptsPath)) {
443
+ return {
444
+ name: "emit-staleness",
445
+ status: "pass",
446
+ message: "No prompts to check",
447
+ details: [],
448
+ fixable: false,
449
+ };
450
+ }
451
+
452
+ const cwd = trellisDir.replace(/\/.trellis$/, "");
453
+ const config = await loadConfig(cwd);
454
+
455
+ const allRecords = await readJsonl<Prompt>(promptsPath);
456
+ const prompts = dedupById(allRecords);
457
+ const activePrompts = prompts.filter((p) => p.status === "active");
458
+
459
+ if (activePrompts.length === 0) {
460
+ return {
461
+ name: "emit-staleness",
462
+ status: "pass",
463
+ message: "No active prompts to emit",
464
+ details: [],
465
+ fixable: false,
466
+ };
467
+ }
468
+
469
+ const details: string[] = [];
470
+ for (const p of activePrompts) {
471
+ const filename = p.emitAs ?? `${p.name}.md`;
472
+ const promptEmitDir = resolveEmitDir(p, config);
473
+ const outPath = join(cwd, promptEmitDir, filename);
474
+ if (!existsSync(outPath)) {
475
+ details.push(`${p.name}: emitted file missing (${filename})`);
476
+ continue;
477
+ }
478
+
479
+ try {
480
+ const result = resolvePrompt(p.name, allRecords, p.pinned);
481
+ const sections = result.sections;
482
+ const expected = `${sections.map((s) => `## ${s.name}\n\n${s.body}`).join("\n\n")}\n`;
483
+ const actual = await Bun.file(outPath).text();
484
+ if (actual !== expected) {
485
+ details.push(`${p.name}: emitted file is stale`);
486
+ }
487
+ } catch {
488
+ details.push(`${p.name}: could not resolve for comparison`);
489
+ }
490
+ }
491
+
492
+ if (details.length > 0) {
493
+ return {
494
+ name: "emit-staleness",
495
+ status: "warn",
496
+ message: `${String(details.length)} stale or missing emitted file(s)`,
497
+ details,
498
+ fixable: false,
499
+ };
500
+ }
501
+ return {
502
+ name: "emit-staleness",
503
+ status: "pass",
504
+ message: "All emitted files current",
505
+ details: [],
506
+ fixable: false,
507
+ };
508
+ }
509
+
510
+ // 7. stale-locks
511
+ function checkStaleLocks(trellisDir: string): DoctorCheck {
512
+ const details: string[] = [];
513
+ try {
514
+ const entries = readdirSync(trellisDir);
515
+ for (const entry of entries) {
516
+ if (!entry.endsWith(".lock")) continue;
517
+ const lockPath = join(trellisDir, entry);
518
+ try {
519
+ const st = statSync(lockPath);
520
+ const age = Date.now() - st.mtimeMs;
521
+ if (age > LOCK_STALE_MS) {
522
+ details.push(`${entry} is stale (${String(Math.round(age / 1000))}s old)`);
523
+ } else {
524
+ details.push(`${entry} exists (${String(Math.round(age / 1000))}s old, may be active)`);
525
+ }
526
+ } catch {
527
+ details.push(`${entry} exists but cannot stat`);
528
+ }
529
+ }
530
+ } catch {
531
+ // .trellis/ might not be readable
532
+ }
533
+ if (details.length > 0) {
534
+ return {
535
+ name: "stale-locks",
536
+ status: "warn",
537
+ message: `${String(details.length)} lock file(s) found`,
538
+ details,
539
+ fixable: true,
540
+ };
541
+ }
542
+ return {
543
+ name: "stale-locks",
544
+ status: "pass",
545
+ message: "No stale locks",
546
+ details: [],
547
+ fixable: false,
548
+ };
549
+ }
550
+
551
+ // 8. version-sync — trellis is bundled in @ag-eco/agentplate-cli, which has a
552
+ // single version source (src/version.ts, exported as VERSION) kept in lockstep
553
+ // with the root package.json by scripts/version-bump.ts. Compare the two.
554
+ function checkVersionSync(): DoctorCheck {
555
+ // Monorepo root package.json: commands → trellis → tools → src → root.
556
+ const pkgPath = join(import.meta.dir, "../../../../package.json");
557
+ try {
558
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version?: string };
559
+ if (!pkg.version) {
560
+ return {
561
+ name: "version-sync",
562
+ status: "warn",
563
+ message: "package.json has no version field",
564
+ details: [],
565
+ fixable: false,
566
+ };
567
+ }
568
+ if (pkg.version !== VERSION) {
569
+ return {
570
+ name: "version-sync",
571
+ status: "fail",
572
+ message: `VERSION (${VERSION}) ≠ package.json (${pkg.version})`,
573
+ details: [],
574
+ fixable: false,
575
+ };
576
+ }
577
+ return {
578
+ name: "version-sync",
579
+ status: "pass",
580
+ message: `${pkg.version} matches package.json`,
581
+ details: [],
582
+ fixable: false,
583
+ };
584
+ } catch {
585
+ return {
586
+ name: "version-sync",
587
+ status: "warn",
588
+ message: "package.json not found or unparseable",
589
+ details: [],
590
+ fixable: false,
591
+ };
592
+ }
593
+ }
594
+
595
+ // Fix logic
596
+ function applyFixes(trellisDir: string, checks: DoctorCheck[]): string[] {
597
+ const fixed: string[] = [];
598
+ for (const check of checks) {
599
+ if (check.status === "pass" || !check.fixable) continue;
600
+ switch (check.name) {
601
+ case "stale-locks": {
602
+ try {
603
+ const entries = readdirSync(trellisDir);
604
+ for (const entry of entries) {
605
+ if (!entry.endsWith(".lock")) continue;
606
+ const lockPath = join(trellisDir, entry);
607
+ try {
608
+ const st = statSync(lockPath);
609
+ if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
610
+ unlinkSync(lockPath);
611
+ fixed.push(`Removed stale ${entry}`);
612
+ }
613
+ } catch {
614
+ // best-effort
615
+ }
616
+ }
617
+ } catch {
618
+ // best-effort
619
+ }
620
+ break;
621
+ }
622
+ }
623
+ }
624
+ return fixed;
625
+ }
626
+
627
+ function printCheck(check: DoctorCheck, verbose: boolean): void {
628
+ if (check.status === "pass" && !verbose) return;
629
+
630
+ const icon =
631
+ check.status === "pass"
632
+ ? palette.brand("✓")
633
+ : check.status === "warn"
634
+ ? chalk.yellow("!")
635
+ : chalk.red("✗");
636
+
637
+ const padded = check.name + " ".repeat(Math.max(20 - check.name.length, 2));
638
+ humanOut(` ${icon} ${chalk.dim(padded)}${check.message}`);
639
+ for (const detail of check.details) {
640
+ humanOut(` ${chalk.dim(detail)}`);
641
+ }
642
+ }
643
+
644
+ export async function run(fix: boolean, verbose: boolean, json: boolean): Promise<void> {
645
+ const cwd = process.cwd();
646
+ const trellisDir = join(cwd, ".trellis");
647
+
648
+ const checks: DoctorCheck[] = [];
649
+
650
+ // 1. config
651
+ const configCheck = await checkConfig(trellisDir);
652
+ checks.push(configCheck);
653
+
654
+ // If config fails, skip remaining checks
655
+ if (configCheck.status === "fail") {
656
+ return reportResults(checks, json, verbose, fix, trellisDir);
657
+ }
658
+
659
+ // 2-3. JSONL integrity
660
+ checks.push(checkPromptsIntegrity(trellisDir));
661
+ checks.push(checkSchemasIntegrity(trellisDir));
662
+
663
+ // 4. schema validation
664
+ checks.push(await checkSchemaValidation(trellisDir));
665
+
666
+ // 4b. loam shape validation
667
+ checks.push(await checkLoamShape(trellisDir));
668
+
669
+ // 4c. loam domain references (optional, warn-only)
670
+ checks.push(await checkLoamDomains(trellisDir));
671
+
672
+ // 5. inheritance
673
+ checks.push(await checkInheritance(trellisDir));
674
+
675
+ // 6. emit staleness
676
+ checks.push(await checkEmitStaleness(trellisDir));
677
+
678
+ // 7. stale locks
679
+ checks.push(checkStaleLocks(trellisDir));
680
+
681
+ // 8. version sync
682
+ checks.push(checkVersionSync());
683
+
684
+ // Apply fixes if requested
685
+ if (fix) {
686
+ const fixableFailures = checks.filter((ch) => ch.fixable && ch.status !== "pass");
687
+ if (fixableFailures.length > 0) {
688
+ const fixedItems = applyFixes(trellisDir, checks);
689
+
690
+ // Re-run all checks after fixes
691
+ const reChecks: DoctorCheck[] = [];
692
+ const reConfig = await checkConfig(trellisDir);
693
+ reChecks.push(reConfig);
694
+ if (reConfig.status !== "fail") {
695
+ reChecks.push(checkPromptsIntegrity(trellisDir));
696
+ reChecks.push(checkSchemasIntegrity(trellisDir));
697
+ reChecks.push(await checkSchemaValidation(trellisDir));
698
+ reChecks.push(await checkLoamShape(trellisDir));
699
+ reChecks.push(await checkLoamDomains(trellisDir));
700
+ reChecks.push(await checkInheritance(trellisDir));
701
+ reChecks.push(await checkEmitStaleness(trellisDir));
702
+ reChecks.push(checkStaleLocks(trellisDir));
703
+ reChecks.push(checkVersionSync());
704
+ }
705
+ return reportResults(reChecks, json, verbose, fix, trellisDir, fixedItems);
706
+ }
707
+ }
708
+
709
+ return reportResults(checks, json, verbose, fix, trellisDir);
710
+ }
711
+
712
+ function reportResults(
713
+ checks: DoctorCheck[],
714
+ jsonMode: boolean,
715
+ verbose: boolean,
716
+ _fix: boolean,
717
+ _trellisDir: string,
718
+ fixedItems?: string[],
719
+ ): void {
720
+ const summary = {
721
+ pass: checks.filter((ch) => ch.status === "pass").length,
722
+ warn: checks.filter((ch) => ch.status === "warn").length,
723
+ fail: checks.filter((ch) => ch.status === "fail").length,
724
+ };
725
+
726
+ if (jsonMode) {
727
+ jsonOut({
728
+ success: summary.fail === 0,
729
+ command: "doctor",
730
+ checks: checks.map((ch) => ({
731
+ name: ch.name,
732
+ status: ch.status,
733
+ message: ch.message,
734
+ details: ch.details,
735
+ fixable: ch.fixable,
736
+ })),
737
+ summary,
738
+ ...(fixedItems && fixedItems.length > 0 ? { fixed: fixedItems } : {}),
739
+ });
740
+ } else {
741
+ humanOut(`\n${chalk.bold("tl doctor")}\n`);
742
+ for (const check of checks) {
743
+ printCheck(check, verbose);
744
+ }
745
+ humanOut(
746
+ `\n ${chalk.dim(`${String(summary.pass)} passed, ${String(summary.warn)} warning, ${String(summary.fail)} failed`)}`,
747
+ );
748
+ if (fixedItems && fixedItems.length > 0) {
749
+ humanOut(`\n${chalk.bold("Fixed:")}`);
750
+ for (const item of fixedItems) {
751
+ humanOut(` ${palette.brand("✓")} ${item}`);
752
+ }
753
+ }
754
+ }
755
+
756
+ if (summary.fail > 0) {
757
+ process.exitCode = 1;
758
+ }
759
+ }
760
+
761
+ export function registerDoctorCommand(program: Command): void {
762
+ program
763
+ .command("doctor")
764
+ .description("Check project health and data integrity")
765
+ .option("--fix", "Fix auto-fixable issues")
766
+ .option("--verbose", "Show all check results including passes")
767
+ .action(async (opts: { fix?: boolean; verbose?: boolean }) => {
768
+ const json: boolean = program.opts().json ?? false;
769
+ await run(opts.fix ?? false, opts.verbose ?? false, json);
770
+ });
771
+ }