@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,48 @@
1
+ const MARKER_START = "<!-- loam:start -->";
2
+ const MARKER_END = "<!-- loam:end -->";
3
+
4
+ export { MARKER_START, MARKER_END };
5
+
6
+ /**
7
+ * Check whether content contains the loam marker section.
8
+ */
9
+ export function hasMarkerSection(content: string): boolean {
10
+ return content.includes(MARKER_START);
11
+ }
12
+
13
+ /**
14
+ * Replace the marker-bounded section with new content.
15
+ * Returns null if no markers found.
16
+ */
17
+ export function replaceMarkerSection(content: string, newSection: string): string | null {
18
+ const startIdx = content.indexOf(MARKER_START);
19
+ const endIdx = content.indexOf(MARKER_END);
20
+ if (startIdx === -1 || endIdx === -1) return null;
21
+
22
+ const before = content.substring(0, startIdx);
23
+ const after = content.substring(endIdx + MARKER_END.length);
24
+
25
+ return before + newSection + after;
26
+ }
27
+
28
+ /**
29
+ * Remove the marker-bounded section entirely.
30
+ * Cleans up extra newlines left behind.
31
+ */
32
+ export function removeMarkerSection(content: string): string {
33
+ const startIdx = content.indexOf(MARKER_START);
34
+ const endIdx = content.indexOf(MARKER_END);
35
+ if (startIdx === -1 || endIdx === -1) return content;
36
+
37
+ const before = content.substring(0, startIdx);
38
+ const after = content.substring(endIdx + MARKER_END.length);
39
+
40
+ return `${(before + after).replace(/\n{3,}/g, "\n\n").trim()}\n`;
41
+ }
42
+
43
+ /**
44
+ * Wrap a snippet in loam markers.
45
+ */
46
+ export function wrapInMarkers(snippet: string): string {
47
+ return `${MARKER_START}\n${snippet}${MARKER_END}`;
48
+ }
@@ -0,0 +1,20 @@
1
+ // Strict numeric-flag parsers. `Number.parseFloat("10abc")` silently strips
2
+ // trailing garbage and `Number.parseInt("3.7", 10)` truncates to 3 — both
3
+ // would let typos through and write nonsense (NaN, partial values) to
4
+ // .loam/expertise/*.jsonl. Use a regex gate + Number() to reject anything
5
+ // that isn't a clean numeric literal. Convention: mx-5b9578.
6
+
7
+ const POSITIVE_INT_RE = /^\d+$/;
8
+ const NON_NEGATIVE_NUMBER_RE = /^\d+(\.\d+)?$/;
9
+
10
+ export function parseStrictPositiveInt(raw: string): number | null {
11
+ if (!POSITIVE_INT_RE.test(raw)) return null;
12
+ const n = Number(raw);
13
+ return Number.isFinite(n) && n >= 1 ? n : null;
14
+ }
15
+
16
+ export function parseStrictNonNegativeNumber(raw: string): number | null {
17
+ if (!NON_NEGATIVE_NUMBER_RE.test(raw)) return null;
18
+ const n = Number(raw);
19
+ return Number.isFinite(n) && n >= 0 ? n : null;
20
+ }
@@ -0,0 +1,44 @@
1
+ import chalk from "chalk";
2
+
3
+ // Loam brand color: brown / soil
4
+ export const brand = chalk.rgb(139, 90, 43);
5
+
6
+ // Shared semantic colors
7
+ export const accent = chalk.rgb(255, 183, 77); // amber — IDs, references
8
+ export const muted = chalk.rgb(120, 120, 110); // stone gray — metadata
9
+
10
+ // Status icons (Set D — minimal, maximum terminal compatibility)
11
+ export const icons = {
12
+ pass: brand("✓"), // success / pass
13
+ warn: chalk.yellow("!"), // warning / blocked
14
+ fail: chalk.red("✗"), // error / fail
15
+ open: brand("-"), // open / pending
16
+ active: chalk.cyan(">"), // in_progress / active
17
+ done: chalk.dim("x"), // closed / done
18
+ } as const;
19
+
20
+ // Quiet mode state
21
+ let _quiet = false;
22
+
23
+ export function setQuiet(v: boolean): void {
24
+ _quiet = v;
25
+ }
26
+
27
+ export function isQuiet(): boolean {
28
+ return _quiet;
29
+ }
30
+
31
+ // Message formatters — mirroring visual-spec.md patterns
32
+ export function printSuccess(msg: string): void {
33
+ if (_quiet) return;
34
+ console.log(`${icons.pass} ${brand(msg)}`);
35
+ }
36
+
37
+ export function printError(msg: string): void {
38
+ console.error(`${icons.fail} ${chalk.red(msg)}`);
39
+ }
40
+
41
+ export function printWarning(msg: string): void {
42
+ if (_quiet) return;
43
+ console.log(`${icons.warn} ${chalk.yellow(msg)}`);
44
+ }
@@ -0,0 +1,135 @@
1
+ import { DEFAULT_PRIME_TIER_WEIGHTS, type PrimeTierWeights } from "../schemas/config.ts";
2
+ import type { ExpertiseRecord } from "../schemas/record.ts";
3
+ import { TRACKERS, type TrackerName } from "./active-work.ts";
4
+ import { fileLivesUnderDir } from "./dir-anchors.ts";
5
+ import type { ActiveContext } from "./git.ts";
6
+ import { fileMatchesAny } from "./git.ts";
7
+ import { computeConfirmationScore } from "./scoring.ts";
8
+
9
+ // Trust-tier ranking for `lm prime` full-mode output (v0.10 slice 3 of the
10
+ // prime overhaul). The corpus is ordered so the most trustworthy records
11
+ // surface first within the budget cap: star-confirmed > foundational >
12
+ // tactical > observational. Within-tier ties preserve insertion order so a
13
+ // stable run-to-run shape lets agents notice when the top of the list shifts.
14
+
15
+ export type ResolvedTierWeights = Required<PrimeTierWeights>;
16
+
17
+ export function resolveTierWeights(override?: PrimeTierWeights): ResolvedTierWeights {
18
+ return { ...DEFAULT_PRIME_TIER_WEIGHTS, ...(override ?? {}) };
19
+ }
20
+
21
+ export function computeTrustScore(r: ExpertiseRecord, weights: ResolvedTierWeights): number {
22
+ const stars = computeConfirmationScore(r);
23
+ const tier = weights[r.classification] ?? 0;
24
+ return stars * weights.star + tier;
25
+ }
26
+
27
+ export function sortByTrust<T extends ExpertiseRecord>(
28
+ records: T[],
29
+ weights: ResolvedTierWeights,
30
+ ): T[] {
31
+ // Stable sort: equal scores keep insertion order so within-tier output is
32
+ // deterministic. Array.prototype.sort is stable in Node ≥ 12 but the
33
+ // index decorator makes the contract explicit and survives engine quirks.
34
+ const decorated = records.map((r, i) => ({
35
+ r,
36
+ i,
37
+ score: computeTrustScore(r, weights),
38
+ }));
39
+ decorated.sort((a, b) => b.score - a.score || a.i - b.i);
40
+ return decorated.map((d) => d.r);
41
+ }
42
+
43
+ // "Why surfaced now" reasons — one per record, picked in priority order so the
44
+ // strongest signal wins. The agent-facing suffix shows the picked reason; the
45
+ // structured shape stays in the type system for future consumers (warren UI,
46
+ // audit reports).
47
+ export type SurfaceReason =
48
+ | { kind: "file_match"; anchor: string }
49
+ | { kind: "tracker_match"; tracker: TrackerName; id: string }
50
+ | { kind: "stars"; count: number }
51
+ | { kind: "recent"; daysAgo: number }
52
+ | { kind: "universal" };
53
+
54
+ // Recency window for the "recent authorship" reason. Aligned with the
55
+ // tactical shelf-life floor so newly-recorded conventions get a why-surfaced
56
+ // hint even before they accumulate confirmations.
57
+ export const RECENT_AUTHORSHIP_DAYS = 7;
58
+
59
+ export function whySurfaced(r: ExpertiseRecord, ctx: ActiveContext | null): SurfaceReason {
60
+ if (ctx) {
61
+ const files = "files" in r && Array.isArray(r.files) ? r.files : undefined;
62
+ if (files && files.length > 0) {
63
+ for (const f of files) {
64
+ if (fileMatchesAny(f, ctx.changedFiles)) {
65
+ return { kind: "file_match", anchor: f };
66
+ }
67
+ }
68
+ }
69
+ if (Array.isArray(r.dir_anchors) && r.dir_anchors.length > 0) {
70
+ for (const d of r.dir_anchors) {
71
+ for (const cf of ctx.changedFiles) {
72
+ if (fileLivesUnderDir(cf, d)) {
73
+ return { kind: "file_match", anchor: d };
74
+ }
75
+ }
76
+ }
77
+ }
78
+ for (const t of TRACKERS) {
79
+ const active = ctx.trackers[t];
80
+ const evValue = r.evidence?.[t];
81
+ if (
82
+ typeof active === "string" &&
83
+ active !== "" &&
84
+ typeof evValue === "string" &&
85
+ active === evValue
86
+ ) {
87
+ return { kind: "tracker_match", tracker: t, id: active };
88
+ }
89
+ }
90
+ }
91
+ const stars = computeConfirmationScore(r);
92
+ if (stars > 0) return { kind: "stars", count: stars };
93
+ const recordedAt = new Date(r.recorded_at);
94
+ if (!Number.isNaN(recordedAt.getTime())) {
95
+ const days = Math.floor((Date.now() - recordedAt.getTime()) / (1000 * 60 * 60 * 24));
96
+ if (days < RECENT_AUTHORSHIP_DAYS) {
97
+ return { kind: "recent", daysAgo: Math.max(0, days) };
98
+ }
99
+ }
100
+ return { kind: "universal" };
101
+ }
102
+
103
+ export function formatSurfaceReason(reason: SurfaceReason): string {
104
+ switch (reason.kind) {
105
+ case "file_match":
106
+ return `why: file match (${reason.anchor})`;
107
+ case "tracker_match":
108
+ return `why: in-progress ${reason.tracker}:${reason.id}`;
109
+ case "stars": {
110
+ const formatted = Number.isInteger(reason.count)
111
+ ? `★${reason.count}`
112
+ : `★${reason.count.toFixed(1)}`;
113
+ return `why: ${formatted} confirmations`;
114
+ }
115
+ case "recent":
116
+ return reason.daysAgo === 0 ? "why: recorded today" : `why: recorded ${reason.daysAgo}d ago`;
117
+ case "universal":
118
+ return "why: applies broadly (no anchors)";
119
+ }
120
+ }
121
+
122
+ // Build a per-record-id annotation map for downstream formatters. Records
123
+ // without an id are skipped — pre-id corpora exist in older projects and the
124
+ // formatters key on id-bearing lines.
125
+ export function buildSurfaceAnnotations(
126
+ records: ExpertiseRecord[],
127
+ ctx: ActiveContext | null,
128
+ ): Map<string, string> {
129
+ const map = new Map<string, string>();
130
+ for (const r of records) {
131
+ if (!r.id) continue;
132
+ map.set(r.id, formatSurfaceReason(whySurfaced(r, ctx)));
133
+ }
134
+ return map;
135
+ }
@@ -0,0 +1,195 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
4
+ import { createRequire } from "node:module";
5
+ import { join } from "node:path";
6
+
7
+ export interface RecipeResult {
8
+ success: boolean;
9
+ message: string;
10
+ }
11
+
12
+ export interface ProviderRecipe {
13
+ install(cwd: string): Promise<RecipeResult>;
14
+ check(cwd: string): Promise<RecipeResult>;
15
+ remove(cwd: string): Promise<RecipeResult>;
16
+ }
17
+
18
+ export type RecipeSource = "builtin" | "filesystem-ts" | "filesystem-sh" | "npm";
19
+
20
+ export interface RecipeWithSource {
21
+ name: string;
22
+ source: RecipeSource;
23
+ recipe: ProviderRecipe;
24
+ path?: string;
25
+ }
26
+
27
+ export interface FilesystemRecipeListing {
28
+ name: string;
29
+ source: "filesystem-ts" | "filesystem-sh";
30
+ path: string;
31
+ }
32
+
33
+ export const NPM_RECIPE_PREFIX = "loam-recipe-";
34
+
35
+ export function recipesDir(cwd: string): string {
36
+ return join(cwd, ".loam", "recipes");
37
+ }
38
+
39
+ export function validateRecipeShape(obj: unknown): obj is ProviderRecipe {
40
+ if (!obj || typeof obj !== "object") return false;
41
+ const r = obj as Record<string, unknown>;
42
+ return (
43
+ typeof r.install === "function" &&
44
+ typeof r.check === "function" &&
45
+ typeof r.remove === "function"
46
+ );
47
+ }
48
+
49
+ function makeShellRecipe(name: string, scriptPath: string): ProviderRecipe {
50
+ const run = (action: "install" | "check" | "remove", cwd: string): Promise<RecipeResult> =>
51
+ new Promise((resolve) => {
52
+ const child = spawn(scriptPath, [action], {
53
+ cwd,
54
+ env: {
55
+ ...process.env,
56
+ LOAM_RECIPE_NAME: name,
57
+ LOAM_RECIPE_ACTION: action,
58
+ },
59
+ stdio: ["ignore", "pipe", "pipe"],
60
+ });
61
+ let stdout = "";
62
+ let stderr = "";
63
+ child.stdout?.on("data", (d: Buffer) => {
64
+ stdout += d.toString();
65
+ });
66
+ child.stderr?.on("data", (d: Buffer) => {
67
+ stderr += d.toString();
68
+ });
69
+ child.on("error", (err) => {
70
+ resolve({
71
+ success: false,
72
+ message: `Failed to execute ${scriptPath}: ${err.message}`,
73
+ });
74
+ });
75
+ child.on("close", (code) => {
76
+ const out = stdout.trim();
77
+ const err = stderr.trim();
78
+ const ok = code === 0;
79
+ const fallback = ok
80
+ ? `${name} ${action} succeeded`
81
+ : `${name} ${action} exited with code ${code ?? "?"}${err ? `: ${err}` : ""}`;
82
+ resolve({ success: ok, message: out || fallback });
83
+ });
84
+ });
85
+
86
+ return {
87
+ install: (cwd) => run("install", cwd),
88
+ check: (cwd) => run("check", cwd),
89
+ remove: (cwd) => run("remove", cwd),
90
+ };
91
+ }
92
+
93
+ export async function loadFilesystemRecipe(
94
+ name: string,
95
+ cwd: string,
96
+ ): Promise<RecipeWithSource | null> {
97
+ const dir = recipesDir(cwd);
98
+ const tsPath = join(dir, `${name}.ts`);
99
+ const shPath = join(dir, `${name}.sh`);
100
+
101
+ if (existsSync(tsPath)) {
102
+ const mod = (await import(tsPath)) as { default?: unknown };
103
+ // Spec is `export default { install, check, remove }`. Reject named-only
104
+ // exports — accepting them silently let users ship recipes that resolved
105
+ // in development (where they remembered the right named imports) but
106
+ // surprised the next consumer who imported them as a default.
107
+ if (mod.default === undefined) {
108
+ throw new Error(
109
+ `Recipe "${name}" at ${tsPath} has no default export. ProviderRecipes must be exposed via \`export default { install, check, remove }\`; named exports alone are not accepted. See examples/recipes/internal-ide.ts for a template.`,
110
+ );
111
+ }
112
+ if (!validateRecipeShape(mod.default)) {
113
+ throw new Error(
114
+ `Recipe "${name}" at ${tsPath} default export is not a valid ProviderRecipe (must have async install/check/remove functions). See examples/recipes/internal-ide.ts for a template.`,
115
+ );
116
+ }
117
+ return { name, source: "filesystem-ts", recipe: mod.default, path: tsPath };
118
+ }
119
+
120
+ if (existsSync(shPath)) {
121
+ return {
122
+ name,
123
+ source: "filesystem-sh",
124
+ recipe: makeShellRecipe(name, shPath),
125
+ path: shPath,
126
+ };
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ export async function loadNpmRecipe(name: string, cwd: string): Promise<RecipeWithSource | null> {
133
+ const pkgName = `${NPM_RECIPE_PREFIX}${name}`;
134
+ const requireFn = createRequire(import.meta.url);
135
+ let resolved: string;
136
+ try {
137
+ resolved = requireFn.resolve(pkgName, { paths: [cwd] });
138
+ } catch {
139
+ return null;
140
+ }
141
+ const mod = (await import(resolved)) as { default?: unknown };
142
+ if (mod.default === undefined) {
143
+ throw new Error(
144
+ `Recipe package "${pkgName}" has no default export. ProviderRecipes must be exposed via \`export default { install, check, remove }\`; named exports alone are not accepted.`,
145
+ );
146
+ }
147
+ if (!validateRecipeShape(mod.default)) {
148
+ throw new Error(
149
+ `Recipe package "${pkgName}" default export is not a valid ProviderRecipe (must have async install/check/remove functions).`,
150
+ );
151
+ }
152
+ return { name, source: "npm", recipe: mod.default, path: resolved };
153
+ }
154
+
155
+ export async function listFilesystemRecipes(cwd: string): Promise<FilesystemRecipeListing[]> {
156
+ const dir = recipesDir(cwd);
157
+ if (!existsSync(dir)) return [];
158
+ const entries = await readdir(dir);
159
+ const out: FilesystemRecipeListing[] = [];
160
+ for (const entry of entries) {
161
+ if (entry.endsWith(".ts")) {
162
+ out.push({
163
+ name: entry.slice(0, -3),
164
+ source: "filesystem-ts",
165
+ path: join(dir, entry),
166
+ });
167
+ } else if (entry.endsWith(".sh")) {
168
+ out.push({
169
+ name: entry.slice(0, -3),
170
+ source: "filesystem-sh",
171
+ path: join(dir, entry),
172
+ });
173
+ }
174
+ }
175
+ return out.sort((a, b) => a.name.localeCompare(b.name));
176
+ }
177
+
178
+ export async function resolveRecipe(
179
+ name: string,
180
+ cwd: string,
181
+ builtins: Record<string, ProviderRecipe>,
182
+ ): Promise<RecipeWithSource | null> {
183
+ const fs = await loadFilesystemRecipe(name, cwd);
184
+ if (fs) return fs;
185
+
186
+ const npm = await loadNpmRecipe(name, cwd);
187
+ if (npm) return npm;
188
+
189
+ const builtin = builtins[name];
190
+ if (builtin) {
191
+ return { name, source: "builtin", recipe: builtin };
192
+ }
193
+
194
+ return null;
195
+ }
@@ -0,0 +1,28 @@
1
+ // Process-wide runtime flags set during CLI bootstrap. Mirrors the
2
+ // setQuiet/isQuiet pattern in palette.ts so global CLI options can opt
3
+ // commands out of strict policies without threading the flag through
4
+ // every function signature.
5
+
6
+ let _allowUnknownTypes = false;
7
+ let _allowDomainMismatch = false;
8
+
9
+ export function setAllowUnknownTypes(value: boolean): void {
10
+ _allowUnknownTypes = value;
11
+ }
12
+
13
+ export function isAllowUnknownTypes(): boolean {
14
+ return _allowUnknownTypes;
15
+ }
16
+
17
+ export function setAllowDomainMismatch(value: boolean): void {
18
+ _allowDomainMismatch = value;
19
+ }
20
+
21
+ export function isAllowDomainMismatch(): boolean {
22
+ return _allowDomainMismatch;
23
+ }
24
+
25
+ export function resetRuntimeFlags(): void {
26
+ _allowUnknownTypes = false;
27
+ _allowDomainMismatch = false;
28
+ }
@@ -0,0 +1,94 @@
1
+ import type { ExpertiseRecord, Outcome } from "../schemas/record.ts";
2
+
3
+ export type { Outcome };
4
+
5
+ /**
6
+ * An ExpertiseRecord with outcome history for confirmation-frequency scoring.
7
+ * Since ExpertiseRecord now includes outcomes?: Outcome[], this is an alias.
8
+ */
9
+ export type ScoredRecord = ExpertiseRecord;
10
+
11
+ /**
12
+ * Returns the number of successful outcomes for a record.
13
+ * Successful outcomes indicate confirmed, working applications of the record.
14
+ */
15
+ export function getSuccessCount(record: ScoredRecord): number {
16
+ if (!record.outcomes || record.outcomes.length === 0) return 0;
17
+ return record.outcomes.filter((o) => o.status === "success").length;
18
+ }
19
+
20
+ /**
21
+ * Returns the number of failed outcomes for a record.
22
+ */
23
+ export function getFailureCount(record: ScoredRecord): number {
24
+ if (!record.outcomes || record.outcomes.length === 0) return 0;
25
+ return record.outcomes.filter((o) => o.status === "failure").length;
26
+ }
27
+
28
+ /**
29
+ * Returns the total number of recorded outcomes (applications) for a record.
30
+ */
31
+ export function getTotalApplications(record: ScoredRecord): number {
32
+ return record.outcomes?.length ?? 0;
33
+ }
34
+
35
+ /**
36
+ * Returns the success rate (0-1) for a record.
37
+ * Partial outcomes are counted as 0.5 (half success).
38
+ * Returns 0 for records with no outcomes.
39
+ */
40
+ export function getSuccessRate(record: ScoredRecord): number {
41
+ const total = getTotalApplications(record);
42
+ if (total === 0) return 0;
43
+ const partialCount = record.outcomes?.filter((o) => o.status === "partial").length ?? 0;
44
+ const successCount = getSuccessCount(record);
45
+ return (successCount + partialCount * 0.5) / total;
46
+ }
47
+
48
+ /**
49
+ * Computes the confirmation-frequency score for a record.
50
+ *
51
+ * The score is the count of successful confirmations (applications where
52
+ * the record's guidance was applied and the outcome was "success").
53
+ * Partial outcomes contribute 0.5 to the score.
54
+ *
55
+ * Records with no outcomes return 0.
56
+ * Records with only failures return 0.
57
+ */
58
+ export function computeConfirmationScore(record: ScoredRecord): number {
59
+ if (!record.outcomes || record.outcomes.length === 0) return 0;
60
+ const successCount = getSuccessCount(record);
61
+ const partialCount = record.outcomes.filter((o) => o.status === "partial").length;
62
+ return successCount + partialCount * 0.5;
63
+ }
64
+
65
+ /**
66
+ * Applies a confirmation-frequency boost to a base score (e.g., a BM25 relevance score).
67
+ *
68
+ * Records with no outcomes (score = 0) are returned unchanged.
69
+ * Records with confirmed applications receive a multiplicative boost proportional
70
+ * to their confirmation score.
71
+ *
72
+ * @param baseScore - The base relevance score (e.g., from BM25)
73
+ * @param record - The record to score
74
+ * @param boostFactor - Multiplier controlling boost magnitude (default: 0.1)
75
+ * @returns The boosted score
76
+ */
77
+ export function applyConfirmationBoost(
78
+ baseScore: number,
79
+ record: ScoredRecord,
80
+ boostFactor = 0.1,
81
+ ): number {
82
+ const confirmationScore = computeConfirmationScore(record);
83
+ if (confirmationScore === 0) return baseScore;
84
+ return baseScore * (1 + boostFactor * confirmationScore);
85
+ }
86
+
87
+ /**
88
+ * Sorts records by confirmation-frequency score, highest first.
89
+ * Records with equal scores maintain their original relative order (stable sort).
90
+ * Records with no outcomes (score = 0) sort to the end.
91
+ */
92
+ export function sortByConfirmationScore<T extends ScoredRecord>(records: T[]): T[] {
93
+ return [...records].sort((a, b) => computeConfirmationScore(b) - computeConfirmationScore(a));
94
+ }
@@ -0,0 +1,116 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { VERSION } from "../../../version.ts";
3
+
4
+ /**
5
+ * Current CLI version. loam is now bundled in @ag-eco/agentplate-cli, so the
6
+ * version comes from the shared package version (src/version.ts) rather than a
7
+ * package.json read relative to this file.
8
+ */
9
+ export function getCurrentVersion(): string {
10
+ return VERSION;
11
+ }
12
+
13
+ /**
14
+ * Fetch the latest published version of loam-cli from npm.
15
+ * Returns null if the registry is unreachable.
16
+ */
17
+ export function getLatestVersion(): string | null {
18
+ try {
19
+ const result = execFileSync("npm", ["view", "@ag-eco/loam-cli", "version"], {
20
+ encoding: "utf-8",
21
+ timeout: 10000,
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ });
24
+ return result.trim();
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Split a semver string into its main (major.minor.patch) segment and its
32
+ * optional prerelease tail. Build metadata (after `+`) is ignored per semver
33
+ * 2.0.0 precedence rules.
34
+ */
35
+ function splitSemver(v: string): { main: string; prerelease: string | null } {
36
+ const noBuild = v.split("+", 1)[0] ?? v;
37
+ const dashIdx = noBuild.indexOf("-");
38
+ if (dashIdx === -1) return { main: noBuild, prerelease: null };
39
+ return {
40
+ main: noBuild.slice(0, dashIdx),
41
+ prerelease: noBuild.slice(dashIdx + 1),
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Parse a single main-version segment to a non-negative integer. Returns 0
47
+ * for empty / non-numeric input so e.g. `"1.2"` still compares cleanly
48
+ * against `"1.2.0"`.
49
+ */
50
+ function parseMainSegment(s: string | undefined): number {
51
+ if (!s) return 0;
52
+ const n = Number(s);
53
+ return Number.isFinite(n) && n >= 0 ? Math.trunc(n) : 0;
54
+ }
55
+
56
+ /**
57
+ * Compare two prerelease identifier lists per semver 2.0.0 §11.4:
58
+ * - identifiers are split on '.'
59
+ * - numeric identifiers compare numerically and rank below non-numeric
60
+ * - non-numeric identifiers compare lexically (ASCII)
61
+ * - a longer prerelease list wins when all leading identifiers match
62
+ */
63
+ function comparePrerelease(a: string, b: string): -1 | 0 | 1 {
64
+ const idsA = a.split(".");
65
+ const idsB = b.split(".");
66
+ const len = Math.max(idsA.length, idsB.length);
67
+ for (let i = 0; i < len; i++) {
68
+ const x = idsA[i];
69
+ const y = idsB[i];
70
+ if (x === undefined) return -1;
71
+ if (y === undefined) return 1;
72
+ const xNumeric = /^\d+$/.test(x);
73
+ const yNumeric = /^\d+$/.test(y);
74
+ if (xNumeric && yNumeric) {
75
+ const nx = Number(x);
76
+ const ny = Number(y);
77
+ if (nx < ny) return -1;
78
+ if (nx > ny) return 1;
79
+ continue;
80
+ }
81
+ if (xNumeric) return -1;
82
+ if (yNumeric) return 1;
83
+ if (x < y) return -1;
84
+ if (x > y) return 1;
85
+ }
86
+ return 0;
87
+ }
88
+
89
+ /**
90
+ * Compare two semver strings (major.minor.patch with optional prerelease).
91
+ * Returns -1 if a < b, 0 if equal, 1 if a > b.
92
+ *
93
+ * Handles prerelease / non-numeric segments per semver 2.0.0: a version with
94
+ * a prerelease tail (e.g. "1.2.3-beta") ranks below the same main version
95
+ * without one ("1.2.3"), and never causes a misleading 0 result when the
96
+ * main components differ.
97
+ */
98
+ export function compareSemver(a: string, b: string): -1 | 0 | 1 {
99
+ const sa = splitSemver(a);
100
+ const sb = splitSemver(b);
101
+ const partsA = sa.main.split(".");
102
+ const partsB = sb.main.split(".");
103
+
104
+ for (let i = 0; i < 3; i++) {
105
+ const segA = parseMainSegment(partsA[i]);
106
+ const segB = parseMainSegment(partsB[i]);
107
+ if (segA < segB) return -1;
108
+ if (segA > segB) return 1;
109
+ }
110
+
111
+ // Main parts equal — apply prerelease precedence.
112
+ if (sa.prerelease === null && sb.prerelease === null) return 0;
113
+ if (sa.prerelease === null) return 1; // a is release, b is prerelease
114
+ if (sb.prerelease === null) return -1;
115
+ return comparePrerelease(sa.prerelease, sb.prerelease);
116
+ }