@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9

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 (648) hide show
  1. package/CHANGELOG.md +1315 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +83 -7
  8. package/SCHEMA.md +569 -98
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +33 -11
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -1,4 +1,14 @@
1
1
  import { describedMd, hintedMd, namedMd, skillsIn, titledMd, tomlBlock, tomlLine, unprefixed, } from "./dialects.js";
2
+ /**
3
+ * The shape most entries share: `<dir>/<sub>/<name>.md` command files in the
4
+ * given dialect, skills under `<dir>/skills/`, and the tool's own dot-directory
5
+ * as its one sighting. An entry that departs from any of the three — another
6
+ * extension, a namespace directory, a second marker, no commands at all — is
7
+ * written out in full below, so the departure is what its entry shows.
8
+ */
9
+ function dotTool(dir, sub, format) {
10
+ return { path: (name) => [dir, sub, `${name}.md`], format, skill: skillsIn(dir), detect: [dir] };
11
+ }
2
12
  /**
3
13
  * The original target and the default. This WRAPPER must stay byte-identical
4
14
  * to what loam has always written — SLASH_COMMANDS (scaffold.ts) derives
@@ -17,22 +27,12 @@ import { describedMd, hintedMd, namedMd, skillsIn, titledMd, tomlBlock, tomlLine
17
27
  * Named and typed, both mistakes are typecheck failures instead. The registry
18
28
  * still lists it first, and the emitted bytes are unchanged.
19
29
  */
