@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,344 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import yaml from "js-yaml";
6
+ import type { DomainConfig, LoamConfig } from "../schemas/config.ts";
7
+ import { DEFAULT_CONFIG } from "../schemas/config.ts";
8
+ import { createExpertiseFile } from "./expertise.ts";
9
+
10
+ const LOAM_DIR = ".loam";
11
+ const CONFIG_FILE = "loam.config.yaml";
12
+ const EXPERTISE_DIR = "expertise";
13
+
14
+ export const GITATTRIBUTES_LINE = ".loam/expertise/*.jsonl merge=union";
15
+
16
+ const INIT_CONFIG_HEADER = `# Loam configuration. See https://github.com/hgoudat/loam for docs.
17
+ #
18
+ # Required fields below. Optional knobs are commented out at the bottom — uncomment
19
+ # to enable. Note: commands that mutate this file (e.g. \`lm add <domain>\`,
20
+ # \`lm record\` auto-creating a domain) rewrite it via the YAML serializer and
21
+ # strip these comments.
22
+
23
+ `;
24
+
25
+ const INIT_CONFIG_OPTIONAL_KNOBS = `
26
+ # ─── Optional knobs (uncomment to enable) ────────────────────────────────────
27
+ #
28
+ # prime:
29
+ # # Pin \`lm prime\`'s unscoped output shape. When unset (recommended), prime
30
+ # # auto-flips to manifest above 100 records or 5 domains and renders full
31
+ # # records otherwise. Set explicitly to override both directions:
32
+ # # - full → always emit full records (skip the auto-flip)
33
+ # # - manifest → always emit the domain index
34
+ # # --full / --manifest / scoping flags always override this on a per-call basis.
35
+ # default_mode: full # one of: full, manifest
36
+ # # Trust-tier ranking weights for full-mode output. Sort score per record =
37
+ # # (★ count * star) + classification weight. Override only the knobs you
38
+ # # want to retune; unset fields keep their default. Higher scores surface
39
+ # # first; within-tier ties preserve insertion order.
40
+ # tier_weights:
41
+ # star: 100 # multiplier on the ★ confirmation count
42
+ # foundational: 50 # base score for foundational records
43
+ # tactical: 20 # base score for tactical records
44
+ # observational: 10 # base score for observational records
45
+ #
46
+ # search:
47
+ # # Multiplier applied to BM25 scores so records with more confirmed outcomes
48
+ # # rank higher. 0 disables (pure BM25). Default 0.1: a record with N successes
49
+ # # gets a (1 + 0.1*N) boost. Override per-call with \`lm search --no-boost\`.
50
+ # boost_factor: 0.1
51
+ #
52
+ # disabled_types:
53
+ # # Names of registered types (built-in or custom) that emit a deprecation
54
+ # # warning on write. Reads still succeed; the type stays in CLI choices so
55
+ # # peers in shared domains don't hard-fail. Use to retire a type gracefully.
56
+ # - failure
57
+ #
58
+ # custom_types:
59
+ # # Project-specific record types. Required + optional fields, dedup_key,
60
+ # # and a summary template. See README for the full schema.
61
+ # hypothesis:
62
+ # required: [statement, prediction]
63
+ # optional: [evidence_files]
64
+ # dedup_key: statement
65
+ # summary: "{statement}" # tokens use single braces; {{statement}} also accepted
66
+ # # aliases: rename a field while still reading legacy on-disk records.
67
+ # # Map canonical (current) name → list of legacy names. At read time,
68
+ # # legacy fields are rewritten to canonical and dropped from the record.
69
+ # aliases:
70
+ # statement: [claim]
71
+ # # Inherit from a built-in type with extends:. Required/optional arrays
72
+ # # merge with the parent's (union); dedup_key, summary, compact, section_title,
73
+ # # extracts_files, and files_field override only when set, otherwise inherit.
74
+ # # Custom-from-custom is not supported in v1.
75
+ # adr:
76
+ # extends: decision
77
+ # required: [decision_status, deciders] # added on top of decision's [title, rationale]
78
+ # summary: "{decision_status}: {title}" # tokens must be declared fields (parent's included)
79
+ #
80
+ # hooks:
81
+ # # Lifecycle hook scripts. Each event maps to an ordered list of shell
82
+ # # commands. Loam invokes each with the relevant payload as JSON on stdin.
83
+ # # Exit 0 = continue. Non-zero from a \`pre-*\` hook blocks the action; from
84
+ # # a \`post-*\` hook emits a warning. \`pre-record\`, \`pre-prime\`, and
85
+ # # \`pre-compact\` may mutate the payload by printing modified JSON on
86
+ # # stdout; \`pre-prune\` is block-or-allow only — its stdout is ignored.
87
+ # #
88
+ # # pre-record: [./.loam/hooks/scan-secrets.sh, ./.loam/hooks/require-owner.sh]
89
+ # # post-record: [./.loam/hooks/post-to-slack.sh]
90
+ # # pre-prime: [./.loam/hooks/filter-by-team.sh]
91
+ # # pre-prune: [./.loam/hooks/digest-then-confirm.sh]
92
+ # # pre-compact: [./.loam/hooks/llm-summarize.sh]
93
+ #
94
+ # hook_settings:
95
+ # # Per-hook execution timeout in milliseconds. Default 5000.
96
+ # timeout_ms: 5000
97
+ `;
98
+
99
+ export function buildInitialConfigYaml(): string {
100
+ const body = yaml.dump(DEFAULT_CONFIG, { lineWidth: -1 });
101
+ return INIT_CONFIG_HEADER + body + INIT_CONFIG_OPTIONAL_KNOBS;
102
+ }
103
+
104
+ export const LOAM_README = `# .loam/
105
+
106
+ This directory is managed by [loam](https://github.com/hgoudat/loam) — a structured expertise layer for coding agents.
107
+
108
+ ## Key Commands
109
+
110
+ - \`lm init\` — Initialize a .loam directory
111
+ - \`lm add\` — Add a new domain
112
+ - \`lm record\` — Record an expertise record
113
+ - \`lm edit\` — Edit an existing record
114
+ - \`lm query\` — Query expertise records
115
+ - \`lm prime [domain]\` — Output a priming prompt (optionally scoped to one domain)
116
+ - \`lm search\` — Search records across domains
117
+ - \`lm status\` — Show domain statistics
118
+ - \`lm validate\` — Validate all records against the schema
119
+ - \`lm prune\` — Remove expired records
120
+
121
+ ## Structure
122
+
123
+ - \`loam.config.yaml\` — Configuration file
124
+ - \`expertise/\` — JSONL files, one per domain
125
+
126
+ ## Configuration
127
+
128
+ Optional knobs in \`loam.config.yaml\`:
129
+
130
+ \`\`\`yaml
131
+ prime:
132
+ default_mode: manifest # or "full". Omit to let \`lm prime\` auto-flip:
133
+ # full output until the corpus exceeds 100 records
134
+ # or 5 domains, then manifest. Set explicitly to pin
135
+ # one mode. Scoping flags (\`--files\`, \`<domain>\`)
136
+ # always force full.
137
+
138
+ search:
139
+ boost_factor: 0.1 # multiplier on BM25 scores for confirmed records.
140
+ # 0 disables (pure BM25). Override with
141
+ # \`lm search --no-boost\`.
142
+ \`\`\`
143
+ `;
144
+
145
+ function gitCommonDir(cwd: string): string | null {
146
+ try {
147
+ const raw = execFileSync("git", ["rev-parse", "--git-common-dir"], {
148
+ cwd,
149
+ encoding: "utf-8",
150
+ stdio: ["pipe", "pipe", "pipe"],
151
+ }).trim();
152
+ if (!raw) return null;
153
+ return resolve(cwd, raw);
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ function resolveWorktreeRoot(cwd: string): string {
160
+ const common = gitCommonDir(cwd);
161
+ if (!common) return cwd;
162
+
163
+ // .git/worktrees/<name> → strip to repo root; .git → already main
164
+ const mainRoot = common.endsWith(".git") ? dirname(common) : dirname(dirname(common));
165
+ const mainResolved = resolve(mainRoot);
166
+
167
+ if (mainResolved === resolve(cwd)) return cwd;
168
+
169
+ // Only redirect if main repo has a .loam/ with config
170
+ const mainLoamConfig = join(mainResolved, LOAM_DIR, CONFIG_FILE);
171
+ if (existsSync(mainLoamConfig)) {
172
+ return mainResolved;
173
+ }
174
+
175
+ return cwd;
176
+ }
177
+
178
+ export function isInsideWorktree(cwd: string = process.cwd()): boolean {
179
+ const common = gitCommonDir(cwd);
180
+ if (!common) return false;
181
+
182
+ // For actual worktrees, --git-common-dir always returns the main .git dir
183
+ // (ends with ".git"). Submodules return /parent/.git/modules/<name> which
184
+ // does NOT end with ".git" — those are not worktrees, avoid false positive.
185
+ if (!common.endsWith(".git")) return false;
186
+
187
+ const mainRoot = dirname(common);
188
+ return resolve(mainRoot) !== resolve(cwd);
189
+ }
190
+
191
+ export function getLoamDir(cwd: string = process.cwd()): string {
192
+ return join(resolveWorktreeRoot(cwd), LOAM_DIR);
193
+ }
194
+
195
+ export function getConfigPath(cwd: string = process.cwd()): string {
196
+ return join(getLoamDir(cwd), CONFIG_FILE);
197
+ }
198
+
199
+ export function getExpertiseDir(cwd: string = process.cwd()): string {
200
+ return join(getLoamDir(cwd), EXPERTISE_DIR);
201
+ }
202
+
203
+ export function validateDomainName(domain: string): void {
204
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(domain)) {
205
+ throw new Error(
206
+ `Invalid domain name: "${domain}". Only alphanumeric characters, hyphens, and underscores are allowed.`,
207
+ );
208
+ }
209
+ }
210
+
211
+ export function getExpertisePath(domain: string, cwd: string = process.cwd()): string {
212
+ validateDomainName(domain);
213
+ return join(getExpertiseDir(cwd), `${domain}.jsonl`);
214
+ }
215
+
216
+ // Legacy on-disk shape was `domains: [a, b]`; current shape is
217
+ // `domains: { a: {}, b: {} }`. Normalize both to the object map at read time so
218
+ // older configs keep working without user migration.
219
+ function normalizeDomains(raw: unknown): Record<string, DomainConfig> {
220
+ if (Array.isArray(raw)) {
221
+ const map: Record<string, DomainConfig> = {};
222
+ for (const name of raw) {
223
+ if (typeof name === "string") map[name] = {};
224
+ }
225
+ return map;
226
+ }
227
+ if (raw && typeof raw === "object") return raw as Record<string, DomainConfig>;
228
+ return {};
229
+ }
230
+
231
+ // Backfill required-by-type top-level sections that a hand-written minimal
232
+ // config may omit. Schema marks `governance` and `classification_defaults` as
233
+ // required, but consumers (doctor, prune, status, compact, prime) destructure
234
+ // them directly and would otherwise crash with a TypeError on a config that
235
+ // only declares `domains:`. Shallow-merge so partial user overrides
236
+ // (e.g. `governance: { max_entries: 50 }`) keep the other defaults.
237
+ function applyConfigDefaults(parsed: LoamConfig): LoamConfig {
238
+ const userGov = (parsed.governance ?? {}) as Partial<LoamConfig["governance"]>;
239
+ const userCD = (parsed.classification_defaults ?? {}) as Partial<
240
+ LoamConfig["classification_defaults"]
241
+ >;
242
+ const userShelf = (userCD.shelf_life ?? {}) as Partial<
243
+ LoamConfig["classification_defaults"]["shelf_life"]
244
+ >;
245
+ parsed.governance = { ...DEFAULT_CONFIG.governance, ...userGov };
246
+ parsed.classification_defaults = {
247
+ shelf_life: { ...DEFAULT_CONFIG.classification_defaults.shelf_life, ...userShelf },
248
+ };
249
+ return parsed;
250
+ }
251
+
252
+ export async function readConfig(cwd: string = process.cwd()): Promise<LoamConfig> {
253
+ const configPath = getConfigPath(cwd);
254
+ let content: string;
255
+ try {
256
+ content = await readFile(configPath, "utf-8");
257
+ } catch (err) {
258
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
259
+ throw new Error("No .loam/ directory found. Run `loam init` to set up this project.");
260
+ }
261
+ throw err;
262
+ }
263
+ let parsed: LoamConfig;
264
+ try {
265
+ parsed = (yaml.load(content) ?? {}) as LoamConfig;
266
+ } catch (err) {
267
+ throw new Error(
268
+ `Failed to parse loam.config.yaml: ${(err as Error).message}. Check the YAML syntax.`,
269
+ );
270
+ }
271
+ if (!parsed || typeof parsed !== "object") {
272
+ // Empty file or scalar at top level — treat as empty object so defaults apply.
273
+ parsed = {} as LoamConfig;
274
+ }
275
+ parsed.domains = normalizeDomains(parsed.domains);
276
+ return applyConfigDefaults(parsed);
277
+ }
278
+
279
+ export async function addDomain(domain: string, cwd: string = process.cwd()): Promise<void> {
280
+ validateDomainName(domain);
281
+ const config = await readConfig(cwd);
282
+ if (!(domain in config.domains)) {
283
+ config.domains[domain] = {};
284
+ await writeConfig(config, cwd);
285
+ }
286
+ const filePath = getExpertisePath(domain, cwd);
287
+ if (!existsSync(filePath)) {
288
+ await createExpertiseFile(filePath);
289
+ }
290
+ }
291
+
292
+ export async function removeDomain(domain: string, cwd: string = process.cwd()): Promise<void> {
293
+ validateDomainName(domain);
294
+ const config = await readConfig(cwd);
295
+ if (!(domain in config.domains)) {
296
+ throw new Error(`Domain "${domain}" not found in config.`);
297
+ }
298
+ delete config.domains[domain];
299
+ await writeConfig(config, cwd);
300
+ const filePath = getExpertisePath(domain, cwd);
301
+ if (existsSync(filePath)) {
302
+ await rm(filePath);
303
+ }
304
+ }
305
+
306
+ export async function writeConfig(config: LoamConfig, cwd: string = process.cwd()): Promise<void> {
307
+ const configPath = getConfigPath(cwd);
308
+ const content = yaml.dump(config, { lineWidth: -1 });
309
+ await writeFile(configPath, content, "utf-8");
310
+ }
311
+
312
+ export async function initLoamDir(cwd: string = process.cwd()): Promise<void> {
313
+ const loamDir = getLoamDir(cwd);
314
+ const expertiseDir = getExpertiseDir(cwd);
315
+ await mkdir(loamDir, { recursive: true });
316
+ await mkdir(expertiseDir, { recursive: true });
317
+
318
+ // Only write default config if none exists — preserve user customizations.
319
+ // Use the templated YAML (with commented-out optional knobs) for discoverability;
320
+ // subsequent writes via writeConfig() round-trip through yaml.dump and lose comments.
321
+ const configPath = getConfigPath(cwd);
322
+ if (!existsSync(configPath)) {
323
+ await writeFile(configPath, buildInitialConfigYaml(), "utf-8");
324
+ }
325
+
326
+ // Create or append .gitattributes with merge=union for JSONL files
327
+ const gitattributesPath = join(cwd, ".gitattributes");
328
+ let existing = "";
329
+ try {
330
+ existing = await readFile(gitattributesPath, "utf-8");
331
+ } catch {
332
+ // File doesn't exist yet — will create it
333
+ }
334
+ if (!existing.includes(GITATTRIBUTES_LINE)) {
335
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
336
+ await writeFile(gitattributesPath, `${existing + separator + GITATTRIBUTES_LINE}\n`, "utf-8");
337
+ }
338
+
339
+ // Create .loam/README.md if missing
340
+ const readmePath = join(loamDir, "README.md");
341
+ if (!existsSync(readmePath)) {
342
+ await writeFile(readmePath, LOAM_README, "utf-8");
343
+ }
344
+ }
@@ -0,0 +1,62 @@
1
+ // Repo-relative POSIX paths. Strip trailing slashes and a leading "./", coerce
2
+ // repo-root markers ("." / "" / "./") to "" so they're filtered out of stored
3
+ // anchors (meaningless to anchor at the repo root). Tolerant of legacy values
4
+ // on disk: never throws — see assertWritableDirAnchor for write-time validation
5
+ // (which rejects absolute paths and parent-traversal segments).
6
+ export function normalizeDirAnchor(input: string): string {
7
+ let v = input.trim();
8
+ if (v === "" || v === ".") return "";
9
+ v = v.replace(/\\+/g, "/");
10
+ v = v.replace(/\/+$/, "");
11
+ if (v.startsWith("./")) v = v.slice(2);
12
+ v = v.replace(/\/+$/, "");
13
+ if (v === "" || v === ".") return "";
14
+ return v;
15
+ }
16
+
17
+ // Throws if the input is unsafe to store as a project-root-relative anchor:
18
+ // absolute paths (`/etc/passwd`, `C:\…`) and parent-traversal segments (`..`)
19
+ // would silently break lm prime/--files matching, doctor's existsSync probe,
20
+ // and anchor-validity decay. Called at write time (lm record / lm edit) so
21
+ // invalid input surfaces as a formatted error before it lands on disk.
22
+ export function assertWritableDirAnchor(raw: string): void {
23
+ const v = raw.trim();
24
+ if (v.length === 0) return;
25
+ if (/^[a-zA-Z]:[\\/]/.test(v) || v.startsWith("/") || v.startsWith("\\")) {
26
+ throw new Error(
27
+ `dir-anchor "${raw}" is an absolute path. Use a project-root-relative path like "src/utils".`,
28
+ );
29
+ }
30
+ const segments = v.replace(/\\+/g, "/").split("/");
31
+ if (segments.includes("..")) {
32
+ throw new Error(
33
+ `dir-anchor "${raw}" contains ".." (parent traversal). Anchors must stay inside the project root.`,
34
+ );
35
+ }
36
+ }
37
+
38
+ export function fileLivesUnderDir(file: string, dir: string): boolean {
39
+ const f = file.replace(/\\+/g, "/");
40
+ const d = normalizeDirAnchor(dir);
41
+ if (d === "") return true;
42
+ return f === d || f.startsWith(`${d}/`);
43
+ }
44
+
45
+ // Heuristic: any directory that is the parent of >= threshold changed files
46
+ // becomes a dir anchor. Returns the deepest such directories, deduped and sorted.
47
+ // Threshold default of 3 keeps single-file edits and pair-edits from generating
48
+ // noisy anchors.
49
+ export function inferDirAnchors(files: string[], threshold = 3): string[] {
50
+ const counts = new Map<string, number>();
51
+ for (const raw of files) {
52
+ const f = raw.replace(/\\+/g, "/");
53
+ const i = f.lastIndexOf("/");
54
+ if (i <= 0) continue;
55
+ const dir = f.substring(0, i);
56
+ counts.set(dir, (counts.get(dir) ?? 0) + 1);
57
+ }
58
+ return [...counts.entries()]
59
+ .filter(([, c]) => c >= threshold)
60
+ .map(([d]) => d)
61
+ .sort();
62
+ }
@@ -0,0 +1,114 @@
1
+ // Per-domain rule helpers shared across record (write-time gate), validate
2
+ // and sync (read-time re-validation), and doctor (diagnostic surfacing).
3
+ // Empty/missing arrays mean "no rule" — back-compat with pre-R-01 configs.
4
+
5
+ import type { ErrorObject } from "ajv";
6
+ import type { TypeRegistry } from "../registry/type-registry.ts";
7
+ import type { LoamConfig } from "../schemas/config.ts";
8
+
9
+ export function getAllowedTypes(config: LoamConfig, domain: string): string[] | null {
10
+ const list = config.domains[domain]?.allowed_types;
11
+ if (!list || list.length === 0) return null;
12
+ return list;
13
+ }
14
+
15
+ export function getRequiredFields(config: LoamConfig, domain: string): string[] | null {
16
+ const list = config.domains[domain]?.required_fields;
17
+ if (!list || list.length === 0) return null;
18
+ return list;
19
+ }
20
+
21
+ // Return the subset of `fields` that are missing or empty on the record.
22
+ // Treats undefined/null/""/empty-array as missing. Top-level field names only;
23
+ // nested paths are out of scope for v1.
24
+ export function findMissingDomainFields(
25
+ record: Record<string, unknown>,
26
+ fields: string[],
27
+ ): string[] {
28
+ const missing: string[] = [];
29
+ for (const field of fields) {
30
+ const value = record[field];
31
+ if (
32
+ value === undefined ||
33
+ value === null ||
34
+ value === "" ||
35
+ (Array.isArray(value) && value.length === 0)
36
+ ) {
37
+ missing.push(field);
38
+ }
39
+ }
40
+ return missing;
41
+ }
42
+
43
+ // Fields owned by BaseRecord — accepted by every type's schema (built-in or
44
+ // custom). A domain.required_fields entry naming one of these is always
45
+ // compatible regardless of allowed_types.
46
+ const BASE_RECORD_FIELDS: ReadonlySet<string> = new Set([
47
+ "id",
48
+ "type",
49
+ "classification",
50
+ "recorded_at",
51
+ "evidence",
52
+ "tags",
53
+ "relates_to",
54
+ "supersedes",
55
+ "outcomes",
56
+ "dir_anchors",
57
+ "supersession_demoted_at",
58
+ "owner",
59
+ "status",
60
+ ]);
61
+
62
+ // Detect required_fields entries that no allowed type can hold. Built-in (and
63
+ // custom) record schemas use `additionalProperties: false`, so a domain that
64
+ // requires a field which isn't declared on any of its allowed types is
65
+ // unsatisfiable: every write fails AJV before the domain gate runs. Returns
66
+ // one entry per offending field with the resolved allowed-type names so
67
+ // callers can render a fix-it hint.
68
+ export function findIncompatibleRequiredFields(
69
+ config: LoamConfig,
70
+ domain: string,
71
+ registry: TypeRegistry,
72
+ ): Array<{ field: string; allowedTypes: string[] }> {
73
+ const required = getRequiredFields(config, domain);
74
+ if (!required) return [];
75
+ const allowedTypeNames = getAllowedTypes(config, domain) ?? registry.names();
76
+ const incompatible: Array<{ field: string; allowedTypes: string[] }> = [];
77
+ for (const field of required) {
78
+ if (BASE_RECORD_FIELDS.has(field)) continue;
79
+ let acceptedByAny = false;
80
+ for (const name of allowedTypeNames) {
81
+ const def = registry.get(name);
82
+ if (!def) continue;
83
+ if (def.required.includes(field) || def.optional.includes(field)) {
84
+ acceptedByAny = true;
85
+ break;
86
+ }
87
+ }
88
+ if (!acceptedByAny) {
89
+ incompatible.push({ field, allowedTypes: [...allowedTypeNames] });
90
+ }
91
+ }
92
+ return incompatible;
93
+ }
94
+
95
+ // Scan AJV errors for `additionalProperties` rejections whose property name is
96
+ // listed in domain.required_fields. Returns the deduped set of such field
97
+ // names so the caller can render a targeted hint instead of the raw AJV soup.
98
+ export function findRejectedRequiredFields(
99
+ errors: readonly ErrorObject[] | null | undefined,
100
+ requiredFields: readonly string[] | null,
101
+ ): string[] {
102
+ if (!errors || !requiredFields || requiredFields.length === 0) return [];
103
+ const requiredSet = new Set(requiredFields);
104
+ const rejected = new Set<string>();
105
+ for (const err of errors) {
106
+ if (err.keyword !== "additionalProperties") continue;
107
+ const params = err.params as { additionalProperty?: unknown };
108
+ const prop = params.additionalProperty;
109
+ if (typeof prop === "string" && requiredSet.has(prop)) {
110
+ rejected.add(prop);
111
+ }
112
+ }
113
+ return [...rejected];
114
+ }