@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,393 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { appendFile, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { getRegistry, type TypeDefinition } from "../registry/type-registry.ts";
4
+ import type { Classification, ExpertiseRecord } from "../schemas/record.ts";
5
+ import { DEFAULT_BM25_PARAMS, searchBM25 } from "./bm25.ts";
6
+ import { isAllowUnknownTypes } from "./runtime-flags.ts";
7
+ import { applyConfirmationBoost } from "./scoring.ts";
8
+
9
+ export interface ReadExpertiseFileOptions {
10
+ // When true, on-disk records whose type is not in the registry are passed
11
+ // through unchanged instead of throwing. Defaults to the process-wide
12
+ // runtime flag (set via --allow-unknown-types).
13
+ allowUnknownTypes?: boolean;
14
+ }
15
+
16
+ /**
17
+ * Rewrite legacy field names to their canonical names per a type's aliases map.
18
+ * Mutates and returns the input. Idempotent: re-running on a record without
19
+ * legacy fields is a no-op. If both canonical and legacy are present, the
20
+ * canonical wins and the legacy field is dropped.
21
+ */
22
+ export function applyAliases(
23
+ raw: Record<string, unknown>,
24
+ aliases: Readonly<Record<string, readonly string[]>> | undefined,
25
+ ): Record<string, unknown> {
26
+ if (!aliases) return raw;
27
+ for (const [canonical, legacyNames] of Object.entries(aliases)) {
28
+ for (const legacy of legacyNames) {
29
+ if (!(legacy in raw)) continue;
30
+ if (
31
+ !(canonical in raw) ||
32
+ raw[canonical] === undefined ||
33
+ raw[canonical] === null ||
34
+ raw[canonical] === ""
35
+ ) {
36
+ raw[canonical] = raw[legacy];
37
+ }
38
+ delete raw[legacy];
39
+ }
40
+ }
41
+ return raw;
42
+ }
43
+
44
+ export async function readExpertiseFile(
45
+ filePath: string,
46
+ opts?: ReadExpertiseFileOptions,
47
+ ): Promise<ExpertiseRecord[]> {
48
+ let content: string;
49
+ try {
50
+ content = await readFile(filePath, "utf-8");
51
+ } catch {
52
+ return [];
53
+ }
54
+
55
+ const allowUnknown = opts?.allowUnknownTypes ?? isAllowUnknownTypes();
56
+ const registry = getRegistry();
57
+ const records: ExpertiseRecord[] = [];
58
+ const allLines = content.split("\n");
59
+ for (let i = 0; i < allLines.length; i++) {
60
+ const line = allLines[i] ?? "";
61
+ const trimmed = line.trim();
62
+ if (trimmed.length === 0) continue;
63
+ // Skip comment lines (used by archive-file banners).
64
+ if (trimmed.startsWith("#")) continue;
65
+ let raw: Record<string, unknown>;
66
+ try {
67
+ raw = JSON.parse(line) as Record<string, unknown>;
68
+ } catch (err) {
69
+ // Without context the bare "Unexpected token …" from V8/JSC is unhelpful
70
+ // — point the operator at the exact file:line, which matters most for
71
+ // archive files that callers rarely open directly.
72
+ const reason = err instanceof Error ? err.message : String(err);
73
+ const preview = trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
74
+ throw new Error(`Malformed JSONL at ${filePath}:${i + 1}: ${reason}. Line: ${preview}`);
75
+ }
76
+ // Normalize legacy outcome (singular) to outcomes (array) for backward compat
77
+ if (
78
+ "outcome" in raw &&
79
+ raw.outcome !== null &&
80
+ raw.outcome !== undefined &&
81
+ !("outcomes" in raw)
82
+ ) {
83
+ const legacy = raw.outcome as Record<string, unknown>;
84
+ raw.outcomes = [
85
+ {
86
+ status: legacy.status,
87
+ ...(legacy.duration !== undefined ? { duration: legacy.duration } : {}),
88
+ ...(legacy.test_results !== undefined ? { test_results: legacy.test_results } : {}),
89
+ ...(legacy.agent !== undefined ? { agent: legacy.agent } : {}),
90
+ },
91
+ ];
92
+ raw.outcome = undefined;
93
+ }
94
+
95
+ const typeName = typeof raw.type === "string" ? raw.type : undefined;
96
+ let def: TypeDefinition | undefined;
97
+ if (typeName) def = registry.get(typeName);
98
+
99
+ if (typeName && !def && !allowUnknown) {
100
+ const idPart = typeof raw.id === "string" ? ` (id=${raw.id})` : "";
101
+ throw new Error(
102
+ `Unknown record type "${typeName}" at ${filePath}:${i + 1}${idPart}. Register it under custom_types in loam.config.yaml, remove the record, or pass --allow-unknown-types to bypass.`,
103
+ );
104
+ }
105
+
106
+ if (def) applyAliases(raw, def.aliases);
107
+
108
+ records.push(raw as unknown as ExpertiseRecord);
109
+ }
110
+ return records;
111
+ }
112
+
113
+ export function generateRecordId(record: ExpertiseRecord): string {
114
+ const def = getRegistry().get(record.type);
115
+ if (!def) {
116
+ throw new Error(`Unknown record type: ${record.type}`);
117
+ }
118
+ const idValue = (record as unknown as Record<string, unknown>)[def.idKey];
119
+ const key = `${record.type}:${String(idValue ?? "")}`;
120
+ return `mx-${createHash("sha256").update(key).digest("hex").slice(0, 6)}`;
121
+ }
122
+
123
+ export async function appendRecord(filePath: string, record: ExpertiseRecord): Promise<void> {
124
+ if (!record.id) {
125
+ record.id = generateRecordId(record);
126
+ }
127
+ const line = `${JSON.stringify(record)}\n`;
128
+ await appendFile(filePath, line, "utf-8");
129
+ }
130
+
131
+ export async function createExpertiseFile(filePath: string): Promise<void> {
132
+ await writeFile(filePath, "", "utf-8");
133
+ }
134
+
135
+ export async function getFileModTime(filePath: string): Promise<Date | null> {
136
+ try {
137
+ const stats = await stat(filePath);
138
+ return stats.mtime;
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ export async function writeExpertiseFile(
145
+ filePath: string,
146
+ records: ExpertiseRecord[],
147
+ ): Promise<void> {
148
+ for (const r of records) {
149
+ if (!r.id) {
150
+ r.id = generateRecordId(r);
151
+ }
152
+ }
153
+ const content =
154
+ records.map((r) => JSON.stringify(r)).join("\n") + (records.length > 0 ? "\n" : "");
155
+ const tmpPath = `${filePath}.tmp.${randomBytes(8).toString("hex")}`;
156
+ await writeFile(tmpPath, content, "utf-8");
157
+ try {
158
+ await rename(tmpPath, filePath);
159
+ } catch (err) {
160
+ try {
161
+ await unlink(tmpPath);
162
+ } catch {
163
+ /* best-effort cleanup */
164
+ }
165
+ throw err;
166
+ }
167
+ }
168
+
169
+ export function countRecords(records: ExpertiseRecord[]): number {
170
+ return records.length;
171
+ }
172
+
173
+ export function filterByType(records: ExpertiseRecord[], type: string): ExpertiseRecord[] {
174
+ return records.filter((r) => r.type === type);
175
+ }
176
+
177
+ export function filterByClassification(
178
+ records: ExpertiseRecord[],
179
+ classification: string,
180
+ ): ExpertiseRecord[] {
181
+ return records.filter((r) => r.classification === classification);
182
+ }
183
+
184
+ export function filterByFile(records: ExpertiseRecord[], file: string): ExpertiseRecord[] {
185
+ const fileLower = file.toLowerCase();
186
+ return records.filter((r) => {
187
+ if ("files" in r && r.files) {
188
+ return r.files.some((f) => f.toLowerCase().includes(fileLower));
189
+ }
190
+ return false;
191
+ });
192
+ }
193
+
194
+ export function findDuplicate(
195
+ existing: ExpertiseRecord[],
196
+ newRecord: ExpertiseRecord,
197
+ ): { index: number; record: ExpertiseRecord } | null {
198
+ const registry = getRegistry();
199
+ const def = registry.get(newRecord.type);
200
+ if (!def) return null;
201
+ const dedupKey = def.dedupKey;
202
+ if (dedupKey === "content_hash") {
203
+ // Phase 2: content-hash dedup for custom types. No built-in uses this.
204
+ return null;
205
+ }
206
+ const newValue = (newRecord as unknown as Record<string, unknown>)[dedupKey];
207
+ for (const [i, record] of existing.entries()) {
208
+ if (record.type !== newRecord.type) continue;
209
+ const value = (record as unknown as Record<string, unknown>)[dedupKey];
210
+ if (value === newValue) {
211
+ return { index: i, record };
212
+ }
213
+ }
214
+ return null;
215
+ }
216
+
217
+ export type ResolveResult =
218
+ | { ok: true; index: number; record: ExpertiseRecord }
219
+ | { ok: false; error: string };
220
+
221
+ /**
222
+ * Resolve an identifier to a record within a domain.
223
+ * Accepts: full ID (mx-abc123), bare hash (abc123), or prefix (abc / mx-abc).
224
+ * Returns the unique matching record or an error if not found / ambiguous.
225
+ */
226
+ export function resolveRecordId(records: ExpertiseRecord[], identifier: string): ResolveResult {
227
+ // Normalize: strip mx- prefix if present to get the hash part
228
+ const hash = identifier.startsWith("mx-") ? identifier.slice(3) : identifier;
229
+
230
+ // Try exact match first
231
+ const exactIndex = records.findIndex((r) => r.id === `mx-${hash}`);
232
+ if (exactIndex !== -1) {
233
+ const exactRecord = records[exactIndex];
234
+ if (exactRecord) return { ok: true, index: exactIndex, record: exactRecord };
235
+ }
236
+
237
+ // Try prefix match
238
+ const matches: Array<{ index: number; record: ExpertiseRecord }> = [];
239
+ for (const [i, rec] of records.entries()) {
240
+ if (rec.id?.startsWith(`mx-${hash}`)) {
241
+ matches.push({ index: i, record: rec });
242
+ }
243
+ }
244
+
245
+ const [firstMatch] = matches;
246
+ if (matches.length === 1 && firstMatch) {
247
+ return { ok: true, index: firstMatch.index, record: firstMatch.record };
248
+ }
249
+
250
+ if (matches.length > 1) {
251
+ const ids = matches.map((m) => m.record.id).join(", ");
252
+ return {
253
+ ok: false,
254
+ error: `Ambiguous identifier "${identifier}" matches ${matches.length} records: ${ids}. Use more characters to disambiguate.`,
255
+ };
256
+ }
257
+
258
+ return {
259
+ ok: false,
260
+ error: `Record "${identifier}" not found. Run \`loam query\` to see record IDs.`,
261
+ };
262
+ }
263
+
264
+ export interface SearchRecordsOptions {
265
+ // Multiplier passed to applyConfirmationBoost. >0 reorders BM25 results
266
+ // so records with confirmed outcomes float up; 0 / undefined = pure BM25.
267
+ boostFactor?: number;
268
+ }
269
+
270
+ /**
271
+ * Search records using BM25 ranking algorithm.
272
+ * Returns records sorted by relevance (highest score first), optionally
273
+ * re-ranked by confirmation-frequency boost.
274
+ */
275
+ export function searchRecords(
276
+ records: ExpertiseRecord[],
277
+ query: string,
278
+ options: SearchRecordsOptions = {},
279
+ ): ExpertiseRecord[] {
280
+ const results = searchBM25(records, query, DEFAULT_BM25_PARAMS);
281
+ const factor = options.boostFactor ?? 0;
282
+ if (factor <= 0) {
283
+ return results.map((r) => r.record);
284
+ }
285
+ const boosted = results.map((r) => ({
286
+ record: r.record,
287
+ score: applyConfirmationBoost(r.score, r.record, factor),
288
+ }));
289
+ boosted.sort((a, b) => b.score - a.score);
290
+ return boosted.map((r) => r.record);
291
+ }
292
+
293
+ export interface DomainHealth {
294
+ governance_utilization: number;
295
+ stale_count: number;
296
+ type_distribution: Record<string, number>;
297
+ classification_distribution: Record<Classification, number>;
298
+ oldest_timestamp: string | null;
299
+ newest_timestamp: string | null;
300
+ }
301
+
302
+ /**
303
+ * Check if a record is stale based on classification and shelf life.
304
+ */
305
+ export function isRecordStale(
306
+ record: ExpertiseRecord,
307
+ now: Date,
308
+ shelfLife: { tactical: number; observational: number },
309
+ ): boolean {
310
+ const classification: Classification = record.classification;
311
+
312
+ if (classification === "foundational") {
313
+ return false;
314
+ }
315
+
316
+ const recordedAt = new Date(record.recorded_at);
317
+ const ageInDays = Math.floor((now.getTime() - recordedAt.getTime()) / (1000 * 60 * 60 * 24));
318
+
319
+ if (classification === "tactical") {
320
+ return ageInDays > shelfLife.tactical;
321
+ }
322
+
323
+ if (classification === "observational") {
324
+ return ageInDays > shelfLife.observational;
325
+ }
326
+
327
+ return false;
328
+ }
329
+
330
+ /**
331
+ * Calculate comprehensive health metrics for a domain.
332
+ */
333
+ export function calculateDomainHealth(
334
+ records: ExpertiseRecord[],
335
+ maxEntries: number,
336
+ shelfLife: { tactical: number; observational: number },
337
+ ): DomainHealth {
338
+ const now = new Date();
339
+
340
+ // Initialize distributions seeded from registry-known type names so custom
341
+ // types (Phase 2+) appear with zero counts when absent.
342
+ const typeDistribution: Record<string, number> = {};
343
+ for (const name of getRegistry().names()) {
344
+ typeDistribution[name] = 0;
345
+ }
346
+
347
+ const classificationDistribution: Record<Classification, number> = {
348
+ foundational: 0,
349
+ tactical: 0,
350
+ observational: 0,
351
+ };
352
+
353
+ let staleCount = 0;
354
+ let oldestTimestamp: string | null = null;
355
+ let newestTimestamp: string | null = null;
356
+
357
+ // Calculate metrics
358
+ for (const record of records) {
359
+ // Type distribution — `??` guards against records of types not in the
360
+ // registry (e.g., Phase 2's --allow-unknown-types escape hatch).
361
+ typeDistribution[record.type] = (typeDistribution[record.type] ?? 0) + 1;
362
+
363
+ // Classification distribution
364
+ classificationDistribution[record.classification]++;
365
+
366
+ // Stale count
367
+ if (isRecordStale(record, now, shelfLife)) {
368
+ staleCount++;
369
+ }
370
+
371
+ // Oldest/newest timestamps
372
+ const recordedAt = record.recorded_at;
373
+ if (!oldestTimestamp || recordedAt < oldestTimestamp) {
374
+ oldestTimestamp = recordedAt;
375
+ }
376
+ if (!newestTimestamp || recordedAt > newestTimestamp) {
377
+ newestTimestamp = recordedAt;
378
+ }
379
+ }
380
+
381
+ // Governance utilization (as percentage, 0-100)
382
+ const governanceUtilization =
383
+ maxEntries > 0 ? Math.round((records.length / maxEntries) * 100) : 0;
384
+
385
+ return {
386
+ governance_utilization: governanceUtilization,
387
+ stale_count: staleCount,
388
+ type_distribution: typeDistribution,
389
+ classification_distribution: classificationDistribution,
390
+ oldest_timestamp: oldestTimestamp,
391
+ newest_timestamp: newestTimestamp,
392
+ };
393
+ }
@@ -0,0 +1,96 @@
1
+ import type { ConventionRecord, ExpertiseRecord, Outcome } from "../schemas/record.ts";
2
+ import { computeConfirmationScore } from "./scoring.ts";
3
+
4
+ export function formatTimeAgo(date: Date): string {
5
+ const now = new Date();
6
+ const diffMs = now.getTime() - date.getTime();
7
+ const diffMins = Math.floor(diffMs / 60000);
8
+ const diffHours = Math.floor(diffMs / 3600000);
9
+ const diffDays = Math.floor(diffMs / 86400000);
10
+
11
+ if (diffMins < 1) return "just now";
12
+ if (diffMins < 60) return `${diffMins}m ago`;
13
+ if (diffHours < 24) return `${diffHours}h ago`;
14
+ return `${diffDays}d ago`;
15
+ }
16
+
17
+ export function formatEvidence(evidence: ConventionRecord["evidence"]): string {
18
+ if (!evidence) return "";
19
+ const parts: string[] = [];
20
+ if (evidence.commit) parts.push(`commit: ${evidence.commit}`);
21
+ if (evidence.date) parts.push(`date: ${evidence.date}`);
22
+ if (evidence.issue) parts.push(`issue: ${evidence.issue}`);
23
+ if (evidence.file) parts.push(`file: ${evidence.file}`);
24
+ return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
25
+ }
26
+
27
+ export function formatOutcome(outcomes: Outcome[] | undefined): string {
28
+ if (!outcomes || outcomes.length === 0) return "";
29
+ const latest = outcomes.at(-1);
30
+ if (!latest) return "";
31
+ const statusSymbol = latest.status === "success" ? "✓" : latest.status === "partial" ? "~" : "✗";
32
+ const parts: string[] = [statusSymbol];
33
+ if (latest.duration !== undefined) parts.push(`${latest.duration}ms`);
34
+ if (latest.agent) parts.push(`@${latest.agent}`);
35
+ if (outcomes.length > 1) parts.push(`(${outcomes.length}x)`);
36
+ return ` [${parts.join(" ")}]`;
37
+ }
38
+
39
+ export function formatLinks(r: ExpertiseRecord): string {
40
+ const parts: string[] = [];
41
+ if (r.relates_to && r.relates_to.length > 0) {
42
+ parts.push(`relates to: ${r.relates_to.join(", ")}`);
43
+ }
44
+ if (r.supersedes && r.supersedes.length > 0) {
45
+ parts.push(`supersedes: ${r.supersedes.join(", ")}`);
46
+ }
47
+ return parts.length > 0 ? ` [${parts.join("; ")}]` : "";
48
+ }
49
+
50
+ export function formatRecordMeta(r: ExpertiseRecord, full: boolean): string {
51
+ if (!full) return formatLinks(r);
52
+ const parts = [`(${r.classification})${formatEvidence(r.evidence)}`];
53
+ if (r.tags && r.tags.length > 0) {
54
+ parts.push(`[tags: ${r.tags.join(", ")}]`);
55
+ }
56
+ return ` ${parts.join(" ")}${formatLinks(r)}`;
57
+ }
58
+
59
+ export function idTag(r: ExpertiseRecord): string {
60
+ return r.id ? `[${r.id}] ` : "";
61
+ }
62
+
63
+ export function truncate(text: string, maxLen = 100): string {
64
+ if (text.length <= maxLen) return text;
65
+ const sentenceEnd = text.search(/[.!?]\s/);
66
+ if (sentenceEnd > 0 && sentenceEnd < maxLen) {
67
+ return text.slice(0, sentenceEnd + 1);
68
+ }
69
+ return `${text.slice(0, maxLen)}...`;
70
+ }
71
+
72
+ function formatClassificationAge(r: ExpertiseRecord): string {
73
+ const c = r.classification;
74
+ if (c === "foundational") return c;
75
+ const age = formatTimeAgo(new Date(r.recorded_at));
76
+ return `${c} ${age}`;
77
+ }
78
+
79
+ export function compactMeta(r: ExpertiseRecord): string {
80
+ const parts: string[] = [];
81
+ if (r.id) parts.push(r.id);
82
+ parts.push(formatClassificationAge(r));
83
+ const score = computeConfirmationScore(r);
84
+ if (score > 0) {
85
+ parts.push(Number.isInteger(score) ? `★${score}` : `★${score.toFixed(1)}`);
86
+ }
87
+ return ` (${parts.join(", ")})`;
88
+ }
89
+
90
+ export function xmlEscape(str: string): string {
91
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
92
+ }
93
+
94
+ export function xmlAttrEscape(str: string): string {
95
+ return xmlEscape(str).replace(/"/g, "&quot;");
96
+ }