@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,752 @@
1
+ /**
2
+ * Tests for loam CLI client.
3
+ *
4
+ * Uses real loam CLI when available (preferred).
5
+ * All tests are skipped if loam is not installed.
6
+ */
7
+
8
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
+ import { mkdtemp } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { AgentError } from "../errors.ts";
13
+ import { cleanupTempDir } from "../test-helpers.ts";
14
+ import { createLoamClient } from "./client.ts";
15
+
16
+ // Check if loam is available
17
+ let hasLoam = false;
18
+ try {
19
+ const proc = Bun.spawn(["which", "lm"], { stdout: "pipe", stderr: "pipe" });
20
+ const exitCode = await proc.exited;
21
+ hasLoam = exitCode === 0;
22
+ } catch {
23
+ hasLoam = false;
24
+ }
25
+
26
+ describe("createLoamClient", () => {
27
+ let tempDir: string;
28
+
29
+ beforeEach(async () => {
30
+ tempDir = await mkdtemp(join(tmpdir(), "loam-test-"));
31
+ });
32
+
33
+ afterEach(async () => {
34
+ await cleanupTempDir(tempDir);
35
+ });
36
+
37
+ /**
38
+ * Helper to initialize git repo in tempDir.
39
+ * Some loam commands (diff, learn) require a git repository.
40
+ */
41
+ async function initGit(): Promise<void> {
42
+ const initProc = Bun.spawn(["git", "init"], {
43
+ cwd: tempDir,
44
+ stdout: "pipe",
45
+ stderr: "pipe",
46
+ });
47
+ await initProc.exited;
48
+
49
+ const configNameProc = Bun.spawn(["git", "config", "user.name", "Test User"], {
50
+ cwd: tempDir,
51
+ stdout: "pipe",
52
+ stderr: "pipe",
53
+ });
54
+ await configNameProc.exited;
55
+
56
+ const configEmailProc = Bun.spawn(["git", "config", "user.email", "test@example.com"], {
57
+ cwd: tempDir,
58
+ stdout: "pipe",
59
+ stderr: "pipe",
60
+ });
61
+ await configEmailProc.exited;
62
+ }
63
+
64
+ /**
65
+ * Helper to initialize loam in tempDir.
66
+ * Creates .loam/ directory and initial structure.
67
+ */
68
+ async function initLoam(): Promise<void> {
69
+ if (!hasLoam) return;
70
+ const proc = Bun.spawn(["lm", "init"], {
71
+ cwd: tempDir,
72
+ stdout: "pipe",
73
+ stderr: "pipe",
74
+ });
75
+ await proc.exited;
76
+ }
77
+
78
+ describe("prime", () => {
79
+ test.skipIf(!hasLoam)("returns non-empty string", async () => {
80
+ await initLoam();
81
+ const client = createLoamClient(tempDir);
82
+ const result = await client.prime();
83
+ expect(result).toBeTruthy();
84
+ expect(typeof result).toBe("string");
85
+ expect(result.length).toBeGreaterThan(0);
86
+ });
87
+
88
+ test.skipIf(!hasLoam)("passes domain args when provided", async () => {
89
+ await initLoam();
90
+ // Add a domain first so we can prime it
91
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
92
+ cwd: tempDir,
93
+ stdout: "pipe",
94
+ stderr: "pipe",
95
+ });
96
+ await addProc.exited;
97
+
98
+ const client = createLoamClient(tempDir);
99
+ const result = await client.prime(["architecture"]);
100
+ expect(typeof result).toBe("string");
101
+ });
102
+
103
+ test.skipIf(!hasLoam)("passes --format flag", async () => {
104
+ await initLoam();
105
+ const client = createLoamClient(tempDir);
106
+ const result = await client.prime([], "markdown");
107
+ expect(typeof result).toBe("string");
108
+ });
109
+
110
+ test.skipIf(!hasLoam)("passes both domains and format", async () => {
111
+ await initLoam();
112
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
113
+ cwd: tempDir,
114
+ stdout: "pipe",
115
+ stderr: "pipe",
116
+ });
117
+ await addProc.exited;
118
+
119
+ const client = createLoamClient(tempDir);
120
+ const result = await client.prime(["architecture"], "xml");
121
+ expect(typeof result).toBe("string");
122
+ });
123
+
124
+ test.skipIf(!hasLoam)("passes --files flag", async () => {
125
+ await initLoam();
126
+ const client = createLoamClient(tempDir);
127
+ const result = await client.prime([], "markdown", {
128
+ files: ["src/config.ts", "src/types.ts"],
129
+ });
130
+ expect(typeof result).toBe("string");
131
+ });
132
+
133
+ test.skipIf(!hasLoam)("passes --exclude-domain flag", async () => {
134
+ await initLoam();
135
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
136
+ cwd: tempDir,
137
+ stdout: "pipe",
138
+ stderr: "pipe",
139
+ });
140
+ await addProc.exited;
141
+
142
+ const client = createLoamClient(tempDir);
143
+ const result = await client.prime([], "markdown", {
144
+ excludeDomain: ["architecture"],
145
+ });
146
+ expect(typeof result).toBe("string");
147
+ });
148
+
149
+ test.skipIf(!hasLoam)("passes both --files and --exclude-domain", async () => {
150
+ await initLoam();
151
+ // Add a domain to exclude
152
+ const addProc = Bun.spawn(["lm", "add", "internal"], {
153
+ cwd: tempDir,
154
+ stdout: "pipe",
155
+ stderr: "pipe",
156
+ });
157
+ await addProc.exited;
158
+
159
+ const client = createLoamClient(tempDir);
160
+ const result = await client.prime([], "markdown", {
161
+ files: ["src/config.ts"],
162
+ excludeDomain: ["internal"],
163
+ });
164
+ expect(typeof result).toBe("string");
165
+ });
166
+
167
+ test.skipIf(!hasLoam)("passes --sort-by-score flag in prime options", async () => {
168
+ await initLoam();
169
+ const client = createLoamClient(tempDir);
170
+ // loam prime --sort-by-score may not be supported in older loam versions;
171
+ // the interface and impl are forward-looking — test accepts both outcomes.
172
+ try {
173
+ const result = await client.prime([], "markdown", { sortByScore: true });
174
+ expect(typeof result).toBe("string");
175
+ } catch (error) {
176
+ expect(error).toBeInstanceOf(AgentError);
177
+ }
178
+ });
179
+
180
+ test.skipIf(!hasLoam)("passes --sort-by-score with --files together", async () => {
181
+ await initLoam();
182
+ const client = createLoamClient(tempDir);
183
+ try {
184
+ const result = await client.prime([], "markdown", {
185
+ files: ["src/config.ts"],
186
+ sortByScore: true,
187
+ });
188
+ expect(typeof result).toBe("string");
189
+ } catch (error) {
190
+ expect(error).toBeInstanceOf(AgentError);
191
+ }
192
+ });
193
+ });
194
+
195
+ describe("status", () => {
196
+ test.skipIf(!hasLoam)("returns LoamStatus shape", async () => {
197
+ await initLoam();
198
+ const client = createLoamClient(tempDir);
199
+ const result = await client.status();
200
+ expect(result).toHaveProperty("domains");
201
+ expect(Array.isArray(result.domains)).toBe(true);
202
+ });
203
+
204
+ test.skipIf(!hasLoam)("with no domains returns empty array", async () => {
205
+ await initLoam();
206
+ const client = createLoamClient(tempDir);
207
+ const result = await client.status();
208
+ expect(result.domains).toEqual([]);
209
+ });
210
+
211
+ test.skipIf(!hasLoam)("includes domain data when domains exist", async () => {
212
+ await initLoam();
213
+ // Add a domain
214
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
215
+ cwd: tempDir,
216
+ stdout: "pipe",
217
+ stderr: "pipe",
218
+ });
219
+ await addProc.exited;
220
+
221
+ const client = createLoamClient(tempDir);
222
+ const result = await client.status();
223
+ expect(result.domains.length).toBeGreaterThan(0);
224
+ // Just verify we got an array with entries, don't check specific structure
225
+ // as loam CLI output format may vary
226
+ });
227
+ });
228
+
229
+ describe("record", () => {
230
+ test.skipIf(!hasLoam)("with required args succeeds", async () => {
231
+ await initLoam();
232
+ // Add domain first
233
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
234
+ cwd: tempDir,
235
+ stdout: "pipe",
236
+ stderr: "pipe",
237
+ });
238
+ await addProc.exited;
239
+
240
+ const client = createLoamClient(tempDir);
241
+ await expect(
242
+ client.record("architecture", {
243
+ type: "convention",
244
+ description: "test convention",
245
+ }),
246
+ ).resolves.toBeUndefined();
247
+ });
248
+
249
+ test.skipIf(!hasLoam)("with optional args succeeds", async () => {
250
+ await initLoam();
251
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
252
+ cwd: tempDir,
253
+ stdout: "pipe",
254
+ stderr: "pipe",
255
+ });
256
+ await addProc.exited;
257
+
258
+ const client = createLoamClient(tempDir);
259
+ await expect(
260
+ client.record("architecture", {
261
+ type: "pattern",
262
+ name: "test-pattern",
263
+ description: "test description",
264
+ title: "Test Pattern",
265
+ rationale: "testing all options",
266
+ tags: ["testing", "example"],
267
+ }),
268
+ ).resolves.toBeUndefined();
269
+ });
270
+
271
+ test.skipIf(!hasLoam)("with multiple tags", async () => {
272
+ await initLoam();
273
+ const addProc = Bun.spawn(["lm", "add", "typescript"], {
274
+ cwd: tempDir,
275
+ stdout: "pipe",
276
+ stderr: "pipe",
277
+ });
278
+ await addProc.exited;
279
+
280
+ const client = createLoamClient(tempDir);
281
+ await expect(
282
+ client.record("typescript", {
283
+ type: "convention",
284
+ description: "multi-tag test",
285
+ tags: ["tag1", "tag2", "tag3"],
286
+ }),
287
+ ).resolves.toBeUndefined();
288
+ });
289
+
290
+ test.skipIf(!hasLoam)("with --stdin flag passes flag to CLI", async () => {
291
+ await initLoam();
292
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
293
+ cwd: tempDir,
294
+ stdout: "pipe",
295
+ stderr: "pipe",
296
+ });
297
+ await addProc.exited;
298
+
299
+ const client = createLoamClient(tempDir);
300
+ // --stdin expects JSON input, which we're not providing, so this will fail
301
+ // but we're testing that the flag is passed correctly
302
+ await expect(
303
+ client.record("testing", {
304
+ type: "convention",
305
+ description: "stdin test",
306
+ stdin: true,
307
+ }),
308
+ ).rejects.toThrow(AgentError);
309
+ });
310
+
311
+ test.skipIf(!hasLoam)("with outcome flags passes them to CLI", async () => {
312
+ await initLoam();
313
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
314
+ cwd: tempDir,
315
+ stdout: "pipe",
316
+ stderr: "pipe",
317
+ });
318
+ await addProc.exited;
319
+
320
+ const client = createLoamClient(tempDir);
321
+ // May succeed or fail depending on loam version, but verifies flags are passed
322
+ try {
323
+ await client.record("testing", {
324
+ type: "convention",
325
+ description: "outcome test",
326
+ outcomeStatus: "success",
327
+ outcomeDuration: 42,
328
+ outcomeTestResults: "15 passed",
329
+ outcomeAgent: "test-agent",
330
+ });
331
+ expect(true).toBe(true);
332
+ } catch (error) {
333
+ expect(error).toBeInstanceOf(AgentError);
334
+ }
335
+ });
336
+
337
+ test.skipIf(!hasLoam)("with outcomeStatus: failure passes flag to CLI", async () => {
338
+ await initLoam();
339
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
340
+ cwd: tempDir,
341
+ stdout: "pipe",
342
+ stderr: "pipe",
343
+ });
344
+ await addProc.exited;
345
+
346
+ const client = createLoamClient(tempDir);
347
+ try {
348
+ await client.record("testing", {
349
+ type: "failure",
350
+ description: "failure outcome test",
351
+ outcomeStatus: "failure",
352
+ });
353
+ expect(true).toBe(true);
354
+ } catch (error) {
355
+ expect(error).toBeInstanceOf(AgentError);
356
+ }
357
+ });
358
+
359
+ test.skipIf(!hasLoam)("with outcomeDuration: 0 passes zero value to CLI", async () => {
360
+ await initLoam();
361
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
362
+ cwd: tempDir,
363
+ stdout: "pipe",
364
+ stderr: "pipe",
365
+ });
366
+ await addProc.exited;
367
+
368
+ const client = createLoamClient(tempDir);
369
+ try {
370
+ await client.record("testing", {
371
+ type: "convention",
372
+ description: "zero duration test",
373
+ outcomeDuration: 0,
374
+ });
375
+ expect(true).toBe(true);
376
+ } catch (error) {
377
+ expect(error).toBeInstanceOf(AgentError);
378
+ }
379
+ });
380
+
381
+ test.skipIf(!hasLoam)("with --evidence-bead flag passes flag to CLI", async () => {
382
+ await initLoam();
383
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
384
+ cwd: tempDir,
385
+ stdout: "pipe",
386
+ stderr: "pipe",
387
+ });
388
+ await addProc.exited;
389
+
390
+ const client = createLoamClient(tempDir);
391
+ // The flag is passed correctly, but may fail if the task ID is invalid
392
+ // or if other required fields are missing. This test documents that the
393
+ // flag is properly passed to the CLI.
394
+ try {
395
+ await client.record("testing", {
396
+ type: "decision",
397
+ description: "bead evidence test",
398
+ evidenceBead: "beads-abc123",
399
+ });
400
+ // If it succeeds, great!
401
+ expect(true).toBe(true);
402
+ } catch (error) {
403
+ // If it fails, verify it's an AgentError (not a type error or similar)
404
+ // which proves the command was executed with the flag
405
+ expect(error).toBeInstanceOf(AgentError);
406
+ }
407
+ });
408
+ });
409
+
410
+ describe("query", () => {
411
+ test.skipIf(!hasLoam)("passes domain arg when provided", async () => {
412
+ await initLoam();
413
+ const addProc = Bun.spawn(["lm", "add", "architecture"], {
414
+ cwd: tempDir,
415
+ stdout: "pipe",
416
+ stderr: "pipe",
417
+ });
418
+ await addProc.exited;
419
+
420
+ const client = createLoamClient(tempDir);
421
+ const result = await client.query("architecture");
422
+ expect(typeof result).toBe("string");
423
+ });
424
+
425
+ test.skipIf(!hasLoam)("query without domain requires --all flag", async () => {
426
+ await initLoam();
427
+ const client = createLoamClient(tempDir);
428
+ // Current implementation doesn't pass --all, so this will fail
429
+ // This documents the current behavior
430
+ await expect(client.query()).rejects.toThrow(AgentError);
431
+ });
432
+ });
433
+
434
+ describe("search", () => {
435
+ test.skipIf(!hasLoam)("returns string output", async () => {
436
+ await initLoam();
437
+ const client = createLoamClient(tempDir);
438
+ const result = await client.search("test");
439
+ expect(typeof result).toBe("string");
440
+ });
441
+
442
+ test.skipIf(!hasLoam)("searches across domains", async () => {
443
+ await initLoam();
444
+ // Add a domain and record
445
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
446
+ cwd: tempDir,
447
+ stdout: "pipe",
448
+ stderr: "pipe",
449
+ });
450
+ await addProc.exited;
451
+
452
+ const client = createLoamClient(tempDir);
453
+ await client.record("testing", {
454
+ type: "convention",
455
+ description: "searchable keyword here",
456
+ });
457
+
458
+ const result = await client.search("searchable");
459
+ expect(typeof result).toBe("string");
460
+ });
461
+
462
+ test.skipIf(!hasLoam)("passes --file flag when provided", async () => {
463
+ await initLoam();
464
+ const client = createLoamClient(tempDir);
465
+ const result = await client.search("test", { file: "src/config.ts" });
466
+ expect(typeof result).toBe("string");
467
+ });
468
+
469
+ test.skipIf(!hasLoam)("passes --sort-by-score flag when provided", async () => {
470
+ await initLoam();
471
+ const client = createLoamClient(tempDir);
472
+ const result = await client.search("test", { sortByScore: true });
473
+ expect(typeof result).toBe("string");
474
+ });
475
+
476
+ test.skipIf(!hasLoam)("passes both --file and --sort-by-score flags", async () => {
477
+ await initLoam();
478
+ const client = createLoamClient(tempDir);
479
+ const result = await client.search("test", { file: "src/config.ts", sortByScore: true });
480
+ expect(typeof result).toBe("string");
481
+ });
482
+
483
+ test.skipIf(!hasLoam)("passes --classification flag when provided", async () => {
484
+ await initLoam();
485
+ const client = createLoamClient(tempDir);
486
+ const result = await client.search("test", { classification: "foundational" });
487
+ expect(typeof result).toBe("string");
488
+ });
489
+
490
+ test.skipIf(!hasLoam)("passes --outcome-status flag when provided (success)", async () => {
491
+ await initLoam();
492
+ const client = createLoamClient(tempDir);
493
+ const result = await client.search("test", { outcomeStatus: "success" });
494
+ expect(typeof result).toBe("string");
495
+ });
496
+
497
+ test.skipIf(!hasLoam)("passes --outcome-status flag when provided (failure)", async () => {
498
+ await initLoam();
499
+ const client = createLoamClient(tempDir);
500
+ const result = await client.search("test", { outcomeStatus: "failure" });
501
+ expect(typeof result).toBe("string");
502
+ });
503
+
504
+ test.skipIf(!hasLoam)("passes all search filters together", async () => {
505
+ await initLoam();
506
+ const client = createLoamClient(tempDir);
507
+ const result = await client.search("test", {
508
+ classification: "tactical",
509
+ outcomeStatus: "success",
510
+ sortByScore: true,
511
+ file: "src/config.ts",
512
+ });
513
+ expect(typeof result).toBe("string");
514
+ });
515
+
516
+ test.skipIf(!hasLoam)("roundtrip: record via API then search and find it", async () => {
517
+ await initLoam();
518
+ const addProc = Bun.spawn(["lm", "add", "roundtrip"], {
519
+ cwd: tempDir,
520
+ stdout: "pipe",
521
+ stderr: "pipe",
522
+ });
523
+ await addProc.exited;
524
+
525
+ const client = createLoamClient(tempDir);
526
+ await client.record("roundtrip", {
527
+ type: "convention",
528
+ description: "unique-roundtrip-marker-xyz",
529
+ });
530
+
531
+ const result = await client.search("unique-roundtrip-marker-xyz");
532
+ expect(typeof result).toBe("string");
533
+ expect(result).toContain("unique-roundtrip-marker-xyz");
534
+ });
535
+ });
536
+
537
+ describe("diff", () => {
538
+ test.skipIf(!hasLoam)("shows expertise changes", async () => {
539
+ await initGit();
540
+ await initLoam();
541
+ const client = createLoamClient(tempDir);
542
+ const result = await client.diff();
543
+ expect(result).toHaveProperty("success");
544
+ expect(result).toHaveProperty("command");
545
+ expect(result).toHaveProperty("domains");
546
+ expect(Array.isArray(result.domains)).toBe(true);
547
+ });
548
+
549
+ test.skipIf(!hasLoam)("passes --since flag", async () => {
550
+ await initGit();
551
+ await initLoam();
552
+ const client = createLoamClient(tempDir);
553
+ const result = await client.diff({ since: "HEAD~5" });
554
+ expect(result).toHaveProperty("success");
555
+ expect(result).toHaveProperty("since");
556
+ });
557
+ });
558
+
559
+ describe("learn", () => {
560
+ test.skipIf(!hasLoam)("suggests domains for learnings", async () => {
561
+ await initGit();
562
+ await initLoam();
563
+ const client = createLoamClient(tempDir);
564
+ const result = await client.learn();
565
+ expect(result).toHaveProperty("success");
566
+ expect(result).toHaveProperty("command");
567
+ expect(result).toHaveProperty("changedFiles");
568
+ expect(Array.isArray(result.changedFiles)).toBe(true);
569
+ });
570
+
571
+ test.skipIf(!hasLoam)("passes --since flag", async () => {
572
+ await initGit();
573
+ await initLoam();
574
+ const client = createLoamClient(tempDir);
575
+ const result = await client.learn({ since: "HEAD~3" });
576
+ expect(result).toHaveProperty("success");
577
+ expect(result).toHaveProperty("changedFiles");
578
+ });
579
+ });
580
+
581
+ describe("prune", () => {
582
+ test.skipIf(!hasLoam)("prunes records", async () => {
583
+ await initLoam();
584
+ const client = createLoamClient(tempDir);
585
+ const result = await client.prune();
586
+ expect(result).toHaveProperty("success");
587
+ expect(result).toHaveProperty("command");
588
+ expect(result).toHaveProperty("totalPruned");
589
+ });
590
+
591
+ test.skipIf(!hasLoam)("supports --dry-run flag", async () => {
592
+ await initLoam();
593
+ const client = createLoamClient(tempDir);
594
+ const result = await client.prune({ dryRun: true });
595
+ expect(result).toHaveProperty("success");
596
+ expect(result).toHaveProperty("dryRun");
597
+ expect(result.dryRun).toBe(true);
598
+ });
599
+ });
600
+
601
+ describe("doctor", () => {
602
+ test.skipIf(!hasLoam)("runs health checks", async () => {
603
+ await initLoam();
604
+ const client = createLoamClient(tempDir);
605
+ const result = await client.doctor();
606
+ expect(result).toHaveProperty("success");
607
+ expect(result).toHaveProperty("command");
608
+ expect(result).toHaveProperty("checks");
609
+ expect(Array.isArray(result.checks)).toBe(true);
610
+ });
611
+
612
+ test.skipIf(!hasLoam)("passes --fix flag", async () => {
613
+ await initLoam();
614
+ const client = createLoamClient(tempDir);
615
+ const result = await client.doctor({ fix: true });
616
+ expect(result).toHaveProperty("success");
617
+ expect(result).toHaveProperty("checks");
618
+ });
619
+ });
620
+
621
+ describe("ready", () => {
622
+ test.skipIf(!hasLoam)("shows recently updated records", async () => {
623
+ await initLoam();
624
+ const client = createLoamClient(tempDir);
625
+ const result = await client.ready();
626
+ expect(result).toHaveProperty("success");
627
+ expect(result).toHaveProperty("command");
628
+ expect(result).toHaveProperty("entries");
629
+ expect(Array.isArray(result.entries)).toBe(true);
630
+ });
631
+
632
+ test.skipIf(!hasLoam)("passes --limit flag", async () => {
633
+ await initLoam();
634
+ const client = createLoamClient(tempDir);
635
+ const result = await client.ready({ limit: 5 });
636
+ expect(result).toHaveProperty("success");
637
+ expect(result).toHaveProperty("count");
638
+ });
639
+
640
+ test.skipIf(!hasLoam)("passes --domain flag", async () => {
641
+ await initLoam();
642
+ const addProc = Bun.spawn(["lm", "add", "testing"], {
643
+ cwd: tempDir,
644
+ stdout: "pipe",
645
+ stderr: "pipe",
646
+ });
647
+ await addProc.exited;
648
+
649
+ const client = createLoamClient(tempDir);
650
+ const result = await client.ready({ domain: "testing" });
651
+ expect(result).toHaveProperty("success");
652
+ expect(result).toHaveProperty("entries");
653
+ });
654
+
655
+ test.skipIf(!hasLoam)("passes --since flag", async () => {
656
+ await initLoam();
657
+ const client = createLoamClient(tempDir);
658
+ const result = await client.ready({ since: "7d" });
659
+ expect(result).toHaveProperty("success");
660
+ expect(result).toHaveProperty("entries");
661
+ });
662
+ });
663
+
664
+ describe("compact", () => {
665
+ test.skipIf(!hasLoam)("runs with --analyze flag", async () => {
666
+ await initLoam();
667
+ const client = createLoamClient(tempDir);
668
+ const result = await client.compact(undefined, { analyze: true });
669
+ expect(result).toHaveProperty("success");
670
+ expect(result).toHaveProperty("command");
671
+ expect(result).toHaveProperty("action");
672
+ });
673
+
674
+ test.skipIf(!hasLoam)("compacts specific domain with --analyze", async () => {
675
+ await initLoam();
676
+ const addProc = Bun.spawn(["lm", "add", "large"], {
677
+ cwd: tempDir,
678
+ stdout: "pipe",
679
+ stderr: "pipe",
680
+ });
681
+ await addProc.exited;
682
+
683
+ const client = createLoamClient(tempDir);
684
+ const result = await client.compact("large", { analyze: true });
685
+ expect(result).toHaveProperty("success");
686
+ expect(result).toHaveProperty("action");
687
+ });
688
+
689
+ test.skipIf(!hasLoam)("passes --auto with --dry-run flags", async () => {
690
+ await initLoam();
691
+ const client = createLoamClient(tempDir);
692
+ const result = await client.compact(undefined, { auto: true, dryRun: true });
693
+ expect(result).toHaveProperty("success");
694
+ expect(result).toHaveProperty("command");
695
+ });
696
+
697
+ test.skipIf(!hasLoam)("passes multiple options", async () => {
698
+ await initLoam();
699
+ const client = createLoamClient(tempDir);
700
+ const result = await client.compact(undefined, {
701
+ auto: true,
702
+ dryRun: true,
703
+ minGroup: 3,
704
+ maxRecords: 20,
705
+ });
706
+ expect(result).toHaveProperty("success");
707
+ expect(result).toHaveProperty("command");
708
+ });
709
+ });
710
+
711
+ describe("error handling", () => {
712
+ test.skipIf(!hasLoam)("throws AgentError when loam command fails", async () => {
713
+ // Don't init loam - operations will fail with "not initialized" error
714
+ const client = createLoamClient(tempDir);
715
+ await expect(client.status()).rejects.toThrow(AgentError);
716
+ });
717
+
718
+ test.skipIf(!hasLoam)("AgentError message contains exit code", async () => {
719
+ const client = createLoamClient(tempDir);
720
+ try {
721
+ await client.status();
722
+ expect.unreachable("Should have thrown AgentError");
723
+ } catch (error) {
724
+ expect(error).toBeInstanceOf(AgentError);
725
+ const agentError = error as AgentError;
726
+ expect(agentError.message).toContain("exit");
727
+ expect(agentError.message).toContain("status");
728
+ }
729
+ });
730
+
731
+ test.skipIf(!hasLoam)("record auto-creates missing domain", async () => {
732
+ await initLoam();
733
+ const client = createLoamClient(tempDir);
734
+ // loam 0.6.1+ auto-creates missing domains instead of erroring
735
+ await expect(
736
+ client.record("nonexistent-domain", {
737
+ type: "convention",
738
+ description: "test",
739
+ }),
740
+ ).resolves.toBeUndefined();
741
+ });
742
+
743
+ test.skipIf(!hasLoam)("handles empty status output correctly", async () => {
744
+ await initLoam();
745
+ const client = createLoamClient(tempDir);
746
+ const result = await client.status();
747
+ // With no domains, should have empty array (not throw)
748
+ expect(result).toHaveProperty("domains");
749
+ expect(result.domains).toEqual([]);
750
+ });
751
+ });
752
+ });