20
- export const CLAUDE = {
21
- path: (name) => [".claude", "commands", `${name}.md`],
22
- format: (c) => `---\ndescription: ${c.description}\nargument-hint: ${c.argumentHint}\n---\n\n${c.body}`,
23
- skill: skillsIn(".claude"),
24
- detect: [".claude"],
25
- };
30
+ export const CLAUDE = dotTool(".claude", "commands", (c) => `---\ndescription: ${c.description}\nargument-hint: ${c.argumentHint}\n---\n\n${c.body}`);
26
31
  export const AGENT_TOOLS = {
27
32
  claude: CLAUDE,
28
33
  // Cursor invokes by flat file name (`/loam-check`); its frontmatter `name`
29
34
  // field spells that invocation form.
30
- cursor: {
31
- path: (name) => [".cursor", "commands", `${name}.md`],
32
- format: (c) => `---\nname: /${c.name}\n---\n\n${c.body}`,
33
- skill: skillsIn(".cursor"),
34
- detect: [".cursor"],
35
- },
35
+ cursor: dotTool(".cursor", "commands", (c) => `---\nname: /${c.name}\n---\n\n${c.body}`),
36
36
  // Copilot prompt files: `.prompt.md` extension, description-only frontmatter.
37
37
  // Detection cannot use `.github/` — almost every repo has one and almost none
38
38
  // of them means Copilot — so it looks for the files Copilot itself reads.
@@ -59,47 +59,42 @@ export const AGENT_TOOLS = {
59
59
  skill: skillsIn(".gemini"),
60
60
  detect: [".gemini"],
61
61
  },
62
- opencode: {
63
- path: (name) => [".opencode", "commands", `${name}.md`],
64
- format: describedMd,
65
- skill: skillsIn(".opencode"),
66
- detect: [".opencode"],
67
- },
62
+ opencode: dotTool(".opencode", "commands", describedMd),
68
63
  // Cline reads workflows from `.clinerules/` — plain markdown, no frontmatter —
69
64
  // but its skills from `.cline/`. The two directories are unrelated, so both
70
65
  // count as a sighting.
71
66
  cline: {
72
67
  path: (name) => [".clinerules", "workflows", `${name}.md`],
73
- format: (c) => `# ${c.name}\n\n${c.body}`,
68
+ format: titledMd,
74
69
  skill: skillsIn(".cline"),
75
70
  detect: [".cline", ".clinerules"],
76
71
  },
77
72
  // Amazon Q surfaces these as its PROMPT library, not as commands: the file is
78
73
  // invoked `@loam-check`, never `/loam-check`. Same file shape regardless.
79
- "amazon-q": {
80
- path: (name) => [".amazonq", "prompts", `${name}.md`],
81
- format: describedMd,
82
- skill: skillsIn(".amazonq"),
83
- detect: [".amazonq"],
84
- },
74
+ "amazon-q": dotTool(".amazonq", "prompts", describedMd),
85
75
  // Antigravity's directory is `.agent/`, which is why the id and the directory
86
76
  // do not match here as they do almost everywhere else.
87
- antigravity: {
88
- path: (name) => [".agent", "workflows", `${name}.md`],
89
- format: describedMd,
90
- skill: skillsIn(".agent"),
91
- detect: [".agent"],
92
- },
77
+ antigravity: dotTool(".agent", "workflows", describedMd),
93
78
  // Auggie is the Augment CLI: id `auggie`, directory `.augment/`.
94
- auggie: {
95
- path: (name) => [".augment", "commands", `${name}.md`],
96
- format: hintedMd,
97
- skill: skillsIn(".augment"),
98
- detect: [".augment"],
99
- },
100
- // Skills-only, deliberately: Codex reads `.codex/skills/` and does not load
101
- // custom command files, so it declares no command emitter at all rather than
102
- // laying down files nothing will ever open.
79
+ auggie: dotTool(".augment", "commands", hintedMd),
80
+ // Skills-only, deliberately: Codex does not load custom command files, so it
81
+ // declares no command emitter at all rather than laying down files nothing
82
+ // will ever open.
83
+ //
84
+ // The skill path is `.codex/skills/`, and that is NOT what Codex documents.
85
+ // Its own skills page (Build skills — developers.openai.com/codex/skills,
86
+ // which now redirects to learn.chatgpt.com/docs/build-skills; checked
87
+ // 2026-10-09) lists only `.agents/skills` — in the working directory, its
88
+ // parents up to the repository root — and never mentions `.codex/skills`;
89
+ // the `agents` entry below quotes it. `.codex/skills/` is undocumented but
90
+ // still loaded (Codex PR #10317 and third-party guides describe it as the
91
+ // older location, slated for deprecation), so the files this entry writes
92
+ // are read today and the break is latent: the day Codex drops it, a repo
93
+ // scaffolded for `codex` alone loses every skill silently. The path stays
94
+ // put on purpose — moving it re-points files in every repo already
95
+ // scaffolded, which is a migration with its own entry, not a comment fix —
96
+ // and choosing `agents` alongside `codex` already gives Codex the
97
+ // documented root.
103
98
  codex: {
104
99
  skill: skillsIn(".codex"),
105
100
  detect: [".codex"],
@@ -130,26 +125,11 @@ export const AGENT_TOOLS = {
130
125
  skill: skillsIn(".devin"),
131
126
  detect: [".devin", ".windsurf"],
132
127
  },
133
- factory: {
134
- path: (name) => [".factory", "commands", `${name}.md`],
135
- format: hintedMd,
136
- skill: skillsIn(".factory"),
137
- detect: [".factory"],
138
- },
139
- junie: {
140
- path: (name) => [".junie", "commands", `${name}.md`],
141
- format: describedMd,
142
- skill: skillsIn(".junie"),
143
- detect: [".junie"],
144
- },
128
+ factory: dotTool(".factory", "commands", hintedMd),
129
+ junie: dotTool(".junie", "commands", describedMd),
145
130
  // Kilo Code workflows carry no frontmatter — the title line is all the
146
131
  // wrapper there is.
147
- kilocode: {
148
- path: (name) => [".kilocode", "workflows", `${name}.md`],
149
- format: titledMd,
150
- skill: skillsIn(".kilocode"),
151
- detect: [".kilocode"],
152
- },
132
+ kilocode: dotTool(".kilocode", "workflows", titledMd),
153
133
  // Kiro uses Copilot's `.prompt.md` extension under its own directory.
154
134
  kiro: {
155
135
  path: (name) => [".kiro", "prompts", `${name}.prompt.md`],
@@ -159,25 +139,10 @@ export const AGENT_TOOLS = {
159
139
  },
160
140
  // Qwen Code retired its TOML commands in favour of markdown + frontmatter,
161
141
  // so this is markdown despite the Gemini lineage.
162
- qwen: {
163
- path: (name) => [".qwen", "commands", `${name}.md`],
164
- format: describedMd,
165
- skill: skillsIn(".qwen"),
166
- detect: [".qwen"],
167
- },
142
+ qwen: dotTool(".qwen", "commands", describedMd),
168
143
  // Roo/Zoo Code: id `roocode`, directory `.roo/`, no frontmatter.
169
- roocode: {
170
- path: (name) => [".roo", "commands", `${name}.md`],
171
- format: titledMd,
172
- skill: skillsIn(".roo"),
173
- detect: [".roo"],
174
- },
175
- trae: {
176
- path: (name) => [".trae", "commands", `${name}.md`],
177
- format: namedMd,
178
- skill: skillsIn(".trae"),
179
- detect: [".trae"],
180
- },
144
+ roocode: dotTool(".roo", "commands", titledMd),
145
+ trae: dotTool(".trae", "commands", namedMd),
181
146
  // The vendor-neutral skills root, and the only id here that names no vendor.
182
147
  // `.agents/skills/<name>/SKILL.md` is read by six of the tools that already
183
148
  // have entries above, each documenting it in its own words:
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../src/core/agent/tools/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The registry of agent tools loam can scaffold for — one entry per tool: its\n * paths, its wrapper dialect, and the marker paths whose presence means the\n * tool is in use. Paths and wrappers follow each tool's own documented\n * convention and are pinned byte-for-byte in test/agents.test.ts.\n */\nimport type { AgentTool } from \"../contract.js\";\nimport {\n describedMd,\n hintedMd,\n namedMd,\n skillsIn,\n titledMd,\n tomlBlock,\n tomlLine,\n unprefixed,\n} from \"./dialects.js\";\n\n/**\n * A registry entry that really does emit command files — both halves of the\n * command adapter present. The distinction is not hypothetical: `codex`\n * declares neither, on purpose. Naming it lets the one entry\n * scaffold.ts derives THROUGH carry that guarantee in its type, instead of at\n * the use site where the only way to state it was a pair of `!`.\n */\ntype CommandEmittingTool = AgentTool & Required<Pick<AgentTool, \"path\" | \"format\">>;\n\n/**\n * The original target and the default. This WRAPPER must stay byte-identical\n * to what loam has always written — SLASH_COMMANDS (scaffold.ts) derives\n * through it —\n * and an already-initialized repo must still read as fully scaffolded\n * (`skipped` is existsSync, never content). What the wrapper wraps now carries\n * a version stamp, which is the one thing about a generated file loam will\n * report on; see `stubbed`.\n *\n * It stands outside the registry object because SLASH_COMMANDS reaches for it\n * by name. That lookup was `AGENT_TOOLS[\"claude\"]!.format!` — two assertions on\n * an exported const evaluated at import time, so renaming the key, or letting\n * this entry lose `format` the way `codex` legitimately has, would have thrown\n * a TypeError while the CLI entry point was still being loaded: before\n * `program.exitOverride()` exists, and therefore with no envelope around it.\n * Named and typed, both mistakes are typecheck failures instead. The registry\n * still lists it first, and the emitted bytes are unchanged.\n */\nexport const CLAUDE: CommandEmittingTool = {\n path: (name) => [\".claude\", \"commands\", `${name}.md`],\n format: (c) =>\n `---\\ndescription: ${c.description}\\nargument-hint: ${c.argumentHint}\\n---\\n\\n${c.body}`,\n skill: skillsIn(\".claude\"),\n detect: [\".claude\"],\n};\n\nexport const AGENT_TOOLS: Record<string, AgentTool> = {\n claude: CLAUDE,\n // Cursor invokes by flat file name (`/loam-check`); its frontmatter `name`\n // field spells that invocation form.\n cursor: {\n path: (name) => [\".cursor\", \"commands\", `${name}.md`],\n format: (c) => `---\\nname: /${c.name}\\n---\\n\\n${c.body}`,\n skill: skillsIn(\".cursor\"),\n detect: [\".cursor\"],\n },\n // Copilot prompt files: `.prompt.md` extension, description-only frontmatter.\n // Detection cannot use `.github/` — almost every repo has one and almost none\n // of them means Copilot — so it looks for the files Copilot itself reads.\n \"github-copilot\": {\n path: (name) => [\".github\", \"prompts\", `${name}.prompt.md`],\n format: describedMd,\n skill: skillsIn(\".github\"),\n detect: [\n \".github/copilot-instructions.md\",\n \".github/instructions\",\n \".github/prompts\",\n \".github/agents\",\n \".github/skills\",\n \".github/workflows/copilot-setup-steps.yml\",\n \".github/.mcp.json\",\n ],\n },\n // Gemini: TOML, and a namespace DIRECTORY — `.gemini/commands/loam/check.toml`\n // is invoked as `/loam:check`, so the file name drops the `loam-` prefix the\n // flat-named tools carry.\n gemini: {\n path: (name) => [\".gemini\", \"commands\", \"loam\", `${unprefixed(name)}.toml`],\n format: (c) =>\n `description = \"${tomlLine(c.description)}\"\\nprompt = \"\"\"\\n${tomlBlock(c.body)}\"\"\"`,\n skill: skillsIn(\".gemini\"),\n detect: [\".gemini\"],\n },\n opencode: {\n path: (name) => [\".opencode\", \"commands\", `${name}.md`],\n format: describedMd,\n skill: skillsIn(\".opencode\"),\n detect: [\".opencode\"],\n },\n // Cline reads workflows from `.clinerules/` — plain markdown, no frontmatter —\n // but its skills from `.cline/`. The two directories are unrelated, so both\n // count as a sighting.\n cline: {\n path: (name) => [\".clinerules\", \"workflows\", `${name}.md`],\n format: (c) => `# ${c.name}\\n\\n${c.body}`,\n skill: skillsIn(\".cline\"),\n detect: [\".cline\", \".clinerules\"],\n },\n // Amazon Q surfaces these as its PROMPT library, not as commands: the file is\n // invoked `@loam-check`, never `/loam-check`. Same file shape regardless.\n \"amazon-q\": {\n path: (name) => [\".amazonq\", \"prompts\", `${name}.md`],\n format: describedMd,\n skill: skillsIn(\".amazonq\"),\n detect: [\".amazonq\"],\n },\n // Antigravity's directory is `.agent/`, which is why the id and the directory\n // do not match here as they do almost everywhere else.\n antigravity: {\n path: (name) => [\".agent\", \"workflows\", `${name}.md`],\n format: describedMd,\n skill: skillsIn(\".agent\"),\n detect: [\".agent\"],\n },\n // Auggie is the Augment CLI: id `auggie`, directory `.augment/`.\n auggie: {\n path: (name) => [\".augment\", \"commands\", `${name}.md`],\n format: hintedMd,\n skill: skillsIn(\".augment\"),\n detect: [\".augment\"],\n },\n // Skills-only, deliberately: Codex reads `.codex/skills/` and does not load\n // custom command files, so it declares no command emitter at all rather than\n // laying down files nothing will ever open.\n codex: {\n skill: skillsIn(\".codex\"),\n detect: [\".codex\"],\n },\n // Continue's prompt files take a bare `.prompt` extension, and `invokable`\n // is what promotes one from a template to something you can call.\n continue: {\n path: (name) => [\".continue\", \"prompts\", `${name}.prompt`],\n format: (c) =>\n `---\\nname: ${c.name}\\ndescription: ${c.description}\\ninvokable: true\\n---\\n\\n${c.body}`,\n skill: skillsIn(\".continue\"),\n detect: [\".continue\"],\n },\n // Crush namespaces like Gemini does — `.crush/commands/loam/check.md` is\n // `/loam:check` — but stays markdown.\n crush: {\n path: (name) => [\".crush\", \"commands\", \"loam\", `${unprefixed(name)}.md`],\n format: namedMd,\n skill: skillsIn(\".crush\"),\n detect: [\".crush\"],\n },\n // Devin Desktop, formerly Windsurf: Cascade-style workflows, and the rename\n // moved the directory from `.windsurf/` to `.devin/`. New files go to the new\n // directory; a repo still holding the old one is still a Devin repo, so both\n // are sightings.\n devin: {\n path: (name) => [\".devin\", \"workflows\", `${name}.md`],\n format: namedMd,\n skill: skillsIn(\".devin\"),\n detect: [\".devin\", \".windsurf\"],\n },\n factory: {\n path: (name) => [\".factory\", \"commands\", `${name}.md`],\n format: hintedMd,\n skill: skillsIn(\".factory\"),\n detect: [\".factory\"],\n },\n junie: {\n path: (name) => [\".junie\", \"commands\", `${name}.md`],\n format: describedMd,\n skill: skillsIn(\".junie\"),\n detect: [\".junie\"],\n },\n // Kilo Code workflows carry no frontmatter — the title line is all the\n // wrapper there is.\n kilocode: {\n path: (name) => [\".kilocode\", \"workflows\", `${name}.md`],\n format: titledMd,\n skill: skillsIn(\".kilocode\"),\n detect: [\".kilocode\"],\n },\n // Kiro uses Copilot's `.prompt.md` extension under its own directory.\n kiro: {\n path: (name) => [\".kiro\", \"prompts\", `${name}.prompt.md`],\n format: describedMd,\n skill: skillsIn(\".kiro\"),\n detect: [\".kiro\"],\n },\n // Qwen Code retired its TOML commands in favour of markdown + frontmatter,\n // so this is markdown despite the Gemini lineage.\n qwen: {\n path: (name) => [\".qwen\", \"commands\", `${name}.md`],\n format: describedMd,\n skill: skillsIn(\".qwen\"),\n detect: [\".qwen\"],\n },\n // Roo/Zoo Code: id `roocode`, directory `.roo/`, no frontmatter.\n roocode: {\n path: (name) => [\".roo\", \"commands\", `${name}.md`],\n format: titledMd,\n skill: skillsIn(\".roo\"),\n detect: [\".roo\"],\n },\n trae: {\n path: (name) => [\".trae\", \"commands\", `${name}.md`],\n format: namedMd,\n skill: skillsIn(\".trae\"),\n detect: [\".trae\"],\n },\n // The vendor-neutral skills root, and the only id here that names no vendor.\n // `.agents/skills/<name>/SKILL.md` is read by six of the tools that already\n // have entries above, each documenting it in its own words:\n //\n // Cursor — \"Skills are automatically loaded from `.agents/skills/`,\n // `.cursor/skills/`, `~/.agents/skills/` … and `~/.cursor/skills/`\"\n // (Agent Skills, cursor.com/docs/skills).\n // GitHub Copilot — \"For project skills, specific to a single repository,\n // create a `.github/skills`, `.claude/skills`, or `.agents/skills`\n // directory in your repository\" (Adding agent skills for GitHub Copilot,\n // docs.github.com/en/copilot → customize-cloud-agent/add-skills).\n // Codex — \"Codex scans `.agents/skills` in every directory from your\n // current working directory up to the repository root\" (Build skills,\n // developers.openai.com/codex/skills).\n // Gemini CLI — `.agents/skills/` is the workspace-tier alias for\n // `.gemini/skills/`, and \"within the same tier … takes precedence over\n // the `.gemini/skills/` directory\" (docs/cli/skills.md in\n // google-gemini/gemini-cli).\n // Zed — \"Skills are loaded from `~/.agents/skills/` and\n // `<worktree>/.agents/skills/` only\" (Agent Skills, zed.dev/docs/ai/skills).\n // Roo Code — `.agents/skills/` is its cross-agent project location, ranked\n // below `.roo/skills/` at the same level (Skills,\n // docs.roocode.com/features/skills).\n //\n // Skills only, for the reason spelled out on `codex`: not one of those six\n // documents a COMMAND file under this root, so a `.agents/commands/` would be\n // files nothing will ever open — the thing this entry exists to stop.\n //\n // Purely ADDITIVE, and that is the design rather than an implementation\n // detail. No tool above loses or re-points its own skill path: a repo already\n // carrying `.claude/skills/` keeps getting them there. This entry is for the\n // team that would rather have ONE copy those six read than six copies of the\n // same bytes, and choosing that is the user's call — which is why selecting\n // `agents` adds a target and never removes one.\n //\n // `detect` is `.agents/skills`, never a bare `.agents/`, for exactly\n // `github-copilot`'s reason. `.agents/` is a shared root that other\n // conventions also write into, so a bare marker would auto-scaffold into any\n // repository where something else had made the directory — the failure that\n // rules out `.github/` one entry up. It is also NOT `antigravity`'s\n // `.agent/`: one letter apart, unrelated conventions, and `existsSync` on\n // either says nothing about the other.\n agents: {\n skill: skillsIn(\".agents\"),\n detect: [\".agents/skills\"],\n },\n};\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../src/core/agent/tools/registry.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,WAAW,EACX,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,UAAU,GACX,MAAM,eAAe,CAAC;AAWvB;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,GAAW,EAAE,GAAW,EAAE,MAAqC;IAC9E,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AACnG,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAwB,OAAO,CAChD,SAAS,EACT,UAAU,EACV,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,CAAC,WAAW,oBAAoB,CAAC,CAAC,YAAY,YAAY,CAAC,CAAC,IAAI,EAAE,CAChG,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAA8B;IACpD,MAAM,EAAE,MAAM;IACd,2EAA2E;IAC3E,qCAAqC;IACrC,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,8EAA8E;IAC9E,8EAA8E;IAC9E,0EAA0E;IAC1E,gBAAgB,EAAE;QAChB,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,YAAY,CAAC;QAC3D,MAAM,EAAE,WAAW;QACnB,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC1B,MAAM,EAAE;YACN,iCAAiC;YACjC,sBAAsB;YACtB,iBAAiB;YACjB,gBAAgB;YAChB,gBAAgB;YAChB,2CAA2C;YAC3C,mBAAmB;SACpB;KACF;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,0BAA0B;IAC1B,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;QAC3E,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CACZ,kBAAkB,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,oBAAoB,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK;QACrF,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC1B,MAAM,EAAE,CAAC,SAAS,CAAC;KACpB;IACD,QAAQ,EAAE,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC;IACvD,+EAA+E;IAC/E,4EAA4E;IAC5E,uBAAuB;IACvB,KAAK,EAAE;QACL,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,EAAE,WAAW,EAAE,GAAG,IAAI,KAAK,CAAC;QAC1D,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;QACzB,MAAM,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC;KAClC;IACD,8EAA8E;IAC9E,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC;IACvD,8EAA8E;IAC9E,uDAAuD;IACvD,WAAW,EAAE,OAAO,CAAC,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC;IACxD,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC;IACjD,6EAA6E;IAC7E,2EAA2E;IAC3E,kBAAkB;IAClB,EAAE;IACF,4EAA4E;IAC5E,0EAA0E;IAC1E,sEAAsE;IACtE,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,mEAAmE;IACnE,2EAA2E;IAC3E,kEAAkE;IAClE,mBAAmB;IACnB,KAAK,EAAE;QACL,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;QACzB,MAAM,EAAE,CAAC,QAAQ,CAAC;KACnB;IACD,2EAA2E;IAC3E,kEAAkE;IAClE,QAAQ,EAAE;QACR,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,IAAI,SAAS,CAAC;QAC1D,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CACZ,cAAc,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,WAAW,6BAA6B,CAAC,CAAC,IAAI,EAAE;QAC1F,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC;QAC5B,MAAM,EAAE,CAAC,WAAW,CAAC;KACtB;IACD,yEAAyE;IACzE,sCAAsC;IACtC,KAAK,EAAE;QACL,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QACxE,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;QACzB,MAAM,EAAE,CAAC,QAAQ,CAAC;KACnB;IACD,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,iBAAiB;IACjB,KAAK,EAAE;QACL,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,KAAK,CAAC;QACrD,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;QACzB,MAAM,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC;KAChC;IACD,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC;IAClD,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC;IACjD,uEAAuE;IACvE,oBAAoB;IACpB,QAAQ,EAAE,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC;IACrD,sEAAsE;IACtE,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,YAAY,CAAC;QACzD,MAAM,EAAE,WAAW;QACnB,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC;QACxB,MAAM,EAAE,CAAC,OAAO,CAAC;KAClB;IACD,2EAA2E;IAC3E,kDAAkD;IAClD,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,CAAC;IAC/C,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC;IAC9C,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC;IAC3C,6EAA6E;IAC7E,4EAA4E;IAC5E,4DAA4D;IAC5D,EAAE;IACF,sEAAsE;IACtE,wEAAwE;IACxE,8CAA8C;IAC9C,2EAA2E;IAC3E,uEAAuE;IACvE,6EAA6E;IAC7E,sEAAsE;IACtE,uEAAuE;IACvE,0EAA0E;IAC1E,2CAA2C;IAC3C,mEAAmE;IACnE,2EAA2E;IAC3E,8DAA8D;IAC9D,iCAAiC;IACjC,0DAA0D;IAC1D,iFAAiF;IACjF,6EAA6E;IAC7E,sDAAsD;IACtD,yCAAyC;IACzC,EAAE;IACF,2EAA2E;IAC3E,8EAA8E;IAC9E,sEAAsE;IACtE,EAAE;IACF,wEAAwE;IACxE,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,gDAAgD;IAChD,EAAE;IACF,qEAAqE;IACrE,oEAAoE;IACpE,6EAA6E;IAC7E,4EAA4E;IAC5E,oEAAoE;IACpE,0EAA0E;IAC1E,uCAAuC;IACvC,MAAM,EAAE;QACN,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC1B,MAAM,EAAE,CAAC,gBAAgB,CAAC;KAC3B;CACF,CAAC","sourcesContent":["/**\n * The registry of agent tools loam can scaffold for — one entry per tool: its\n * paths, its wrapper dialect, and the marker paths whose presence means the\n * tool is in use. Paths and wrappers follow each tool's own documented\n * convention and are pinned byte-for-byte in test/agents.test.ts.\n */\nimport type { AgentTool, CommandContent } from \"../contract.js\";\nimport {\n describedMd,\n hintedMd,\n namedMd,\n skillsIn,\n titledMd,\n tomlBlock,\n tomlLine,\n unprefixed,\n} from \"./dialects.js\";\n\n/**\n * A registry entry that really does emit command files — both halves of the\n * command adapter present. The distinction is not hypothetical: `codex`\n * declares neither, on purpose. Naming it lets the one entry\n * scaffold.ts derives THROUGH carry that guarantee in its type, instead of at\n * the use site where the only way to state it was a pair of `!`.\n */\ntype CommandEmittingTool = AgentTool & Required<Pick<AgentTool, \"path\" | \"format\">>;\n\n/**\n * The shape most entries share: `<dir>/<sub>/<name>.md` command files in the\n * given dialect, skills under `<dir>/skills/`, and the tool's own dot-directory\n * as its one sighting. An entry that departs from any of the three — another\n * extension, a namespace directory, a second marker, no commands at all — is\n * written out in full below, so the departure is what its entry shows.\n */\nfunction dotTool(dir: string, sub: string, format: (c: CommandContent) => string): CommandEmittingTool {\n return { path: (name) => [dir, sub, `${name}.md`], format, skill: skillsIn(dir), detect: [dir] };\n}\n\n/**\n * The original target and the default. This WRAPPER must stay byte-identical\n * to what loam has always written — SLASH_COMMANDS (scaffold.ts) derives\n * through it —\n * and an already-initialized repo must still read as fully scaffolded\n * (`skipped` is existsSync, never content). What the wrapper wraps now carries\n * a version stamp, which is the one thing about a generated file loam will\n * report on; see `stubbed`.\n *\n * It stands outside the registry object because SLASH_COMMANDS reaches for it\n * by name. That lookup was `AGENT_TOOLS[\"claude\"]!.format!` — two assertions on\n * an exported const evaluated at import time, so renaming the key, or letting\n * this entry lose `format` the way `codex` legitimately has, would have thrown\n * a TypeError while the CLI entry point was still being loaded: before\n * `program.exitOverride()` exists, and therefore with no envelope around it.\n * Named and typed, both mistakes are typecheck failures instead. The registry\n * still lists it first, and the emitted bytes are unchanged.\n */\nexport const CLAUDE: CommandEmittingTool = dotTool(\n \".claude\",\n \"commands\",\n (c) => `---\\ndescription: ${c.description}\\nargument-hint: ${c.argumentHint}\\n---\\n\\n${c.body}`,\n);\n\nexport const AGENT_TOOLS: Record<string, AgentTool> = {\n claude: CLAUDE,\n // Cursor invokes by flat file name (`/loam-check`); its frontmatter `name`\n // field spells that invocation form.\n cursor: dotTool(\".cursor\", \"commands\", (c) => `---\\nname: /${c.name}\\n---\\n\\n${c.body}`),\n // Copilot prompt files: `.prompt.md` extension, description-only frontmatter.\n // Detection cannot use `.github/` — almost every repo has one and almost none\n // of them means Copilot — so it looks for the files Copilot itself reads.\n \"github-copilot\": {\n path: (name) => [\".github\", \"prompts\", `${name}.prompt.md`],\n format: describedMd,\n skill: skillsIn(\".github\"),\n detect: [\n \".github/copilot-instructions.md\",\n \".github/instructions\",\n \".github/prompts\",\n \".github/agents\",\n \".github/skills\",\n \".github/workflows/copilot-setup-steps.yml\",\n \".github/.mcp.json\",\n ],\n },\n // Gemini: TOML, and a namespace DIRECTORY — `.gemini/commands/loam/check.toml`\n // is invoked as `/loam:check`, so the file name drops the `loam-` prefix the\n // flat-named tools carry.\n gemini: {\n path: (name) => [\".gemini\", \"commands\", \"loam\", `${unprefixed(name)}.toml`],\n format: (c) =>\n `description = \"${tomlLine(c.description)}\"\\nprompt = \"\"\"\\n${tomlBlock(c.body)}\"\"\"`,\n skill: skillsIn(\".gemini\"),\n detect: [\".gemini\"],\n },\n opencode: dotTool(\".opencode\", \"commands\", describedMd),\n // Cline reads workflows from `.clinerules/` — plain markdown, no frontmatter —\n // but its skills from `.cline/`. The two directories are unrelated, so both\n // count as a sighting.\n cline: {\n path: (name) => [\".clinerules\", \"workflows\", `${name}.md`],\n format: titledMd,\n skill: skillsIn(\".cline\"),\n detect: [\".cline\", \".clinerules\"],\n },\n // Amazon Q surfaces these as its PROMPT library, not as commands: the file is\n // invoked `@loam-check`, never `/loam-check`. Same file shape regardless.\n \"amazon-q\": dotTool(\".amazonq\", \"prompts\", describedMd),\n // Antigravity's directory is `.agent/`, which is why the id and the directory\n // do not match here as they do almost everywhere else.\n antigravity: dotTool(\".agent\", \"workflows\", describedMd),\n // Auggie is the Augment CLI: id `auggie`, directory `.augment/`.\n auggie: dotTool(\".augment\", \"commands\", hintedMd),\n // Skills-only, deliberately: Codex does not load custom command files, so it\n // declares no command emitter at all rather than laying down files nothing\n // will ever open.\n //\n // The skill path is `.codex/skills/`, and that is NOT what Codex documents.\n // Its own skills page (Build skills — developers.openai.com/codex/skills,\n // which now redirects to learn.chatgpt.com/docs/build-skills; checked\n // 2026-10-09) lists only `.agents/skills` — in the working directory, its\n // parents up to the repository root — and never mentions `.codex/skills`;\n // the `agents` entry below quotes it. `.codex/skills/` is undocumented but\n // still loaded (Codex PR #10317 and third-party guides describe it as the\n // older location, slated for deprecation), so the files this entry writes\n // are read today and the break is latent: the day Codex drops it, a repo\n // scaffolded for `codex` alone loses every skill silently. The path stays\n // put on purpose — moving it re-points files in every repo already\n // scaffolded, which is a migration with its own entry, not a comment fix —\n // and choosing `agents` alongside `codex` already gives Codex the\n // documented root.\n codex: {\n skill: skillsIn(\".codex\"),\n detect: [\".codex\"],\n },\n // Continue's prompt files take a bare `.prompt` extension, and `invokable`\n // is what promotes one from a template to something you can call.\n continue: {\n path: (name) => [\".continue\", \"prompts\", `${name}.prompt`],\n format: (c) =>\n `---\\nname: ${c.name}\\ndescription: ${c.description}\\ninvokable: true\\n---\\n\\n${c.body}`,\n skill: skillsIn(\".continue\"),\n detect: [\".continue\"],\n },\n // Crush namespaces like Gemini does — `.crush/commands/loam/check.md` is\n // `/loam:check` — but stays markdown.\n crush: {\n path: (name) => [\".crush\", \"commands\", \"loam\", `${unprefixed(name)}.md`],\n format: namedMd,\n skill: skillsIn(\".crush\"),\n detect: [\".crush\"],\n },\n // Devin Desktop, formerly Windsurf: Cascade-style workflows, and the rename\n // moved the directory from `.windsurf/` to `.devin/`. New files go to the new\n // directory; a repo still holding the old one is still a Devin repo, so both\n // are sightings.\n devin: {\n path: (name) => [\".devin\", \"workflows\", `${name}.md`],\n format: namedMd,\n skill: skillsIn(\".devin\"),\n detect: [\".devin\", \".windsurf\"],\n },\n factory: dotTool(\".factory\", \"commands\", hintedMd),\n junie: dotTool(\".junie\", \"commands\", describedMd),\n // Kilo Code workflows carry no frontmatter — the title line is all the\n // wrapper there is.\n kilocode: dotTool(\".kilocode\", \"workflows\", titledMd),\n // Kiro uses Copilot's `.prompt.md` extension under its own directory.\n kiro: {\n path: (name) => [\".kiro\", \"prompts\", `${name}.prompt.md`],\n format: describedMd,\n skill: skillsIn(\".kiro\"),\n detect: [\".kiro\"],\n },\n // Qwen Code retired its TOML commands in favour of markdown + frontmatter,\n // so this is markdown despite the Gemini lineage.\n qwen: dotTool(\".qwen\", \"commands\", describedMd),\n // Roo/Zoo Code: id `roocode`, directory `.roo/`, no frontmatter.\n roocode: dotTool(\".roo\", \"commands\", titledMd),\n trae: dotTool(\".trae\", \"commands\", namedMd),\n // The vendor-neutral skills root, and the only id here that names no vendor.\n // `.agents/skills/<name>/SKILL.md` is read by six of the tools that already\n // have entries above, each documenting it in its own words:\n //\n // Cursor — \"Skills are automatically loaded from `.agents/skills/`,\n // `.cursor/skills/`, `~/.agents/skills/` … and `~/.cursor/skills/`\"\n // (Agent Skills, cursor.com/docs/skills).\n // GitHub Copilot — \"For project skills, specific to a single repository,\n // create a `.github/skills`, `.claude/skills`, or `.agents/skills`\n // directory in your repository\" (Adding agent skills for GitHub Copilot,\n // docs.github.com/en/copilot → customize-cloud-agent/add-skills).\n // Codex — \"Codex scans `.agents/skills` in every directory from your\n // current working directory up to the repository root\" (Build skills,\n // developers.openai.com/codex/skills).\n // Gemini CLI — `.agents/skills/` is the workspace-tier alias for\n // `.gemini/skills/`, and \"within the same tier … takes precedence over\n // the `.gemini/skills/` directory\" (docs/cli/skills.md in\n // google-gemini/gemini-cli).\n // Zed — \"Skills are loaded from `~/.agents/skills/` and\n // `<worktree>/.agents/skills/` only\" (Agent Skills, zed.dev/docs/ai/skills).\n // Roo Code — `.agents/skills/` is its cross-agent project location, ranked\n // below `.roo/skills/` at the same level (Skills,\n // docs.roocode.com/features/skills).\n //\n // Skills only, for the reason spelled out on `codex`: not one of those six\n // documents a COMMAND file under this root, so a `.agents/commands/` would be\n // files nothing will ever open — the thing this entry exists to stop.\n //\n // Purely ADDITIVE, and that is the design rather than an implementation\n // detail. No tool above loses or re-points its own skill path: a repo already\n // carrying `.claude/skills/` keeps getting them there. This entry is for the\n // team that would rather have ONE copy those six read than six copies of the\n // same bytes, and choosing that is the user's call — which is why selecting\n // `agents` adds a target and never removes one.\n //\n // `detect` is `.agents/skills`, never a bare `.agents/`, for exactly\n // `github-copilot`'s reason. `.agents/` is a shared root that other\n // conventions also write into, so a bare marker would auto-scaffold into any\n // repository where something else had made the directory — the failure that\n // rules out `.github/` one entry up. It is also NOT `antigravity`'s\n // `.agent/`: one letter apart, unrelated conventions, and `existsSync` on\n // either says nothing about the other.\n agents: {\n skill: skillsIn(\".agents\"),\n detect: [\".agents/skills\"],\n },\n};\n"]}
@@ -25,9 +25,11 @@ the code. \`loam validate\` grades form and joins, never depth: green means the
25
25
  files agree with each other, and the bar is what "done" is measured against — a
26
26
  thin baseline that validates is thin, not done.
27
27
 
28
- 0. If there is no ./loam.json here, wire the repo first:
29
- \`loam init --docs <path-to-docs-repo> --service $1\` (add \`--create\` ONLY if the
30
- docs repo does not exist yet), then \`loam doctor\` — it resolves \`docsDir\`,
28
+ 0. If there is no ./loam.json here, wire the repo first with
29
+ \`loam init --docs <path-to-docs-repo> --service $1 --agent-profile service\`.
30
+ ONLY if the docs repo does not exist yet, this is the single-repo trial: run
31
+ \`loam init --docs <path-to-docs-repo> --create --service $1\` instead, and its one
32
+ checkout keeps the full profile. Then \`loam doctor\` — it resolves \`docsDir\`,
31
33
  checks the docs repo is one, and reports the service binding. Do not go on with
32
34
  \`docs-missing\`, \`services-missing\` or any \`doctor.*\` blocker standing.
33
35
  Every \`doctor\` finding carries a \`fix\` field spelling the exact command —
@@ -1 +1 @@
1
- {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/adopt.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EACT,kFAAkF;IACpF,YAAY,EAAE,cAAc;IAC5B,OAAO,EACL,iOAAiO;IACnO,UAAU,EAAE,iCAAiC;IAC7C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,+EAA+E;QAC/E,2IAA2I;QAC3I,gKAAgK;QAChK,yKAAyK;QACzK,kIAAkI;QAClI,yNAAyN;KAC1N;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyOP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** The adoption protocol: write one service's baseline docs from its code, as draft. */\nexport const LOAM_ADOPT: CommandContent = {\n name: \"loam-adopt\",\n description:\n \"Adopt a service — write its baseline docs from its code, as draft, then validate\",\n argumentHint: \"<service-id>\",\n purpose:\n \"Write one service's baseline documentation into the docs repo from its code, as `draft`. You read the code; loam states the work and checks the result — it never reads the service, so anything you cannot show, do not write.\",\n invocation: \"loam instructions loam-adopt $1\",\n placeholders: [\"service\"],\n spine: [\n \"wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet\",\n \"`loam adopt` — the brief: the order to read the service in, every file to write, the grammar of each, and what the fleet map already says\",\n \"walk the service in the brief's order — shape first, then one surface at a time — keeping the list of every path you open, because that list becomes `sources`\",\n \"write the artifacts, everything `status: draft` — the model EXTENDS the fleet map — then `loam subsystem sync` for the renderer wiring, which is loam's and never yours\",\n \"validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible\",\n \"hand back with what you could not determine from the code, which directories you never opened, the branch-to-scenario count per operation, and three behaviours your documents do not describe — then let a human vouch\",\n ],\n body: `Write one service's baseline documentation into the loam docs repo (its path is\n\\`docsDir\\` in ./loam.json). You read the code; loam states the work and checks the\nresult. It never reads the service — so anything you cannot show, do not write.\n\n**The bar this artifact set aims at is reproducibility**: a reader should be able\nto answer, from these files alone — what this boundary exposes, what it reaches,\nwhat shapes it exchanges, how it is run, and what pages whom — without opening\nthe code. \\`loam validate\\` grades form and joins, never depth: green means the\nfiles agree with each other, and the bar is what \"done\" is measured against — a\nthin baseline that validates is thin, not done.\n\n0. If there is no ./loam.json here, wire the repo first:\n \\`loam init --docs <path-to-docs-repo> --service $1\\` (add \\`--create\\` ONLY if the\n docs repo does not exist yet), then \\`loam doctor\\` — it resolves \\`docsDir\\`,\n checks the docs repo is one, and reports the service binding. Do not go on with\n \\`docs-missing\\`, \\`services-missing\\` or any \\`doctor.*\\` blocker standing.\n Every \\`doctor\\` finding carries a \\`fix\\` field spelling the exact command —\n type that, not an approximation of it.\n If this repo is already wired, \\`loam status --json\\` in the docs repo is the\n fastest way to see whether $1 is even the service that owes work next: its\n \\`next.adopt\\` entries name the services with nothing written down yet.\n1. \\`loam adopt --service $1 --json\\`. That output IS the brief:\n - \\`warnings[]\\` — read these FIRST. A near-miss against an existing service id\n usually means \\`$1\\` is a typo, and a brief followed to the letter then produces\n a complete, validating baseline for a service that does not exist.\n - \\`targets[]\\` — every file to write. \\`action: \"diff\"\\` means the file ALREADY EXISTS:\n read it, diff your findings against it, report what disagrees. Do not replace it.\n \\`action: \"edit\"\\` appears on exactly one target — \\`architecture/landscape.likec4\\`,\n the WHOLE FLEET's map: add this service's element and edges to it, never rewrite it.\n It appears while nothing resolves to the service AND while the element that does\n has no edge touching it; \\`targets[].shape\\` says which of the two you are in.\n It does NOT appear when loam could not READ the map (\\`landscape.modelled: null\\`\n — a broken landscape, or a broken sibling under \\`architecture/\\`): nothing is\n known about what the map already draws, so no write to the whole fleet's map is\n briefed. \\`landscape.instruction\\` carries the answer and names the documents that\n failed; fix those and re-run.\n - \\`targets[].shape\\` — the grammar of each artifact, and \\`example\\` where one is\n shorter than a description. Every rule there is one a later check depends on;\n rules nothing checks are in \\`unchecked[]\\` instead, and are still worth following.\n - \\`landscape\\` — the elements and edges the fleet already has for this service.\n Bind to them; do not draw a second version of the same box. \\`landscape.expects\\`\n lists operations other services already call — your openapi.yaml owes them.\n \\`landscape.instruction\\` is the write the fleet map still owes this service; it\n is \\`null\\` once an element resolves to it AND at least one edge ANYWHERE in the\n \\`architecture/\\` project touches that element (\\`landscape.touched\\`), and only\n then — one edge closes the state, so draw every call in one pass.\n \\`landscape.attested\\` lists the\n calls \\`services/$1/model.likec4\\` already declares across its boundary — those\n are the edges the map owes, collapsed to the service; an empty list on a\n service with no model is not licence to invent one, and the instruction says so.\n - \\`frontmatter\\` — what to put in the header of every markdown artifact.\n - \\`checks[]\\` — what \\`loam validate\\` will run. \\`unchecked[]\\` — what it will not.\n2. Read \\`AGENTS.md\\` at the docs repo root, then walk the service — \\`walk[]\\` in the\n same \\`adopt\\` output is the order, and it is an order, not a menu. Each stop\n names what to open, what to take from it, and which artifacts it feeds\n (\\`lands\\`). The first two stops fix what the service IS — how many processes,\n built from what — before any surface is enumerated: an agent that opens the\n HTTP routes first has already decided the service is an API by the time it\n meets the scheduler, and the consumer group and the outbox never get written\n down. Do not stop at the surface you recognise fastest.\n \\`walkClose\\` is the question that ends it: **list the directories you did not\n open.** Nothing downstream can find a service you documented a third of —\n \\`api.ungoverned\\` grades the operations you WROTE, and is silent about the twelve\n you never did.\n **Keep a list of every path you actually open.** That list becomes \\`sources\\` —\n written as files and directories, never glob patterns — and it is the only line\n tying the document to the repository. Never pad it with paths you did not read:\n it is a record, and \\`loam vouch\\` hashes exactly what it names.\n3. Write the artifacts under \\`services/$1/\\`, in the order the brief lists them, and\n make the landscape edit the brief asks for — the element where none resolves, the\n edges where the element stands alone, and the map's own housekeeping\n \\`unchecked[]\\` names: a placeholder tag to clear, a curated view to add the\n element to.\n **model.likec4 EXTENDS the fleet map.** It declares no \\`specification\\` block and\n no copy of anything the map already draws: its whole body is\n \\`model { extend <fqn> { …containers… } }\\`, where \\`<fqn>\\` is the FULLY-QUALIFIED\n id of the element bound to \\`services/$1/\\` (the brief's \\`landscape.elements\\` names\n it; \\`extend orderService\\` resolves to nothing when the id is\n \\`marketplace.orderService\\`). Kinds and tags come from\n \\`architecture/landscape.likec4\\` — a kind you need and the map does not declare\n (\\`element database\\`, \\`element queue\\`) is added THERE, once, in the same landscape\n edit. Cross-boundary edges are written OUTSIDE the extend block, from the container\n that makes the call to the other party's element as the map spells it; an element\n this file declares outside its own is \\`c4.element-unowned\\` (warn), and which fix\n applies depends on whose it is — a store or component this service OWNS belongs\n INSIDE the \\`extend\\` block (its id becomes \\`<fqn>.<name>\\`), a system the service\n merely reaches is declared in the map instead, and another service's internals\n belong in that service's model. If you are DIFFING an existing model\n that carries its own \\`specification\\`, leave it alone: that shape is still legal\n and still parsed on its own, and \\`c4.declaration-diverged\\` is what reports where\n its copies of the map's elements have drifted. Migrating one is the recipe in\n SCHEMA.md, \"Two shapes of a service model\" — not something to do inside an\n adoption you were not asked to widen.\n A flow behind one of your arch.spec.md requirements — a \\`dynamic view\\`\n over this service's own containers — goes in a \\`.likec4\\` beside model.likec4\n (\\`services/$1/usecases/<name>.likec4\\` by convention; \\`views.likec4\\` reads the\n same, because the renderer's per-service project reads every \\`.likec4\\` under the\n directory and so does loam), tagged \\`#req-<Requirement-ID>\\` of a requirement in\n this service's spec.md or arch.spec.md, with the tag declared in the map's\n \\`specification\\` block or in a tags-only \\`specification { tag req-… }\\` beside the\n \\`extend\\` — in exactly ONE of the two. Declared in both, the model's own project\n holds the tag twice: the error lands on whichever document carries the second\n declaration, as \\`c4.invalid\\` (an error, and the model's grading is suspended\n behind it) when that is model.likec4, or as \\`usecase.flow-invalid\\` when it is the\n sibling. Step 4 grades it. It never goes in \\`architecture/usecases/\\`,\n which cannot see your containers and turns the whole fleet \\`landscape.invalid\\`; a\n flow that crosses services goes there and is tagged \\`#cap-\\`. Never a \\`#cap-\\` tag\n beside a model. Then run \\`loam subsystem sync\\` (from\n this repo or the docs repo — it resolves \\`docsDir\\`): once the docs root carries\n the \\`likec4.config.json\\` step 0's doctor asks for, it keeps that file's\n \\`exclude\\` list covering exactly the service directories whose models stand alone —\n so an extending model stays inside the root project, where it is the only place it\n parses and where the renderer draws it beside the map — and writes\n \\`services/<…>/<id>/likec4.config.json\\` beside each STANDALONE model that has none.\n Both are loam's — never write one yourself, never list it in \\`sources\\`, never\n diff it, never hand-edit the root \\`exclude\\`'s \\`services/\\` entries — and \\`sync\\`\n leaves what it wrote untracked for you to commit. A repo whose models all extend\n the map usually has nothing for \\`sync\\` to write here, and that is the healthy\n state, not a skipped step.\n Everything \\`status: draft\\`. Never write \\`last_verified\\`, \\`sources_digest\\`,\n \\`content_digest\\` or \\`sources_files\\`.\n4. \\`loam validate --service $1 --json\\`. Fix every error. \\`c4.valid\\` reading\n \"extends the fleet map\" is the confirmation that the model was read as one\n document with the map; \\`c4.invalid\\` on this run means the model plus the map do\n not parse together, and every error is YOURS even where the message names\n \\`architecture/landscape.likec4\\` — the map parses clean on its own, or\n \\`spine.landscape-invalid\\` would say so instead and this model could not be read\n at all. \\`c4.element-unowned\\` (warn) is an element you declared outside the\n element you extend — nest it inside the \\`extend\\` block if this service owns it,\n declare it in the map if the service only reaches it. \\`sources.unvouched\\` is\n expected on a fresh baseline — it closes when a person vouches, not when you do.\n \\`sources.unwalked\\` is the walk graded against the repository: its \\`details\\` name\n the top-level paths git tracks and \\`sources\\` never reached into. Treat each one\n as a place to go back to, not as a line to silence — the fix is reading it, or\n one sentence in step 6 saying why this document does not owe it.\n \\`usecase.*\\` findings on this run are about the flows beside model.likec4:\n \\`usecase.step-unbacked\\` is a hop no relationship in model.likec4 backs,\n \\`usecase.step-contested\\` two edges backing one hop and naming different\n operations, \\`usecase.requirement-unresolved\\` a \\`#req-\\` tag naming no\n \\`Requirement-ID:\\` of this service's spec.md or arch.spec.md,\n \\`usecase.capability-unresolved\\` a \\`#cap-\\` tag beside a model (drop it — a\n capability is claimed on the fleet map, never inside one service), and\n \\`usecase.flow-invalid\\` a file beside the model that does not read — the flows\n were not graded, the model still was. \\`usecase.step-unlinked\\` never fires on a hop\n whose caller and provider resolve to the same service — a service owes no\n operationId to itself — but a hop from such a flow into another service's element\n (a stand-in model.likec4 declares for a sibling, with no \\`metadata { op }\\`) still\n warns here, exactly as it does on the fleet map.\n5. \\`loam validate --all --json\\` in the docs repo. This is the run that grades the\n fleet map: \\`landscape.service-unmodelled\\` means the element never landed,\n \\`landscape.binding-unknown\\` / \\`landscape.binding-duplicate\\` mean it landed wrong,\n and \\`landscape.missing\\` means there is no map at all. A baseline that passes step 4\n and fails this one is documented and invisible.\n \\`landscape.service-isolated\\` (a warning) means the element landed with no edge\n anywhere in \\`architecture/\\` while \\`services/$1/model.likec4\\` declares calls across\n its boundary — the map owes those edges. It goes quiet on the FIRST edge, so draw\n every call the brief listed in one pass: nothing will name the ones you leave out.\n A service whose model declares no such call and whose element\n has no edge is SILENT here: \\`landscape.touched: false\\` in step 1's brief is the\n only place that state is named, so read it before this run rather than hoping\n for it in it.\n Six warnings on this run are about the RENDERER's wiring rather than about any\n document, and one command fixes four of them: \\`service.model-excluded\\` (the root\n \\`likec4.config.json\\` excludes a directory holding a model that extends the map, so\n the renderer never loads it), \\`service.model-unexcluded\\` (its mirror, and the most\n damaging — a model that STANDS ALONE whose directory the root \\`exclude\\` does not\n cover merges into the root project and duplicates every kind the map declares,\n which blanks the whole project rather than one service),\n \\`service.likec4-config-stray\\` (a project file beside an extending model, which\n claims the model out of the root project),\n \\`service.likec4-config-missing\\` (a model that stands alone with no project file of\n its own) and, when the first four are clear, \\`c4.fleet-project-invalid\\` — every\n document reads clean where loam grades it and the ONE project the renderer builds\n out of the map plus every extending model does not parse, because something is\n declared in two of them. The first four: run \\`loam subsystem sync\\` — it rewrites\n the root \\`exclude\\`, writes the missing project file and DELETES the stray one. The\n fifth is an authoring fix: the message names the file and line, and the answer is\n to declare the tag or element once, in the map or in the single service that owns it.\n A sixth reads the same list and is NOT one of sync's: \\`landscape.excluded\\` says the\n root \\`exclude\\` covers \\`architecture/landscape.likec4\\` itself, so the renderer has no\n map to draw at all and every extending model in the root project resolves against\n nothing — delete or narrow that entry by hand, at the line the finding quotes. While\n it stands, \\`c4.fleet-project-invalid\\` is not graded at all: that check reads the\n renderer's project, so the one entry would come back once per model instead of once as\n the cause. Fix it first, then re-run and read the fifth.\n Done, stated once: step 4 clean when run from inside the service's own repo, this\n run reporting no \\`landscape.*\\` finding, no \\`service.*\\` renderer warning and no\n \\`c4.fleet-project-invalid\\`, and\n step 1's brief re-run reporting \\`landscape.touched: true\\` — or, for a service that\n truly makes and receives no call, a stated reason its element is edgeless.\n The fleet run is never SILENT —\n \\`sources.unverifiable-from-here\\` (severity ok) appears per service as a\n confirmation, not work — so \"keep going until validation is quiet\" is the wrong\n loop; the two runs are the test.\n6. Hand back, and say seven things:\n - what you could not determine from the code;\n - what the existing artifacts disagreed with;\n - which parts you are least sure of;\n - what you did not open — every path \\`sources.unwalked\\` named, with one line\n each on why the baseline does not owe it. That list is the only account\n anybody gets of how much of the service was actually read; loam can name the\n paths, and it can never say whether skipping one was right;\n - **two counts per operation**: how many decisions its code makes — permission\n checks, conditionally required fields, defaults and normalisations, refused\n transitions — and how many scenarios you wrote for it.\n \\`12 branches / 3 scenarios\\` is a fact a reader can act on; \"the\n requirements look complete\" is not, and no check loam has can tell the two\n apart;\n - **how well this service is covered by its own tests**, and how many of them\n became scenarios. Say the number the service's own coverage tooling\n reports, and where you read it. This is the ceiling on the whole baseline:\n a well-covered service can be documented from evidence, and a service with\n almost no tests can only be documented from a reading of its code, which is\n a weaker document however carefully it is written. Nothing in loam can\n compute this — say it, or nobody ever knows which kind they are holding;\n - **three behaviours this service has that your documents do not describe.**\n Not \"is this enough\" — that question is answered yes by every agent that\n ever wrote a baseline, including the ones that documented a third of a\n service, because the only thing it can be answered against is the document\n itself. Name three. If you looked for three and found none, say exactly\n that: it is a claim a reviewer can go and check, which \"it is complete\"\n is not.\n Then a human runs \\`loam vouch --service $1\\` in the service's own repo.\n\nAn empty docs repo's \\`loam status --json\\` names this recipe's own states as its\nfirst-hour ladder: \\`next.author-landscape\\` (the fleet map is missing, or still the\nscaffold's untouched bytes — step 3's landscape edit is what discharges it),\n\\`next.bind-service\\` (no service repository is wired to the fleet yet — step 0) and\n\\`next.adopt-first\\` (services/ is empty — this protocol, from step 1). Landing on\none of those codes means: run this recipe, starting at the step it points at.\n\nWhere the code does not say, write that it does not say. A confident sentence about\nbehaviour nobody can find is the one failure mode none of loam's checks can catch.\n`,\n};\n"]}
1
+ {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/adopt.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EACT,kFAAkF;IACpF,YAAY,EAAE,cAAc;IAC5B,OAAO,EACL,iOAAiO;IACnO,UAAU,EAAE,iCAAiC;IAC7C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,+EAA+E;QAC/E,2IAA2I;QAC3I,gKAAgK;QAChK,yKAAyK;QACzK,kIAAkI;QAClI,yNAAyN;KAC1N;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2OP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** The adoption protocol: write one service's baseline docs from its code, as draft. */\nexport const LOAM_ADOPT: CommandContent = {\n name: \"loam-adopt\",\n description:\n \"Adopt a service — write its baseline docs from its code, as draft, then validate\",\n argumentHint: \"<service-id>\",\n purpose:\n \"Write one service's baseline documentation into the docs repo from its code, as `draft`. You read the code; loam states the work and checks the result — it never reads the service, so anything you cannot show, do not write.\",\n invocation: \"loam instructions loam-adopt $1\",\n placeholders: [\"service\"],\n spine: [\n \"wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet\",\n \"`loam adopt` — the brief: the order to read the service in, every file to write, the grammar of each, and what the fleet map already says\",\n \"walk the service in the brief's order — shape first, then one surface at a time — keeping the list of every path you open, because that list becomes `sources`\",\n \"write the artifacts, everything `status: draft` — the model EXTENDS the fleet map — then `loam subsystem sync` for the renderer wiring, which is loam's and never yours\",\n \"validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible\",\n \"hand back with what you could not determine from the code, which directories you never opened, the branch-to-scenario count per operation, and three behaviours your documents do not describe — then let a human vouch\",\n ],\n body: `Write one service's baseline documentation into the loam docs repo (its path is\n\\`docsDir\\` in ./loam.json). You read the code; loam states the work and checks the\nresult. It never reads the service — so anything you cannot show, do not write.\n\n**The bar this artifact set aims at is reproducibility**: a reader should be able\nto answer, from these files alone — what this boundary exposes, what it reaches,\nwhat shapes it exchanges, how it is run, and what pages whom — without opening\nthe code. \\`loam validate\\` grades form and joins, never depth: green means the\nfiles agree with each other, and the bar is what \"done\" is measured against — a\nthin baseline that validates is thin, not done.\n\n0. If there is no ./loam.json here, wire the repo first with\n \\`loam init --docs <path-to-docs-repo> --service $1 --agent-profile service\\`.\n ONLY if the docs repo does not exist yet, this is the single-repo trial: run\n \\`loam init --docs <path-to-docs-repo> --create --service $1\\` instead, and its one\n checkout keeps the full profile. Then \\`loam doctor\\` — it resolves \\`docsDir\\`,\n checks the docs repo is one, and reports the service binding. Do not go on with\n \\`docs-missing\\`, \\`services-missing\\` or any \\`doctor.*\\` blocker standing.\n Every \\`doctor\\` finding carries a \\`fix\\` field spelling the exact command —\n type that, not an approximation of it.\n If this repo is already wired, \\`loam status --json\\` in the docs repo is the\n fastest way to see whether $1 is even the service that owes work next: its\n \\`next.adopt\\` entries name the services with nothing written down yet.\n1. \\`loam adopt --service $1 --json\\`. That output IS the brief:\n - \\`warnings[]\\` — read these FIRST. A near-miss against an existing service id\n usually means \\`$1\\` is a typo, and a brief followed to the letter then produces\n a complete, validating baseline for a service that does not exist.\n - \\`targets[]\\` — every file to write. \\`action: \"diff\"\\` means the file ALREADY EXISTS:\n read it, diff your findings against it, report what disagrees. Do not replace it.\n \\`action: \"edit\"\\` appears on exactly one target — \\`architecture/landscape.likec4\\`,\n the WHOLE FLEET's map: add this service's element and edges to it, never rewrite it.\n It appears while nothing resolves to the service AND while the element that does\n has no edge touching it; \\`targets[].shape\\` says which of the two you are in.\n It does NOT appear when loam could not READ the map (\\`landscape.modelled: null\\`\n — a broken landscape, or a broken sibling under \\`architecture/\\`): nothing is\n known about what the map already draws, so no write to the whole fleet's map is\n briefed. \\`landscape.instruction\\` carries the answer and names the documents that\n failed; fix those and re-run.\n - \\`targets[].shape\\` — the grammar of each artifact, and \\`example\\` where one is\n shorter than a description. Every rule there is one a later check depends on;\n rules nothing checks are in \\`unchecked[]\\` instead, and are still worth following.\n - \\`landscape\\` — the elements and edges the fleet already has for this service.\n Bind to them; do not draw a second version of the same box. \\`landscape.expects\\`\n lists operations other services already call — your openapi.yaml owes them.\n \\`landscape.instruction\\` is the write the fleet map still owes this service; it\n is \\`null\\` once an element resolves to it AND at least one edge ANYWHERE in the\n \\`architecture/\\` project touches that element (\\`landscape.touched\\`), and only\n then — one edge closes the state, so draw every call in one pass.\n \\`landscape.attested\\` lists the\n calls \\`services/$1/model.likec4\\` already declares across its boundary — those\n are the edges the map owes, collapsed to the service; an empty list on a\n service with no model is not licence to invent one, and the instruction says so.\n - \\`frontmatter\\` — what to put in the header of every markdown artifact.\n - \\`checks[]\\` — what \\`loam validate\\` will run. \\`unchecked[]\\` — what it will not.\n2. Read \\`AGENTS.md\\` at the docs repo root, then walk the service — \\`walk[]\\` in the\n same \\`adopt\\` output is the order, and it is an order, not a menu. Each stop\n names what to open, what to take from it, and which artifacts it feeds\n (\\`lands\\`). The first two stops fix what the service IS — how many processes,\n built from what — before any surface is enumerated: an agent that opens the\n HTTP routes first has already decided the service is an API by the time it\n meets the scheduler, and the consumer group and the outbox never get written\n down. Do not stop at the surface you recognise fastest.\n \\`walkClose\\` is the question that ends it: **list the directories you did not\n open.** Nothing downstream can find a service you documented a third of —\n \\`api.ungoverned\\` grades the operations you WROTE, and is silent about the twelve\n you never did.\n **Keep a list of every path you actually open.** That list becomes \\`sources\\` —\n written as files and directories, never glob patterns — and it is the only line\n tying the document to the repository. Never pad it with paths you did not read:\n it is a record, and \\`loam vouch\\` hashes exactly what it names.\n3. Write the artifacts under \\`services/$1/\\`, in the order the brief lists them, and\n make the landscape edit the brief asks for — the element where none resolves, the\n edges where the element stands alone, and the map's own housekeeping\n \\`unchecked[]\\` names: a placeholder tag to clear, a curated view to add the\n element to.\n **model.likec4 EXTENDS the fleet map.** It declares no \\`specification\\` block and\n no copy of anything the map already draws: its whole body is\n \\`model { extend <fqn> { …containers… } }\\`, where \\`<fqn>\\` is the FULLY-QUALIFIED\n id of the element bound to \\`services/$1/\\` (the brief's \\`landscape.elements\\` names\n it; \\`extend orderService\\` resolves to nothing when the id is\n \\`marketplace.orderService\\`). Kinds and tags come from\n \\`architecture/landscape.likec4\\` — a kind you need and the map does not declare\n (\\`element database\\`, \\`element queue\\`) is added THERE, once, in the same landscape\n edit. Cross-boundary edges are written OUTSIDE the extend block, from the container\n that makes the call to the other party's element as the map spells it; an element\n this file declares outside its own is \\`c4.element-unowned\\` (warn), and which fix\n applies depends on whose it is — a store or component this service OWNS belongs\n INSIDE the \\`extend\\` block (its id becomes \\`<fqn>.<name>\\`), a system the service\n merely reaches is declared in the map instead, and another service's internals\n belong in that service's model. If you are DIFFING an existing model\n that carries its own \\`specification\\`, leave it alone: that shape is still legal\n and still parsed on its own, and \\`c4.declaration-diverged\\` is what reports where\n its copies of the map's elements have drifted. Migrating one is the recipe in\n SCHEMA.md, \"Two shapes of a service model\" — not something to do inside an\n adoption you were not asked to widen.\n A flow behind one of your arch.spec.md requirements — a \\`dynamic view\\`\n over this service's own containers — goes in a \\`.likec4\\` beside model.likec4\n (\\`services/$1/usecases/<name>.likec4\\` by convention; \\`views.likec4\\` reads the\n same, because the renderer's per-service project reads every \\`.likec4\\` under the\n directory and so does loam), tagged \\`#req-<Requirement-ID>\\` of a requirement in\n this service's spec.md or arch.spec.md, with the tag declared in the map's\n \\`specification\\` block or in a tags-only \\`specification { tag req-… }\\` beside the\n \\`extend\\` — in exactly ONE of the two. Declared in both, the model's own project\n holds the tag twice: the error lands on whichever document carries the second\n declaration, as \\`c4.invalid\\` (an error, and the model's grading is suspended\n behind it) when that is model.likec4, or as \\`usecase.flow-invalid\\` when it is the\n sibling. Step 4 grades it. It never goes in \\`architecture/usecases/\\`,\n which cannot see your containers and turns the whole fleet \\`landscape.invalid\\`; a\n flow that crosses services goes there and is tagged \\`#cap-\\`. Never a \\`#cap-\\` tag\n beside a model. Then run \\`loam subsystem sync\\` (from\n this repo or the docs repo — it resolves \\`docsDir\\`): once the docs root carries\n the \\`likec4.config.json\\` step 0's doctor asks for, it keeps that file's\n \\`exclude\\` list covering exactly the service directories whose models stand alone —\n so an extending model stays inside the root project, where it is the only place it\n parses and where the renderer draws it beside the map — and writes\n \\`services/<…>/<id>/likec4.config.json\\` beside each STANDALONE model that has none.\n Both are loam's — never write one yourself, never list it in \\`sources\\`, never\n diff it, never hand-edit the root \\`exclude\\`'s \\`services/\\` entries — and \\`sync\\`\n leaves what it wrote untracked for you to commit. A repo whose models all extend\n the map usually has nothing for \\`sync\\` to write here, and that is the healthy\n state, not a skipped step.\n Everything \\`status: draft\\`. Never write \\`last_verified\\`, \\`sources_digest\\`,\n \\`content_digest\\` or \\`sources_files\\`.\n4. \\`loam validate --service $1 --json\\`. Fix every error. \\`c4.valid\\` reading\n \"extends the fleet map\" is the confirmation that the model was read as one\n document with the map; \\`c4.invalid\\` on this run means the model plus the map do\n not parse together, and every error is YOURS even where the message names\n \\`architecture/landscape.likec4\\` — the map parses clean on its own, or\n \\`spine.landscape-invalid\\` would say so instead and this model could not be read\n at all. \\`c4.element-unowned\\` (warn) is an element you declared outside the\n element you extend — nest it inside the \\`extend\\` block if this service owns it,\n declare it in the map if the service only reaches it. \\`sources.unvouched\\` is\n expected on a fresh baseline — it closes when a person vouches, not when you do.\n \\`sources.unwalked\\` is the walk graded against the repository: its \\`details\\` name\n the top-level paths git tracks and \\`sources\\` never reached into. Treat each one\n as a place to go back to, not as a line to silence — the fix is reading it, or\n one sentence in step 6 saying why this document does not owe it.\n \\`usecase.*\\` findings on this run are about the flows beside model.likec4:\n \\`usecase.step-unbacked\\` is a hop no relationship in model.likec4 backs,\n \\`usecase.step-contested\\` two edges backing one hop and naming different\n operations, \\`usecase.requirement-unresolved\\` a \\`#req-\\` tag naming no\n \\`Requirement-ID:\\` of this service's spec.md or arch.spec.md,\n \\`usecase.capability-unresolved\\` a \\`#cap-\\` tag beside a model (drop it — a\n capability is claimed on the fleet map, never inside one service), and\n \\`usecase.flow-invalid\\` a file beside the model that does not read — the flows\n were not graded, the model still was. \\`usecase.step-unlinked\\` never fires on a hop\n whose caller and provider resolve to the same service — a service owes no\n operationId to itself — but a hop from such a flow into another service's element\n (a stand-in model.likec4 declares for a sibling, with no \\`metadata { op }\\`) still\n warns here, exactly as it does on the fleet map.\n5. \\`loam validate --all --json\\` in the docs repo. This is the run that grades the\n fleet map: \\`landscape.service-unmodelled\\` means the element never landed,\n \\`landscape.binding-unknown\\` / \\`landscape.binding-duplicate\\` mean it landed wrong,\n and \\`landscape.missing\\` means there is no map at all. A baseline that passes step 4\n and fails this one is documented and invisible.\n \\`landscape.service-isolated\\` (a warning) means the element landed with no edge\n anywhere in \\`architecture/\\` while \\`services/$1/model.likec4\\` declares calls across\n its boundary — the map owes those edges. It goes quiet on the FIRST edge, so draw\n every call the brief listed in one pass: nothing will name the ones you leave out.\n A service whose model declares no such call and whose element\n has no edge is SILENT here: \\`landscape.touched: false\\` in step 1's brief is the\n only place that state is named, so read it before this run rather than hoping\n for it in it.\n Six warnings on this run are about the RENDERER's wiring rather than about any\n document, and one command fixes four of them: \\`service.model-excluded\\` (the root\n \\`likec4.config.json\\` excludes a directory holding a model that extends the map, so\n the renderer never loads it), \\`service.model-unexcluded\\` (its mirror, and the most\n damaging — a model that STANDS ALONE whose directory the root \\`exclude\\` does not\n cover merges into the root project and duplicates every kind the map declares,\n which blanks the whole project rather than one service),\n \\`service.likec4-config-stray\\` (a project file beside an extending model, which\n claims the model out of the root project),\n \\`service.likec4-config-missing\\` (a model that stands alone with no project file of\n its own) and, when the first four are clear, \\`c4.fleet-project-invalid\\` — every\n document reads clean where loam grades it and the ONE project the renderer builds\n out of the map plus every extending model does not parse, because something is\n declared in two of them. The first four: run \\`loam subsystem sync\\` — it rewrites\n the root \\`exclude\\`, writes the missing project file and DELETES the stray one. The\n fifth is an authoring fix: the message names the file and line, and the answer is\n to declare the tag or element once, in the map or in the single service that owns it.\n A sixth reads the same list and is NOT one of sync's: \\`landscape.excluded\\` says the\n root \\`exclude\\` covers \\`architecture/landscape.likec4\\` itself, so the renderer has no\n map to draw at all and every extending model in the root project resolves against\n nothing — delete or narrow that entry by hand, at the line the finding quotes. While\n it stands, \\`c4.fleet-project-invalid\\` is not graded at all: that check reads the\n renderer's project, so the one entry would come back once per model instead of once as\n the cause. Fix it first, then re-run and read the fifth.\n Done, stated once: step 4 clean when run from inside the service's own repo, this\n run reporting no \\`landscape.*\\` finding, no \\`service.*\\` renderer warning and no\n \\`c4.fleet-project-invalid\\`, and\n step 1's brief re-run reporting \\`landscape.touched: true\\` — or, for a service that\n truly makes and receives no call, a stated reason its element is edgeless.\n The fleet run is never SILENT —\n \\`sources.unverifiable-from-here\\` (severity ok) appears per service as a\n confirmation, not work — so \"keep going until validation is quiet\" is the wrong\n loop; the two runs are the test.\n6. Hand back, and say seven things:\n - what you could not determine from the code;\n - what the existing artifacts disagreed with;\n - which parts you are least sure of;\n - what you did not open — every path \\`sources.unwalked\\` named, with one line\n each on why the baseline does not owe it. That list is the only account\n anybody gets of how much of the service was actually read; loam can name the\n paths, and it can never say whether skipping one was right;\n - **two counts per operation**: how many decisions its code makes — permission\n checks, conditionally required fields, defaults and normalisations, refused\n transitions — and how many scenarios you wrote for it.\n \\`12 branches / 3 scenarios\\` is a fact a reader can act on; \"the\n requirements look complete\" is not, and no check loam has can tell the two\n apart;\n - **how well this service is covered by its own tests**, and how many of them\n became scenarios. Say the number the service's own coverage tooling\n reports, and where you read it. This is the ceiling on the whole baseline:\n a well-covered service can be documented from evidence, and a service with\n almost no tests can only be documented from a reading of its code, which is\n a weaker document however carefully it is written. Nothing in loam can\n compute this — say it, or nobody ever knows which kind they are holding;\n - **three behaviours this service has that your documents do not describe.**\n Not \"is this enough\" — that question is answered yes by every agent that\n ever wrote a baseline, including the ones that documented a third of a\n service, because the only thing it can be answered against is the document\n itself. Name three. If you looked for three and found none, say exactly\n that: it is a claim a reviewer can go and check, which \"it is complete\"\n is not.\n Then a human runs \\`loam vouch --service $1\\` in the service's own repo.\n\nAn empty docs repo's \\`loam status --json\\` names this recipe's own states as its\nfirst-hour ladder: \\`next.author-landscape\\` (the fleet map is missing, or still the\nscaffold's untouched bytes — step 3's landscape edit is what discharges it),\n\\`next.bind-service\\` (no service repository is wired to the fleet yet — step 0) and\n\\`next.adopt-first\\` (services/ is empty — this protocol, from step 1). Landing on\none of those codes means: run this recipe, starting at the step it points at.\n\nWhere the code does not say, write that it does not say. A confident sentence about\nbehaviour nobody can find is the one failure mode none of loam's checks can catch.\n`,\n};\n"]}
@@ -77,7 +77,7 @@ and say it.
77
77
  | \`spec.requirement-id-invalid\` | a \`Requirement-ID\` violates \`[A-Za-z][A-Za-z0-9._-]{0,127}\` | replace it with a portable stable ID; do not remove an established ID merely to silence the check |
78
78
  | \`spec.requirement-id-repeated\` | one requirement declares \`Requirement-ID\` more than once | keep exactly one identity line |
79
79
  | \`spec.requirement-id-duplicate\` | one ID identifies multiple requirements in the same file | give each requirement its own stable ID |
80
- | \`openapi.generated-stale\` (warn) | \`loam.json\` names \`contracts.openapi\` — where this repo's BUILD writes its contract — and that document differs from the committed \`services/<id>/openapi.yaml\`. The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a contract the service no longer serves. Compared by a digest over canonical JSON, so key order and formatting never fire it. Service-repo only; silent with no \`contracts\` block | review the difference and copy the generated document across — loam never writes it for you, because the committed contract is one somebody agreed to, not a cache of the build |
80
+ | \`openapi.generated-stale\` (warn) | \`loam.json\` names \`contracts.openapi\` — where this repo's BUILD writes its contract — and that document differs from the committed \`services/<id>/openapi.yaml\`. The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a contract the service no longer serves. Compared by a digest over canonical JSON, so key order and formatting never fire it. The \`details\` name the operationIds that differ — served but not in the committed copy, committed but no longer served — and the message says when the sets agree, so a reworded description reads apart from vanished endpoints. Service-repo only; silent with no \`contracts\` block | review the difference and copy the generated document across — loam never writes it for you, because the committed contract is one somebody agreed to, not a cache of the build |
81
81
  | \`contracts.source-missing\` (error) | \`contracts.openapi\` names a path and nothing is there — usually CI validating before it builds | run the build first, or correct the path |
82
82
  | \`contracts.source-invalid\` (error) | \`contracts.openapi\` resolves outside the repository, or names a file that does not parse as YAML or JSON | correct the path, or fix the document the build emitted |
83
83
  | \`spec.unknown-directive\` (warn) | a requirement body line whose key is one or two edits from a directive and is not one — \`Realises:\`, \`Capabilties:\`, \`Opertaions:\`. This is the one case where the corpus is quieter than it looks: an unrecognised key is prose, so the join it resembles simply does not exist and every check over it stays green for want of anything to fail. Measured by edit distance, not by prefix, so ordinary prose keys (\`Note:\`, \`Context:\`, \`Owner:\`) do not fire it; fenced blocks and table cells are skipped, because a directive spelling inside an example is a sample value | correct the spelling, or reword the line so it does not read as a directive |
@@ -94,7 +94,7 @@ and say it.
94
94
  | \`openapi.ref-unresolved\` (warn here; error at archive plan time) | internal \`$ref\`(s) in the living contract that resolve to nothing in the document — the operation validates while its schema is silently absent. External refs (not starting \`#/\`) are never checked | define the missing component or fix the pointer the \`details\` name |
95
95
  | \`openapi.baseline-stale\` | the living operation — or a pinned path-level key or component — changed after this delta edited it | somebody landed a change to it in between. Re-read the living value, fold in what you still mean, then \`loam rebase <FEAT>\` |
96
96
  | \`openapi.baseline-missing\` (warn, gates archive; one per service) | operations, path-level keys or components in this feature's openapi.yaml carry no baseline (\`x-loam-based-on\` / \`x-loam-baselines\`) | run \`loam rebase <FEAT>\`. Until you do, archive refuses: the merge cannot tell the surfaces you EDITED from the ones you merely restated, so it would upsert all of them — and every restated one reverts whatever landed on it. \`--approve\` merges unpinned deliberately |
97
- | \`openapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest, one on an operation the living contract has no slot for, a malformed \`x-loam-baselines\` record, or a record entry naming a surface the delta does not restate (or whose living value is gone) | \`loam rebase\` writes both; a new value has no living version to be based on, so it carries no entry |
97
+ | \`openapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest, one on an operation the living contract has no slot for, a malformed \`x-loam-baselines\` record, or a record entry naming a surface the delta does not restate (or whose living value is gone) | \`loam rebase\` writes both; a new value has no living version to be based on, so it carries no entry. A pinned operation whose living one a parallel feature has since REMOVED lands here too, and \`openapi.op-revived\` fires beside it — the pinned quote reports both: re-read the living contract and delete the whole operation from the delta — dropping only the pin clears this one and leaves \`openapi.op-revived\`, because unpinned it reads as an addition. Keep it only if this feature deliberately brings the operation back: then drop the pin and archive with \`--approve\` past \`openapi.op-revived\`. One a parallel feature MOVED to another slot (or retired here, before another re-added it elsewhere) lands here as well, and the message names where it lives now: delete the quote and make any change there — archiving past it while it still lives there would answer the operationId at both addresses; to move it back on purpose, drop the pin and add a removal marker at that address, or define another operation there. If you edited it, settle with that feature first |
98
98
  | \`spec-api.op-undefined\` | a LIVING requirement's \`Operations:\` line names an operationId this service's openapi.yaml does not define (the same code fires inside a feature delta) | define the endpoint, or correct the \`Operations:\` line — the two axes disagree about what the service exposes |
99
99
  | \`api.ungoverned\` (warn) | operation(s) no requirement's \`Operations:\` line names | write the requirement, or link an existing one |
100
100
  | \`api.ops-unlinked\` (warn) | operations AND requirements exist but zero \`Operations:\` lines join them — the API axis is vacuously green | link each requirement to the operations it governs |
@@ -104,7 +104,7 @@ and say it.
104
104
  | \`spine.op-link-missing\` (warn) | a landscape "Calls" edge into this service with no \`metadata { op }\` | link it to the operationId |
105
105
  | \`spine.op-deprecated\` (warn) | a landscape edge calls an operation this service's OpenAPI marks \`deprecated: true\` — the consumer is standing on a contract being retired | migrate the consumer to the replacement operation; deprecation is the first step of retiring an op, and the op stays defined until a human removes it |
106
106
  | \`service.no-asyncapi\` | no asyncapi.yaml, and message link(s) already point into it — a landscape edge's \`metadata { publishes }\` / \`metadata { consumes }\`, or a LIVING requirement's \`Publishes:\` / \`Consumes:\` line. The \`details\` list the stranded message names. Silent when nothing joins into it: an async contract is optional, and most services touch no topic | put the file back — every link the \`details\` name resolves to nothing until it is there |
107
- | \`asyncapi.invalid\` | asyncapi.yaml exists but does not parse — an unreadable contract proves nothing, so no \`event.*\` or message-spine finding is graded against it. The same code fires for a FEATURE's own asyncapi.yaml under \`--feature\`, where it gates the archive and suspends the service's event-axis checks the same way | fix the YAML first — the event axis is unchecked until it reads |
107
+ | \`asyncapi.invalid\` | asyncapi.yaml exists but does not read as AsyncAPI 3.0: it does not parse, or its \`asyncapi:\` field says 1.x or 2.x (whose \`publish\`/\`subscribe\` loam does not read — the \`details\` name the version). An unreadable contract proves nothing, so no \`event.*\` or message-spine finding is graded against it. The same code fires for a FEATURE's own asyncapi.yaml under \`--feature\`, where it gates the archive and suspends the service's event-axis checks the same way | fix the YAML first, or upgrade an older document with \`asyncapi convert --target-version=3.0.0\` (the AsyncAPI CLI) — the event axis is unchecked until it reads |
108
108
  | \`asyncapi.remove-marker-living\` | a living asyncapi.yaml carries feature-only \`x-loam-remove: true\` — at a \`channels.<key>\` / \`operations.<key>\` / \`components.messages.<key>\` slot, or nested on an inline channel message | remove the marker from the living contract; retire a slot through a feature delta's asyncapi.yaml instead |
109
109
  | \`asyncapi.duplicate-message\` (warn) | one message name is declared in two slots — every join on the name (an edge's \`metadata { publishes }\`, a requirement's \`Publishes:\` line) then picks one of them arbitrarily | give each declaration its own name, or factor the second into a \`$ref\` at the first |
110
110
  | \`asyncapi.payload-undescribed\` (warn) | message(s) whose payload declares no shape at all — absent, or bare \`type: object\` with not one property. The name joins the spine while nothing defines what a consumer would read. A presence probe: a payload declaring a non-JSON \`schemaFormat\` (Avro) is never judged | declare \`properties\` on the payload, or the \`schemaFormat\` of its non-JSON schema |
@@ -116,7 +116,7 @@ and say it.
116
116
  | \`asyncapi.message-contested\` (warn) | two or more services declare they send one message name — every consumer's join picks one of them arbitrarily | namespace the message by its owning domain; one message has one producer, and which contract a consumer reads must not be a coin flip |
117
117
  | \`event.messages-unlinked\` (warn) | messages AND requirements exist but zero \`Publishes:\`/\`Consumes:\` lines join them — the event axis is vacuously green | link each requirement to the messages it governs |
118
118
  | \`event.ungoverned\` (warn) | message(s) declared in asyncapi.yaml that no requirement's \`Publishes:\`/\`Consumes:\` line names — \`api.ungoverned\`'s event twin, and the per-message half \`event.messages-unlinked\` cannot see: that code stays silent once even one message is linked | write the requirement that governs each named message, or drop the declaration |
119
- | \`covers.unknown\` (warn) | a \`Covers:\` entry in arch.spec.md resolves to no element, edge, alert or SLI — living and feature deltas alike | fix the id (the message offers close ones); a mistyped entry silently costs the coverage it was written for |
119
+ | \`covers.unknown\` (warn) | a \`Covers:\` entry in arch.spec.md resolves to no element, edge, alert or SLI — living and feature deltas alike. Not raised for a service whose model.likec4 could not be read (\`c4.invalid\`, or \`spine.landscape-invalid\` for a model that extends the map): nothing is graded against a model nobody could read | fix the id (the message offers close ones); a mistyped entry silently costs the coverage it was written for |
120
120
  | \`usecase.step-unbacked\` | a hop of a \`#req-\`-tagged \`dynamic view\` in this service's own LikeC4 project — any \`.likec4\` beside model.likec4, or a view inside it — that NO relationship in model.likec4 backs. LikeC4 reports no error for it, so the diagram renders and the renderer's project parses; only this convicts it. The message reads \`<svc>: services/<tree>/<file> — dynamic view '<id>' step N\` | draw the edge in \`model { }\` (with \`metadata { op '<operationId>' }\` where it is a call), or, if the hop is a return step, write it as \`<target> <- <source>\` |
121
121
  | \`usecase.step-contested\` (warn) | two or more relationships in model.likec4 back one hop of a service-local flow and name DIFFERENT operations — an absent \`metadata { op }\` counts as a value — so nothing can say which one the hop exercises; \`details\` lists every candidate | give the candidates one operation, or draw the hop between the elements that name exactly one |
122
122
  | \`usecase.step-unlinked\` (warn) | the same code as the fleet row below, raised on a service-local flow: the relationship backing a hop of a \`#req-\`-tagged \`dynamic view\` beside model.likec4 carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's caller is not a person and its target resolves to a real \`services/<id>/\` — the stand-in a model declares for a sibling service is the case that reaches it here. Never between two containers of THIS service: a service owes no operationId to itself. The \`--all\` row spells out all four mechanical guards | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
@@ -153,13 +153,13 @@ and say it.
153
153
  | \`gherkin.stale\` (warn) | a generated scenario's digest matches no living scenario while its requirement still exists — the spec moved under the file (a reworded scenario reports stale + missing together) | regenerate; never edit a generated file to catch it up |
154
154
  | \`gherkin.orphaned\` (warn) | a generated file whose requirement no longer exists in the living spec (a file for a feature still in flight is exempt — it answers to its feature until it archives) | regenerate; the orphaned file is deleted and reported |
155
155
  | \`gherkin.path-outside\` | this repo's \`gherkinDir\` resolves outside the repository — a \`..\` segment, an absolute path, or a symlink pointing away — so generating would write where nothing here can review it. Refused before any file is read, not repaired | point \`gherkinDir\` in loam.json at a directory INSIDE this repository; it is the suite's own home, and a path outside it is a write nobody's diff would show |
156
- | \`evidence.checked\` (ok) | every evidence pin on the federated verification records this service attested re-resolved clean against this working tree. Fires only in the service's own repo, like \`sources.*\` — from the docs repo the whole \`evidence\` family is silent | nothing — it is the confirmation |
156
+ | \`evidence.checked\` (ok) | every evidence pin on the federated verification records this service attested re-resolved clean against this working tree. Fires only in the service's own repo, like \`sources.*\` — from the docs repo the service target's whole \`evidence\` family is silent, and only \`evidence.record-unreadable\` still fires there, on the feature target | nothing — it is the confirmation |
157
157
  | \`evidence.unpinned\` (ok) | agent-confirmed citations recorded before evidence pins existed, so nothing can grade their drift | the next \`loam verify <FEAT> --service <id> --record\` stamps pins; not work on its own |
158
158
  | \`evidence.unresolved\` (warn) | a pinned citation cannot be re-checked: the cited file is gone, unreadable, unsafe, not a regular file, or the cited line is past its end | re-read the evidence in this tree, re-answer, and re-record from this repo — a pin lint never moves the verdict by itself |
159
159
  | \`evidence.moved\` (warn) | a cited file's normalized sha256 differs from the pin while the cited line's text survives — the mildest drift: the file changed around the citation | re-read the cited file; re-record once the answers are re-checked |
160
160
  | \`evidence.line-changed\` (warn) | the cited line no longer says what the pin recorded — the citation no longer shows what it showed | re-read the evidence and re-answer; the claim may still be true, but this line no longer backs it |
161
161
  | \`evidence.token-missing\` (warn) | the cited file contained the literal the claim asserts (the operationId, message name or edge op the pin carries as \`token\`) at the attested commit, and no longer does — a token the commit never contained is warned once at record time and never stamped, so this grade always convicts a disappearance | check whether the code moved or the claim stopped being true; re-answer and re-record from this repo |
162
- | \`evidence.record-unreadable\` (warn) | a feature's verification.yaml exists but cannot be read as a record (broken YAML, a malformed \`evidence_pins\` block, a summary contradicting its claims) — none of its evidence was checked, and a silent skip would read as clean | repair the YAML by hand, or re-record from this repo; loam never overwrites a record it could not read |
162
+ | \`evidence.record-unreadable\` (warn) | a file of a feature's verification record (its \`verification.yaml\`, or a \`verification/<svc>.yaml\`) exists but cannot be read as a record (broken YAML, git conflict markers, a malformed \`evidence_pins\` block, a summary contradicting its claims) — none of its evidence was checked, and a silent skip would read as clean | follow the repair the finding names for that file: this service's own \`verification/<svc>.yaml\` may be repaired, or deleted and re-recorded from this repo; the one-file \`verification.yaml\` may hold other services' attestations, so it is repaired or restored from version control, never deleted. loam never overwrites a record it could not read |
163
163
 
164
164
  \`--feature <FEAT-id>\` — a change's three axes against each other. The SAME checks
165
165
  gate \`loam archive\` (errors block it; warnings never do), so a clean run here is
@@ -180,25 +180,27 @@ what lets a feature ship:
180
180
  | \`c4-api.op-undefined\` | an edge calls an operation the target does not expose | a broken contract between services — fix the caller or add the endpoint |
181
181
  | \`c4-api.op-pending\` (warn) | the called operation is defined by another feature in flight | archive that feature first |
182
182
  | \`c4-api.op-deprecated\` (warn) | a NEW tagged edge consumes an operation the living provider's OpenAPI marks \`deprecated: true\` — building new consumption on a dying op (quiet when this feature's own openapi delta drops the flag: that IS the un-deprecation) | point the edge at the replacement operation, or say why the deprecated one is right; never gates archive |
183
- | \`c4-api.op-removing\` | a NEW tagged edge consumes an operation this feature removes | remove or redirect the new edge; one feature cannot retire and add consumption of the same operation |
183
+ | \`c4-api.op-removing\` | a NEW tagged edge consumes an operation this feature removes — with a removal marker, or by defining another operation at its method and path so the merge leaves it defined nowhere | remove or redirect the new edge; one feature cannot retire and add consumption of the same operation |
184
184
  | \`openapi.remove-target-missing\` | an \`x-loam-remove: true\` marker's exact path+method does not exist in living OpenAPI | update the stale marker to the current slot, or drop it if the operation is already gone |
185
185
  | \`openapi.remove-target-mismatch\` | the marker's operationId differs from the living operation at that path+method | name the living operation exactly; loam never deletes the different operation occupying the slot |
186
- | \`openapi.remove-marker-missing\` | a REMOVED requirement governs an operation but the feature has no removal marker | add the exact path/method operation with its operationId and \`x-loam-remove: true\` to the feature openapi.yaml |
186
+ | \`openapi.remove-marker-missing\` | a REMOVED requirement governs an operation the service's living contract still defines, but the feature has no removal marker for it. Owed only while there is a living operation to retire: not for an id the service's own contract does not define (a consumer's REMOVED requirement naming another service's operation), and not for one this delta already replaces at its method and path. A living contract that does not parse proves neither, so the marker is still owed there | add the exact path/method operation with its operationId and \`x-loam-remove: true\` to the feature openapi.yaml |
187
187
  | \`openapi.remove-marker-unjustified\` | a removal marker is not named by any REMOVED requirement's \`Operations:\` line | remove the marker or retire the governing requirement in the same feature |
188
188
  | \`openapi.remove-marker-anonymous\` | an \`x-loam-remove: true\` marker with no \`operationId\` — loam cannot tell which operation it retires, and the marker itself would be written into the living contract | name the operation the marker retires, exactly as the living contract spells it |
189
- | \`openapi.remove-op-consumed\` | the feature retires an operation the LIVING fleet still consumes — a landscape edge's \`metadata { op }\`, or another service's living requirement naming it | fix the consumer in the same feature (redirect the edge, retire its requirement), or archive with \`--approve\` if the consumer is already gone in code |
189
+ | \`openapi.remove-op-consumed\` | the feature takes out of the contract an operation the fleet would still consume after this merge — read once for the gate and \`loam diff\` alike: an edge of the \`architecture/\` project with \`metadata { op }\` into the provider; another service's requirement (spec.md or arch.spec.md) whose \`Operations:\` line names it, unless that service's own contract defines the same id (then it names its own); a use-case hop attributed to it; and the provider's OWN spec.md requirements (an arch.spec.md \`Operations:\` line is never graded against its own contract, so the provider's strands nothing) and self-edges still naming it. Every requirement is read as the merge would leave it — one this feature MODIFIES to drop the id or REMOVES no longer counts, another service's included, and an unappliable spec delta fails closed. Two shapes: a removal marker retires it, or the delta defines ANOTHER operation at its method and path, and the contract the merge would write then defines it nowhere (the merge writes per method and path, so it replaces it there). The message names what replaces it and where; \`details[]\` lists the consumers. A consumer document loam cannot read — a spec.md or arch.spec.md that is not UTF-8, the provider's own spec.md included, or a service id two directories claim — refuses too, named in \`details[]\`: nobody could prove it does not name the operation. An edge into the provider drawn with no \`metadata { op }\`, or a use-case hop whose edges name different operations, does not refuse (\`loam diff\` lists them as doubts) | make each document listed readable (re-save it as UTF-8, or leave one directory per service id); fix each consumer in the same change — drop or redraw the edge by editing the living map (the landscape merge only adds edges, so a \`delta.likec4\` can never take one away), MODIFY or REMOVE the requirement in this feature's \`specs/<service>/\` delta (a REMOVED one owes no marker where the overwrite already takes the operation out) — or archive with \`--approve\` if the consumer is already gone in code. An overwrite: define the replaced operation at another address in the same delta if it is to stay; for a rename, redraw the living edge and restate the governing requirement as a MODIFIED naming the new operation |
190
+ | \`openapi.op-revived\` (warn, gates archive) | the feature's openapi.yaml spells an operation the living contract no longer has, and an ARCHIVED feature's removal marker retired it — or spells it at an address an archived feature MOVED it away from (or retired it from, before another re-added it elsewhere), so the merge would answer it at both addresses (one operationId at two slots). Judged on the contract the merge would write: an operation the living contract still holds that ends up at one address is not graded — moved back with its own removal marker where it lives now, or swapped out there by another operation, with or without an operationId — and a removal marker where the operation does not live removes nothing, so it excuses nothing. The message names the operation, the archived feature(s), what each did there (moved it, and where to, or retired it) and, apart from that, where it lives now — and, when another operation lives at the address the delta spells it at, names that one too (one written with no operationId by what it is): the merge writes per method and path, so archiving replaces it there. Absent from living reads like an addition, so the merge would put it back. Read from the markers under \`features/archive/\`, counted only where that feature's snapshot shows the merge carried them out: an operation removed by hand-editing a living contract, or retired by an archive since deleted from disk, is invisible to it | retired: delete the quote if you only restated it; archive with \`--approve\` if the feature brings it back on purpose (drop its \`x-loam-based-on\` first — a pin over nothing is \`openapi.baseline-invalid\`). Two addresses: delete the quote and make any change at the address the message names; to move it back on purpose, add a removal marker for it at that address in the same delta (or define another operation there) — and where another operation lives at the quoted address now, the delta replaces it, so define that one at another address in it if it is to stay. Where the delta already spells that address, no marker can stand beside what it holds there, and the message offers \`--approve\` instead |
190
191
  | \`c4-event.message-undefined\` | a tagged edge \`publishes\`/\`consumes\` a message its bound service's asyncapi contract — as this feature's merge would leave it; feature ∪ living where no delta exists — declares no matching \`action: send\`/\`receive\` operation for | declare the operation in the feature's asyncapi.yaml, or correct the edge |
191
192
  | \`c4-event.message-pending\` (warn) | the named message is introduced by another feature in flight | archive that feature first |
192
193
  | \`spec-event.message-undefined\` | a delta requirement's \`Publishes:\`/\`Consumes:\` line names a message the service's asyncapi contract — as this feature's merge would leave it, so a retired declaration stops answering and a leftover line on a surviving requirement is caught before it breaks the post-archive fleet — does not declare in that direction; the same code validate gives the living-side breach | declare it in the feature's asyncapi.yaml, retire the line with its requirement, or correct it |
193
194
  | \`spec-event.message-pending\` (warn) | the named message is introduced by another feature in flight | archive that feature first |
194
195
  | \`asyncapi.baseline-missing\` (warn, gates archive; one per service) | slots or component surfaces in this feature's asyncapi.yaml carry no baseline pin (\`x-loam-based-on\` / \`x-loam-baselines\`) | run \`loam rebase <FEAT>\` — until then the merge cannot tell the slots and surfaces you EDITED from the ones you merely restated, and every restated one would revert whatever landed on it. \`--approve\` merges unpinned deliberately |
195
196
  | \`asyncapi.baseline-stale\` | the living slot, or the living component surface, changed after this delta pinned it | somebody landed a change in between. Re-read the living value, fold in what you still mean, then \`loam rebase <FEAT>\` |
196
- | \`asyncapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest or one on a slot the living contract has no counterpart for; a malformed \`x-loam-baselines\` record, or an entry of it naming a component surface the delta does not declare (or whose living value is gone) | \`loam rebase\` writes both; a new channel, operation, message or component has no living version to be based on, so it carries no pin |
197
+ | \`asyncapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest or one on a slot the living contract has no counterpart for; a malformed \`x-loam-baselines\` record, or an entry of it naming a component surface the delta does not declare (or whose living value is gone) | \`loam rebase\` writes both; a new channel, operation, message or component has no living version to be based on, so it carries no pin. A pinned slot whose living one a parallel feature has since REMOVED lands here too: re-read the living contract and delete the slot from the delta, unless this feature deliberately brings it back — then drop the pin and archive with \`--approve\` past \`asyncapi.slot-revived\`. A pinned \`components.messages\` key whose message a parallel feature MOVED elsewhere (another key, or inline under a channel) — or retired there, before another re-declared it elsewhere — lands here as well, and the message names where it is declared now: delete the quote and make any change there — archiving past it would declare the name in two places; to move it back on purpose, drop the pin and take the name out of there as \`asyncapi.slot-revived\`'s row says |
198
+ | \`asyncapi.slot-revived\` (warn, gates archive) | the feature's asyncapi.yaml declares a \`channels\`/\`operations\` slot, or a message by name, that the living contract no longer has, and an ARCHIVED feature retired it — a slot marker, or a channel removal that took the channel's inline messages (named from that feature's \`.loam-before/\` snapshot) — or declares a message at a key an archived feature MOVED it away from (or retired it from, before another re-declared it elsewhere), so the merge would declare its name in two places (the message says what that feature did there, where the name is declared now, and any other message declared at that key now, which archiving replaces there) — judged on the contract the merge would write, so a delta that also takes the name out of where it is declared now (restating or retiring the channel it stands inline in, a marker at its \`components.messages\` key, or another message declared at that key) is moving it back, and is not graded. Absent from living reads like an addition, so the merge would put it back. A message inline under a channel the delta brings back is that channel's finding, unless the merge would also declare its name elsewhere — then it is a two-places finding of its own. A removal made by hand-editing a living contract, or an archive since deleted from disk, is invisible to it | retired: delete the quote if you only restated it; archive with \`--approve\` if the feature brings it back on purpose. Two places: delete the quote and make any change where the message says it is declared now; to move it back on purpose, take it out of there in the same delta — a removal marker at a \`components.messages\` key, AND every channel (or operation) the message names as still \`$ref\`-ing that key restated with the \`$ref\` pointed at the quoted key (retired alone, the key leaves them pointing at nothing, and their send stops sending the message: \`asyncapi.remove-message-consumed\`); for a declaration inline under a channel, restate channel \`<ck>\` with a \`$ref\`, or retire channel \`<ck>\` (a marker nested on the inline message retires nothing: \`asyncapi.remove-marker-inline\`); where another message is declared at the quoted key now, define it at another key to keep it. Where the delta already spells that key, the message offers \`--approve\` instead |
197
199
  | \`asyncapi.remove-target-missing\` | an \`x-loam-remove: true\` marker addresses a slot the living asyncapi does not have | update the stale marker to the current key, or drop it if the slot is already gone |
198
200
  | \`asyncapi.remove-target-mismatch\` | the marker's message name differs from the living declaration at that key | name the living message exactly; loam never deletes a different message occupying the slot |
199
- | \`asyncapi.remove-marker-missing\` | a REMOVED requirement's \`Publishes:\`/\`Consumes:\` line names a message the living contract still declares, with no matching marker in the feature's asyncapi.yaml | add the \`components.messages\` entry with the message's name and \`x-loam-remove: true\`; a message the living contract declares only INLINE under a channel is retired by that channel's own removal marker (\`channels.<key>: {x-loam-remove: true}\`) instead |
201
+ | \`asyncapi.remove-marker-missing\` | a REMOVED requirement's \`Publishes:\`/\`Consumes:\` line names a message the living contract still declares, and the contract the merge would write still declares it, with no matching marker in the feature's asyncapi.yaml (a message this delta already takes out — a slot re-keyed to another name — owes none) | add the \`components.messages\` entry with the message's name and \`x-loam-remove: true\`; a message the living contract declares only INLINE under a channel is retired by that channel's own removal marker (\`channels.<key>: {x-loam-remove: true}\`) instead |
200
202
  | \`asyncapi.remove-marker-unjustified\` | a message-removal marker no REMOVED requirement's \`Publishes:\`/\`Consumes:\` line justifies (channel and operation slots need exactness only — requirements never join on their keys) | remove the marker, or retire the governing requirement in the same feature |
201
- | \`asyncapi.remove-message-consumed\` | the feature retires a message the LIVING fleet still consumes — a landscape \`consumes\` edge, or another service's living \`Consumes:\` requirement | fix the consumer in the same feature (drop the edge, retire its requirement), or archive with \`--approve\` if it is already gone in code |
203
+ | \`asyncapi.remove-message-consumed\` | the feature retires a message — a marker, or a removal or edit that stops the service sending it (a slot re-keyed, a send turned to receive) — that the fleet would still name after this merge: a \`consumes\` edge of the \`architecture/\` project, another service's \`Consumes:\` line in either spec file, or a use-case hop backed by such an edge, while nothing would still send the message after this merge (another service's contract, as the merge leaves it, keeps them served, and so does an \`#external\` element whose \`publishes\` edge is in the living map — the outside producer \`spine.message-unproduced\` accepts — unless this feature's \`specs/<service>/\` makes that element a service); or the provider's OWN \`Publishes:\` line or \`publishes\` edge (the spine grades that edge on its source), whoever else sends it — each read as the merge would leave it; \`details[]\` lists them. A consumer document loam cannot read (a spec file that is not UTF-8, the provider's own included) or a service id two directories claim refuses too, named in \`details[]\` — another service's only while nothing would still send the message | make each document listed readable (re-save it as UTF-8, or leave one directory per service id); fix each in the same change (drop the consumes edge, or drop or redraw the provider's publishes edge, in the living map, MODIFY or REMOVE the requirement in this feature's \`specs/<service>/\` delta — a REMOVED one owes no marker for a message the merge already takes out), keep it sent — by an operation in this service, or, for the consumers, by one in another service's asyncapi.yaml (living or in this feature's delta) or by an \`#external\` element's \`publishes\` edge in the living map — or archive with \`--approve\` if it is already gone in code |
202
204
  | \`asyncapi.message-conflict\` (warn) | two features in flight add or edit the same (service, message) | whichever archives second replaces the other's declaration wholesale — coordinate before either ships |
203
205
  | \`c4.op-ungoverned\` (warn) | an operation is called but no requirement governs it | write the requirement |
204
206
  | \`c4.op-link-missing\` (warn) | a "Calls" edge in the delta with no \`metadata { op }\` | link it to the operationId |
@@ -206,7 +208,7 @@ what lets a feature ship:
206
208
  | \`api.op-unconsumed\` (warn) | an added operation no edge consumes | model the caller, or say why it is provider-only |
207
209
  | \`service.no-requirement-delta\` (warn) | a new service with no spec delta | write \`specs/<svc>/spec.md\` |
208
210
  | \`scaffold.placeholder\` (warn, gates archive) | a feature document still carries \`loam new\`'s exact placeholder text — a \`TODO — name the behaviour\` requirement, a \`TODO — name the case\` scenario, an \`<angle-bracket>\` fill-in, or a \`TODO — what this service owns\` description | write the real content; the merge would otherwise publish a requirement or description nobody authored. \`--approve\` overrides |
209
- | \`intent.empty\` (warn, gates archive) | intent.md is missing, or its Why/Scope hold nothing outside the scaffold's own comments | write why the feature exists — it is the first thing a reviewer reads. \`--approve\` overrides |
211
+ | \`intent.empty\` (warn, gates archive) | intent.md is missing, or its Why/Scope hold nothing outside the scaffold's own comments — questions under \`## Open questions\` do not count | write why the feature exists — it is the first thing a reviewer reads. \`--approve\` overrides |
210
212
  | \`archedge.uncovered\` (warn) | no scenario names a tagged edge (a heuristic) | write the scenario, or say why the edge needs none |
211
213
  | \`c4.uncovered\` (warn) | a NEW tagged element or edge in delta.likec4 that no arch requirement covers via \`Covers:\` — the mechanical check, not the heuristic above | add the requirement to \`specs/<svc>/arch.spec.md\` (outbox? retries? alerts?), or the architectural obligations ship unchecked |
212
214
  | \`capability.uncovered\` (warn, GATES archive) | a requirement a feature's \`features/<FEAT>/capabilities/<id>/spec.md\` ADDS that no ADDED or MODIFIED requirement in the same feature's \`specs/\` deltas names with \`Realizes: <capability-id>#<Requirement-ID>\`, subject = the capability id. A WARNING because the document is legal — writing a promise ahead of the fleet is the intended use — that GATES because the merge is what is unsafe: once it lands, the only thing that will ever mention the promise again is \`capability.requirement-unrealized\`, a fleet-scope warning nobody reads. MODIFIED is exempt (the promise may already be realized by living text), and a requirement with no \`Requirement-ID:\` is left to \`capability.requirement-unidentified\` alone | add the \`Realizes:\` line to the service requirement that carries part of the promise; or, if the promise CROSSES services, write the flow that keeps it as \`features/<FEAT>/usecases/<name>.likec4\` — a views-only document holding one \`dynamic view\` tagged \`#cap-<capability>\` and \`#req-<requirement>\`, which \`loam archive\` merges into \`architecture/usecases/\` and \`loam unarchive\` takes back; or \`loam archive <FEAT> --approve\` to land the promise ahead of the fleet and let \`capability.requirement-unrealized\` carry it |
@@ -235,6 +237,7 @@ what lets a feature ship:
235
237
  | \`delta.baseline-stale\` | the living requirement changed after this delta was written against it | someone landed a change in between and this MODIFIED would replace it outright. Re-read the living requirement, fold in what you still mean, then \`loam rebase <FEAT>\`. Re-pinning without re-reading is how you overwrite them on purpose |
236
238
  | \`delta.baseline-missing\` (warn, gates archive) | a MODIFIED/REMOVED requirement with no \`Based-On:\` | run \`loam rebase <FEAT>\` — until then nothing can say whether the living text moved under this delta, and archive refuses to merge it. Expected on deltas adopted from OpenSpec, which never carried the line: one rebase pins the whole feature, and \`--approve\` merges unpinned deliberately |
237
239
  | \`delta.baseline-invalid\` | a \`Based-On:\` that is not a digest, declared twice, or sitting on an ADDED requirement | \`loam rebase\` writes the value; an ADDED requirement has no living version to be based on, so drop the line or make it MODIFIED |
240
+ | \`evidence.record-unreadable\` (warn) | a file of this feature's verification record (\`verification.yaml\`, or a \`verification/<svc>.yaml\`) exists but cannot be read as a record — broken YAML, git conflict markers after a merge, a shape that is not a record, a summary contradicting its claims. The same code and sentence the service target raises from a service repo; here it fires from anywhere, the docs repo included, because the record lives there. Readability only: the verdict stays \`loam verify\`'s and \`loam status\`'s, and as a warning it never stops \`loam archive\` | repair the file the finding names, following the recipe it carries: a \`verification/<svc>.yaml\` holds one service's word and may be deleted and recorded again from that service's repo; the one-file \`verification.yaml\` may hold other services' attestations, so it is repaired or restored from version control, never deleted. loam never overwrites a record it could not read |
238
241
 
239
242
  \`--all\` — everything above for every target, plus the fleet cross-check:
240
243
 
@@ -285,6 +288,7 @@ intent.md, both modes:
285
288
  | \`frontmatter.field-mismatch\` | the doc names a different service/feature than the one it lives under | fix the frontmatter, or move the file |
286
289
  | \`frontmatter.status-unknown\` | a status nobody defined (\`verifed\`) | use the documented vocabulary — a typo here reads as unverified forever |
287
290
  | \`frontmatter.field-missing\` (warn) | owner, status or the identity field is absent | fill them in |
291
+ | \`frontmatter.owner-mismatch\` (warn) | one service's spec.md and arch.spec.md both name an \`owner\` and the two differ — \`loam show\` and \`loam context\` print only spec.md's, so the other is a second answer nobody sees. The \`details\` list each file's value. Compares the two files only, never CODEOWNERS | make the two agree; only a person knows which one is current |
288
292
  | \`sources.absent\` (warn) | the doc names no sources | nothing ties it to the code, so nothing can tell you when it goes stale |
289
293
  | \`sources.path-missing\` | a listed source no longer exists, or is a glob pattern (no longer supported) | the code moved — re-read it and update the doc, do not just fix the path. For a pattern, name files or directories instead |
290
294
  | \`sources.path-outside\` | a listed source resolves OUTSIDE the service's repository — an absolute path, a \`..\` escape, or a path that only exists on one machine. loam will not hash bytes beyond the repo boundary, so the tie the entry claims cannot be checked | name the path repo-relative, inside the repository; if the document was really written from another repo's files, it belongs to that repo |
@@ -292,7 +296,7 @@ intent.md, both modes:
292
296
  | \`sources.empty\` (warn) | the declared \`sources\` exist but expand to no files at all — an empty directory, or a tree the repository ignores. A digest over nothing reads as current forever | point \`sources\` at the files the document was actually written from; \`loam vouch\` refuses to stamp this state with the same sentence |
293
297
  | \`sources.skipped\` (warn) | a path under a listed source was found but NOT hashed: a symlink loam will not follow (its target is outside the repository, dangling, or not a file or directory) — the digest cannot go stale over bytes it never read | not yours to close by editing the document: either the symlink should not be under a listed source, or \`sources\` should name the real path. Report it |
294
298
  | \`sources.unvouched\` (warn) | \`sources\` with no \`sources_digest\` — nobody ever stamped it | leave it: vouching is a human's reading, not yours |
295
- | \`sources.unwalked\` (warn) | \`sources\` leaves whole top-level paths of the service repo untouched — the \`details\` name them, measured against the files git tracks. The one completeness signal loam can compute: every other check compares the documents with each other, and a corpus agrees with itself perfectly while describing a third of a service. Silent where git cannot answer (not a repository, not installed) | open them. Each one is either a part of the service nobody read — go back to the walk \`loam adopt\` printed — or something this document legitimately does not owe, and then say which and why in the hand-back. Do NOT add paths to \`sources\` that you did not read: the list is a record of what was opened, and padding it destroys the only tie the document has to the code |
299
+ | \`sources.unwalked\` (warn) | \`sources\` leaves whole top-level paths of the service repo untouched — the \`details\` name them, measured against the files git tracks — minus loam's own (\`loam.json\` and the agent files it recorded) and \`.gitignore\`, which no document owes a reading of. The one completeness signal loam can compute: every other check compares the documents with each other, and a corpus agrees with itself perfectly while describing a third of a service. Silent where git cannot answer (not a repository, not installed) | open them. Each one is either a part of the service nobody read — go back to the walk \`loam adopt\` printed — or something this document legitimately does not owe, and then say which and why in the hand-back. Do NOT add paths to \`sources\` that you did not read: the list is a record of what was opened, and padding it destroys the only tie the document has to the code |
296
300
  | \`content.stale\` (warn) | the spec's body changed since it was vouched — \`status: verified\` is standing over words nobody has read. Unlike \`sources.*\` it needs no service repo, so it fires from the docs repo too | if you edited the doc, that is the point: report it and ask a human to re-vouch. Never revert the doc or touch the digest just to silence it |
297
301
  | \`sources.sampled-vouch\` (warn) | a person vouched after reading a recorded SAMPLE of the document — the stamp carries \`vouch_scope: sampled k/n seed=…\`, so \`status: verified\` stands over sections nobody read. The status and the \`vouched\` rung are unchanged and correct; this is what says the reading behind them was partial. A \`vouch_scope\` that does not decode fires it too, deliberately: an unreadable scope must never read as a full vouch. Doc-side, so it fires from the docs repo as well | not yours to clear. NEVER edit the document, and never touch or delete the \`vouch_scope\` field, to silence it — deleting it would forge a full vouch out of a partial one. A human runs a full \`loam vouch --service <id>\` (no \`--sample\`) once they have read the rest, which removes the field; \`loam vouch --pack --service <id>\` lists exactly what that sample left unread |
298
302
  | \`sources.resolved\` (ok) | every listed source still exists in the service repo | nothing — it is the confirmation |
@@ -309,13 +313,13 @@ contract too):
309
313
  | code | what it means | what to do |
310
314
  |---|---|---|
311
315
  | \`living.requirement-outside-requirements\` | the LIVING spec holds a requirement outside \`## Requirements\`, and the merge rewrites only that section — it would land in the file twice | re-home the requirement under \`## Requirements\`, then re-run; \`--approve\` does not override this |
312
- | \`openapi.op-modified\` (warn) | the feature redefines an operation the living OpenAPI already has — the merge overwrites the living definition wholesale | make sure the redefinition is intended; if not, align the feature's openapi.yaml with the living one |
316
+ | \`openapi.op-modified\` (warn) | the feature redefines an operation the living OpenAPI already has — the merge overwrites the living definition wholesale. The message ends with whether the CURRENT fleet names the operation, and \`details\` lists who does — landscape edges, other services' living requirements, use-case hops — then any consumer that could not be checked; the same scan \`loam diff\` runs. Whether the change breaks them is not graded | make sure the redefinition is intended and safe for every consumer listed (\`oasdiff breaking\` on the two contract states tells a breaking change from an additive one); if not, align the feature's openapi.yaml with the living one |
313
317
  | \`openapi.path-item-modified\` (warn) | the delta redefines a PATH-level key (\`parameters\`, \`servers\`, \`summary\`, an \`x-\`) the living OpenAPI already has — the overwrite applies to EVERY operation on that path, including ones this feature never mentions | keep path-level keys out of the delta unless changing them for every operation on the path is the intent |
314
318
  | \`service.no-model\` (warn) | the archive creates \`services/<id>/\` but nothing writes its \`model.likec4\` | run \`loam adopt --service <id>\` after the archive lands, or the new service ships without a C4 center and \`validate --all\` grades it incomplete |
315
319
  | \`openapi.component-modified\` (warn) | a component this delta declares — whether the merged operations reference it or the components ARE the delta — already exists in the living OpenAPI with different content, and the merge copies the feature's version over it wholesale | make sure the redefinition is intended; if not, align the feature's component with the living one |
316
320
  | \`openapi.ref-unresolved\` | a \`$ref\` reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — the merge would write a dangling reference | define the missing component or fix the ref; \`--approve\` merges the dangling reference anyway. External refs (not starting \`#/\`) are never checked |
317
321
  | \`openapi.remove-marker-path-level\` | an \`x-loam-remove: true\` written at PATH level, beside the methods instead of inside the operation being retired — it names no operation, so it retires nothing, and it is not a contract key either | move the marker inside the operation (with its \`operationId\`), or delete it. The merge is safe either way — a feature-only key is never published into the living contract — but the removal you asked for will not happen |
318
- | \`asyncapi.message-modified\` (warn) | the delta redefines a \`components.messages\` slot the living AsyncAPI already has — the merge overwrites the living message wholesale | make sure the redefinition is intended; if not, align the feature's asyncapi.yaml with the living one, then \`loam rebase\` so the restatement classifies as a quote |
322
+ | \`asyncapi.message-modified\` (warn) | the delta redefines a \`components.messages\` slot the living AsyncAPI already has — the merge overwrites the living message wholesale. The message ends with whether the CURRENT fleet consumes it, and \`details\` lists who does — \`consumes\` edges, other services' \`Consumes:\` lines, use-case hops — then any consumer that could not be checked; the same scan \`loam diff\` runs. Whether the change breaks them is not graded | make sure the redefinition is intended and safe for every consumer listed; if not, align the feature's asyncapi.yaml with the living one, then \`loam rebase\` so the restatement classifies as a quote |
319
323
  | \`asyncapi.channel-modified\` (warn) | the delta redefines a \`channels\` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included (they are channel interior, never slots of their own) | make sure the redefinition is intended; if not, align the feature's channel with the living one |
320
324
  | \`asyncapi.operation-modified\` (warn) | the delta redefines an \`operations\` slot the living AsyncAPI already has — the merge overwrites it wholesale | make sure the redefinition is intended; if not, align the feature's operation with the living one |
321
325
  | \`asyncapi.component-modified\` (warn) | the delta declares a component SURFACE outside \`components.messages\` — a \`components.schemas\` entry, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale | make sure the redefinition is intended; if not, align the feature's component with the living one, then \`loam rebase\` so the restatement classifies as a quote |