@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.13

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 (1256) hide show
  1. package/CHANGELOG.md +5992 -82
  2. package/COMPARISON.md +168 -63
  3. package/CONTRIBUTING.md +121 -0
  4. package/MIGRATING-from-OpenSpec.md +238 -55
  5. package/QUICKSTART.md +302 -0
  6. package/README.md +653 -144
  7. package/ROADMAP.md +1035 -0
  8. package/SCHEMA.md +3916 -263
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +927 -0
  11. package/dist/cli.js +133 -21
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +204 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +309 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +74 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/closing.js +42 -0
  20. package/dist/commands/archive/closing.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/asyncapi.js +172 -0
  22. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  24. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  25. package/dist/commands/archive/plan/contracts/openapi.js +193 -0
  26. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  28. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  29. package/dist/commands/archive/plan/document/head.js +151 -0
  30. package/dist/commands/archive/plan/document/head.js.map +1 -0
  31. package/dist/commands/archive/plan/document/links.js +284 -0
  32. package/dist/commands/archive/plan/document/links.js.map +1 -0
  33. package/dist/commands/archive/plan/document/run.js +136 -0
  34. package/dist/commands/archive/plan/document/run.js.map +1 -0
  35. package/dist/commands/archive/plan/gate.js +319 -0
  36. package/dist/commands/archive/plan/gate.js.map +1 -0
  37. package/dist/commands/archive/plan/landscape.js +298 -0
  38. package/dist/commands/archive/plan/landscape.js.map +1 -0
  39. package/dist/commands/archive/plan/model/extending.js +85 -0
  40. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  41. package/dist/commands/archive/plan/refusal.js +106 -0
  42. package/dist/commands/archive/plan/refusal.js.map +1 -0
  43. package/dist/commands/archive/plan/specs.js +318 -0
  44. package/dist/commands/archive/plan/specs.js.map +1 -0
  45. package/dist/commands/archive/plan/state.js +30 -0
  46. package/dist/commands/archive/plan/state.js.map +1 -0
  47. package/dist/commands/archive/run.js +355 -0
  48. package/dist/commands/archive/run.js.map +1 -0
  49. package/dist/commands/archive/shipped.js +49 -0
  50. package/dist/commands/archive/shipped.js.map +1 -0
  51. package/dist/commands/context/context.js +166 -0
  52. package/dist/commands/context/context.js.map +1 -0
  53. package/dist/commands/context/print.js +305 -0
  54. package/dist/commands/context/print.js.map +1 -0
  55. package/dist/commands/delta/delta.js +367 -0
  56. package/dist/commands/delta/delta.js.map +1 -0
  57. package/dist/commands/delta/print.js +221 -0
  58. package/dist/commands/delta/print.js.map +1 -0
  59. package/dist/commands/dependencies.js +6 -8
  60. package/dist/commands/dependencies.js.map +1 -1
  61. package/dist/commands/diff/diff.js +86 -0
  62. package/dist/commands/diff/diff.js.map +1 -0
  63. package/dist/commands/diff/print.js +38 -0
  64. package/dist/commands/diff/print.js.map +1 -0
  65. package/dist/commands/doctor.js +65 -5
  66. package/dist/commands/doctor.js.map +1 -1
  67. package/dist/commands/explain/explain.js +227 -0
  68. package/dist/commands/explain/explain.js.map +1 -0
  69. package/dist/commands/explore.js +39 -28
  70. package/dist/commands/explore.js.map +1 -1
  71. package/dist/commands/gate/gate.js +152 -0
  72. package/dist/commands/gate/gate.js.map +1 -0
  73. package/dist/commands/gate/print.js +66 -0
  74. package/dist/commands/gate/print.js.map +1 -0
  75. package/dist/commands/gherkin/commit.js +105 -0
  76. package/dist/commands/gherkin/commit.js.map +1 -0
  77. package/dist/commands/gherkin/gherkin.js +262 -0
  78. package/dist/commands/gherkin/gherkin.js.map +1 -0
  79. package/dist/commands/gherkin/reconcile.js +146 -0
  80. package/dist/commands/gherkin/reconcile.js.map +1 -0
  81. package/dist/commands/gherkin/render.js +77 -0
  82. package/dist/commands/gherkin/render.js.map +1 -0
  83. package/dist/commands/init/example.js +204 -0
  84. package/dist/commands/init/example.js.map +1 -0
  85. package/dist/commands/init/first-hour.js +72 -0
  86. package/dist/commands/init/first-hour.js.map +1 -0
  87. package/dist/commands/{init.js → init/init.js} +142 -71
  88. package/dist/commands/init/init.js.map +1 -0
  89. package/dist/commands/init/mcp.js +131 -0
  90. package/dist/commands/init/mcp.js.map +1 -0
  91. package/dist/commands/init/options.js +78 -0
  92. package/dist/commands/init/options.js.map +1 -0
  93. package/dist/commands/instructions.js +72 -14
  94. package/dist/commands/instructions.js.map +1 -1
  95. package/dist/commands/list/campaign/campaign.js +63 -0
  96. package/dist/commands/list/campaign/campaign.js.map +1 -0
  97. package/dist/commands/list/campaign/owners.js +65 -0
  98. package/dist/commands/list/campaign/owners.js.map +1 -0
  99. package/dist/commands/list/json.js +155 -0
  100. package/dist/commands/list/json.js.map +1 -0
  101. package/dist/commands/list/list.js +358 -0
  102. package/dist/commands/list/list.js.map +1 -0
  103. package/dist/commands/list/print.js +344 -0
  104. package/dist/commands/list/print.js.map +1 -0
  105. package/dist/commands/list/review.js +120 -0
  106. package/dist/commands/list/review.js.map +1 -0
  107. package/dist/commands/list/suites/suites.js +140 -0
  108. package/dist/commands/list/suites/suites.js.map +1 -0
  109. package/dist/commands/list/views.js +108 -0
  110. package/dist/commands/list/views.js.map +1 -0
  111. package/dist/commands/mcp/dispatch.js +238 -0
  112. package/dist/commands/mcp/dispatch.js.map +1 -0
  113. package/dist/commands/mcp/mcp.js +75 -0
  114. package/dist/commands/mcp/mcp.js.map +1 -0
  115. package/dist/commands/mcp/serve.js +55 -0
  116. package/dist/commands/mcp/serve.js.map +1 -0
  117. package/dist/commands/migrate-openspec/materialize/active.js +175 -0
  118. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  119. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  120. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  121. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  122. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  123. package/dist/commands/migrate-openspec/materialize/stage.js +143 -0
  124. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  125. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  126. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  127. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  128. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  129. package/dist/commands/migrate-openspec/openspec/decisions.js +86 -0
  130. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  131. package/dist/commands/migrate-openspec/openspec/error.js +18 -0
  132. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  133. package/dist/commands/migrate-openspec/openspec/mapping.js +162 -0
  134. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  135. package/dist/commands/migrate-openspec/openspec/paths.js +40 -0
  136. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  137. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  138. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  139. package/dist/commands/migrate-openspec/print.js +89 -0
  140. package/dist/commands/migrate-openspec/print.js.map +1 -0
  141. package/dist/commands/new/capability-template.js +118 -0
  142. package/dist/commands/new/capability-template.js.map +1 -0
  143. package/dist/commands/new/commit.js +62 -0
  144. package/dist/commands/new/commit.js.map +1 -0
  145. package/dist/commands/new/modifies.js +206 -0
  146. package/dist/commands/new/modifies.js.map +1 -0
  147. package/dist/commands/new/new.js +328 -0
  148. package/dist/commands/new/new.js.map +1 -0
  149. package/dist/commands/new/templates.js +279 -0
  150. package/dist/commands/new/templates.js.map +1 -0
  151. package/dist/commands/open.js +199 -0
  152. package/dist/commands/open.js.map +1 -0
  153. package/dist/commands/policy/format.js +152 -0
  154. package/dist/commands/policy/format.js.map +1 -0
  155. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +38 -11
  156. package/dist/commands/policy/gate.js.map +1 -0
  157. package/dist/commands/rebase/edit.js +106 -0
  158. package/dist/commands/rebase/edit.js.map +1 -0
  159. package/dist/commands/rebase/living.js +185 -0
  160. package/dist/commands/rebase/living.js.map +1 -0
  161. package/dist/commands/rebase/plan.js +245 -0
  162. package/dist/commands/rebase/plan.js.map +1 -0
  163. package/dist/commands/rebase/rebase.js +300 -0
  164. package/dist/commands/rebase/rebase.js.map +1 -0
  165. package/dist/commands/seed/commit.js +221 -0
  166. package/dist/commands/seed/commit.js.map +1 -0
  167. package/dist/commands/seed/plan.js +278 -0
  168. package/dist/commands/seed/plan.js.map +1 -0
  169. package/dist/commands/seed/seed.js +174 -0
  170. package/dist/commands/seed/seed.js.map +1 -0
  171. package/dist/commands/show/feature.js +182 -0
  172. package/dist/commands/show/feature.js.map +1 -0
  173. package/dist/commands/show/introduced.js +157 -0
  174. package/dist/commands/show/introduced.js.map +1 -0
  175. package/dist/commands/show/marks.js +53 -0
  176. package/dist/commands/show/marks.js.map +1 -0
  177. package/dist/commands/show/service.js +239 -0
  178. package/dist/commands/show/service.js.map +1 -0
  179. package/dist/commands/show/show.js +87 -0
  180. package/dist/commands/show/show.js.map +1 -0
  181. package/dist/commands/status/fleet-next.js +90 -0
  182. package/dist/commands/status/fleet-next.js.map +1 -0
  183. package/dist/commands/status/json.js +47 -0
  184. package/dist/commands/status/json.js.map +1 -0
  185. package/dist/commands/status/print.js +209 -0
  186. package/dist/commands/status/print.js.map +1 -0
  187. package/dist/commands/status/status.js +115 -0
  188. package/dist/commands/status/status.js.map +1 -0
  189. package/dist/commands/steps/steps.js +269 -0
  190. package/dist/commands/steps/steps.js.map +1 -0
  191. package/dist/commands/subsystem/edit.js +192 -0
  192. package/dist/commands/subsystem/edit.js.map +1 -0
  193. package/dist/commands/subsystem/move.js +325 -0
  194. package/dist/commands/subsystem/move.js.map +1 -0
  195. package/dist/commands/subsystem/query.js +113 -0
  196. package/dist/commands/subsystem/query.js.map +1 -0
  197. package/dist/commands/subsystem/subsystem.js +98 -0
  198. package/dist/commands/subsystem/subsystem.js.map +1 -0
  199. package/dist/commands/subsystem/sync.js +318 -0
  200. package/dist/commands/subsystem/sync.js.map +1 -0
  201. package/dist/commands/subsystem/txn/exclude.js +132 -0
  202. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  203. package/dist/commands/subsystem/txn/links.js +128 -0
  204. package/dist/commands/subsystem/txn/links.js.map +1 -0
  205. package/dist/commands/subsystem/txn/report.js +174 -0
  206. package/dist/commands/subsystem/txn/report.js.map +1 -0
  207. package/dist/commands/subsystem/txn/txn.js +196 -0
  208. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  209. package/dist/commands/subsystem/txn/views.js +249 -0
  210. package/dist/commands/subsystem/txn/views.js.map +1 -0
  211. package/dist/commands/tasks/actions.js +107 -0
  212. package/dist/commands/tasks/actions.js.map +1 -0
  213. package/dist/commands/tasks/render.js +253 -0
  214. package/dist/commands/tasks/render.js.map +1 -0
  215. package/dist/commands/tasks/tasks.js +119 -0
  216. package/dist/commands/tasks/tasks.js.map +1 -0
  217. package/dist/commands/unarchive/manifest.js +176 -0
  218. package/dist/commands/unarchive/manifest.js.map +1 -0
  219. package/dist/commands/unarchive/restore.js +254 -0
  220. package/dist/commands/unarchive/restore.js.map +1 -0
  221. package/dist/commands/unarchive/unarchive.js +59 -0
  222. package/dist/commands/unarchive/unarchive.js.map +1 -0
  223. package/dist/commands/validate/arch-coverage.js +245 -0
  224. package/dist/commands/validate/arch-coverage.js.map +1 -0
  225. package/dist/commands/validate/checks/fleet-shape.js +204 -0
  226. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  227. package/dist/commands/validate/checks/health-deps.js +58 -0
  228. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  229. package/dist/commands/validate/checks/introduced.js +102 -0
  230. package/dist/commands/validate/checks/introduced.js.map +1 -0
  231. package/dist/commands/validate/checks/requirements.js +264 -0
  232. package/dist/commands/validate/checks/requirements.js.map +1 -0
  233. package/dist/commands/validate/checks/sources.js +85 -0
  234. package/dist/commands/validate/checks/sources.js.map +1 -0
  235. package/dist/commands/validate/coverage/covers-after.js +105 -0
  236. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  237. package/dist/commands/validate/feature.js +222 -0
  238. package/dist/commands/validate/feature.js.map +1 -0
  239. package/dist/commands/validate/fleet/census.js +93 -0
  240. package/dist/commands/validate/fleet/census.js.map +1 -0
  241. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  242. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  243. package/dist/commands/validate/fleet/landscape.js +371 -0
  244. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  245. package/dist/commands/validate/fleet/load.js +118 -0
  246. package/dist/commands/validate/fleet/load.js.map +1 -0
  247. package/dist/commands/validate/fleet/map/attest.js +98 -0
  248. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  249. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  250. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  251. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  252. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  253. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  254. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  255. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  256. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  257. package/dist/commands/validate/fleet/obligations.js +266 -0
  258. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  259. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  260. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  261. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  262. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  263. package/dist/commands/validate/fleet/scorecard/print.js +71 -0
  264. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  265. package/dist/commands/validate/fleet/scorecard/scorecard.js +162 -0
  266. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  267. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  268. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  269. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  270. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  271. package/dist/commands/validate/fleet/views/ids.js +75 -0
  272. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  273. package/dist/commands/validate/fleet/views/projects.js +278 -0
  274. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  275. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  276. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  277. package/dist/commands/validate/fleet/views/stale.js +52 -0
  278. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  279. package/dist/commands/validate/links/corpus.js +89 -0
  280. package/dist/commands/validate/links/corpus.js.map +1 -0
  281. package/dist/commands/validate/links/glossary.js +75 -0
  282. package/dist/commands/validate/links/glossary.js.map +1 -0
  283. package/dist/commands/validate/render/repeats.js +63 -0
  284. package/dist/commands/validate/render/repeats.js.map +1 -0
  285. package/dist/commands/validate/render/rollups.js +115 -0
  286. package/dist/commands/validate/render/rollups.js.map +1 -0
  287. package/dist/commands/validate/render/text.js +199 -0
  288. package/dist/commands/validate/render/text.js.map +1 -0
  289. package/dist/commands/validate/report.js +145 -0
  290. package/dist/commands/validate/report.js.map +1 -0
  291. package/dist/commands/validate/scope.js +113 -0
  292. package/dist/commands/validate/scope.js.map +1 -0
  293. package/dist/commands/validate/service/api.js +252 -0
  294. package/dist/commands/validate/service/api.js.map +1 -0
  295. package/dist/commands/validate/service/contracts/generated.js +134 -0
  296. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  297. package/dist/commands/validate/service/events/events.js +241 -0
  298. package/dist/commands/validate/service/events/events.js.map +1 -0
  299. package/dist/commands/validate/service/events/message-producers.js +77 -0
  300. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  301. package/dist/commands/validate/service/evidence-pins.js +177 -0
  302. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  303. package/dist/commands/validate/service/model/diverged.js +226 -0
  304. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  305. package/dist/commands/validate/service/model/grade.js +163 -0
  306. package/dist/commands/validate/service/model/grade.js.map +1 -0
  307. package/dist/commands/validate/service/service.js +310 -0
  308. package/dist/commands/validate/service/service.js.map +1 -0
  309. package/dist/commands/validate/service/specs.js +279 -0
  310. package/dist/commands/validate/service/specs.js.map +1 -0
  311. package/dist/commands/validate/service/spine.js +148 -0
  312. package/dist/commands/validate/service/spine.js.map +1 -0
  313. package/dist/commands/validate/service/usecases/tags.js +113 -0
  314. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  315. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  316. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  317. package/dist/commands/validate/validate.js +363 -0
  318. package/dist/commands/validate/validate.js.map +1 -0
  319. package/dist/commands/verify/cross/diff.js +178 -0
  320. package/dist/commands/verify/cross/diff.js.map +1 -0
  321. package/dist/commands/verify/evidence/contract.js +32 -0
  322. package/dist/commands/verify/evidence/contract.js.map +1 -0
  323. package/dist/commands/verify/evidence/pins.js +52 -0
  324. package/dist/commands/verify/evidence/pins.js.map +1 -0
  325. package/dist/commands/verify/evidence/read.js +81 -0
  326. package/dist/commands/verify/evidence/read.js.map +1 -0
  327. package/dist/commands/verify/frozen.js +263 -0
  328. package/dist/commands/verify/frozen.js.map +1 -0
  329. package/dist/commands/verify/record.js +278 -0
  330. package/dist/commands/verify/record.js.map +1 -0
  331. package/dist/commands/verify/report.js +206 -0
  332. package/dist/commands/verify/report.js.map +1 -0
  333. package/dist/commands/verify/results.js +328 -0
  334. package/dist/commands/verify/results.js.map +1 -0
  335. package/dist/commands/verify/verify.js +220 -0
  336. package/dist/commands/verify/verify.js.map +1 -0
  337. package/dist/commands/vouch/contract.js +11 -0
  338. package/dist/commands/vouch/contract.js.map +1 -0
  339. package/dist/commands/vouch/pack/contract.js +2 -0
  340. package/dist/commands/vouch/pack/contract.js.map +1 -0
  341. package/dist/commands/vouch/pack/pack.js +266 -0
  342. package/dist/commands/vouch/pack/pack.js.map +1 -0
  343. package/dist/commands/vouch/pack/print.js +266 -0
  344. package/dist/commands/vouch/pack/print.js.map +1 -0
  345. package/dist/commands/vouch/pack/sections.js +141 -0
  346. package/dist/commands/vouch/pack/sections.js.map +1 -0
  347. package/dist/commands/vouch/prompt/confirm.js +46 -0
  348. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  349. package/dist/commands/vouch/report.js +101 -0
  350. package/dist/commands/vouch/report.js.map +1 -0
  351. package/dist/commands/vouch/run.js +230 -0
  352. package/dist/commands/vouch/run.js.map +1 -0
  353. package/dist/commands/vouch/sample/plan.js +111 -0
  354. package/dist/commands/vouch/sample/plan.js.map +1 -0
  355. package/dist/commands/vouch/sample/print.js +109 -0
  356. package/dist/commands/vouch/sample/print.js.map +1 -0
  357. package/dist/commands/vouch/vet/verify.js +155 -0
  358. package/dist/commands/vouch/vet/verify.js.map +1 -0
  359. package/dist/commands/vouch/vouch.js +169 -0
  360. package/dist/commands/vouch/vouch.js.map +1 -0
  361. package/dist/core/agent/agents-md/command-map.js +299 -0
  362. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  363. package/dist/core/agent/agents-md/map/explain.js +46 -0
  364. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  365. package/dist/core/agent/agents-md/map/lenses/context.js +50 -0
  366. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  367. package/dist/core/agent/agents-md/map/lenses/diff.js +59 -0
  368. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  369. package/dist/core/agent/agents-md/map/lenses/gate.js +65 -0
  370. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  371. package/dist/core/agent/agents-md/map/mcp.js +44 -0
  372. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  373. package/dist/core/agent/agents-md/map/status.js +78 -0
  374. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  375. package/dist/core/agent/agents-md/map/subsystem.js +104 -0
  376. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  377. package/dist/core/agent/agents-md/map/usecases.js +138 -0
  378. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  379. package/dist/core/agent/agents-md/refusals.js +273 -0
  380. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  381. package/dist/core/agent/agents-md.js +203 -0
  382. package/dist/core/agent/agents-md.js.map +1 -0
  383. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +60 -0
  384. package/dist/core/agent/agents-stamp.js.map +1 -0
  385. package/dist/core/agent/contract.js +2 -0
  386. package/dist/core/agent/contract.js.map +1 -0
  387. package/dist/core/agent/protocol.js +193 -0
  388. package/dist/core/agent/protocol.js.map +1 -0
  389. package/dist/core/agent/scaffold.js +254 -0
  390. package/dist/core/agent/scaffold.js.map +1 -0
  391. package/dist/core/agent/support/report.js +144 -0
  392. package/dist/core/agent/support/report.js.map +1 -0
  393. package/dist/core/agent/tools/dialects.js +151 -0
  394. package/dist/core/agent/tools/dialects.js.map +1 -0
  395. package/dist/core/agent/tools/registry.js +193 -0
  396. package/dist/core/agent/tools/registry.js.map +1 -0
  397. package/dist/core/agent/workflows/adopt.js +280 -0
  398. package/dist/core/agent/workflows/adopt.js.map +1 -0
  399. package/dist/core/agent/workflows/check.js +373 -0
  400. package/dist/core/agent/workflows/check.js.map +1 -0
  401. package/dist/core/agent/workflows/closing.js +323 -0
  402. package/dist/core/agent/workflows/closing.js.map +1 -0
  403. package/dist/core/agent/workflows/feature.js +220 -0
  404. package/dist/core/agent/workflows/feature.js.map +1 -0
  405. package/dist/core/agent/workflows/implement.js +188 -0
  406. package/dist/core/agent/workflows/implement.js.map +1 -0
  407. package/dist/core/agent/workflows/reference/authoring.js +311 -0
  408. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  409. package/dist/core/agent/workflows/reference/codes.js +67 -0
  410. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  411. package/dist/core/agent/workflows/reference/done-check.js +210 -0
  412. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  413. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  414. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  415. package/dist/core/agent/workflows/reference/reference.js +19 -0
  416. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  417. package/dist/core/agent/workflows/reference/spine.js +338 -0
  418. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  419. package/dist/core/asyncapi/baseline/plan.js +43 -0
  420. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  421. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  422. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  423. package/dist/core/asyncapi/depth.js +164 -0
  424. package/dist/core/asyncapi/depth.js.map +1 -0
  425. package/dist/core/asyncapi/digest.js +146 -0
  426. package/dist/core/asyncapi/digest.js.map +1 -0
  427. package/dist/core/asyncapi/merge/components.js +92 -0
  428. package/dist/core/asyncapi/merge/components.js.map +1 -0
  429. package/dist/core/asyncapi/merge/error.js +38 -0
  430. package/dist/core/asyncapi/merge/error.js.map +1 -0
  431. package/dist/core/asyncapi/merge/markers.js +290 -0
  432. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  433. package/dist/core/asyncapi/merge/merge.js +195 -0
  434. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  435. package/dist/core/asyncapi/merge/pin.js +83 -0
  436. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  437. package/dist/core/asyncapi/model.js +11 -0
  438. package/dist/core/asyncapi/model.js.map +1 -0
  439. package/dist/core/asyncapi/producers.js +50 -0
  440. package/dist/core/asyncapi/producers.js.map +1 -0
  441. package/dist/core/asyncapi/read.js +263 -0
  442. package/dist/core/asyncapi/read.js.map +1 -0
  443. package/dist/core/brief/brief.js +285 -0
  444. package/dist/core/brief/brief.js.map +1 -0
  445. package/dist/core/brief/checks.js +385 -0
  446. package/dist/core/brief/checks.js.map +1 -0
  447. package/dist/core/brief/landscape.js +273 -0
  448. package/dist/core/brief/landscape.js.map +1 -0
  449. package/dist/core/brief/map/extend.js +84 -0
  450. package/dist/core/brief/map/extend.js.map +1 -0
  451. package/dist/core/brief/map/owed.js +266 -0
  452. package/dist/core/brief/map/owed.js.map +1 -0
  453. package/dist/core/brief/targets.js +304 -0
  454. package/dist/core/brief/targets.js.map +1 -0
  455. package/dist/core/brief/unchecked.js +58 -0
  456. package/dist/core/brief/unchecked.js.map +1 -0
  457. package/dist/core/c4/arch.js +190 -0
  458. package/dist/core/c4/arch.js.map +1 -0
  459. package/dist/core/c4/direction/direction.js +114 -0
  460. package/dist/core/c4/direction/direction.js.map +1 -0
  461. package/dist/core/c4/likec4.js +243 -0
  462. package/dist/core/c4/likec4.js.map +1 -0
  463. package/dist/core/c4/locate/unlocated.js +150 -0
  464. package/dist/core/c4/locate/unlocated.js.map +1 -0
  465. package/dist/core/c4/parsed/deployment.js +160 -0
  466. package/dist/core/c4/parsed/deployment.js.map +1 -0
  467. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  468. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  469. package/dist/core/c4/parsed/specification.js +78 -0
  470. package/dist/core/c4/parsed/specification.js.map +1 -0
  471. package/dist/core/c4/parsed/values.js +110 -0
  472. package/dist/core/c4/parsed/values.js.map +1 -0
  473. package/dist/core/c4/parsed/view-ids.js +113 -0
  474. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  475. package/dist/core/c4/prefetch/fleet.js +134 -0
  476. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  477. package/dist/core/c4/project/architecture.js +161 -0
  478. package/dist/core/c4/project/architecture.js.map +1 -0
  479. package/dist/core/c4/project/batch.js +140 -0
  480. package/dist/core/c4/project/batch.js.map +1 -0
  481. package/dist/core/c4/project/documents.js +89 -0
  482. package/dist/core/c4/project/documents.js.map +1 -0
  483. package/dist/core/c4/project/load.js +219 -0
  484. package/dist/core/c4/project/load.js.map +1 -0
  485. package/dist/core/c4/project/staged.js +277 -0
  486. package/dist/core/c4/project/staged.js.map +1 -0
  487. package/dist/core/c4/resolve/attested.js +0 -0
  488. package/dist/core/c4/resolve/attested.js.map +1 -0
  489. package/dist/core/c4/resolve/external.js +51 -0
  490. package/dist/core/c4/resolve/external.js.map +1 -0
  491. package/dist/core/c4/resolve/service.js +174 -0
  492. package/dist/core/c4/resolve/service.js.map +1 -0
  493. package/dist/core/c4/resolve/steps.js +290 -0
  494. package/dist/core/c4/resolve/steps.js.map +1 -0
  495. package/dist/core/c4/resolve/systems.js +154 -0
  496. package/dist/core/c4/resolve/systems.js.map +1 -0
  497. package/dist/core/c4/root-project/exclude.js +352 -0
  498. package/dist/core/c4/root-project/exclude.js.map +1 -0
  499. package/dist/core/c4/seed/fleet-file.js +201 -0
  500. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  501. package/dist/core/c4/seed/items.js +91 -0
  502. package/dist/core/c4/seed/items.js.map +1 -0
  503. package/dist/core/c4/seed/stamp.js +48 -0
  504. package/dist/core/c4/seed/stamp.js.map +1 -0
  505. package/dist/core/c4/seed/template.js +173 -0
  506. package/dist/core/c4/seed/template.js.map +1 -0
  507. package/dist/core/c4/service-model/created/models.js +96 -0
  508. package/dist/core/c4/service-model/created/models.js.map +1 -0
  509. package/dist/core/c4/service-model/documents.js +47 -0
  510. package/dist/core/c4/service-model/documents.js.map +1 -0
  511. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  512. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  513. package/dist/core/c4/service-model/load.js +165 -0
  514. package/dist/core/c4/service-model/load.js.map +1 -0
  515. package/dist/core/c4/service-model/renderer.js +128 -0
  516. package/dist/core/c4/service-model/renderer.js.map +1 -0
  517. package/dist/core/c4/service-model/shape.js +106 -0
  518. package/dist/core/c4/service-model/shape.js.map +1 -0
  519. package/dist/core/c4/service-model/slice.js +188 -0
  520. package/dist/core/c4/service-model/slice.js.map +1 -0
  521. package/dist/core/c4/source-mask.js +369 -0
  522. package/dist/core/c4/source-mask.js.map +1 -0
  523. package/dist/core/c4/source-scan.js +308 -0
  524. package/dist/core/c4/source-scan.js.map +1 -0
  525. package/dist/core/c4/splice/authored-source.js +167 -0
  526. package/dist/core/c4/splice/authored-source.js.map +1 -0
  527. package/dist/core/c4/splice/contract.js +30 -0
  528. package/dist/core/c4/splice/contract.js.map +1 -0
  529. package/dist/core/c4/splice/delta-blocks.js +126 -0
  530. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  531. package/dist/core/c4/splice/identity/declared.js +87 -0
  532. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  533. package/dist/core/c4/splice/identity/edges.js +73 -0
  534. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  535. package/dist/core/c4/splice/identity/existing.js +80 -0
  536. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  537. package/dist/core/c4/splice/identity/interior.js +80 -0
  538. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  539. package/dist/core/c4/splice/identity/owner.js +50 -0
  540. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  541. package/dist/core/c4/splice/landscape-merge.js +337 -0
  542. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  543. package/dist/core/c4/splice/model/merge.js +151 -0
  544. package/dist/core/c4/splice/model/merge.js.map +1 -0
  545. package/dist/core/c4/splice/placement.js +298 -0
  546. package/dist/core/c4/splice/placement.js.map +1 -0
  547. package/dist/core/c4/splice/text/spot.js +131 -0
  548. package/dist/core/c4/splice/text/spot.js.map +1 -0
  549. package/dist/core/c4/workspace.js +166 -0
  550. package/dist/core/c4/workspace.js.map +1 -0
  551. package/dist/core/capabilities/authoring/notes.js +141 -0
  552. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  553. package/dist/core/capabilities/capabilities.js +124 -0
  554. package/dist/core/capabilities/capabilities.js.map +1 -0
  555. package/dist/core/capabilities/delta/doc.js +47 -0
  556. package/dist/core/capabilities/delta/doc.js.map +1 -0
  557. package/dist/core/capabilities/delta/overlay.js +129 -0
  558. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  559. package/dist/core/capabilities/delta/summary.js +57 -0
  560. package/dist/core/capabilities/delta/summary.js.map +1 -0
  561. package/dist/core/capabilities/delta/tree.js +43 -0
  562. package/dist/core/capabilities/delta/tree.js.map +1 -0
  563. package/dist/core/capabilities/delta/uncovered.js +222 -0
  564. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  565. package/dist/core/capabilities/findings.js +338 -0
  566. package/dist/core/capabilities/findings.js.map +1 -0
  567. package/dist/core/capabilities/realizes/findings.js +174 -0
  568. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  569. package/dist/core/capabilities/realizes/join.js +123 -0
  570. package/dist/core/capabilities/realizes/join.js.map +1 -0
  571. package/dist/core/capabilities/rollup.js +205 -0
  572. package/dist/core/capabilities/rollup.js.map +1 -0
  573. package/dist/core/capabilities/tree.js +104 -0
  574. package/dist/core/capabilities/tree.js.map +1 -0
  575. package/dist/core/capabilities/usecase-join.js +267 -0
  576. package/dist/core/capabilities/usecase-join.js.map +1 -0
  577. package/dist/core/coherence/authoring/scaffold.js +185 -0
  578. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  579. package/dist/core/coherence/authoring/sentinels.js +75 -0
  580. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  581. package/dist/core/coherence/calls/called.js +52 -0
  582. package/dist/core/coherence/calls/called.js.map +1 -0
  583. package/dist/core/coherence/coherence.js +332 -0
  584. package/dist/core/coherence/coherence.js.map +1 -0
  585. package/dist/core/coherence/consumed/operations.js +70 -0
  586. package/dist/core/coherence/consumed/operations.js.map +1 -0
  587. package/dist/core/coherence/consumed/refusal.js +38 -0
  588. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  589. package/dist/core/coherence/covers/health.js +85 -0
  590. package/dist/core/coherence/covers/health.js.map +1 -0
  591. package/dist/core/coherence/declared.js +295 -0
  592. package/dist/core/coherence/declared.js.map +1 -0
  593. package/dist/core/coherence/events/declared.js +191 -0
  594. package/dist/core/coherence/events/declared.js.map +1 -0
  595. package/dist/core/coherence/events/events.js +335 -0
  596. package/dist/core/coherence/events/events.js.map +1 -0
  597. package/dist/core/coherence/events/grades.js +189 -0
  598. package/dist/core/coherence/events/grades.js.map +1 -0
  599. package/dist/core/coherence/events/lookups.js +63 -0
  600. package/dist/core/coherence/events/lookups.js.map +1 -0
  601. package/dist/core/coherence/events/produced.js +100 -0
  602. package/dist/core/coherence/events/produced.js.map +1 -0
  603. package/dist/core/coherence/living.js +198 -0
  604. package/dist/core/coherence/living.js.map +1 -0
  605. package/dist/core/coherence/lookups.js +95 -0
  606. package/dist/core/coherence/lookups.js.map +1 -0
  607. package/dist/core/coherence/pending.js +39 -0
  608. package/dist/core/coherence/pending.js.map +1 -0
  609. package/dist/core/coherence/retired/archived.js +285 -0
  610. package/dist/core/coherence/retired/archived.js.map +1 -0
  611. package/dist/core/coherence/retired/event-pair.js +62 -0
  612. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  613. package/dist/core/coherence/retired/merged.js +79 -0
  614. package/dist/core/coherence/retired/merged.js.map +1 -0
  615. package/dist/core/coherence/retired/retirement.js +49 -0
  616. package/dist/core/coherence/retired/retirement.js.map +1 -0
  617. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  618. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  619. package/dist/core/coherence/retired/revival/revived.js +297 -0
  620. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  621. package/dist/core/conflict-markers.js +60 -0
  622. package/dist/core/conflict-markers.js.map +1 -0
  623. package/dist/core/consumers/after.js +113 -0
  624. package/dist/core/consumers/after.js.map +1 -0
  625. package/dist/core/consumers/consumers.js +296 -0
  626. package/dist/core/consumers/consumers.js.map +1 -0
  627. package/dist/core/consumers/current.js +211 -0
  628. package/dist/core/consumers/current.js.map +1 -0
  629. package/dist/core/consumers/scan.js +46 -0
  630. package/dist/core/consumers/scan.js.map +1 -0
  631. package/dist/core/consumers/state.js +27 -0
  632. package/dist/core/consumers/state.js.map +1 -0
  633. package/dist/core/delta/claims.js +73 -0
  634. package/dist/core/delta/claims.js.map +1 -0
  635. package/dist/core/delta/delta.js +341 -0
  636. package/dist/core/delta/delta.js.map +1 -0
  637. package/dist/core/delta/document.js +177 -0
  638. package/dist/core/delta/document.js.map +1 -0
  639. package/dist/core/delta/scope.js +48 -0
  640. package/dist/core/delta/scope.js.map +1 -0
  641. package/dist/core/delta/select.js +237 -0
  642. package/dist/core/delta/select.js.map +1 -0
  643. package/dist/core/dependencies/dependencies.js +141 -0
  644. package/dist/core/dependencies/dependencies.js.map +1 -0
  645. package/dist/core/dependencies/facts.js +165 -0
  646. package/dist/core/dependencies/facts.js.map +1 -0
  647. package/dist/core/dependencies/fanin.js +88 -0
  648. package/dist/core/dependencies/fanin.js.map +1 -0
  649. package/dist/core/dependencies/graph.js +106 -0
  650. package/dist/core/dependencies/graph.js.map +1 -0
  651. package/dist/core/deployment/delta.js +191 -0
  652. package/dist/core/deployment/delta.js.map +1 -0
  653. package/dist/core/deployment/objects.js +145 -0
  654. package/dist/core/deployment/objects.js.map +1 -0
  655. package/dist/core/diff/base-git.js +230 -0
  656. package/dist/core/diff/base-git.js.map +1 -0
  657. package/dist/core/diff/base-state.js +237 -0
  658. package/dist/core/diff/base-state.js.map +1 -0
  659. package/dist/core/diff/joins.js +136 -0
  660. package/dist/core/diff/joins.js.map +1 -0
  661. package/dist/core/diff/scope/changed-paths.js +204 -0
  662. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  663. package/dist/core/diff/scope/edges.js +82 -0
  664. package/dist/core/diff/scope/edges.js.map +1 -0
  665. package/dist/core/diff/semantic.js +312 -0
  666. package/dist/core/diff/semantic.js.map +1 -0
  667. package/dist/core/docs.js +37 -85
  668. package/dist/core/docs.js.map +1 -1
  669. package/dist/core/doctor/agents.js +238 -0
  670. package/dist/core/doctor/agents.js.map +1 -0
  671. package/dist/core/doctor/config.js +139 -0
  672. package/dist/core/doctor/config.js.map +1 -0
  673. package/dist/core/doctor/doctor.js +355 -0
  674. package/dist/core/doctor/doctor.js.map +1 -0
  675. package/dist/core/doctor/report.js +13 -0
  676. package/dist/core/doctor/report.js.map +1 -0
  677. package/dist/core/doctor/reports/scan.js +242 -0
  678. package/dist/core/doctor/reports/scan.js.map +1 -0
  679. package/dist/core/doctor/residue.js +110 -0
  680. package/dist/core/doctor/residue.js.map +1 -0
  681. package/dist/core/document/apply.js +224 -0
  682. package/dist/core/document/apply.js.map +1 -0
  683. package/dist/core/{frontmatter.js → document/frontmatter.js} +103 -2
  684. package/dist/core/document/frontmatter.js.map +1 -0
  685. package/dist/core/document/grammar/directives.js +143 -0
  686. package/dist/core/document/grammar/directives.js.map +1 -0
  687. package/dist/core/document/parse.js +286 -0
  688. package/dist/core/document/parse.js.map +1 -0
  689. package/dist/core/document/scenarios.js +238 -0
  690. package/dist/core/document/scenarios.js.map +1 -0
  691. package/dist/core/document/spec.js +234 -0
  692. package/dist/core/document/spec.js.map +1 -0
  693. package/dist/core/{config.js → envelope/config.js} +89 -18
  694. package/dist/core/envelope/config.js.map +1 -0
  695. package/dist/core/envelope/json.js +209 -0
  696. package/dist/core/envelope/json.js.map +1 -0
  697. package/dist/core/{version.js → envelope/version.js} +3 -3
  698. package/dist/core/envelope/version.js.map +1 -0
  699. package/dist/core/explain/families.js +180 -0
  700. package/dist/core/explain/families.js.map +1 -0
  701. package/dist/core/explain/fix-tables.js +178 -0
  702. package/dist/core/explain/fix-tables.js.map +1 -0
  703. package/dist/core/explain/lookup.js +194 -0
  704. package/dist/core/explain/lookup.js.map +1 -0
  705. package/dist/core/explain/refusals.js +49 -0
  706. package/dist/core/explain/refusals.js.map +1 -0
  707. package/dist/core/explain/terms/documents.js +227 -0
  708. package/dist/core/explain/terms/documents.js.map +1 -0
  709. package/dist/core/explain/terms/done-check.js +144 -0
  710. package/dist/core/explain/terms/done-check.js.map +1 -0
  711. package/dist/core/explain/terms/entry.js +14 -0
  712. package/dist/core/explain/terms/entry.js.map +1 -0
  713. package/dist/core/explain/terms/stages.js +71 -0
  714. package/dist/core/explain/terms/stages.js.map +1 -0
  715. package/dist/core/explain/terms/tasks.js +97 -0
  716. package/dist/core/explain/terms/tasks.js.map +1 -0
  717. package/dist/core/explain/terms.js +103 -0
  718. package/dist/core/explain/terms.js.map +1 -0
  719. package/dist/core/explore/describe.js +105 -0
  720. package/dist/core/explore/describe.js.map +1 -0
  721. package/dist/core/explore/explore.js +204 -0
  722. package/dist/core/explore/explore.js.map +1 -0
  723. package/dist/core/fleet-context.js +174 -146
  724. package/dist/core/fleet-context.js.map +1 -1
  725. package/dist/core/gate/checks.js +208 -0
  726. package/dist/core/gate/checks.js.map +1 -0
  727. package/dist/core/gate/partners.js +246 -0
  728. package/dist/core/gate/partners.js.map +1 -0
  729. package/dist/core/gate/report.js +47 -0
  730. package/dist/core/gate/report.js.map +1 -0
  731. package/dist/core/gate/verification.js +176 -0
  732. package/dist/core/gate/verification.js.map +1 -0
  733. package/dist/core/gherkin/digest.js +60 -0
  734. package/dist/core/gherkin/digest.js.map +1 -0
  735. package/dist/core/gherkin/emit.js +177 -0
  736. package/dist/core/gherkin/emit.js.map +1 -0
  737. package/dist/core/gherkin/read.js +76 -0
  738. package/dist/core/gherkin/read.js.map +1 -0
  739. package/dist/core/gherkin/stale.js +210 -0
  740. package/dist/core/gherkin/stale.js.map +1 -0
  741. package/dist/core/gherkin/stamp.js +77 -0
  742. package/dist/core/gherkin/stamp.js.map +1 -0
  743. package/dist/core/gherkin/steps/catalogue.js +154 -0
  744. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  745. package/dist/core/gherkin/steps/inventory.js +98 -0
  746. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  747. package/dist/core/gherkin/steps/phrase.js +72 -0
  748. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  749. package/dist/core/glossary/delta.js +109 -0
  750. package/dist/core/glossary/delta.js.map +1 -0
  751. package/dist/core/glossary/tree.js +73 -0
  752. package/dist/core/glossary/tree.js.map +1 -0
  753. package/dist/core/{concurrency.js → kernel/concurrency.js} +19 -0
  754. package/dist/core/kernel/concurrency.js.map +1 -0
  755. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  756. package/dist/core/kernel/document-bytes.js.map +1 -0
  757. package/dist/core/kernel/fences.js +42 -0
  758. package/dist/core/kernel/fences.js.map +1 -0
  759. package/dist/core/kernel/ids/capability.js +72 -0
  760. package/dist/core/kernel/ids/capability.js.map +1 -0
  761. package/dist/core/kernel/ids/dirs.js +33 -0
  762. package/dist/core/kernel/ids/dirs.js.map +1 -0
  763. package/dist/core/kernel/ids/feature.js +75 -0
  764. package/dist/core/kernel/ids/feature.js.map +1 -0
  765. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  766. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  767. package/dist/core/kernel/ids/service.js +156 -0
  768. package/dist/core/kernel/ids/service.js.map +1 -0
  769. package/dist/core/kernel/ids/subsystem.js +63 -0
  770. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  771. package/dist/core/{path-safety.js → kernel/path-safety.js} +56 -4
  772. package/dist/core/kernel/path-safety.js.map +1 -0
  773. package/dist/core/{records.js → kernel/records.js} +11 -0
  774. package/dist/core/kernel/records.js.map +1 -0
  775. package/dist/core/links/backlinks.js +66 -0
  776. package/dist/core/links/backlinks.js.map +1 -0
  777. package/dist/core/links/case.js +74 -0
  778. package/dist/core/links/case.js.map +1 -0
  779. package/dist/core/links/corpus.js +118 -0
  780. package/dist/core/links/corpus.js.map +1 -0
  781. package/dist/core/links/findings.js +110 -0
  782. package/dist/core/links/findings.js.map +1 -0
  783. package/dist/core/links/parse.js +265 -0
  784. package/dist/core/links/parse.js.map +1 -0
  785. package/dist/core/mcp/argv.js +212 -0
  786. package/dist/core/mcp/argv.js.map +1 -0
  787. package/dist/core/mcp/author-tools.js +73 -0
  788. package/dist/core/mcp/author-tools.js.map +1 -0
  789. package/dist/core/mcp/framing.js +46 -0
  790. package/dist/core/mcp/framing.js.map +1 -0
  791. package/dist/core/mcp/protocol.js +280 -0
  792. package/dist/core/mcp/protocol.js.map +1 -0
  793. package/dist/core/mcp/tools.js +324 -0
  794. package/dist/core/mcp/tools.js.map +1 -0
  795. package/dist/core/obligations/obligations.js +106 -0
  796. package/dist/core/obligations/obligations.js.map +1 -0
  797. package/dist/core/openapi/baseline/gate.js +207 -0
  798. package/dist/core/openapi/baseline/gate.js.map +1 -0
  799. package/dist/core/openapi/baseline/plan.js +142 -0
  800. package/dist/core/openapi/baseline/plan.js.map +1 -0
  801. package/dist/core/openapi/baseline/record.js +174 -0
  802. package/dist/core/openapi/baseline/record.js.map +1 -0
  803. package/dist/core/openapi/contract-text.js +33 -0
  804. package/dist/core/openapi/contract-text.js.map +1 -0
  805. package/dist/core/openapi/contract-tree.js +301 -0
  806. package/dist/core/openapi/contract-tree.js.map +1 -0
  807. package/dist/core/openapi/depth.js +136 -0
  808. package/dist/core/openapi/depth.js.map +1 -0
  809. package/dist/core/openapi/digest.js +239 -0
  810. package/dist/core/openapi/digest.js.map +1 -0
  811. package/dist/core/{openapi.js → openapi/doc.js} +54 -108
  812. package/dist/core/openapi/doc.js.map +1 -0
  813. package/dist/core/openapi/merge/components.js +199 -0
  814. package/dist/core/openapi/merge/components.js.map +1 -0
  815. package/dist/core/openapi/merge/error.js +16 -0
  816. package/dist/core/openapi/merge/error.js.map +1 -0
  817. package/dist/core/openapi/merge/markers.js +204 -0
  818. package/dist/core/openapi/merge/markers.js.map +1 -0
  819. package/dist/core/openapi/merge/merge.js +232 -0
  820. package/dist/core/openapi/merge/merge.js.map +1 -0
  821. package/dist/core/openapi/merge/pin.js +110 -0
  822. package/dist/core/openapi/merge/pin.js.map +1 -0
  823. package/dist/core/openspec/decide/capabilities.js +244 -0
  824. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  825. package/dist/core/openspec/decide/dispositions.js +63 -0
  826. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  827. package/dist/core/openspec/decide/renames.js +179 -0
  828. package/dist/core/openspec/decide/renames.js.map +1 -0
  829. package/dist/core/openspec/decide/verdict.js +84 -0
  830. package/dist/core/openspec/decide/verdict.js.map +1 -0
  831. package/dist/core/openspec/inventory.js +220 -0
  832. package/dist/core/openspec/inventory.js.map +1 -0
  833. package/dist/core/openspec/model/mapping.js +78 -0
  834. package/dist/core/openspec/model/mapping.js.map +1 -0
  835. package/dist/core/openspec/model/model.js +70 -0
  836. package/dist/core/openspec/model/model.js.map +1 -0
  837. package/dist/core/openspec/scan/changes.js +241 -0
  838. package/dist/core/openspec/scan/changes.js.map +1 -0
  839. package/dist/core/openspec/scan/config.js +191 -0
  840. package/dist/core/openspec/scan/config.js.map +1 -0
  841. package/dist/core/openspec/scan/shape.js +304 -0
  842. package/dist/core/openspec/scan/shape.js.map +1 -0
  843. package/dist/core/openspec/scan/walk.js +116 -0
  844. package/dist/core/openspec/scan/walk.js.map +1 -0
  845. package/dist/core/openspec/scan/workspace.js +71 -0
  846. package/dist/core/openspec/scan/workspace.js.map +1 -0
  847. package/dist/core/owners/codeowners.js +141 -0
  848. package/dist/core/owners/codeowners.js.map +1 -0
  849. package/dist/core/pack/features.js +120 -0
  850. package/dist/core/pack/features.js.map +1 -0
  851. package/dist/core/pack/joins.js +112 -0
  852. package/dist/core/pack/joins.js.map +1 -0
  853. package/dist/core/pack/living.js +157 -0
  854. package/dist/core/pack/living.js.map +1 -0
  855. package/dist/core/pack/pack.js +120 -0
  856. package/dist/core/pack/pack.js.map +1 -0
  857. package/dist/core/permissions/permissions.js +87 -0
  858. package/dist/core/permissions/permissions.js.map +1 -0
  859. package/dist/core/projection/api.js +84 -0
  860. package/dist/core/projection/api.js.map +1 -0
  861. package/dist/core/projection/arch-slice.js +63 -0
  862. package/dist/core/projection/arch-slice.js.map +1 -0
  863. package/dist/core/projection/events.js +34 -0
  864. package/dist/core/projection/events.js.map +1 -0
  865. package/dist/core/projection/introduced.js +14 -0
  866. package/dist/core/projection/introduced.js.map +1 -0
  867. package/dist/core/provenance/findings.js +244 -0
  868. package/dist/core/provenance/findings.js.map +1 -0
  869. package/dist/core/provenance/git.js +201 -0
  870. package/dist/core/provenance/git.js.map +1 -0
  871. package/dist/core/provenance/gitq/moves.js +139 -0
  872. package/dist/core/provenance/gitq/moves.js.map +1 -0
  873. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  874. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  875. package/dist/core/provenance/sample/scope.js +238 -0
  876. package/dist/core/provenance/sample/scope.js.map +1 -0
  877. package/dist/core/provenance/sample/sections.js +61 -0
  878. package/dist/core/provenance/sample/sections.js.map +1 -0
  879. package/dist/core/provenance/sources.js +280 -0
  880. package/dist/core/provenance/sources.js.map +1 -0
  881. package/dist/core/provenance/stamp.js +220 -0
  882. package/dist/core/provenance/stamp.js.map +1 -0
  883. package/dist/core/provenance/walk.js +157 -0
  884. package/dist/core/provenance/walk.js.map +1 -0
  885. package/dist/core/repo/authored/paths.js +210 -0
  886. package/dist/core/repo/authored/paths.js.map +1 -0
  887. package/dist/core/repo/entries.js +126 -0
  888. package/dist/core/repo/entries.js.map +1 -0
  889. package/dist/core/repo/paths.js +322 -0
  890. package/dist/core/repo/paths.js.map +1 -0
  891. package/dist/core/repo/repo.js +308 -0
  892. package/dist/core/repo/repo.js.map +1 -0
  893. package/dist/core/repo/service-target.js +153 -0
  894. package/dist/core/repo/service-target.js.map +1 -0
  895. package/dist/core/repo/state.js +58 -0
  896. package/dist/core/repo/state.js.map +1 -0
  897. package/dist/core/repo/tree/find.js +61 -0
  898. package/dist/core/repo/tree/find.js.map +1 -0
  899. package/dist/core/repo/tree/fs.js +90 -0
  900. package/dist/core/repo/tree/fs.js.map +1 -0
  901. package/dist/core/repo/tree/marker.js +61 -0
  902. package/dist/core/repo/tree/marker.js.map +1 -0
  903. package/dist/core/repo/tree/render/body.js +258 -0
  904. package/dist/core/repo/tree/render/body.js.map +1 -0
  905. package/dist/core/repo/tree/render/projects.js +182 -0
  906. package/dist/core/repo/tree/render/projects.js.map +1 -0
  907. package/dist/core/repo/tree/render/service/header.js +125 -0
  908. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  909. package/dist/core/repo/tree/render/service/peers.js +28 -0
  910. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  911. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  912. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  913. package/dist/core/repo/tree/render/service/views.js +78 -0
  914. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  915. package/dist/core/repo/tree/render/stale.js +42 -0
  916. package/dist/core/repo/tree/render/stale.js.map +1 -0
  917. package/dist/core/repo/tree/render/text.js +84 -0
  918. package/dist/core/repo/tree/render/text.js.map +1 -0
  919. package/dist/core/repo/tree/render/views.js +293 -0
  920. package/dist/core/repo/tree/render/views.js.map +1 -0
  921. package/dist/core/repo/tree/walk.js +255 -0
  922. package/dist/core/repo/tree/walk.js.map +1 -0
  923. package/dist/core/results.js +146 -14
  924. package/dist/core/results.js.map +1 -1
  925. package/dist/core/scaffold/landscape.js +239 -0
  926. package/dist/core/scaffold/landscape.js.map +1 -0
  927. package/dist/core/scaffold/legacy.js +271 -0
  928. package/dist/core/scaffold/legacy.js.map +1 -0
  929. package/dist/core/scaffold/readme.js +84 -0
  930. package/dist/core/scaffold/readme.js.map +1 -0
  931. package/dist/core/staging/commit.js +252 -0
  932. package/dist/core/staging/commit.js.map +1 -0
  933. package/dist/core/staging/interrupted.js +96 -0
  934. package/dist/core/staging/interrupted.js.map +1 -0
  935. package/dist/core/staging/lock.js +268 -0
  936. package/dist/core/staging/lock.js.map +1 -0
  937. package/dist/core/staging/recovery/finding.js +35 -0
  938. package/dist/core/staging/recovery/finding.js.map +1 -0
  939. package/dist/core/staging/recovery/intent.js +95 -0
  940. package/dist/core/staging/recovery/intent.js.map +1 -0
  941. package/dist/core/staging/recovery/recover.js +204 -0
  942. package/dist/core/staging/recovery/recover.js.map +1 -0
  943. package/dist/core/staging/recovery/residue.js +62 -0
  944. package/dist/core/staging/recovery/residue.js.map +1 -0
  945. package/dist/core/staging/snapshot/manifest.js +188 -0
  946. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  947. package/dist/core/staging/snapshot/snapshot.js +196 -0
  948. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  949. package/dist/core/staging/txn/forward.js +181 -0
  950. package/dist/core/staging/txn/forward.js.map +1 -0
  951. package/dist/core/staging/txn/journal.js +139 -0
  952. package/dist/core/staging/txn/journal.js.map +1 -0
  953. package/dist/core/staging/txn/transaction.js +75 -0
  954. package/dist/core/staging/txn/transaction.js.map +1 -0
  955. package/dist/core/staging/writes.js +113 -0
  956. package/dist/core/staging/writes.js.map +1 -0
  957. package/dist/core/status/actions/execution.js +60 -0
  958. package/dist/core/status/actions/execution.js.map +1 -0
  959. package/dist/core/status/contracts.js +126 -0
  960. package/dist/core/status/contracts.js.map +1 -0
  961. package/dist/core/status/feature/artifacts.js +196 -0
  962. package/dist/core/status/feature/artifacts.js.map +1 -0
  963. package/dist/core/status/feature/feature.js +214 -0
  964. package/dist/core/status/feature/feature.js.map +1 -0
  965. package/dist/core/status/feature/next.js +163 -0
  966. package/dist/core/status/feature/next.js.map +1 -0
  967. package/dist/core/status/feature/state.js +21 -0
  968. package/dist/core/status/feature/state.js.map +1 -0
  969. package/dist/core/status/feature/words.js +96 -0
  970. package/dist/core/status/feature/words.js.map +1 -0
  971. package/dist/core/status/fleet/fleet.js +216 -0
  972. package/dist/core/status/fleet/fleet.js.map +1 -0
  973. package/dist/core/status/fleet/next.js +302 -0
  974. package/dist/core/status/fleet/next.js.map +1 -0
  975. package/dist/core/status/interrupted.js +97 -0
  976. package/dist/core/status/interrupted.js.map +1 -0
  977. package/dist/core/status/report.js +31 -0
  978. package/dist/core/status/report.js.map +1 -0
  979. package/dist/core/status/scan.js +41 -0
  980. package/dist/core/status/scan.js.map +1 -0
  981. package/dist/core/status/verification.js +216 -0
  982. package/dist/core/status/verification.js.map +1 -0
  983. package/dist/core/tasks/packet.js +199 -0
  984. package/dist/core/tasks/packet.js.map +1 -0
  985. package/dist/core/tasks/record.js +43 -0
  986. package/dist/core/tasks/record.js.map +1 -0
  987. package/dist/core/tasks/sources.js +181 -0
  988. package/dist/core/tasks/sources.js.map +1 -0
  989. package/dist/core/tasks/stamps.js +76 -0
  990. package/dist/core/tasks/stamps.js.map +1 -0
  991. package/dist/core/tasks/unit.js +0 -0
  992. package/dist/core/tasks/unit.js.map +1 -0
  993. package/dist/core/usecases/capability.js +166 -0
  994. package/dist/core/usecases/capability.js.map +1 -0
  995. package/dist/core/usecases/delta/claims.js +140 -0
  996. package/dist/core/usecases/delta/claims.js.map +1 -0
  997. package/dist/core/usecases/delta/flows.js +101 -0
  998. package/dist/core/usecases/delta/flows.js.map +1 -0
  999. package/dist/core/usecases/delta/modified.js +315 -0
  1000. package/dist/core/usecases/delta/modified.js.map +1 -0
  1001. package/dist/core/usecases/delta/overlay.js +76 -0
  1002. package/dist/core/usecases/delta/overlay.js.map +1 -0
  1003. package/dist/core/usecases/fleet.js +296 -0
  1004. package/dist/core/usecases/fleet.js.map +1 -0
  1005. package/dist/core/usecases/grade/capability-tag.js +93 -0
  1006. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  1007. package/dist/core/usecases/grade/feature.js +88 -0
  1008. package/dist/core/usecases/grade/feature.js.map +1 -0
  1009. package/dist/core/usecases/grade/requirement-tag.js +94 -0
  1010. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  1011. package/dist/core/usecases/grade/steps.js +266 -0
  1012. package/dist/core/usecases/grade/steps.js.map +1 -0
  1013. package/dist/core/usecases/grade/usecases.js +112 -0
  1014. package/dist/core/usecases/grade/usecases.js.map +1 -0
  1015. package/dist/core/usecases/operations.js +123 -0
  1016. package/dist/core/usecases/operations.js.map +1 -0
  1017. package/dist/core/usecases/place.js +44 -0
  1018. package/dist/core/usecases/place.js.map +1 -0
  1019. package/dist/core/usecases/service/exclude.js +119 -0
  1020. package/dist/core/usecases/service/exclude.js.map +1 -0
  1021. package/dist/core/usecases/service/flows.js +164 -0
  1022. package/dist/core/usecases/service/flows.js.map +1 -0
  1023. package/dist/core/usecases/suites/participants.js +83 -0
  1024. package/dist/core/usecases/suites/participants.js.map +1 -0
  1025. package/dist/core/usecases/suites/suites.js +45 -0
  1026. package/dist/core/usecases/suites/suites.js.map +1 -0
  1027. package/dist/core/usecases/touch.js +101 -0
  1028. package/dist/core/usecases/touch.js.map +1 -0
  1029. package/dist/core/verify/answers.js +180 -0
  1030. package/dist/core/verify/answers.js.map +1 -0
  1031. package/dist/core/verify/build.js +170 -0
  1032. package/dist/core/verify/build.js.map +1 -0
  1033. package/dist/core/verify/checklist.js +259 -0
  1034. package/dist/core/verify/checklist.js.map +1 -0
  1035. package/dist/core/verify/claims/identity.js +61 -0
  1036. package/dist/core/verify/claims/identity.js.map +1 -0
  1037. package/dist/core/verify/cross/diff.js +97 -0
  1038. package/dist/core/verify/cross/diff.js.map +1 -0
  1039. package/dist/core/verify/evidence/contract.js +169 -0
  1040. package/dist/core/verify/evidence/contract.js.map +1 -0
  1041. package/dist/core/verify/location.js +67 -0
  1042. package/dist/core/verify/location.js.map +1 -0
  1043. package/dist/core/verify/pins/lint.js +158 -0
  1044. package/dist/core/verify/pins/lint.js.map +1 -0
  1045. package/dist/core/verify/pins/pin.js +95 -0
  1046. package/dist/core/verify/pins/pin.js.map +1 -0
  1047. package/dist/core/verify/read/file.js +224 -0
  1048. package/dist/core/verify/read/file.js.map +1 -0
  1049. package/dist/core/verify/read/shape.js +169 -0
  1050. package/dist/core/verify/read/shape.js.map +1 -0
  1051. package/dist/core/verify/read/union.js +105 -0
  1052. package/dist/core/verify/read/union.js.map +1 -0
  1053. package/dist/core/verify/record.js +165 -0
  1054. package/dist/core/verify/record.js.map +1 -0
  1055. package/dist/core/verify/store/commit.js +99 -0
  1056. package/dist/core/verify/store/commit.js.map +1 -0
  1057. package/dist/core/verify/store/render.js +61 -0
  1058. package/dist/core/verify/store/render.js.map +1 -0
  1059. package/dist/core/vocabulary/health.js +91 -0
  1060. package/dist/core/vocabulary/health.js.map +1 -0
  1061. package/dist/core/vocabulary/issue.js +59 -0
  1062. package/dist/core/vocabulary/issue.js.map +1 -0
  1063. package/dist/core/vocabulary/maturity.js +216 -0
  1064. package/dist/core/vocabulary/maturity.js.map +1 -0
  1065. package/dist/core/vocabulary/report.js +90 -0
  1066. package/dist/core/vocabulary/report.js.map +1 -0
  1067. package/dist/core/vocabulary/steps.js +266 -0
  1068. package/dist/core/vocabulary/steps.js.map +1 -0
  1069. package/dist/core/workspace/discover.js +239 -0
  1070. package/dist/core/workspace/discover.js.map +1 -0
  1071. package/dist/core/workspace/render.js +52 -0
  1072. package/dist/core/workspace/render.js.map +1 -0
  1073. package/examples/README.md +232 -0
  1074. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  1075. package/examples/docs/architecture/capabilities.yaml +31 -0
  1076. package/examples/docs/architecture/landscape.likec4 +336 -0
  1077. package/examples/docs/architecture/obligations.yaml +23 -0
  1078. package/examples/docs/architecture/permissions.yaml +50 -0
  1079. package/examples/docs/architecture/services.likec4 +70 -0
  1080. package/examples/docs/architecture/subsystems.likec4 +18 -0
  1081. package/examples/docs/architecture/usecases/order-notification.likec4 +74 -0
  1082. package/examples/docs/capabilities/checkout/spec.md +55 -0
  1083. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  1084. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  1085. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  1086. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  1087. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  1088. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  1089. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +132 -0
  1090. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  1091. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  1092. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  1093. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  1094. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  1095. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +31 -0
  1096. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  1097. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  1098. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  1099. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  1100. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  1101. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  1102. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  1103. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  1104. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  1105. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  1106. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +73 -0
  1107. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  1108. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  1109. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  1110. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  1111. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  1112. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  1113. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  1114. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  1115. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  1116. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  1117. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  1118. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  1119. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  1120. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  1121. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  1122. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  1123. package/examples/docs/glossary/order.md +15 -0
  1124. package/examples/docs/glossary/payments/authorization.md +14 -0
  1125. package/examples/docs/likec4.config.json +8 -0
  1126. package/examples/docs/loam.json +3 -0
  1127. package/examples/docs/services/checkout-web/model.likec4 +92 -0
  1128. package/examples/docs/services/checkout-web/spec.md +91 -0
  1129. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  1130. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  1131. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  1132. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  1133. package/examples/docs/services/order-service/arch.spec.md +134 -0
  1134. package/examples/docs/services/order-service/asyncapi.yaml +109 -0
  1135. package/examples/docs/services/order-service/health.yaml +57 -0
  1136. package/examples/docs/services/order-service/model.likec4 +126 -0
  1137. package/examples/docs/services/order-service/openapi.yaml +257 -0
  1138. package/examples/docs/services/order-service/runbook.md +62 -0
  1139. package/examples/docs/services/order-service/spec.md +134 -0
  1140. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  1141. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  1142. package/examples/docs/services/payment-service/arch.spec.md +76 -0
  1143. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  1144. package/examples/docs/services/payment-service/health.yaml +39 -0
  1145. package/examples/docs/services/payment-service/model.likec4 +68 -0
  1146. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  1147. package/examples/docs/services/payment-service/runbook.md +37 -0
  1148. package/examples/docs/services/payment-service/spec.md +88 -0
  1149. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  1150. package/examples/docs/services/platform/identity-service/arch.spec.md +120 -0
  1151. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  1152. package/examples/docs/services/platform/identity-service/model.likec4 +81 -0
  1153. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  1154. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  1155. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  1156. package/examples/docs/services/platform/notification-service/asyncapi.yaml +200 -0
  1157. package/examples/docs/services/platform/notification-service/health.yaml +40 -0
  1158. package/examples/docs/services/platform/notification-service/model.likec4 +97 -0
  1159. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  1160. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  1161. package/examples/docs/services/platform/subsystem.yaml +2 -0
  1162. package/examples/fleet.yaml +57 -0
  1163. package/package.json +19 -6
  1164. package/dist/commands/adopt.js +0 -240
  1165. package/dist/commands/adopt.js.map +0 -1
  1166. package/dist/commands/archive.js +0 -1313
  1167. package/dist/commands/archive.js.map +0 -1
  1168. package/dist/commands/delta.js +0 -388
  1169. package/dist/commands/delta.js.map +0 -1
  1170. package/dist/commands/docs-repo-gate.js.map +0 -1
  1171. package/dist/commands/format.js +0 -17
  1172. package/dist/commands/format.js.map +0 -1
  1173. package/dist/commands/gherkin.js +0 -309
  1174. package/dist/commands/gherkin.js.map +0 -1
  1175. package/dist/commands/init.js.map +0 -1
  1176. package/dist/commands/list.js +0 -295
  1177. package/dist/commands/list.js.map +0 -1
  1178. package/dist/commands/migrate-openspec.js +0 -950
  1179. package/dist/commands/migrate-openspec.js.map +0 -1
  1180. package/dist/commands/new.js +0 -413
  1181. package/dist/commands/new.js.map +0 -1
  1182. package/dist/commands/rebase.js +0 -356
  1183. package/dist/commands/rebase.js.map +0 -1
  1184. package/dist/commands/show.js +0 -320
  1185. package/dist/commands/show.js.map +0 -1
  1186. package/dist/commands/status.js +0 -262
  1187. package/dist/commands/status.js.map +0 -1
  1188. package/dist/commands/unarchive.js +0 -317
  1189. package/dist/commands/unarchive.js.map +0 -1
  1190. package/dist/commands/validate.js +0 -1773
  1191. package/dist/commands/validate.js.map +0 -1
  1192. package/dist/commands/verify.js +0 -775
  1193. package/dist/commands/verify.js.map +0 -1
  1194. package/dist/commands/vouch.js +0 -421
  1195. package/dist/commands/vouch.js.map +0 -1
  1196. package/dist/core/agent.js +0 -2173
  1197. package/dist/core/agent.js.map +0 -1
  1198. package/dist/core/agents-stamp.js.map +0 -1
  1199. package/dist/core/arch.js +0 -130
  1200. package/dist/core/arch.js.map +0 -1
  1201. package/dist/core/asyncapi.js +0 -253
  1202. package/dist/core/asyncapi.js.map +0 -1
  1203. package/dist/core/brief.js +0 -755
  1204. package/dist/core/brief.js.map +0 -1
  1205. package/dist/core/coherence.js +0 -577
  1206. package/dist/core/coherence.js.map +0 -1
  1207. package/dist/core/concurrency.js.map +0 -1
  1208. package/dist/core/config.js.map +0 -1
  1209. package/dist/core/delta.js +0 -476
  1210. package/dist/core/delta.js.map +0 -1
  1211. package/dist/core/dependencies.js +0 -356
  1212. package/dist/core/dependencies.js.map +0 -1
  1213. package/dist/core/doctor.js +0 -596
  1214. package/dist/core/doctor.js.map +0 -1
  1215. package/dist/core/document-bytes.js.map +0 -1
  1216. package/dist/core/explore.js +0 -225
  1217. package/dist/core/explore.js.map +0 -1
  1218. package/dist/core/frontmatter.js.map +0 -1
  1219. package/dist/core/gherkin.js +0 -463
  1220. package/dist/core/gherkin.js.map +0 -1
  1221. package/dist/core/health.js +0 -67
  1222. package/dist/core/health.js.map +0 -1
  1223. package/dist/core/ids.js +0 -122
  1224. package/dist/core/ids.js.map +0 -1
  1225. package/dist/core/issue.js +0 -9
  1226. package/dist/core/issue.js.map +0 -1
  1227. package/dist/core/json.js +0 -74
  1228. package/dist/core/json.js.map +0 -1
  1229. package/dist/core/likec4.js +0 -489
  1230. package/dist/core/likec4.js.map +0 -1
  1231. package/dist/core/maturity.js +0 -66
  1232. package/dist/core/maturity.js.map +0 -1
  1233. package/dist/core/openapi-merge.js +0 -552
  1234. package/dist/core/openapi-merge.js.map +0 -1
  1235. package/dist/core/openapi.js.map +0 -1
  1236. package/dist/core/openspec-inventory.js +0 -1440
  1237. package/dist/core/openspec-inventory.js.map +0 -1
  1238. package/dist/core/path-safety.js.map +0 -1
  1239. package/dist/core/provenance.js +0 -787
  1240. package/dist/core/provenance.js.map +0 -1
  1241. package/dist/core/records.js.map +0 -1
  1242. package/dist/core/repo.js +0 -438
  1243. package/dist/core/repo.js.map +0 -1
  1244. package/dist/core/report.js +0 -47
  1245. package/dist/core/report.js.map +0 -1
  1246. package/dist/core/spec.js +0 -505
  1247. package/dist/core/spec.js.map +0 -1
  1248. package/dist/core/staging.js +0 -890
  1249. package/dist/core/staging.js.map +0 -1
  1250. package/dist/core/status.js +0 -1132
  1251. package/dist/core/status.js.map +0 -1
  1252. package/dist/core/steps.js +0 -72
  1253. package/dist/core/steps.js.map +0 -1
  1254. package/dist/core/verify.js +0 -807
  1255. package/dist/core/verify.js.map +0 -1
  1256. package/dist/core/version.js.map +0 -1
@@ -1,2173 +0,0 @@
1
- /**
2
- * The agent contract: what `loam init` lays down so a coding agent can run the
3
- * cycle without being told it each time.
4
- *
5
- * AGENTS.md goes into the docs repo — it travels with the thing it describes.
6
- * The command and skill files go into the repo `init` runs in, because that is
7
- * where the agent is invoked — for whichever tools the repo shows signs of, via
8
- * the AGENT_TOOLS registry below: one shared body, a per-tool path and wrapper.
9
- * Neither is ever overwritten: they are starting points, and a team's edits to
10
- * them outrank ours.
11
- *
12
- * Two deliveries, one body. A slash command has to be TYPED, so it only ever
13
- * reaches an agent whose operator already knows loam exists. A skill is loaded
14
- * by the model itself when the task matches its `description`, which is how the
15
- * protocol reaches an agent that was never told about it. That difference is
16
- * the whole reason both are written by default.
17
- *
18
- * The stamp on the first line is the one concession to that never-refresh
19
- * contract: it records which loam wrote the file, so `loam validate --all` can
20
- * say when the tables below describe a binary that no longer exists
21
- * (`agents.stale` — detection only, never a rewrite; agents-stamp.ts).
22
- *
23
- * The per-tool command and skill files carry the SAME stamp, for the same
24
- * reason and with the same answer: `loam doctor` reports `doctor.agent-files-stale`
25
- * and never rewrites. Without it, absence was the only drift loam could see —
26
- * a command file mangled to one line left doctor saying `healthy: true`, which
27
- * across a hundred repositories is invisible rot with no repair path.
28
- */
29
- import { existsSync } from "node:fs";
30
- import { mkdir, writeFile } from "node:fs/promises";
31
- import { dirname, join } from "node:path";
32
- import { agentsStampLine } from "./agents-stamp.js";
33
- import { featureIdProblem, serviceIdProblem } from "./ids.js";
34
- import { LOAM_VERSION } from "./version.js";
35
- export const AGENTS_MD = `${agentsStampLine(LOAM_VERSION)}
36
- # Working in this docs repo
37
-
38
- This is a **loam** docs repo: the shared source of truth for a fleet of services —
39
- their architecture, their contracts, and what they are required to do. Everything
40
- here is plain files. \`loam\` reads and writes them; delete \`loam\` and the docs remain.
41
-
42
- ## Layout
43
-
44
- \`\`\`
45
- architecture/landscape.likec4 the living C4 model of the whole fleet
46
- services/<svc>/
47
- model.likec4 this service's C4
48
- spec.md its living requirements (current state)
49
- arch.spec.md its living ARCHITECTURE requirements (outbox, retries, alerts)
50
- openapi.yaml its API contract
51
- adrs/ runbook.md health.yaml why it is like this, how to run it
52
- features/<FEAT>/ a change in flight
53
- intent.md why, in business terms
54
- delta.likec4 the architecture change, tagged #<FEAT>
55
- specs/<svc>/spec.md the requirement change for one service
56
- specs/<svc>/arch.spec.md the architectural requirement change, same delta grammar
57
- specs/<svc>/openapi.yaml the endpoints this feature adds
58
- verification.yaml what was checked once the code was built
59
- features/archive/<FEAT>/ shipped changes — the evolution history
60
- \`\`\`
61
-
62
- ## \`loam.json\` — the wiring, in every repo
63
-
64
- A fleet is **N + 1 repositories**: this docs repo, and one per service. Every one
65
- of them carries its own \`loam.json\`, and every one of them commits it — that
66
- file is how any agent, human or CI runner standing in a directory finds the rest
67
- of the fleet. There is no environment variable and no global config; if a
68
- command cannot see a \`loam.json\` at or above the working directory, it refuses
69
- with \`no-config\` rather than guessing.
70
-
71
- \`\`\`json
72
- { "docsDir": "../docs-repo", "service": "payment-service", "gherkinDir": "features" }
73
- \`\`\`
74
-
75
- - \`docsDir\` — where the docs repo is, **stored exactly as it was passed** and
76
- resolved against the directory holding the \`loam.json\`. So \`../docs-repo\`
77
- keeps working on every machine that checks the repos out side by side, which
78
- is the point of committing the file. An absolute path is a warning
79
- (\`doctor.docs-absolute\`): it resolves only on the machine that ran \`loam init\`.
80
- The docs repo's own \`loam.json\` says \`"docsDir": "."\`.
81
- - \`service\` — the canonical id of the service THIS repo contains, i.e. the
82
- \`services/<id>/\` directory it is allowed to speak for. \`loam vouch\`,
83
- \`loam gherkin\` and \`loam verify --service\` all bind to it; without it they
84
- refuse (\`repository-unavailable\`) rather than write another service's
85
- documents from a repo that is not that service's.
86
- - \`gherkinDir\` — optional, default \`features\`: the directory \`loam gherkin\`
87
- writes its own \`loam/\` subtree into.
88
- - \`agentTools\` — optional: the agent tools \`loam init\` has written command and
89
- skill files for here, accumulated across runs. Written by \`init\`, read by
90
- \`loam doctor\`; nothing else depends on it, and a config without it loads.
91
-
92
- **Step 0 of everything below, once per repo:**
93
-
94
- \`\`\`sh
95
- loam init --docs ../docs-repo --service payment-service # in EACH service repo
96
- loam doctor # before you trust any of it
97
- \`\`\`
98
-
99
- \`loam init --docs <dir>\` **joins** an existing docs repo; creating a new one
100
- takes \`--create\`, so a mistyped path cannot quietly scaffold a second source of
101
- truth beside the real one. \`loam doctor\` is the read-only preflight: it reads
102
- loam.json defensively, resolves \`docsDir\`, probes access, checks \`services/\` and
103
- the fleet map, and reports the service binding — run it first when anything
104
- behaves as though the fleet were empty.
105
-
106
- **Living vs delta.** \`services/<svc>/spec.md\` is the complete current state.
107
- \`features/<FEAT>/specs/<svc>/spec.md\` is a diff against it, reviewed as a diff, with
108
- requirements grouped under \`## ADDED\`, \`## MODIFIED\` or \`## REMOVED Requirements\`.
109
- \`loam archive\` folds the delta into the living state.
110
-
111
- ## The architecture spec axis
112
-
113
- The business spec will never mention the transactional outbox — that is
114
- architecture. Retries, idempotency, metrics, alerts: real obligations no business
115
- scenario states, and exactly where generated code cuts corners unless the
116
- obligations are derived mechanically. They live in \`arch.spec.md\` — living and
117
- delta, same grammar, same delta algebra, merged by \`loam archive\` through the
118
- same code path (the two files are separate requirement namespaces).
119
-
120
- Where a business requirement carries \`Operations:\`, an arch requirement carries
121
- \`Covers:\` — the model objects its scenarios exercise, comma-separated:
122
-
123
- \`\`\`
124
- Covers: paymentService.db, paymentService -> kafka, alert:payment_5xx, sli:availability
125
- \`\`\`
126
-
127
- a C4 element (id, or the service a bound element stands for), an edge
128
- (\`source -> target\`), or a health signal the service's health.yaml declares.
129
- Every entry must resolve — \`covers.unknown\` (warn) is the typo guard, because a
130
- mistyped entry silently costs the coverage it was written for.
131
-
132
- Coverage is then derived, never trusted, as two warnings: a feature's NEW tagged
133
- elements and edges want a covering requirement in its arch.spec.md deltas
134
- (\`c4.uncovered\`), and every alert/SLI health.yaml declares wants one in the
135
- living arch spec (\`health.uncovered\`). None of this gates \`loam archive\`;
136
- \`--strict\` is the CI escalation. An absent arch.spec.md is not a finding —
137
- partial adoption is supported — but write one for any service with real
138
- architecture, or its obligations ship unchecked.
139
-
140
- **Test levels, mapped once.** A business scenario is an acceptance test. An arch
141
- scenario is an integration/ops test — the outbox relay under a dead broker, the
142
- retry that stays idempotent, the alert rule that actually fires. \`api.exposes\`
143
- is a contract test. Unit tests sit below spec granularity and are your TDD
144
- concern, not the specs'.
145
-
146
- ## The generated Gherkin suite
147
-
148
- Scenarios are the acceptance criteria, and \`loam gherkin\` makes them
149
- executable: real \`.feature\` files, written into the SERVICE'S repo — the one
150
- loam command that writes there, because tests live with the code they gate.
151
- Output lands in \`<gherkinDir>/loam/\` (\`gherkinDir\` is an optional loam.json
152
- fact, default \`features\`), and that \`loam/\` subdirectory is loam's own
153
- derived space: regeneration rewrites its scope and deletes its orphans, so
154
- never hand-edit inside it — step definitions and hand-written features belong
155
- outside it, and loam never touches a byte outside \`loam/\`.
156
-
157
- - \`loam gherkin FEAT-101\` — the feature's ADDED/MODIFIED requirements for this
158
- service, both spec axes. Files carry \`@FEAT-101\`; arch-axis files add
159
- \`@architecture\` and are named \`arch--<slug>.feature\` — their scenarios are
160
- integration/ops tests, not acceptance tests.
161
- - \`loam gherkin --service <id>\` (no feature) — the full living suite from
162
- spec.md + arch.spec.md: the regression skeleton a legacy service gets at
163
- adoption.
164
-
165
- One file per requirement (\`Feature:\` is the requirement name). Bullet lines
166
- opening with Given/When/Then/And/But — the \`- **WHEN** ...\` convention
167
- included — become steps; every other body line is kept as scenario
168
- description. Each scenario is tagged \`@loam-digest-<16hex>\`: the same body
169
- hash \`loam verify\` folds into its claim ids, riding into cucumber's JSON
170
- report as a tag, so the suite, the claim and the report cannot quietly
171
- disagree about what a scenario says. \`loam validate
172
- --service <id>\`, run in the service repo, grades the suite by those digests
173
- (\`gherkin.missing\` / \`gherkin.stale\` / \`gherkin.orphaned\`, all warn) — the
174
- fix is always regeneration, never editing a generated file.
175
-
176
- The flow, closed end to end: \`loam gherkin FEAT-101\` → write step definitions
177
- (outside \`loam/\`) → run the suite with a JSON report
178
- (\`cucumber-js --format json:report.json\`) → implement until green →
179
- \`loam verify FEAT-101 --service <id> --results report.json [--record rest.json]\`,
180
- run in that same service repo. The digest
181
- tags ride through the runner into the report, so the \`scenario.tested\` claims
182
- are answered by the green run itself — mechanically, never by anybody's word.
183
- See "The done-check" for what \`--record\` still covers.
184
-
185
- ## Frontmatter — who vouched for this, and from what
186
-
187
- Every markdown artifact opens with a YAML block:
188
-
189
- \`\`\`yaml
190
- ---
191
- service: payment-service # or feature: FEAT-101
192
- status: verified # services: draft -> verified
193
- # features: proposed -> in_progress -> built -> done
194
- owner: payments-team
195
- last_verified: 2026-07-31 # written by \`loam vouch\` — do not hand-edit
196
- sources: # files/directories in the SERVICE'S repo this was written from — no globs
197
- - src/main/java/com/shop/payment/
198
- sources_digest: 6f1c0a… # written by \`loam vouch\` — do not hand-edit
199
- content_digest: 9b2f41… # written by \`loam vouch\` — do not hand-edit
200
- sources_files: | # written by \`loam vouch\` — do not hand-edit
201
- 9b2f41… src/main/java/com/shop/payment/Api.java
202
- ---
203
- \`\`\`
204
-
205
- \`sources\` matters more than it looks. Everything else loam checks is internal
206
- consistency — and a corpus can agree with itself perfectly while describing nothing
207
- that exists. \`sources\` is the one thing tying a document to code: \`loam validate\`,
208
- run inside that service's repository, checks every listed path is still there.
209
- Entries are literal files and directories only — a directory covers everything
210
- beneath it. Glob patterns are refused loudly (\`loam vouch\` will not stamp them,
211
- \`loam validate\` errors on them): a pattern dialect nobody can be sure of digests
212
- a different file set than intended, corrupting the staleness signal silently.
213
-
214
- \`sources_digest\` is what makes that tie say something over time: a hash of the
215
- CONTENT of those files, taken when a human last vouched for the document. Every
216
- later \`loam validate\` re-computes it, so it can tell a document nobody has checked
217
- (\`sources.unvouched\`) from one that still matches the code (\`sources.current\`) from
218
- one the code has moved out from under (\`sources.stale\`).
219
-
220
- \`content_digest\` is the same promise about the document itself: a hash of the
221
- body below the frontmatter, stamped by the same vouch. It closes the other half of
222
- the forgery — editing a spec after it was vouched used to leave \`status: verified\`
223
- standing over words nobody read. \`loam validate\` recomputes it wherever it can
224
- read the doc (no service repo needed, so it fires from the docs repo too) and
225
- reports \`content.stale\` (warn): only a person can say whether verified still
226
- holds of the new words.
227
-
228
- Write \`sources\` for anything you author from reading code, and leave \`status: draft\`
229
- until a human has read it. Promoting draft to verified is their call, not yours, and
230
- it has its own command — \`loam vouch --service <id>\`, run inside that service's repo,
231
- which stamps the status, the date, the identity (\`vouched_by\`, from git), both digests
232
- and the per-file index
233
- (\`sources_files\`, which is what lets a later \`sources.stale\` name the paths that
234
- moved) together — into the living spec.md
235
- and, when the service has one, arch.spec.md, in one all-or-nothing run: a file whose
236
- sources cannot be verified refuses the whole vouch. Never write those six fields by
237
- hand: a status with no digest behind it is a claim with nothing behind it. If the
238
- document changed under vouch between the read and the write, it refuses with
239
- \`vouch-raced\` and stamps nothing — re-read and re-run.
240
-
241
- **\`loam vouch\` is not yours to run**, and it is built so that it cannot be. It is
242
- the one command whose output is a claim about a HUMAN act — everything else loam
243
- checks is internal consistency, which fluent prose satisfies on its own — so it
244
- refuses \`vouch-unattended\` when stdin is not a terminal (and in \`--json\` mode,
245
- where a question cannot be asked), answers \`vouch-declined\` when the person says
246
- no, and refuses \`vouch-unattributable\` when git can name nobody, because a stamp
247
- with no \`vouched_by\` behind it records only that the word was written. \`--yes\`
248
- exists for a person's own scripted run; typing it on someone's behalf is
249
- writing their name. The generated command and skill files pre-approve loam's
250
- read-only and authoring verbs one by one and deliberately not this one: when
251
- the work is done, hand back and say a vouch is owed.
252
-
253
- ## The ID spine
254
-
255
- Three artifacts describe the same call in three languages. They are joined by the
256
- **operationId**, and that join is what \`loam validate\` checks:
257
-
258
- - architecture — a C4 edge names the operation it calls:
259
- \`checkoutWeb -> paymentService 'Calls authorizePayment' { metadata { op 'authorizePayment' } }\`
260
- - behaviour — a requirement declares the operations it governs:
261
- \`Operations: authorizePayment\`
262
- - contract — the provider's \`openapi.yaml\` defines it:
263
- \`operationId: authorizePayment\`
264
-
265
- Spell it identically in all three places. A mismatch is an error, not a warning:
266
- an edge calling an operation nobody defines is a broken contract between services.
267
-
268
- Features are joined the same way: a delta's new elements and edges carry the
269
- feature id as a LikeC4 tag (\`#FEAT-101\`), and that tag is exactly what
270
- \`loam archive\` folds into the living landscape. Untagged elements in a delta are
271
- context for the diagram, not changes.
272
-
273
- ## The requirement baseline — \`Based-On:\`
274
-
275
- A MODIFIED requirement carries its FULL new text, not a diff, so archive does not
276
- merge your wording into the living one — it REPLACES it. When two features rewrite
277
- the same requirement, the loser loses everything they wrote, scenarios and all.
278
-
279
- \`loam validate\` names the other feature while both are in flight
280
- (\`delta.modified-conflict\`), but that warning cannot see the case that actually
281
- happens: the first feature archives, stops being an active feature, the second
282
- revalidates GREEN, and its archive lands text written against a document that no
283
- longer exists — \`+0 ~1 -0\`, exit 0, nobody told.
284
-
285
- So a MODIFIED or REMOVED requirement pins the living version it was written
286
- against, directly under its identity:
287
-
288
- \`\`\`markdown
289
- ### Requirement: Cancel an order
290
- Requirement-ID: REQ-042
291
- Based-On: 3bcc7a40f2b23ff3
292
- \`\`\`
293
-
294
- You do not compute that value — \`loam rebase FEAT-101\` writes it, for every
295
- MODIFIED/REMOVED requirement in the feature, from the living text as it stands
296
- right now. Run it when you finish authoring a delta, and again after you resolve
297
- a collision. Archive refuses on a stale pin (\`delta.baseline-stale\`) and says
298
- both digests.
299
-
300
- **Restamping is not resolving.** A pin claims "I read this version". If
301
- \`loam rebase\` reports a requirement as moved, re-read the living requirement and
302
- fold in what you still mean BEFORE you ship — otherwise you have re-pinned to a
303
- document you never read, and archive will merge your text over theirs with
304
- loam's blessing. There is no automatic three-way merge and there will not be one:
305
- requirement prose does not merge, people do.
306
-
307
- A delta adopted from OpenSpec has no such line and gets a warning
308
- (\`delta.baseline-missing\`), never a refusal — an entire migrated corpus that
309
- cannot archive is not a safety property.
310
-
311
- ### The same pin on the contract axis — and why it matters more there
312
-
313
- A feature's \`openapi.yaml\` is a COMPLETE document, not a patch: you restate the
314
- living contract around the slot you are changing, and the merge upserts every
315
- operation the document spells. So the collision on this axis needs no overlap at
316
- all. Two features touching one service, editing DIFFERENT operations, destroy
317
- each other — the second to archive quietly pushes its authoring-time copy of the
318
- operation it never meant to touch back over whatever landed in between.
319
-
320
- The same marker fixes it, as a vendor extension beside \`x-loam-remove\`:
321
-
322
- \`\`\`yaml
323
- paths:
324
- /orders/{id}/cancel:
325
- post:
326
- operationId: cancelOrder
327
- x-loam-based-on: 86feabe621ed887d
328
- \`\`\`
329
-
330
- \`loam rebase\` writes one on EVERY operation in the delta, pinned to the living
331
- version. That is what lets the merge tell a QUOTE from an EDIT:
332
-
333
- - pin equals the operation's own content → you quoted it → **the merge skips it
334
- entirely** and the living contract keeps whatever it holds. Archive prints
335
- \`· quotes …\` so the plan never silently writes less than your delta spells.
336
- - pin equals the living operation → you edited it, nobody else did → merged.
337
- - pin matches neither → you edited it AND somebody landed a change to it →
338
- \`openapi.baseline-stale\`, refused.
339
-
340
- Not covered, deliberately, and worth knowing: path-level keys (\`parameters\`,
341
- \`servers\`) and \`components/schemas\` are still upserted wholesale, so a restated
342
- schema can still revert another feature's change to it —
343
- \`openapi.path-item-modified\` and \`openapi.component-modified\` are your only
344
- warning there. Removal markers carry no pin either; \`openapi.remove-target-mismatch\`
345
- is what guards those.
346
-
347
- ## Which element IS which service
348
-
349
- An element says which service it is with \`metadata { service 'payment-service' }\`.
350
- Without one, its **title** is used instead — which is what most of this repo relies
351
- on, and also the trap: rename a box in a diagram and every check joining it to
352
- \`services/<svc>/\` silently stops matching. Bind an element whenever its title is
353
- not exactly the directory name, and prefer binding over renaming a directory.
354
-
355
- \`services/\` is the list of services — there is no manifest, and none should be
356
- added. \`loam validate --all\` compares that list to the landscape both ways: a
357
- directory nothing draws is an error, and an element with no directory is a warning.
358
- Systems that are not ours — kafka, a payment gateway — carry \`#external\`, which
359
- says so once and stops the warning for good.
360
-
361
- ## The cycle
362
-
363
- 0. **Wire the repo** — \`loam init --docs <path-to-docs-repo> --service <id>\` in
364
- each service repo, then \`loam doctor\`. Once per repository, before anything
365
- else; see "\`loam.json\` — the wiring" above. Every step below runs in a repo
366
- that has been through this one.
367
- 1. **Adopt** — a service with no documentation at all starts at
368
- \`loam adopt --service <id> --json\`, which briefs you on the baseline to write.
369
- See "Adopting a service" below. Once per service, not once per feature.
370
- 2. **Understand** — \`loam status --json\` first if you joined halfway or lost the
371
- session: it says what is in flight and what the next step is, for the fleet or
372
- for one feature (\`loam status FEAT-101 --json\`). Then \`loam list --json\` and
373
- \`loam show <service> --json\`.
374
- Then \`loam explore <service> --json\` around the services you believe are
375
- involved — it writes nothing and answers the one question the next step takes
376
- as an argument: which services this change actually touches. It reports the
377
- ring one hop out in the fleet map (\`neighbours\`), how far each service's
378
- documentation has got (\`maturity\`), what each already exposes, the features
379
- already in flight over the same ground (\`overlaps\`), and the \`loam new\` line
380
- the seeds imply (\`scaffold\`). \`--op <operationId>\` seeds from an operation
381
- when you know the call but not who owns it.
382
- Never propose a change to a service you have not read.
383
- 3. **Scaffold** — \`loam new FEAT-101 --title "..." --touches <existing> --new-service <new>\`.
384
- The scaffold leaves a \`delta.likec4\` whose context elements are commented out —
385
- a requirements-only feature should DELETE that file rather than ship an empty
386
- one — and gives every \`--new-service\` a \`specs/<svc>/arch.spec.md\` alongside.
387
- 4. **Author** the four files the scaffold left as TODO: intent, delta.likec4,
388
- a spec.md per service, an openapi.yaml per new service. Finish with
389
- \`loam rebase FEAT-101\`: it pins every MODIFIED/REMOVED requirement and every
390
- operation in the contract delta to the living version you wrote it against.
391
- On the contract axis run it even when you changed nothing but one endpoint —
392
- that is exactly what marks the REST of the document as quotation the merge
393
- must not write. See "The requirement baseline".
394
- 5. **Check** — \`loam validate FEAT-101 --json\`. Fix every error before writing code.
395
- \`loam dependencies --json\` says whether another feature in flight has to
396
- archive first.
397
- 6. **Build** — \`loam delta FEAT-101 --service <svc> --json\` is the task for one service:
398
- intent, its requirement delta with scenarios verbatim, and the edges around it.
399
- In the service's own repo, \`loam gherkin FEAT-101\` then emits those scenarios as
400
- \`.feature\` files under \`<gherkinDir>/loam/\` — write step definitions against them
401
- **first**, outside \`loam/\`, and implement until they pass.
402
- If \`architecture.errors\` is non-empty the payload keeps \`ok: true\` (the command
403
- ran) but the exit code is 1: the C4 slice is empty because delta.likec4 did not
404
- parse, not because the feature changes no architecture — do not build on it.
405
- 7. **Verify** — \`loam verify FEAT-101 --json\` turns the feature's own promises into a
406
- checklist. The scenario claims are answered by the cucumber report
407
- (\`--results report.json\`); answer the rest with evidence and record them
408
- (\`--record answers.json --service <id>\`), **from each affected service's own
409
- repo**. See "The done-check".
410
- 8. **Ship** — \`loam archive FEAT-101\` once the code is merged. \`--dry-run\` shows
411
- every file the merge would write, and writes none of them.
412
-
413
- ## Adopting a service
414
-
415
- \`loam adopt --service <id>\` does not read code and never will. Nothing
416
- deterministic can look at a legacy service and say what its architecture MEANS,
417
- and a guessed model is worse than none: everybody downstream has to re-derive it
418
- to know whether to believe it. So loam takes the half of the job that is
419
- mechanical — stating the work, and checking the result — and you do the reading.
420
-
421
- The brief names every file to write, the grammar each must be in, what the
422
- landscape already says about the service (bind to those elements, do not draw a
423
- parallel fleet), the frontmatter, and the checks that follow. It also lists what
424
- **no** check will ever catch. Read that list: everything on it is a way to be
425
- wrong quietly, and \`loam validate\` passing means nothing about any of it.
426
-
427
- One of the targets is not under \`services/<id>/\`: \`architecture/landscape.likec4\`,
428
- the fleet map, carried with \`action: "edit"\` and a \`landscape.instruction\`
429
- spelling out the block this service owes it. That target appears only while
430
- nothing in the map resolves to the service. **Add to that file; never rewrite
431
- it** — every other service's elements and edges are in there. A service that is
432
- fully documented and undrawn is \`landscape.service-unmodelled\` (error) and is
433
- invisible to every cross-service check: no edge into it can be graded against
434
- its openapi.yaml, and no feature can draw a call to it.
435
-
436
- An adoption ends with two runs, not one: \`loam validate --service <id> --json\`
437
- for the baseline, then \`loam validate --all --json\` in the docs repo — the
438
- fleet cross-check the first invocation does not perform is exactly where a
439
- landscape edit that did not land shows up.
440
-
441
- Two rules the brief repeats and this file will too. An artifact that already
442
- exists is reported as \`action: "diff"\` — read it and report what disagrees;
443
- never replace it, because a document somebody wrote is evidence. And everything
444
- you write is \`status: draft\`: promotion is \`loam vouch\`, run by a person in
445
- the service's own repository.
446
-
447
- ## The done-check
448
-
449
- \`loam verify <FEAT>\` derives a checklist from the feature's own artifacts —
450
- one claim per new service, per operation the delta adds, per tagged edge that
451
- names an operation, and per scenario of every changed requirement, arch.spec.md
452
- deltas included (those claims name their file, and the test they ask for is an
453
- integration/ops test, not an acceptance test). Each claim has a stable id, so
454
- two runs are diffable and an answer cannot drift onto a different question.
455
-
456
- Two answer channels, and they never overlap. The \`scenario.tested\` claims are
457
- the TEST RUNNER's to answer: run the generated suite with a cucumber JSON
458
- report (\`cucumber-js --format json:report.json\`) and pass it back —
459
- \`loam verify <FEAT> --results report.json\`. A claim is confirmed only when a
460
- report scenario carrying its \`@loam-digest-<16hex>\` tag ran at least one step
461
- and every step passed — every occurrence, when the report holds a re-run, and
462
- every before/after hook with it. A failed, undefined, pending or skipped step,
463
- a failed hook, or no matching scenario at all, is \`unconfirmed\` with the
464
- reason. The digest is the only identity: a reworded spec scenario matches
465
- nothing until the suite is regenerated and re-run. When a report exists,
466
- ALWAYS pass \`--results\` — under it the runner owns every scenario claim, and
467
- an answers-file entry for one is refused.
468
-
469
- \`--record\` without \`--results\` is the fallback for a service with no runnable
470
- suite yet: there your word answers the scenario claims too. It still works, and
471
- it costs something you must know about. The record says who answered
472
- (\`answered_by: agent\`), and that answer changes the VERDICT: a feature whose
473
- scenario claims rest on an agent's word is **attested**, not verified.
474
- \`verify --json\` reports \`verdict: "attested"\` with \`verified: false\` and an
475
- \`attested\` count, \`loam status\` marks it and adds a \`next.verify-attested\` step,
476
- and the notice rides in \`notices[]\` under \`verify.scenario-attested\` (warn —
477
- nothing gates on it, because a legacy service with no suite must still be able
478
- to ship). Answer them mechanically the moment a suite runs:
479
- \`loam verify <FEAT> --results <report.json>\`.
480
-
481
- Two more codes belong to the record itself. \`verify.record-miscounted\` (error):
482
- a \`verification.yaml\` whose \`summary\` block contradicts its own \`claims[]\` — the
483
- record is refused as unreadable rather than believed in either half, and
484
- re-recording is the repair, never editing the counts. \`verify.digest-contested\`
485
- (warn): two or more services word a scenario identically, so they share one
486
- \`@loam-digest-…\` tag and no single cucumber report can say whose suite ran it —
487
- those scenario claims are left unconfirmed, and each service records its own
488
- from its own repository with \`--service\`.
489
-
490
- A consumed report is written down: \`report\` on the record (or on that
491
- repository's attestation) carries the path, a sha256 of the bytes, the file's
492
- mtime and how many tagged scenarios it held. That identifies WHICH file
493
- answered the claims, and — when the file is committed — that it matches the
494
- attested commit. It does not prove the file came from executing that commit; no
495
- digest can.
496
-
497
- Everything else you answer from the code, and \`--record answers.json\` takes
498
- those back, writing \`features/<FEAT>/verification.yaml\` — it travels into the
499
- archive with the feature and reads without loam. With \`--results\`, the answers
500
- file must answer exactly the non-scenario claims: an entry for a scenario
501
- claim is refused (\`answers-mismatch\` — the runner owns it), and \`--results\`
502
- alone is refused while non-scenario claims are outstanding. Every recorded
503
- verdict says who answered it (\`answered_by: runner | agent\`), so a reviewer
504
- can tell a green run from somebody's word.
505
-
506
- **A cross-service feature is verified once per service, and the form matters.**
507
- The claims of one feature belong to several services, and each service's code
508
- lives in its own repository — so the recording form is federated:
509
-
510
- \`\`\`sh
511
- # in EACH affected service's own repo, not in the docs repo:
512
- loam verify FEAT-101 --service payment-service --results report.json --record answers.json
513
- \`\`\`
514
-
515
- \`--service <id>\` narrows the checklist to that service's claims and binds the
516
- answers to this repository's commit, producing one **attestation** per service
517
- inside a single \`schema: 2\` \`verification.yaml\`. Re-running it in the next
518
- repo adds that repo's attestation and leaves the others standing. Omitting
519
- \`--service\` in a repo whose \`loam.json\` declares one substitutes it, so the
520
- short form is safe there; a \`--service\` naming a service this repo does not
521
- declare is refused (\`service-mismatch\`), and \`--service\` from a repo that
522
- declares none is refused (\`repository-unavailable\`) — an attestation has to be
523
- bound to the code it is about.
524
-
525
- ⚠ **The legacy all-at-once form \`loam verify <FEAT> --record answers.json\` with
526
- no \`--service\` writes the whole record from one place.** Over a record that
527
- already carries other services' attestations it is refused outright
528
- (\`record-federated\`, naming the attestors) rather than merged or migrated — the
529
- alternative was one repo silently erasing nine others' evidence. Use it only for
530
- a single-service feature that has never been recorded federated. A
531
- \`verification.yaml\` that exists but cannot be read is \`record-unreadable\`: it is
532
- never overwritten and never reported as "no record".
533
-
534
- Reading is not writing: \`loam verify <FEAT> --service <id>\` **without**
535
- \`--record\` is a pure lens on the checklist and works from anywhere, docs repo
536
- included. Every run lists all subjects in \`services\`, and a recorded run reports
537
- in \`discarded\` every previous answer the new record does not carry.
538
-
539
- It refuses an answer set that does not answer the current checklist: an id that
540
- is not on it (\`answers-mismatch\`), a claim with no answer (same), or a
541
- \`confirmed\` with no evidence (\`answers-unevidenced\`). Evidence is \`file:line\`.
542
- A claim you cannot show is \`unconfirmed\` with a note saying why — that is a
543
- successful record, and it is more useful to the next reader than a yes.
544
-
545
- **Nothing gates on it.** \`loam archive\` will ship an unverified feature. That
546
- is deliberate: coherence is something loam computed from the documents, and a
547
- verdict is your word about code loam never read. A gate in front of shipping
548
- would only teach everyone that the cheapest way past it is to say yes. The
549
- record is for the reviewer who comes later, so leave it true.
550
-
551
- ## What you author, what loam derives
552
-
553
- Authored by hand: \`intent.md\`, \`delta.likec4\`, \`specs/<svc>/spec.md\`,
554
- \`specs/<svc>/arch.spec.md\`, \`specs/<svc>/openapi.yaml\`, ADRs, runbooks, health.
555
-
556
- Written by loam: the merge into \`services/<svc>/spec.md\`, \`services/<svc>/arch.spec.md\`,
557
- \`services/<svc>/openapi.yaml\` and \`architecture/landscape.likec4\` at archive time.
558
-
559
- **Do not hand-edit the living landscape to add a feature's changes.** Put them in a
560
- delta and archive it — otherwise the change has no intent, no requirement and no
561
- history behind it.
562
-
563
- ## Rules the validator enforces
564
-
565
- - every requirement has at least one \`#### Scenario:\` — scenarios are the acceptance
566
- criteria and the source for tests;
567
- - every operationId a requirement governs exists in that service's OpenAPI;
568
- - every C4 edge's \`op\` resolves to an operation the target actually exposes;
569
- - every operation is governed by some requirement (warning);
570
- - a "Calls" edge with no \`metadata { op }\` is unlinked (warning);
571
- - **the map is complete**: every \`services/<svc>/\` has an element in the landscape,
572
- and every element that looks like a service has a directory (warning) or is
573
- tagged \`#external\`;
574
- - **the diff applies**: a \`MODIFIED\` or \`REMOVED\` requirement exists in the living
575
- spec, an \`ADDED\` one does not, and a section heading matches the grammar exactly;
576
- - a \`Requirement-ID: <id>\` is optional, but when present it is valid and unique;
577
- archive selects by it, so carry the same ID through \`MODIFIED\` to rename a heading;
578
- - **every requirement in a delta is under a delta section**: one under a prose heading
579
- (\`## Behavior\`) is documentation as far as the merge is concerned and is dropped
580
- (warning). \`## Requirements\` is exempt — quoting the living state is legal.
581
-
582
- That last pair of rules exists because each of its failures used to be silent.
583
- \`MODIFIED\` of a requirement that does not exist merged as a creation; \`ADDED\` of one
584
- that does exist REPLACED it, scenarios and all; a near-miss heading like
585
- \`## ADDED Requirement\` (singular) parses as plain prose, so archive merged nothing and
586
- said nothing; and a requirement under \`## Behavior\` vanished the same way.
587
-
588
- Write the delta section heading on its own line exactly as \`## ADDED Requirements\`.
589
- The heading is the only thing that gives a requirement its kind: without a matching
590
- one, archive merges the requirement as nothing.
591
-
592
- Severity and gating are two different questions. An ERROR means the document is
593
- invalid — \`loam validate\` fails. A finding with \`gates: true\` means \`loam archive\`
594
- will refuse — the merge is unsafe. They usually agree (errors gate, warnings do
595
- not), and where they diverge the \`--json\` output says so: \`delta.requirement-not-merged\`
596
- is a warning (the shape is legal OpenSpec) that gates (the merge would drop the
597
- requirement). Leave a warning standing only deliberately.
598
-
599
- ## Reading loam's output
600
-
601
- Every command takes \`--json\`, and the envelope holds even when the INVOCATION
602
- is wrong: with \`--json\` anywhere in the arguments, an unknown flag, unknown
603
- command or missing argument still yields \`{ ok: false, error: { code:
604
- "invalid-option" } }\` on stdout with exit 1 (commander's own diagnostic goes
605
- to stderr; \`--help\` and \`--version\` output pass through untouched). Without
606
- \`--json\` the same refusal is plain text on stderr with nothing on stdout.
607
-
608
- Branch on \`findings[].code\`, never on the prose — the wording changes, the codes do
609
- not. The code-by-code fix table lives in the \`/loam-check\` command \`loam init\` lays
610
- down. \`init\` writes that body twice for every tool it configures: once as a slash
611
- command in the tool's own command directory (you type it), and once as an Agent
612
- Skill at \`<tool-dir>/skills/loam-check/SKILL.md\` (the model loads it by itself when
613
- the task matches the skill's description). Same body, two ways in.
614
-
615
- Which tools get them is detected from the repo: \`loam init\` scans for the
616
- dot-directories of the tools it knows (\`.claude\`, \`.cursor\`, \`.gemini\`, …) and
617
- writes for the ones it finds, falling back to Claude Code when it finds none.
618
- \`loam init --tools <ids|all>\` overrides the scan — an unknown id is refused
619
- (\`invalid-option\`) rather than skipped — and \`--no-commands\` / \`--no-skills\`
620
- each suppress one delivery. The tools written for are recorded in
621
- \`loam.json\` as \`agentTools\`, which is how \`loam doctor\` tells a file missing
622
- because the binary grew a new command from one missing because nobody ever
623
- selected that tool. The map of which invocation surfaces what:
624
-
625
- - \`loam status [<FEAT>]\` is the orientation surface — the question you have when
626
- you join a repository halfway, or come back having lost the session, and the one
627
- every other command assumed you could already answer. It writes nothing and
628
- stores nothing: there is no state file, every answer is re-derived from the
629
- files, so a document someone edited in another window is visible on the next run
630
- with nothing to invalidate. Artifacts come back as \`missing\` (owed, nothing in
631
- the way — write it now), \`blocked\` (not written and not writable yet; the entry
632
- names what comes first), \`draft\` (on disk, and the shared checks report an error
633
- against it — what exists is wrong), \`ready\` (on disk and clean, but something
634
- outside the documents — code, a test run, a recording — still has to answer it)
635
- or \`done\`. The payload's reason to exist is \`next[]\`: ordered, first entry
636
- first, each carrying a code and the literal command to run. \`next.recover-commit\`
637
- outranks every other step in both forms and is never elided: a \`.loam-commit\`
638
- journal says an archive or unarchive was killed mid-commit, so some of the files
639
- everything below is derived from may be half-written. Its command is that exact
640
- archive/unarchive re-run, which repairs from the pre-image first, under the lock
641
- — except when the journal itself cannot be read, where it is \`loam doctor\` and
642
- the repair is a human's comparison against version control. Fleet-wide the rest are
643
- \`next.adopt-bound\` (this repository's own loam.json names a service the docs repo
644
- has no directory for at all — it outranks every other service's partial adoption,
645
- because it is the only step that is about the repo you are standing in, and it is
646
- the same state \`loam doctor\` reports as \`doctor.service-unknown\`),
647
- \`next.adopt\` (a service with no spec.md — nothing about it is written down, so
648
- no feature can be graded against it), \`next.complete-service\` (a living spec.md
649
- with no model.likec4 beside it), \`next.feature\` (something is in flight;
650
- ask \`loam status <FEAT>\` about it), \`next.archive\` (authored and verified —
651
- ship it, and everything waiting on it is released), \`next.fleet-clean\`
652
- (nothing is owed), \`next.elided\` (the fleet list hit its cap and says how many
653
- steps of the same kinds it left out — it is ordered most-unblocking first, so
654
- work down it and re-run) and \`next.fleet-gate\` (always last while anything is
655
- outstanding: \`loam validate --all\` is what CI runs, and the fleet form grades
656
- nothing itself). On one feature: \`next.author-intent\`, \`next.touch-service\`
657
- (no per-service delta at all yet), \`next.author-spec\`, \`next.author-openapi\`,
658
- \`next.author-scenarios\`, \`next.rebase\` (requirements or operations with no
659
- baseline pin — until they have one the merge cannot tell what it EDITS from what
660
- it merely quotes), \`next.archive-first\` (another feature in flight has to land
661
- before this one), \`next.fix-coherence\` (the three axes disagree and archive
662
- refuses), \`next.generate-tests\` (a per-service delta carries scenarios no test
663
- run has answered — \`loam gherkin <FEAT> --service <svc>\` in that service's own
664
- repository), \`next.verify\` (the done-check has not been started — or its record
665
- will not read, or it answers a checklist the feature has since moved out from
666
- under, which is not the same as a finished one), \`next.verify-unconfirmed\`
667
- (started, and claims are still open — close them from each affected service's
668
- own repository), \`next.attest-service\` (this repo IS one of the services that
669
- owes an answer, so the step is bound to this commit), \`next.verify-attested\`
670
- (every claim is confirmed but a scenario rests on an agent's word rather than a
671
- green run — see "The done-check"), \`next.archive\`, and \`next.archived\` for one
672
- that shipped.
673
- It grades nothing of its own: the verdict is the one \`loam validate --feature\`
674
- and \`loam archive\` compute — status takes the UNION of what both of them refuse,
675
- so it may be more pessimistic than either and can never be greener than both.
676
- \`verification\` carries \`verdict\` (\`verified\` | \`attested\` | \`unverified\`)
677
- and \`attested\` (how many scenario claims rest on an agent's word) beside the
678
- recounted totals, and \`checks.issues[]\` carries \`gates\` and \`details\` on every
679
- finding.
680
- - \`loam validate --service <id>\` grades one service's own axes: \`service.unknown\`,
681
- \`service.no-model\`, \`service.no-spec\`, \`service.no-openapi\`, \`c4.invalid\`,
682
- \`requirements.missing-scenarios\`, \`requirements.stepless-scenario\`,
683
- \`spec.merge-conflict\`, \`spec.duplicate-requirement\`,
684
- \`spec.no-requirements\`, \`spec.repeated-operations\`, \`spec.repeated-covers\`,
685
- \`openapi.invalid\`, \`openapi.duplicate-operationid\` (one operationId in two
686
- (path, method) slots — every join on the id then picks one of them arbitrarily),
687
- \`api.ungoverned\`, \`api.ops-unlinked\`,
688
- \`api.requirement-deprecated\`, \`spec-api.op-undefined\` (the living spec's own
689
- \`Operations:\` lines, not only a delta's), \`spine.landscape-invalid\`, \`spine.op-undefined\`,
690
- \`spine.op-link-missing\`, \`spine.op-deprecated\`,
691
- the async contract axis (AsyncAPI 3): \`service.no-asyncapi\`, \`asyncapi.invalid\`,
692
- \`asyncapi.duplicate-message\`, \`spine.message-undefined\`,
693
- \`spec-event.message-undefined\`, \`spine.message-unproduced\`,
694
- \`asyncapi.message-contested\`, \`event.messages-unlinked\`, \`event.covered\`, and
695
- the architecture spec axis: \`covers.unknown\`, \`health.invalid\`, \`health.uncovered\`. Run inside
696
- the service's own repo, once a generated suite exists under
697
- \`<gherkinDir>/loam/\`, it also grades that suite against the living specs:
698
- \`gherkin.missing\`, \`gherkin.stale\`, \`gherkin.orphaned\` (all warn — the fix is
699
- always regeneration, never editing a generated file). A service that never ran
700
- \`loam gherkin\` stays quiet, and a file tagged with a feature still in flight
701
- answers to that feature, not to the living spec it has not merged into yet.
702
- - \`loam validate --feature <id>\` grades a change's three axes against each other and
703
- against the fleet in flight: \`delta.invalid\`, \`delta.nothing-tagged\`,
704
- \`delta.service-unknown\`,
705
- \`spec-api.op-undefined\`, \`spec-api.op-pending\`, \`c4-api.op-undefined\`,
706
- \`c4-api.op-pending\`, \`c4-api.op-deprecated\`, \`c4.op-ungoverned\`, \`c4.op-link-missing\`,
707
- \`api.op-unconsumed\`, \`service.no-requirement-delta\`, \`archedge.uncovered\`,
708
- \`spec.repeated-operations\` / \`spec.repeated-covers\` (on the feature's own
709
- spec deltas — same silent-line-loss check as service scope),
710
- \`spec.merge-conflict\` / \`requirements.stepless-scenario\` (same codes as
711
- service scope, graded on the feature's spec.md and arch.spec.md deltas —
712
- both breaches merge into the living document),
713
- the architecture spec axis (\`c4.uncovered\`, plus \`covers.unknown\` on the
714
- feature's arch.spec.md deltas), and
715
- the delta-shape group: \`delta.unknown-section\`, \`delta.no-delta-sections\`,
716
- \`delta.requirement-not-merged\`, \`delta.modified-unknown\`, \`delta.removed-unknown\`,
717
- \`delta.added-duplicate\`, \`delta.added-near-duplicate\`, \`delta.modified-pending\`,
718
- \`delta.removed-pending\`, \`delta.added-conflict\`, \`delta.modified-conflict\`,
719
- \`delta.living-duplicate-requirement\`, and the API-removal group:
720
- \`openapi.remove-marker-anonymous\`, \`openapi.remove-op-consumed\`.
721
- Where a finding names another feature in flight (\`delta.*-pending\`,
722
- \`delta.added-conflict\`, \`delta.modified-conflict\`, \`spec-api.op-pending\`,
723
- \`c4-api.op-pending\`) the ORDER is the answer, and \`loam dependencies --json\`
724
- is what computes it: \`order\` is a dependency-first sequence, \`conflicts\`
725
- the pairs no ordering fixes, \`cycles\` the ones that need a human.
726
- - \`loam validate --all\` runs both of those for everything and adds the fleet
727
- cross-check: \`landscape.missing\`, \`landscape.invalid\`,
728
- \`landscape.merge-conflict\`, \`landscape.service-unmodelled\`,
729
- \`landscape.service-undocumented\`, \`landscape.binding-unknown\`,
730
- \`landscape.binding-duplicate\`, \`service.id-invalid\` (a \`services/<id>/\`
731
- directory no loam command can address — fleet scope only, and graded before
732
- the map is opened, so it stands even when the landscape is missing or
733
- unreadable), plus a per-service
734
- \`sources.unverifiable-from-here\` (severity \`ok\`, one per service whose
735
- \`sources\` only its own repo can check — it is a confirmation, not work, and
736
- the fleet rollup line is derived from those findings), and one check on this very file — \`agents.stale\`
737
- (warn) — when the version stamp on line 1 (\`<!-- generated by loam vX.Y.Z -->\`)
738
- is missing or older than the running binary: the tables here may describe a
739
- loam that no longer exists, so review this file against the current \`--help\`
740
- and update the stamp line. A hand-curated file silences it the same way, by
741
- keeping the stamp current. The file is never refreshed automatically — your
742
- edits outrank the template, so detection is all loam does.
743
- - \`loam doctor\` is read-only local/fleet preflight — the first thing to run in a
744
- repo that behaves as though the fleet were empty, and every finding carries a
745
- \`fix\`. Its blockers are
746
- \`doctor.config-missing\`, \`doctor.config-invalid\`, \`doctor.docs-missing\`,
747
- \`doctor.services-missing\`, and the three that say the fleet map cannot be read
748
- at all — \`doctor.landscape-merge-conflict\` (it still holds \`<<<<<<<\` markers,
749
- so it is two halves of two different maps), \`doctor.landscape-invalid\` (it does
750
- not parse) and \`doctor.landscape-unreadable\` (it is there but could not be
751
- read). It also grades what a WRITE that did not finish left in the docs repo,
752
- reported as \`writePath\` beside the findings: \`doctor.docs-locked\` (a held
753
- \`.loam-lock\` — a warning while its holder is alive, since the answer is to wait
754
- and re-run, and a BLOCKER once the holder is a process that no longer exists on
755
- this host, since nothing will ever release it and every archive and unarchive
756
- refuses \`docs-busy\` until it is deleted), \`doctor.commit-interrupted\` (blocker:
757
- a \`.loam-commit\` naming an archive or unarchive that was killed mid-commit, so
758
- the living docs may be half-written — re-run that exact command and it recovers
759
- first, under the lock), \`doctor.commit-unreadable\` (blocker, and the worst case:
760
- the record of which files that commit had already written cannot be parsed, so
761
- nothing can grade it — compare against version control by hand) and
762
- \`doctor.staging-temps\` (warn: orphaned \`.loam-*.tmp\` scratch that was never
763
- linked into place — litter, not damage). Accessibility, portability and
764
- incomplete binding stay warnings:
765
- \`doctor.docs-unreadable\`, \`doctor.docs-readonly\`, \`doctor.docs-absolute\`
766
- (\`docsDir\` stored as an absolute path in a committed loam.json — it resolves
767
- only on the machine that ran \`loam init\`), \`doctor.inventory-unreadable\`,
768
- \`doctor.landscape-missing\`, \`doctor.service-unbound\` (no \`service\` in loam.json —
769
- never raised inside the docs repo itself, where having none is the correct state
770
- and binding one would be meaningless), \`doctor.service-unknown\` (loam.json names
771
- a service the docs repo has no directory for — the same state \`loam status\`
772
- reports as \`next.adopt-bound\`), \`doctor.likec4-config-missing\` (the docs repo
773
- has no \`likec4.config.json\`, so pointing LikeC4's own renderer at it fails:
774
- loam parses each \`.likec4\` file alone and each declares its own
775
- \`specification\` block, so a workspace load merges them and every declaration
776
- reads as a duplicate — the \`fix\` field carries the exact file to write),
777
- and the two about this repo's own generated command and skill files:
778
- \`doctor.agent-files-missing\` — some of them are absent, because the repo was
779
- initialized by an older binary or they were deleted — and
780
- \`doctor.agent-files-stale\` — some that ARE here carry no
781
- \`<!-- generated by loam vX.Y.Z -->\` stamp, or one older than the running
782
- binary, so the protocol and code tables they instruct an agent with may
783
- describe a loam that no longer exists. EXPECT the second one on the first
784
- \`loam doctor\` after an upgrade, on a repo nobody has touched: stamping is
785
- newer than the files, so nothing written by an earlier loam carries a stamp
786
- at all, and every one of them reads as unstamped. That is the intended
787
- reading — an unstamped file is one nobody has confirmed still describes this
788
- binary — but it means the finding is not evidence that anything was edited or
789
- broken.
790
- For the first, run the command the finding's own \`fix\` field spells: it names
791
- this repo's \`docsDir\`, its service binding and its tool ids explicitly, and
792
- \`loam init\` writes only the command and skill files that are absent and never
793
- touches one that already exists. (It does rewrite \`loam.json\` — but a re-run
794
- with no \`--docs\` keeps the pointer the file already commits, so it cannot move
795
- the fleet under you. Reach for \`--create\` only to make a docs repo that does
796
- not exist yet; beside a working \`docsDir\` it scaffolds a second, empty source
797
- of truth and \`validate --all\` then goes green over an empty fleet.)
798
- For the second there is no command: the repair is by hand, one file at a
799
- time. Read each file the finding names against the body this loam writes,
800
- then set (or add) its stamp line to \`<!-- generated by loam vX.Y.Z -->\` for
801
- the running version — or, if you have no local edits worth keeping, delete
802
- that file and re-run \`loam init\`, which lays the current one down. Neither is
803
- ever regenerated in place: your edits outrank the template, so detection is
804
- all loam does, and the stamp is a human's claim that the file still means what
805
- it says rather than a refresh. Leaving either finding standing costs entry
806
- points and the accuracy of what the entry points say, nothing else.
807
- - \`loam rebase <FEAT>\` writes the baseline pins: \`Based-On:\` under every
808
- MODIFIED/REMOVED requirement in the feature's spec deltas, and
809
- \`x-loam-based-on\` on every operation in its openapi.yaml, from the living text
810
- as it stands right now. Run it when you finish authoring a delta and again
811
- after you resolve a collision — it is the one mechanism standing between two
812
- features in flight and silently reverting each other's landed work. It refuses
813
- rather than inventing a pin, and re-pinning is not resolving: see
814
- "The requirement baseline".
815
- - \`loam dependencies [<FEAT>]\` derives the ordering of the features in flight
816
- from the artifacts themselves — requirement identities and OpenAPI
817
- operationIds, never from validator prose. It is the answer to every
818
- "another feature in flight does this first" finding, and to
819
- \`delta.added-conflict\` / \`delta.modified-conflict\`, which no ordering fixes.
820
- - \`loam explore [<service>...]\` reads the fleet around a change nobody has
821
- written down yet, and writes nothing. It exists because \`loam new\`'s
822
- \`--touches\` list is the hardest call in the cycle and the only one nothing
823
- downstream catches: a list short by one service produces a feature that
824
- validates, archives and ships with a consumer nobody updated. What it derives
825
- is mechanical — the one-hop ring, each service's rung, its living operations,
826
- who already calls whom, and which active features cover the same services. It
827
- does NOT decide which of those neighbours you actually change; that is a
828
- judgement about intent, and loam does not make those.
829
- - \`loam instructions [<workflow>] [args...]\` prints one of the six workflow
830
- protocols — \`loam-adopt\`, \`loam-feature\`, \`loam-implement\`, \`loam-check\`,
831
- \`loam-verify\`, \`loam-ship\` — with \`$1\`, \`$2\` filled in from the arguments you
832
- pass. The protocol ships inside the binary, so it describes the loam you are
833
- about to run rather than the one that scaffolded the repository; the command
834
- and skill files \`loam init\` writes are pointers at it. It reads no
835
- \`loam.json\` and no docs repo, deliberately: \`loam-adopt\`'s own first step is
836
- to run \`loam init\` when there is no config, so it cannot be the step that
837
- requires one.
838
- - \`loam audit-openspec <root>\` is the read-only OpenSpec inventory; an explicit
839
- \`--write-mapping\` writes a non-overwriting, digest-bound decision skeleton
840
- outside the source tree. \`loam migrate-openspec <root> --map <file>\` validates
841
- that mapping and stays dry-run unless both \`--apply\` and an empty \`--target\`
842
- are supplied. Living/active shape diagnostics are \`openspec.workspace-empty\`
843
- (the audited root holds no living spec and no active change, so there is
844
- nothing to migrate and no verdict over it can be \`ready\`),
845
- \`openspec.specs-missing\`,
846
- \`openspec.config-invalid\`, \`openspec.external-store-pointer\`,
847
- \`openspec.living-empty\`, \`openspec.living-requirements-outside-section\`,
848
- \`openspec.living-delta-section\`, \`openspec.nonstandard-living-spec\` (markdown
849
- under \`specs/\` named neither spec.md nor design.md, so no capability reads
850
- it), \`openspec.change-metadata-invalid\`,
851
- \`openspec.change-schema-unresolved\`, \`openspec.skip-specs-with-specs\`,
852
- \`openspec.change-no-specs\`, \`openspec.change-empty\`,
853
- \`openspec.change-without-delta-sections\`,
854
- \`openspec.change-requirements-outside-delta-sections\`,
855
- \`openspec.change-quoted-requirements\` (requirements under a \`## Requirements\`
856
- heading INSIDE a change delta — the shape OpenSpec's own living-spec template
857
- mandates, and one that stages nothing, so loam refuses it rather than counting
858
- requirements it cannot route),
859
- \`openspec.hidden-change-directory\` (a dot-prefixed directory under
860
- \`changes/\` is not enumerated as a change, so nothing under it migrates —
861
- rename it or move it out; under \`changes/archive/\` the same code is an archive
862
- diagnostic and does not gate, because frozen history never blocks),
863
- \`openspec.nonstandard-change-spec\`, \`openspec.renamed-malformed\`,
864
- \`openspec.requirement-id-invalid\`, \`openspec.requirement-id-repeated\`,
865
- \`openspec.requirement-id-duplicate\` (a stable \`Requirement-ID\` that is
866
- malformed, declared twice, or shared by two requirements — identity is never
867
- inferred from an invalid declaration, so the RENAMED chain cannot be followed
868
- through it), \`openspec.symlink-unsupported\`, and \`openspec.non-utf8-artifact\`;
869
- the same shapes in frozen history are non-blocking archive diagnostics.
870
- Mapping refuses stale or incomplete decisions under \`mapping.source-missing\`,
871
- \`mapping.source-root-mismatch\`, \`mapping.source-digest-mismatch\`,
872
- \`mapping.unknown-capability\`, \`mapping.unknown-requirement\`,
873
- \`mapping.requirement-allocation-missing\`,
874
- \`mapping.requirement-service-unknown\`, \`mapping.service-allocation-empty\`,
875
- \`mapping.unknown-change\`, \`mapping.change-title-missing\`,
876
- \`mapping.feature-id-invalid\`, \`mapping.feature-id-duplicate\`,
877
- \`mapping.unknown-rename\`, \`mapping.invalid-requirement-id\`,
878
- \`mapping.rename-source-missing\`, \`mapping.rename-source-ambiguous\`,
879
- \`mapping.rename-source-id-invalid\`, \`mapping.rename-target-conflict\`,
880
- \`mapping.rename-existing-id-conflict\`, \`mapping.rename-id-conflict\`,
881
- \`mapping.rename-double-source\`, \`mapping.rename-double-target\`,
882
- \`mapping.rename-chain\`,
883
- \`mapping.unknown-artifact\`, and \`mapping.invalid-artifact-disposition\`.
884
- - Repository containment failures are explicit: \`sources.path-outside\` rejects a
885
- provenance source outside the service repo and \`gherkin.path-outside\` rejects a
886
- generated-suite target outside it. Federated verify recording refuses under
887
- \`service-mismatch\`, \`unknown-service\`, or \`repository-unavailable\` when the
888
- selected service, fleet identity, and repository commit cannot be bound safely,
889
- and under \`record-federated\` when the legacy all-at-once form would erase other
890
- repositories' attestations.
891
- - A file loam cannot read is reported, never skipped: \`service.unreadable\` and
892
- \`feature.unreadable\` (both errors) name the path and say that nothing about
893
- that target was checked while the rest of the fleet still was. The whole docs
894
- repo being unreachable is a refusal instead: \`docs-missing\` (\`docsDir\` points
895
- at nothing) or \`services-missing\` (it is a directory with no \`services/\` —
896
- usually the service repo after a typo in \`docsDir\`).
897
- - Both modes read frontmatter (\`frontmatter.missing\`, \`frontmatter.malformed\`,
898
- \`frontmatter.field-mismatch\`,
899
- \`frontmatter.status-unknown\`, \`frontmatter.field-missing\`); a service's spec.md —
900
- and its arch.spec.md, same conventions — additionally carries the sources chain
901
- (\`sources.absent\`, \`sources.path-missing\`, \`sources.empty\`,
902
- \`sources.skipped\`, \`sources.unvouched\`, \`sources.stale\`) and the doc-side freshness check
903
- (\`content.stale\`) — the one provenance warning that needs no service repo, so
904
- it is reported from the docs repo too, \`--service\` and \`--all\` alike.
905
- - \`loam archive\` alone reports the breaches only the merge computation can see:
906
- \`living.requirement-outside-requirements\` (error), \`openapi.op-modified\` (warn),
907
- \`openapi.path-item-modified\` (warn), \`openapi.component-modified\` (warn),
908
- \`openapi.ref-unresolved\` (error), \`openapi.remove-marker-path-level\` (error —
909
- an \`x-loam-remove: true\` written at PATH level, beside the methods rather than
910
- inside one: it addresses no operation, so it retires nothing, and it is not a
911
- contract key either) and \`service.no-model\` (warn — the archive creates
912
- \`services/<id>/\`, or puts a service in the landscape the fleet has no directory
913
- for at all, and nothing writes its model.likec4).
914
-
915
- \`loam validate <target>\` is the positional spelling of the first two: a feature id
916
- or a service id, tried in that order, so the feature wins when one name could be
917
- both and \`--service\`/\`--feature\` force the reading. When the name really is both,
918
- the run says so — \`target.ambiguous\` (warn) names the reading it took and the
919
- flag that forces the other, and \`--json\` carries \`resolvedKind\` — because a
920
- silent precedence rule is a report about a target nobody asked for. The
921
- positional together with \`--all\`, \`--service\` or \`--feature\` is refused
922
- (\`invalid-option\`).
923
-
924
- Two rendering levers change nothing else: \`loam validate --errors-only\` drops
925
- the confirmations from the TEXT view (the \`--json\` payload is byte-identical),
926
- and \`loam list --needs-work\` narrows the service list to the ones with
927
- something missing — the adoption worklist.
928
-
929
- \`--strict\` (every targeting mode, \`--all\` included) exits 1 when any error
930
- or warning exists — \`ok\`-severity findings are confirmations and never trip
931
- it. It changes the exit code and nothing else:
932
- \`valid\` still means "no errors", and the \`--json\` payload stays byte-for-byte
933
- what it was. The stricter grade is a per-invocation lever, visible in the CI
934
- pipeline that passes the flag — deliberately not a per-repo profile.
935
-
936
- \`loam delta <FEAT> --json\` exits 1 when either authored document behind the brief
937
- failed to parse — \`architecture.errors\` non-empty, or \`openapi.unreadable\` true —
938
- with \`ok: true\` and the full payload intact: the empty C4 slice means the delta
939
- did not parse, and an empty \`api\` beside \`openapi.unreadable\` means the contract
940
- delta did not parse, neither of them "this feature changes nothing there". Branch
941
- on the exit code before consuming either slice as a task brief.
942
-
943
- Findings with severity \`ok\` are confirmations, not work: \`c4.valid\`, \`delta.valid\`,
944
- \`requirements.covered\`, \`api.covered\`, \`spine.resolved\`, \`event.covered\`, \`coherence.ok\`,
945
- \`landscape.matched\`, \`archedge.covered\`, \`sources.resolved\`, \`sources.current\`,
946
- \`gherkin.current\`.
947
-
948
- A finding's \`subject\` names the service it is about. The envelope separates \`ok\` (the
949
- command ran) from \`valid\` (the docs pass). A refusal is \`ok: false\` with a stable
950
- \`error.code\`: \`no-config\` / \`config-invalid\` (no loam.json / a corrupt one),
951
- \`docs-missing\` / \`services-missing\` (a \`docsDir\` that points at nothing, or at a
952
- directory that is not a docs repo — the fleet is unreadable, not empty),
953
- \`unknown-target\` (no such service or feature), \`invalid-option\` (flags that contradict
954
- each other, or a value that cannot be right — a \`loam list\` section that is not
955
- services or features, or a service id that is not a legal
956
- \`services/<id>/\` directory name, included), \`already-exists\` (\`loam new\` refusing
957
- to scaffold over an existing feature), \`sources-absent\` / \`sources-path-missing\` /
958
- \`vouch-raced\` (\`loam vouch\` refusing to stamp — the last one when the document
959
- changed between the read and the write, so nothing was written),
960
- \`not-coherent\` / \`living-outside-requirements\` /
961
- \`archive-exists\` / \`merge-failed\` / \`rollback-incomplete\` / \`docs-busy\` /
962
- \`commit-interrupted\`
963
- (\`loam archive\` — see the
964
- archive gate below; \`docs-busy\` means another archive or unarchive holds the docs
965
- repo's lock, nothing was read or written, and re-running once it finishes works),
966
- \`feature-active\` / \`snapshot-missing\` / \`snapshot-stale\` / \`snapshot-corrupt\` /
967
- \`restore-failed\` / \`rollback-incomplete\` / \`docs-busy\` / \`commit-interrupted\`
968
- (\`loam unarchive\` — the
969
- \`restore-failed\` / \`rollback-incomplete\` pair splits
970
- exactly as archive's does; see "Taking an archive back"). \`commit-interrupted\` is
971
- the pair's shared refusal: a previous archive or unarchive was killed mid-commit
972
- and this run cannot repair it on its own — a half-written file has been edited
973
- since, the repairing pre-image is gone or altered, or \`.loam-commit\` itself
974
- cannot be read. \`loam doctor\` names the same state as
975
- \`doctor.commit-interrupted\`. And \`gherkin-conflict\`
976
- (\`loam gherkin <FEAT>\` would overwrite a \`.feature\` file owned by another
977
- feature still in flight — the whole emission refuses and names the owner),
978
- \`answers-unreadable\` / \`answers-mismatch\` /
979
- \`answers-unevidenced\` / \`record-federated\` / \`record-unreadable\`
980
- (\`loam verify --record\` / \`--results\` — an unreadable or
981
- unrecognizable cucumber report refuses under \`answers-unreadable\` too;
982
- \`record-federated\` and \`record-unreadable\` are the two records loam will not
983
- overwrite), and
984
- \`internal\` — an unexpected throw, the one code with no stable meaning.
985
-
986
- \`--all\` reports a target per service, a target per feature in flight, and one target
987
- of kind \`landscape\` for the fleet-level checks that belong to no single service.
988
-
989
- Three different words for three different questions, and no command conflates them:
990
- \`ok\` — the command ran; \`valid\` — the documents pass (\`validate\`); \`verified\` —
991
- somebody says the code was built and showed evidence (\`verify\`). A feature can be
992
- valid and unverified, or verified and incoherent. Read the one you meant.
993
-
994
- ## The archive gate
995
-
996
- The three axes agreeing is called **coherence**, and \`loam validate --feature\` reports
997
- it as such. \`loam archive\` runs the same coherence check first and refuses a feature
998
- with GATING issues — every error, plus the rare warning marked \`gates: true\` because
999
- the merge would silently drop authored content even though the document is legal.
1000
- Advisory warnings never block: archive prints them and proceeds. Each one still names
1001
- something real — usually something the merge will drop or overwrite — so read them
1002
- before the merge runs, not after.
1003
-
1004
- \`--approve\` overrides the gating issues — only those, and archive prints exactly which
1005
- ones it overrode. It is a human decision, not an agent's: if archive refuses, fix the
1006
- breach or hand it back.
1007
-
1008
- Breaches only the merge computation itself can see are reported at plan time,
1009
- after the gate. \`living.requirement-outside-requirements\` (error): the LIVING spec
1010
- holds a requirement outside \`## Requirements\`, and the merge rewrites only that
1011
- section, so the requirement would land in the file twice — \`--approve\` does not
1012
- override it, because the duplication is mechanical, not a judgment call; re-home the
1013
- requirement first. \`openapi.op-modified\` (warn): the feature redefines an operation
1014
- the living OpenAPI already has, and the merge overwrites the living definition
1015
- wholesale.
1016
-
1017
- The OpenAPI merge also carries the merged operations' \`$ref\` closure: every
1018
- \`#/components/<kind>/<name>\` they reference — recursively, a component's own refs
1019
- included — is copied from the feature document into the living one, so an operation
1020
- never lands pointing at a schema that stayed behind. \`openapi.component-modified\`
1021
- (warn): a carried component overwrites a living one that differs, wholesale, same
1022
- discipline as an operation. \`openapi.ref-unresolved\` (error, \`--approve\`
1023
- overrides): a ref reachable from the merged operations resolves in neither
1024
- document, so merging would write a dangling reference. External refs — URLs, file
1025
- paths, anything not starting \`#/\` — are out of scope: left untouched, never gated.
1026
-
1027
- ## Taking an archive back
1028
-
1029
- \`loam unarchive <FEAT>\` restores the living docs and re-opens the feature. It works
1030
- by putting bytes back, not by inverting the merge — archive copies every file it is
1031
- about to overwrite into \`features/archive/<FEAT>/.loam-before/\` first, because the
1032
- previous text of a \`MODIFIED\` requirement is written down nowhere else. Do not edit
1033
- or delete that directory, and never reconstruct an old living spec by hand from an
1034
- archived delta: what the requirement said BEFORE is not in there, and a plausible
1035
- reconstruction is a lie the next reader has no way to catch.
1036
-
1037
- Every pre-image is digested when the archive writes it, and re-checked before the
1038
- restore stages anything: \`snapshot-corrupt\` means a pre-image's bytes no longer
1039
- match the digest archive recorded for them, so restoring it would write text
1040
- nobody authored. \`--force\` does NOT override that one — the damage is to the
1041
- undo itself, not to the living docs — and the fix is version control.
1042
-
1043
- It refuses rather than guesses, under codes you can branch on: \`feature-active\` (a
1044
- feature of that id is in flight again), \`snapshot-missing\` (archived before loam
1045
- recorded this — the docs have to come back from version control), \`snapshot-stale\`
1046
- (a merged file changed after the archive, so restoring would revert someone else's
1047
- work). \`--force\` overrides the last one, and like \`--approve\` it is a human's call.
1048
-
1049
- A restore that fails outright splits the same way archive's does: \`restore-failed\`
1050
- means nothing was restored or everything was rolled back — the living docs are
1051
- unchanged, fix the reported cause and re-run; \`rollback-incomplete\` means the
1052
- restore failed AND some files could not be put back — stop and hand it to a human,
1053
- the message lists the files to check.
1054
-
1055
- ## Dropping a feature
1056
-
1057
- A feature that was never archived is one directory and nothing else: no living doc
1058
- references it until \`loam archive\` merges it. To abandon it, delete the directory —
1059
- \`git rm -r features/<FEAT-dir>\` — and version control keeps the record of the
1060
- attempt. An ARCHIVED feature is the opposite, its content folded into the living
1061
- docs: run \`loam unarchive <FEAT>\` first, then delete. There is no \`loam abandon\`,
1062
- deliberately — a removal that computes nothing is what version control is for.
1063
- `;
1064
- const COMMANDS = [
1065
- {
1066
- name: "loam-adopt",
1067
- description: "Adopt a service — write its baseline docs from its code, as draft, then validate",
1068
- argumentHint: "<service-id>",
1069
- purpose: "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.",
1070
- invocation: "loam instructions loam-adopt $1",
1071
- placeholders: ["service"],
1072
- spine: [
1073
- "wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet",
1074
- "`loam adopt` — the brief: every file to write, the grammar of each, and what the fleet map already says",
1075
- "read the code, keeping the list of every path you open — that list becomes `sources`",
1076
- "write the artifacts, everything `status: draft`",
1077
- "validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible",
1078
- "hand back with what you could not determine from the code, and let a human vouch",
1079
- ],
1080
- body: `Write one service's baseline documentation into the loam docs repo (its path is
1081
- \`docsDir\` in ./loam.json). You read the code; loam states the work and checks the
1082
- result. It never reads the service — so anything you cannot show, do not write.
1083
-
1084
- 0. If there is no ./loam.json here, wire the repo first:
1085
- \`loam init --docs <path-to-docs-repo> --service $1\` (add \`--create\` ONLY if the
1086
- docs repo does not exist yet), then \`loam doctor\` — it resolves \`docsDir\`,
1087
- checks the docs repo is one, and reports the service binding. Do not go on with
1088
- \`docs-missing\`, \`services-missing\` or any \`doctor.*\` blocker standing.
1089
- Every \`doctor\` finding carries a \`fix\` field spelling the exact command —
1090
- type that, not an approximation of it.
1091
- If this repo is already wired, \`loam status --json\` in the docs repo is the
1092
- fastest way to see whether $1 is even the service that owes work next: its
1093
- \`next.adopt\` entries name the services with nothing written down yet.
1094
- 1. \`loam adopt --service $1 --json\`. That output IS the brief:
1095
- - \`warnings[]\` — read these FIRST. A near-miss against an existing service id
1096
- usually means \`$1\` is a typo, and a brief followed to the letter then produces
1097
- a complete, validating baseline for a service that does not exist.
1098
- - \`targets[]\` — every file to write. \`action: "diff"\` means the file ALREADY EXISTS:
1099
- read it, diff your findings against it, report what disagrees. Do not replace it.
1100
- \`action: "edit"\` appears on exactly one target — \`architecture/landscape.likec4\`,
1101
- the WHOLE FLEET's map: add this service's element and edges to it, never rewrite it.
1102
- - \`targets[].shape\` — the grammar of each artifact, and \`example\` where one is
1103
- shorter than a description. Every rule there is one a later check depends on;
1104
- rules nothing checks are in \`unchecked[]\` instead, and are still worth following.
1105
- - \`landscape\` — the elements and edges the fleet already has for this service.
1106
- Bind to them; do not draw a second version of the same box. \`landscape.expects\`
1107
- lists operations other services already call — your openapi.yaml owes them.
1108
- \`landscape.instruction\` is the write the fleet map still owes this service; it
1109
- is \`null\` once an element resolves to it, and only then.
1110
- - \`frontmatter\` — what to put in the header of every markdown artifact.
1111
- - \`checks[]\` — what \`loam validate\` will run. \`unchecked[]\` — what it will not.
1112
- 2. Read \`AGENTS.md\` at the docs repo root, then read the code: entry points, HTTP
1113
- routes and handlers, published events, config, deploy manifests, tests.
1114
- **Keep a list of every path you actually open.** That list becomes \`sources\` —
1115
- written as files and directories, never glob patterns — and it is the only line
1116
- tying the document to the repository.
1117
- 3. Write the artifacts under \`services/$1/\`, in the order the brief lists them, and
1118
- make the landscape edit the brief asks for.
1119
- Everything \`status: draft\`. Never write \`last_verified\`, \`sources_digest\`,
1120
- \`content_digest\` or \`sources_files\`.
1121
- 4. \`loam validate --service $1 --json\`. Fix every error. \`sources.unvouched\` is
1122
- expected on a fresh baseline — it closes when a person vouches, not when you do.
1123
- 5. \`loam validate --all --json\` in the docs repo. This is the run that grades the
1124
- fleet map: \`landscape.service-unmodelled\` means the element never landed,
1125
- \`landscape.binding-unknown\` / \`landscape.binding-duplicate\` mean it landed wrong,
1126
- and \`landscape.missing\` means there is no map at all. A baseline that passes step 4
1127
- and fails this one is documented and invisible.
1128
- 6. Hand back, and say three things: what you could not determine from the code, what
1129
- the existing artifacts disagreed with, and which parts you are least sure of.
1130
- Then a human runs \`loam vouch --service $1\` in the service's own repo.
1131
-
1132
- Where the code does not say, write that it does not say. A confident sentence about
1133
- behaviour nobody can find is the one failure mode none of loam's checks can catch.
1134
- `,
1135
- },
1136
- {
1137
- name: "loam-feature",
1138
- description: "Start a new loam feature — scaffold it, then author the C4 delta and requirement deltas",
1139
- argumentHint: `<FEAT-id> "<title>"`,
1140
- purpose: "Start a feature in the docs repo: decide which services it touches, scaffold it, then author the C4 delta and one requirement delta per service. The `--touches` list is the hardest call in the cycle and nothing downstream catches one that is short by a service.",
1141
- invocation: 'loam instructions loam-feature $1 "$2"',
1142
- // $2 is the feature's human title: any string is a legal one.
1143
- placeholders: ["feature", "free"],
1144
- spine: [
1145
- "read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on",
1146
- "`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground",
1147
- "`loam status` / `loam list` / `loam show` — what exists and what is already owed",
1148
- "`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces",
1149
- "author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi",
1150
- "`loam rebase` — pin every MODIFIED requirement and every operation to the living version you wrote against",
1151
- "validate the feature, then read `loam dependencies` before anyone starts building",
1152
- ],
1153
- body: `Start a new feature in the loam docs repo (its path is \`docsDir\` in ./loam.json).
1154
-
1155
- 1. Read \`AGENTS.md\` at the root of the docs repo first — it defines the ID spine
1156
- everything here depends on.
1157
- 2. Understand the current state before proposing a change:
1158
- - \`loam status --json\` — what is already in flight and what it is waiting on.
1159
- Run this FIRST if you joined this repository halfway or lost the session:
1160
- \`next[]\` is ordered and each entry carries the literal command to run, so it
1161
- tells you whether the work is to start $1 at all. \`loam status $1 --json\`
1162
- narrows it to one feature once $1 exists
1163
- - \`loam list --json\` — what services exist, and what documentation they are missing
1164
- - \`loam show <service> --json\` — what a service owns, exposes, and who already calls it
1165
- 2b. \`loam explore <service> --json --as $1\` around every service you believe this
1166
- feature touches. This is the step that decides step 3's \`--touches\` list, and it
1167
- is the only decision in the cycle nothing downstream catches — a list short by one
1168
- service produces a feature that validates, archives and ships with a consumer
1169
- nobody updated. Read four fields:
1170
- - \`neighbours\` — the services one hop out in the fleet map. Weigh each one. loam
1171
- knows they are connected, not whether you change them, and it deliberately
1172
- leaves them OUT of the suggested command line rather than guessing for you
1173
- - \`services[].maturity\` — a service at \`empty\` or \`partial\` has no baseline to
1174
- write a delta against, so the work starts with \`loam adopt --service <id>\`, not
1175
- with this feature
1176
- - \`services[].operations\` — what each already exposes, so an operation you are
1177
- about to "add" that is already there becomes a MODIFIED requirement instead
1178
- - \`overlaps\` — active features already carrying a delta for these services.
1179
- \`loam dependencies --json\` (step 7) then says which must archive first
1180
- \`--op <operationId>\` seeds from an operation when you know the call but not who
1181
- owns it. A seed naming no \`services/<id>/\` is reported in \`unknown\` with the
1182
- closest real ids rather than refused — the feature may be introducing it, and a
1183
- typo looks identical until you read that list. \`scaffold\` is the literal step-3
1184
- command line the seeds imply, with \`--new-service\` where the service does not
1185
- exist yet.
1186
- 3. Scaffold it: \`loam new $1 --title "$2"\`, adding \`--touches <id>\` for every service the
1187
- feature touches and \`--new-service <id>\` for every one it introduces. Every id goes
1188
- through the \`services/<id>/\` grammar, so a typo is refused (\`invalid-option\`)
1189
- rather than scaffolded; a \`--touches\` that matches nothing existing comes back as
1190
- a \`note\` with the close ids, not as a failure — read it, because a \`--touches\`
1191
- naming a service that does not exist is \`delta.service-unknown\` at validate time
1192
- and a phantom \`services/<typo>/\` directory if it ever archives.
1193
- 4. Author what the templates left as TODO:
1194
- - \`intent.md\` — the problem in business terms
1195
- - \`delta.likec4\` — new elements and edges, each tagged \`#$1\`; every call edge carries
1196
- \`metadata { op '<operationId>' }\`. For a requirements-only feature DELETE this
1197
- file: an empty delta is legal, but a scaffold left half-filled is not
1198
- - \`specs/<svc>/spec.md\` — one behaviour per requirement, SHALL, at least one
1199
- Given/When/Then scenario; uncomment \`Operations:\` once the operation exists
1200
- - \`specs/<svc>/arch.spec.md\` — the architectural obligations (outbox, retries,
1201
- idempotency, alerts), same grammar; a \`Covers:\` line per requirement naming the
1202
- tagged elements/edges it accounts for, or \`c4.uncovered\` says nothing does
1203
- - \`specs/<svc>/openapi.yaml\` — define every operationId the edges reference
1204
- 5. **\`loam rebase $1\`** — the step between authoring and checking, and the one that
1205
- stops two features in flight from silently deleting each other's work. It pins
1206
- every MODIFIED/REMOVED requirement (\`Based-On:\`) and every operation in the
1207
- contract delta (\`x-loam-based-on\`) to the living version you wrote against.
1208
- Run it on the contract axis even if you changed exactly one endpoint: that is
1209
- what marks the REST of the document as quotation the merge must not write back.
1210
- Without the pins, \`delta.baseline-missing\` / \`openapi.baseline-missing\` warn,
1211
- nothing refuses, and the second feature to archive reverts the first — with
1212
- \`+0 ~1 -0\`, exit 0, and nobody told.
1213
- Restamping is not resolving: if rebase reports a requirement as MOVED, re-read
1214
- the living text and fold in what you still mean BEFORE you ship.
1215
- 6. \`loam validate --feature $1 --json\`. Do not stop while \`valid\` is false.
1216
- 7. \`loam dependencies $1 --json\` before anyone starts building: it says which
1217
- features in flight have to archive first (\`order\`) and which collisions no
1218
- ordering fixes (\`conflicts\`, \`cycles\`). Every conflict it names is one \`loam rebase\`
1219
- cannot fix for you — two intentions have to be merged by people.
1220
-
1221
- The operation's three spellings — the edge's \`op\`, the requirement's \`Operations:\`
1222
- line, and the OpenAPI \`operationId\` — must match exactly.
1223
- `,
1224
- },
1225
- {
1226
- name: "loam-implement",
1227
- description: "Implement a loam feature's slice for one service — generated gherkin first, tests from it, then code",
1228
- argumentHint: "<FEAT-id> [service]",
1229
- purpose: "Build one service's part of a feature, in that service's own repository. The generated Gherkin is the acceptance criterion — it comes before the step definitions, and those come before the code.",
1230
- invocation: "loam instructions loam-implement $1 $2",
1231
- placeholders: ["feature", "service"],
1232
- spine: [
1233
- "`loam status` — where this feature actually is, and what is owed before you start",
1234
- "`loam delta` — the task: the intent, every requirement verbatim, the endpoints, and the calls in and out",
1235
- "`loam gherkin` in the service's repo — the digest-stamped `.feature` files. Never edit them",
1236
- "write step definitions for the generated scenarios first, outside the generated directory",
1237
- "implement until the suite passes, running it with a JSON report",
1238
- "honour the contract in both directions: expose every inbound operation, call every outbound one",
1239
- "`loam rebase` if building made you change the feature's documents at all, then validate the feature",
1240
- ],
1241
- body: `Implement one service's part of a feature. Every step runs in the SERVICE'S own
1242
- repository, which needs its own committed ./loam.json — if there is none,
1243
- \`loam init --docs <path-to-docs-repo> --service $2\` then \`loam doctor\` first.
1244
-
1245
- 0. \`loam status $1 --json\` — where this feature actually is. Run it before
1246
- anything else if you joined halfway or lost the session: it says which
1247
- artifacts are still owed, whether another feature has to archive first, and
1248
- what the next step is, each \`next[]\` entry carrying the literal command. It
1249
- writes nothing, so it is always safe to re-run.
1250
- 1. \`loam delta $1 --service $2 --json\` (drop \`--service\` to use the service configured
1251
- in ./loam.json). That output IS the task:
1252
- - \`intent\` — why this exists
1253
- - \`requirements[]\` — what to build, with \`scenarios[].lines\` verbatim
1254
- - \`archRequirements[]\` — the architectural obligations (outbox, retries, alerts),
1255
- same shape; their scenarios become integration/ops tests, and \`covers\` names
1256
- the model objects each exercises
1257
- - \`architecture\` — whether this service is new, and the calls in and out of it
1258
- - \`api\` — the endpoints this feature adds or retires for this service: path,
1259
- method, operationId and summary, with removals spelled \`REMOVE <METHOD> <path>\`
1260
- - \`openapi\` — whether that contract could be read at all: \`unreadable\`, plus an
1261
- \`error\` when the parser gave a message. It rides beside \`api\` rather than
1262
- inside it because \`api\` stays the operations array it has always been, and it
1263
- is what tells an empty \`api\` apart from a contract delta that did not parse
1264
- - \`services\` — every service this feature projects onto, so a run without
1265
- \`--service\` tells you which ones to ask for rather than guessing
1266
- Exit 1 with \`ok: true\` means one of the two authored documents behind this brief
1267
- did not parse: \`architecture.errors\` non-empty (delta.likec4), or
1268
- \`openapi.unreadable\` true (the feature's openapi.yaml for this service). The empty
1269
- slice is the parse failure itself, not "nothing changes there". Stop and fix that
1270
- document before building anything — for the delta, \`loam validate $1 --json\` names
1271
- the error; the contract delta is not graded by validate, so read the parser message
1272
- the payload carries.
1273
- 2. In the service's repo, \`loam gherkin $1 --json\` — one \`.feature\` file per changed
1274
- requirement lands under \`<gherkinDir>/loam/\` (default \`features/loam/\`), scenarios
1275
- digest-stamped, arch requirements tagged \`@architecture\`. Those files ARE the
1276
- acceptance criteria. Never edit them: regeneration rewrites the directory, and
1277
- \`loam validate\` reports the suite stale by digest, not by your intentions.
1278
- 3. Write step definitions for the generated scenarios FIRST — outside \`loam/\`.
1279
- Do not paraphrase a scenario into something easier to pass; it is the acceptance
1280
- criterion someone else reviews against.
1281
- 4. Implement until the suite passes — run it with a JSON report
1282
- (\`cucumber-js --format json:report.json\`):
1283
- \`loam verify $1 --service $2 --results report.json\` consumes that report as the
1284
- done-check's answer sheet. Record from THIS repo, with \`--service\` — the
1285
- \`--service\`-less \`--record\` form writes the whole record from one place and is
1286
- refused (\`record-federated\`) once another service has attested.
1287
- 5. Honour the contract: every operation in \`architecture.inbound\` must exist under
1288
- exactly that operationId, and every one in \`architecture.outbound\` must be called.
1289
- 6. If building made you change the feature's documents at all — a requirement's
1290
- wording, an operation in its openapi.yaml — **\`loam rebase $1\`** before you
1291
- validate. Every edit you make in the docs repo while other features are in
1292
- flight is written against a living document that may have moved since the
1293
- feature directory was created, and the pins are the only thing that lets the
1294
- merge tell an EDIT from a QUOTE. \`delta.baseline-missing\` and
1295
- \`openapi.baseline-missing\` are the warnings that say you skipped it;
1296
- \`delta.baseline-stale\` / \`openapi.baseline-stale\` are archive refusing because
1297
- somebody landed a change underneath you — re-read theirs, fold in what you
1298
- still mean, then rebase again. Re-pinning without re-reading is how you
1299
- overwrite someone else's requirement with loam's blessing.
1300
- 7. \`loam validate --feature $1 --json\` before handing back.
1301
-
1302
- If the requirement is ambiguous, say so and stop — do not invent behaviour and
1303
- leave the spec disagreeing with the code.
1304
- `,
1305
- },
1306
- {
1307
- name: "loam-check",
1308
- description: "Validate the loam docs repo and fix what it reports",
1309
- argumentHint: "[--all | <FEAT-id> | <service>]",
1310
- purpose: "Run loam's checks and fix what they find. Branch on `findings[].code`, never on the prose — and remember that a coherence finding marked `gates` stops `loam archive` even when it is only a warning.",
1311
- // No `$1`, unlike the other five. This body carries no placeholder at all —
1312
- // `loam-check`'s argument is a target you pass to `loam validate`, not
1313
- // something the protocol interpolates — and the argument hint's FIRST form
1314
- // is `--all`, so `loam instructions loam-check $1` expanded to
1315
- // `loam instructions loam-check --all`, which commander reads as an unknown
1316
- // option and refuses with exit 1. A pointer file whose one instruction
1317
- // fails leaves an agent with no protocol at all, which is a state the fat
1318
- // body could never reach. The command still tolerates a leading-dash
1319
- // argument (see `allowUnknownOption` in commands/instructions.ts); this is
1320
- // the half of the fix that stops loam printing the broken line in the first
1321
- // place.
1322
- invocation: "loam instructions loam-check",
1323
- // The one protocol whose body names no placeholder: its target (`--all`, a
1324
- // feature id, a service id) is spelled inside the `loam validate` line the
1325
- // agent chooses, not substituted into the page.
1326
- placeholders: [],
1327
- spine: [
1328
- "`loam validate --all` in the docs repo for the fleet, or `loam validate <target>` for one feature or service",
1329
- "fix every error, and every finding that gates the archive",
1330
- "leave an advisory warning standing only if you can say why, and say it",
1331
- "re-run until the grade is the one you meant",
1332
- ],
1333
- body: `Run loam's checks and fix what they find.
1334
-
1335
- - whole fleet: \`loam validate --all --json\` (run in the docs repo)
1336
- - one target: \`loam validate <FEAT-id | service-id> --json\` — the feature reading is
1337
- tried first; \`--feature <id>\` / \`--service <id>\` force one when a name could be both,
1338
- and \`target.ambiguous\` (warn) says when a name really was both and which reading was taken
1339
- - \`--strict\` (any mode) exits 1 on any warning too. Exit code only: \`valid\` and the
1340
- payload do not change, so the stricter grade lives in the CI invocation, not the repo
1341
- - \`--errors-only\` drops the \`ok\` confirmations from the text view; the \`--json\`
1342
- payload is byte-identical either way
1343
- - refusals before any grading: \`docs-missing\` / \`services-missing\` (\`docsDir\` in
1344
- ./loam.json points at nothing, or at a directory with no \`services/\`) — run
1345
- \`loam doctor\` and fix the wiring, do not read an empty report as a clean fleet
1346
-
1347
- Branch on \`findings[].code\`, not the prose. Errors fail validate; a coherence finding
1348
- with \`gates: true\` will stop \`loam archive\` even when it is a warning. Fix every
1349
- error and every gating finding. Leave an advisory warning only if you can say why,
1350
- and say it.
1351
-
1352
- \`--service <id>\` — one service's own axes (\`--all\` runs these for every service):
1353
-
1354
- | code | what it means | what to do |
1355
- |---|---|---|
1356
- | \`service.unknown\` | no \`services/<id>/\` at all — the id is a typo until proven otherwise | use one of the ids the message offers, or \`loam list services\`; never \`loam adopt\` a misspelling |
1357
- | \`service.no-model\` | the directory is real but there is no model.likec4 | run \`loam adopt\` for it — without the C4 center nothing else has anywhere to hang |
1358
- | \`c4.invalid\` | model.likec4 does not parse | fix this first — an unreadable axis makes every other check meaningless |
1359
- | \`requirements.missing-scenarios\` | a requirement with no scenario | add the acceptance criteria; do not delete the requirement |
1360
- | \`requirements.stepless-scenario\` | a scenario with a heading and no recognizable steps — it satisfies the coverage rule and tests nothing: cucumber runs it vacuously green and \`loam verify --results\` can never confirm it. Graded on spec.md and arch.spec.md alike, in \`--service\` and \`--feature\` scope both, because a stepless scenario in a delta merges into the living spec and is called covered forever after | write the body as \`- **Given/When/Then**\` bullets |
1361
- | \`spec.merge-conflict\` | a requirement document still holds \`<<<<<<<\` / \`=======\` / \`>>>>>>>\` markers — nobody wrote what it now says. Graded on living spec.md/arch.spec.md and on a feature's deltas of them, in both scopes | resolve the conflict in the file the finding names. \`loam archive\` refuses (\`merge-failed\`) on a conflicted living document it would rewrite, and \`--approve\` does not override it: the merge rewrites the requirements section and takes whichever marker lines fall inside it, so a conflict anyone can see would become a document nobody can tell is wrong |
1362
- | \`spec.duplicate-requirement\` | one \`### Requirement:\` name defined twice in one living spec.md or arch.spec.md — a merge edits only the first, the rest live on as stale copies | keep exactly one block per name (merge the bodies); the two files are separate namespaces, so a name in both is fine |
1363
- | \`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 |
1364
- | \`spec.requirement-id-repeated\` | one requirement declares \`Requirement-ID\` more than once | keep exactly one identity line |
1365
- | \`spec.requirement-id-duplicate\` | one ID identifies multiple requirements in the same file | give each requirement its own stable ID |
1366
- | \`spec.repeated-operations\` (warn) | a second \`Operations:\` line in one requirement body — the last line REPLACES the earlier ones, whose list is silently lost (living specs and feature deltas alike) | merge them into one comma-separated \`Operations:\` line |
1367
- | \`spec.repeated-covers\` (warn) | same for \`Covers:\` — the last line wins, the earlier list is silently lost | merge them into one comma-separated \`Covers:\` line |
1368
- | \`spec.no-requirements\` (warn) | a LIVING spec.md with no \`### Requirement:\` block at all — every requirement-driven check below it is vacuous, not passing | write the requirements, or say in the file why the service has none yet |
1369
- | \`service.unreadable\` | an artifact of this service could not be read (permissions, a dangling symlink) — nothing about the service was checked, and the rest of the fleet still was | fix the file the finding names; a service silently skipped would have read as a clean one |
1370
- | \`service.no-spec\` (warn) | no living spec.md — a part-adopted service, legal but unchecked | write it; until it exists, requirement coverage and API governance are vacuous |
1371
- | \`service.no-openapi\` (error, or warn) | no openapi.yaml — deleted, renamed, or never written. ERROR when something already written down joins into the absent file: a LIVING non-REMOVED requirement's \`Operations:\` line, or a landscape edge carrying \`metadata { op }\` into this service. The finding's \`details\` list the stranded operationIds. WARN when nothing joins into it but the landscape cannot prove nobody calls this service (it is absent, or it does not parse). Silent when the landscape parses and no edge calls an operation here — a worker, a cron or a UI owes no contract | error: put the file back — every link the \`details\` name resolves to nothing until it is there. If the operations are genuinely gone, retire them through a feature delta (REMOVED requirement + \`x-loam-remove\` marker) instead of deleting the contract out from under them. warn: write the contract, or model the service in the fleet map so it shows no one expects an API |
1372
- | \`openapi.invalid\` | openapi.yaml exists but does not parse — an unreadable contract proves nothing, so no \`api.*\` or spine finding is graded against it (before this code, the empty parse graded every inbound edge \`spine.op-undefined\`) | fix the YAML first — the API axis is unchecked until it reads |
1373
- | \`openapi.remove-marker-living\` | a living openapi.yaml contains feature-only \`x-loam-remove: true\` | remove the marker from living; retire the operation through a feature delta with a matching REMOVED requirement |
1374
- | \`openapi.duplicate-operationid\` (warn) | one operationId occupies two (path, method) slots in a living contract — every join on the id (a requirement's \`Operations:\` line, an edge's \`metadata { op }\`, a removal marker) then picks one of them arbitrarily | give each slot its own id; the identity of an operation is its path and method, and the id is how the other axes name it |
1375
- | \`openapi.baseline-stale\` | the living operation changed after this delta edited it | somebody landed a change to it in between. Re-read the living operation, fold in what you still mean, then \`loam rebase <FEAT>\` |
1376
- | \`openapi.baseline-missing\` (warn, one per service) | operations in this feature's openapi.yaml carry no \`x-loam-based-on\` | run \`loam rebase <FEAT>\`. Until you do, the merge cannot tell the operations you EDITED from the ones you merely restated, so it upserts all of them — and every restated one reverts whatever landed on it |
1377
- | \`openapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest, or one on an operation the living contract has no slot for | \`loam rebase\` writes the value; a new operation has no living version to be based on, so drop the marker there |
1378
- | \`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 |
1379
- | \`api.ungoverned\` (warn) | operation(s) no requirement's \`Operations:\` line names | write the requirement, or link an existing one |
1380
- | \`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 |
1381
- | \`api.requirement-deprecated\` (warn) | a requirement's \`Operations:\` list resolves only to operations the OpenAPI marks \`deprecated: true\` — the behaviour it governs is on its way out | migrate the requirement to the replacement operation, or retire it with the ops it governs |
1382
- | \`spine.landscape-invalid\` | the living landscape does not parse, so the C4↔API spine cannot be checked | fix architecture/landscape.likec4 first |
1383
- | \`spine.op-undefined\` | a landscape edge calls an operation this service's OpenAPI does not define | a broken contract between services — fix the edge or add the endpoint |
1384
- | \`spine.op-link-missing\` (warn) | a landscape "Calls" edge into this service with no \`metadata { op }\` | link it to the operationId |
1385
- | \`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 |
1386
- | \`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 |
1387
- | \`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 | fix the YAML first — the event axis is unchecked until it reads |
1388
- | \`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 |
1389
- | \`spine.message-undefined\` | a landscape edge \`publishes\`/\`consumes\` a message this service's asyncapi.yaml declares no \`action: send\`/\`receive\` operation for | declare the operation, or correct the edge — the fleet map and the contract disagree about what this service puts on the wire |
1390
- | \`spec-event.message-undefined\` | a LIVING requirement's \`Publishes:\` / \`Consumes:\` line names a message this service's asyncapi.yaml does not declare in that direction | declare it, or correct the line |
1391
- | \`spine.message-unproduced\` | this service consumes a message NO service in the fleet declares an \`action: send\` for. The inverse of \`spine.op-undefined\`, and it has no HTTP analog: on the API axis the provider owns the contract and the check is local, while an event's schema lives in the producer's repo | find the producer and have it declare the message, or correct the name — a consumer joined to nothing reads a payload nothing defines |
1392
- | \`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 |
1393
- | \`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 |
1394
- | \`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 |
1395
- | \`health.invalid\` (warn) | health.yaml exists but does not parse — alert/SLI ids are unreadable, so \`Covers: alert:/sli:\` entries and health coverage are unchecked (a missing health.yaml is legal and silent; an unreadable one used to masquerade as \`covers.unknown\` typos) | fix the YAML — the health axis resumes once it reads |
1396
- | \`health.uncovered\` (warn) | health.yaml declares an alert or SLI no arch.spec.md requirement covers | write the arch requirement with \`Covers: alert:<id>\` / \`Covers: sli:<id>\` — a signal nothing tests is dashboard decoration |
1397
- | \`gherkin.missing\` (warn) | a living scenario no generated .feature scenario carries a digest for — the suite has no test for these words. Fires only in the service's repo, once \`<gherkinDir>/loam/\` exists | \`loam gherkin --service <id>\` regenerates the suite |
1398
- | \`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 |
1399
- | \`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 |
1400
-
1401
- \`--feature <FEAT-id>\` — a change's three axes against each other. The SAME checks
1402
- gate \`loam archive\` (errors block it; warnings never do), so a clean run here is
1403
- what lets a feature ship:
1404
-
1405
- | code | what it means | what to do |
1406
- |---|---|---|
1407
- | \`delta.invalid\` | delta.likec4 does not parse | fix this first — an unreadable axis makes every other check meaningless |
1408
- | \`delta.nothing-tagged\` | the delta declares elements/relationships but none carry \`#<FEAT>\` | tag what IS the change — untagged parts are context, and archive merges only tags |
1409
- | \`delta.service-unknown\` | a \`specs/<svc>/\` directory addresses a service that neither exists as \`services/<svc>/\` nor is introduced by this feature's own tagged delta.likec4 — a typo in \`--touches\`, which archive would otherwise materialise as a phantom service directory | fix the id (the message offers close ones); suspended while \`delta.invalid\` stands, because an unparseable delta cannot be asked what it introduces |
1410
- | \`spec.merge-conflict\` | a spec delta (\`specs/<svc>/spec.md\` or \`arch.spec.md\`) still holds \`<<<<<<<\` / \`=======\` / \`>>>>>>>\` markers — the same code and the same sentence as service scope, because the markers merge into the living document as prose under somebody's requirement | resolve the conflict before archive; the merge rewrites the requirements section and the marker lines inside it disappear with it |
1411
- | \`requirements.stepless-scenario\` | a scenario in a spec delta with a heading and no steps — same code as service scope, and it merges into the living spec as a requirement the coverage rule calls covered from then on | write the body as \`- **Given/When/Then**\` bullets |
1412
- | \`spec-api.op-undefined\` | a requirement governs an operation its provider's OpenAPI does not define | define the endpoint, or correct the \`Operations:\` line |
1413
- | \`spec-api.op-pending\` (warn) | the governed operation is defined by another feature in flight | archive that feature first |
1414
- | \`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 |
1415
- | \`c4-api.op-pending\` (warn) | the called operation is defined by another feature in flight | archive that feature first |
1416
- | \`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 |
1417
- | \`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 |
1418
- | \`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 |
1419
- | \`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 |
1420
- | \`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 |
1421
- | \`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 |
1422
- | \`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 |
1423
- | \`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 |
1424
- | \`c4.op-ungoverned\` (warn) | an operation is called but no requirement governs it | write the requirement |
1425
- | \`c4.op-link-missing\` (warn) | a "Calls" edge in the delta with no \`metadata { op }\` | link it to the operationId |
1426
- | \`api.op-unconsumed\` (warn) | an added operation no edge consumes | model the caller, or say why it is provider-only |
1427
- | \`service.no-requirement-delta\` (warn) | a new service with no spec delta | write \`specs/<svc>/spec.md\` |
1428
- | \`archedge.uncovered\` (warn) | no scenario names a tagged edge (a heuristic) | write the scenario, or say why the edge needs none |
1429
- | \`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 |
1430
- | \`delta.unknown-section\` | a heading that nearly matches the delta grammar | fix it — everything under it merges as NOTHING today, silently |
1431
- | \`delta.no-delta-sections\` | requirements, but no \`## ADDED/MODIFIED/REMOVED Requirements\` section anywhere — the whole delta would merge nothing | put every changed requirement under its delta section |
1432
- | \`delta.requirement-not-merged\` (warn, gates archive) | a requirement under a prose heading (\`## Behavior\`) instead of a delta section | move it under \`## ADDED\`/\`## MODIFIED\`/\`## REMOVED Requirements\` — as written, archive drops it. If it really is documentation, quote it under \`## Requirements\`, which is exempt |
1433
- | \`delta.requirement-id-invalid\` / \`delta.requirement-id-repeated\` / \`delta.requirement-id-duplicate\` | a stable ID is malformed or ambiguous inside the delta | repair it before archive; identity is never inferred from an invalid declaration |
1434
- | \`delta.living-requirement-id-invalid\` | the living file's IDs are malformed or ambiguous | repair the living spec first; the delta cannot select it safely |
1435
- | \`delta.requirement-identity-collision\` | the delta's ID and heading point at different living requirements | fix the ID or heading; archive will not guess which identity should win |
1436
- | \`delta.modified-unknown\` | MODIFIED a requirement the living spec does not have | use ADDED, or fix the name (a spelling slip reads as a different requirement) |
1437
- | \`delta.removed-unknown\` | REMOVED one that does not exist | drop the section, or fix the name |
1438
- | \`delta.added-duplicate\` | ADDED a name the living spec already has | use MODIFIED — as written, the merge REPLACES the living requirement |
1439
- | \`delta.added-near-duplicate\` (warn) | ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist | match the living spelling and use MODIFIED, or pick a distinct name |
1440
- | \`delta.living-duplicate-requirement\` | two requirements in the LIVING document share one heading — MODIFIED rewrites only the first and REMOVED deletes both, so no delta applies to it predictably | fix the living spec first (merge the bodies, or give each a distinct heading and a \`Requirement-ID\`); the delta cannot select safely until you do |
1441
- | \`delta.modified-pending\` (warn) | the requirement is introduced by another feature in flight | archive that feature first — \`loam dependencies --json\` computes the whole ordering (\`order\`), so you do not have to work it out per finding |
1442
- | \`delta.removed-pending\` (warn) | REMOVED something another feature in flight introduces | archive that feature first; \`loam dependencies --json\` says in which order |
1443
- | \`delta.added-conflict\` (warn) | two features in flight add the same requirement | whichever archives first lands it; the other's ADDED then collides with the living spec (\`delta.added-duplicate\`, error) and its archive is refused — coordinate now, or rework the later one as MODIFIED after the first ships. No ordering fixes it, which is why \`loam dependencies\` reports it under \`conflicts\` rather than in \`order\` |
1444
- | \`delta.modified-conflict\` (warn) | another feature in flight MODIFIES or REMOVES the same living requirement | both deltas apply cleanly, so whichever archives second replaces the other's text wholesale — \`loam dependencies\` lists it under \`conflicts\`; merge the two intentions before either ships |
1445
- | \`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 |
1446
- | \`delta.baseline-missing\` (warn) | 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. Expected on deltas adopted from OpenSpec, which never carried the line |
1447
- | \`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 |
1448
-
1449
- \`--all\` — everything above for every target, plus the fleet cross-check:
1450
-
1451
- | code | what it means | what to do |
1452
- |---|---|---|
1453
- | \`landscape.missing\` | \`architecture/landscape.likec4\` does not exist — an ERROR as soon as \`services/\` holds one service, a warning only in an empty docs repo | create it: a \`specification { ... }\` block declaring the kinds, then a \`model { ... }\` block with one bound element per service (\`metadata { service '<id>' }\`) and one edge per cross-service call carrying \`metadata { op '<operationId>' }\`. With no fleet map every cross-service check is blind, not passing |
1454
- | \`landscape.invalid\` | the living landscape does not parse | fix it first — the fleet cross-check cannot run against a document nobody can read |
1455
- | \`service.id-invalid\` | a \`services/<id>/\` directory whose name is not a legal service id — every authoring command refuses it, so nothing in that directory can be changed through loam. Fleet scope only: the SET of service directories is what makes the id a question, and a \`--service\` run is already refused by its own guard before it gets this far. Graded before the map is opened, so it stands even when the landscape is missing or unreadable | rename the directory, then update its \`service:\` frontmatter, its \`metadata { service }\` binding in the landscape, and any \`features/<FEAT>/specs/<id>/\` naming it |
1456
- | \`landscape.merge-conflict\` | the fleet map still holds conflict markers — it is two halves of two different maps, and every cross-service check reads it. Checked BEFORE the parse and returns like \`landscape.invalid\`: nothing is concluded from the file, so no \`landscape.service-unmodelled\` fires behind it | resolve it keeping BOTH sides' elements and edges; ten people adopting ten services into one landscape.likec4 in the same week is how it happens. \`loam archive\` refuses on it too (\`merge-failed\`, not overridable with \`--approve\`) |
1457
- | \`landscape.service-unmodelled\` | a \`services/<svc>/\` no element in the landscape resolves to | draw it, or bind an existing element with \`metadata { service '<svc>' }\` — the fleet map is incomplete until you do |
1458
- | \`landscape.service-undocumented\` (warn) | a landscape element with no \`services/<id>/\` | document the service, bind the element to the directory it means, or tag it \`#external\` if it is not ours |
1459
- | \`landscape.binding-unknown\` | an element's \`metadata { service }\` names a directory that does not exist | fix the id or create the service — a binding is a claim, and this one is false |
1460
- | \`landscape.binding-duplicate\` (warn) | two elements resolve to the same \`services/<id>/\` | every element→service join picks one of them arbitrarily, so the other's edges are filed under a service that does not own them — bind each element to the directory it actually is, or tag the stray one \`#external\` |
1461
- | \`feature.unreadable\` | an artifact of a feature in flight could not be read — nothing about that feature was graded, and the rest of the fleet still was | fix the file the finding names; a feature silently skipped would have read as a clean one |
1462
- | \`sources.unverifiable-from-here\` (ok) | one finding PER SERVICE whose \`sources\` only its own repo can check — a confirmation, not work, so it never trips \`--strict\`, and it is present in \`--service\` runs from the docs repo too | run \`loam validate --service <id>\` from inside that service's repo to actually grade its provenance; the fleet rollup line is derived from these findings |
1463
- | \`agents.stale\` (warn) | the docs repo's AGENTS.md has no version stamp, or one older than the running binary — its flag and code tables may describe a loam that no longer exists | review AGENTS.md against the current \`loam --help\`, then update the \`<!-- generated by loam vX.Y.Z -->\` line; a hand-curated file silences it by bumping the stamp itself. Never regenerate the file — the team's edits outrank the template |
1464
-
1465
- frontmatter and provenance — services' spec.md and arch.spec.md, and features'
1466
- intent.md, both modes:
1467
-
1468
- | code | what it means | what to do |
1469
- |---|---|---|
1470
- | \`frontmatter.missing\` (warn) | no frontmatter at all | add owner, status and sources |
1471
- | \`frontmatter.malformed\` | the frontmatter block does not parse as YAML — owner, status and sources are unreadable, which is not the same fact as absent | fix the header; the field checks and the sources chain resume once it parses — never add fields to a block YAML refuses |
1472
- | \`frontmatter.field-mismatch\` | the doc names a different service/feature than the one it lives under | fix the frontmatter, or move the file |
1473
- | \`frontmatter.status-unknown\` | a status nobody defined (\`verifed\`) | use the documented vocabulary — a typo here reads as unverified forever |
1474
- | \`frontmatter.field-missing\` (warn) | owner, status or the identity field is absent | fill them in |
1475
- | \`sources.absent\` (warn) | the doc names no sources | nothing ties it to the code, so nothing can tell you when it goes stale |
1476
- | \`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 |
1477
- | \`sources.stale\` (warn) | the source files changed since the doc was vouched for — the finding names which paths were added, changed or removed, from the \`sources_files\` index the stamp carries | re-read the code at exactly those paths, correct the doc, then ask a human to \`loam vouch --service <id>\` |
1478
- | \`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 |
1479
- | \`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 |
1480
- | \`sources.unvouched\` (warn) | \`sources\` with no \`sources_digest\` — nobody ever stamped it | leave it: vouching is a human's reading, not yours |
1481
- | \`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 |
1482
-
1483
- \`loam archive\` alone — breaches only the merge computation can see, reported at
1484
- plan time (they never appear in \`validate\`):
1485
-
1486
- | code | what it means | what to do |
1487
- |---|---|---|
1488
- | \`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 |
1489
- | \`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 |
1490
- | \`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 |
1491
- | \`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 |
1492
- | \`openapi.component-modified\` (warn) | a component the merged operations reference already exists in the living OpenAPI with different content — 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 |
1493
- | \`openapi.ref-unresolved\` | a \`$ref\` reachable from the merged operations 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 |
1494
- | \`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 |
1495
-
1496
- \`sources.stale\` and \`content.stale\` are the warnings you cannot close by yourself.
1497
- Fix what the code now says, then hand it back — the stamp is a person's claim to
1498
- have read it.
1499
- `,
1500
- },
1501
- {
1502
- name: "loam-verify",
1503
- description: "Verify a built loam feature against its own promises, claim by claim, with evidence",
1504
- argumentHint: "<FEAT-id>",
1505
- purpose: "Check that the code somebody built is the feature that was designed. loam derives the questions; a test runner and you answer them. loam never reads the service, so a verdict is worth exactly what its evidence is worth.",
1506
- invocation: "loam instructions loam-verify $1",
1507
- placeholders: ["feature"],
1508
- spine: [
1509
- "`loam verify` — the checklist, derived from the feature's own artifacts",
1510
- "run the generated suite with a JSON report: the scenario claims are the runner's to answer, not yours",
1511
- "answer the remaining claims yourself, every one carrying a `file:line` that resolves",
1512
- "record from each affected service's own repository, bound to that service",
1513
- "read the verdict honestly — a scenario confirmed on your word is `attested`, not `verified`",
1514
- ],
1515
- body: `Check that the code somebody just built is the feature that was designed.
1516
-
1517
- loam derives the questions; the test runner and you answer them. loam never
1518
- reads the service, so a verdict is worth exactly what its evidence is worth.
1519
-
1520
- 1. \`loam verify $1 --json\`. \`claims[]\` is the checklist, derived from the feature's
1521
- own artifacts — one claim per new service, per operation its openapi delta adds,
1522
- per tagged edge that names an operation, and per scenario of every changed
1523
- requirement, arch.spec.md deltas included. Each has a stable \`id\` and a
1524
- \`subject\` (the service whose code answers it); a claim that says arch.spec.md
1525
- wants an integration/ops test, not an acceptance test. An ARCHIVED feature returns its record as frozen history instead
1526
- (\`frozen: true\`) and \`--record\` / \`--results\` refuse it — \`loam unarchive\`
1527
- first if the answers really must change.
1528
- 2. The \`scenario.tested\` claims are the runner's to answer, not yours. In the
1529
- service's repo, run the generated suite with a JSON report —
1530
- \`npx cucumber-js --format json:report.json\` (cucumber-jvm, behave and
1531
- SpecFlow emit the same format). The generated scenarios carry
1532
- \`@loam-digest-…\` tags that ride into the report; that digest is the match,
1533
- so never retitle or hand-edit a generated scenario to make one pass.
1534
- If there is no runnable suite yet you MAY answer them yourself, and the record
1535
- will say so: \`answered_by: agent\` makes the feature **attested**, not verified
1536
- (\`verified: false\`, \`verdict: "attested"\`, the \`verify.scenario-attested\`
1537
- notice, and a \`next.verify-attested\` step in \`loam status\`). Nothing gates on
1538
- it — a legacy service with no suite must still be able to ship — but say so
1539
- when you hand back, and close it with \`--results\` the moment a suite runs.
1540
- \`verify.digest-contested\` (warn) is the case where two services word a
1541
- scenario identically: they share one digest, so no single report can say whose
1542
- suite ran it, and each service must record its own with \`--service\`.
1543
- 3. Answer every OTHER claim by finding it in the code. Not by reasoning that it
1544
- must be there — by opening the file:
1545
- - \`service.exists\` — the service is deployable: its build, its entry point.
1546
- - \`api.exposes\` — the route handler serving that operationId, not the spec that
1547
- declares it.
1548
- - \`c4.calls\` — the call site in the CALLER.
1549
- 4. Write those answers as JSON — evidence is \`file:line\`, one entry per place it can
1550
- be seen:
1551
- \`\`\`json
1552
- { "answers": [
1553
- { "id": "<claim id>", "verdict": "confirmed", "evidence": ["src/split/Api.ts:42"] },
1554
- { "id": "<claim id>", "verdict": "unconfirmed", "note": "the split service has no entry point yet" }
1555
- ] }
1556
- \`\`\`
1557
- 5. Record, **in each affected service's own repository**, one service at a time:
1558
-
1559
- \`\`\`sh
1560
- loam verify $1 --service <id> --results report.json --record answers.json --json
1561
- \`\`\`
1562
-
1563
- \`--service <id>\` narrows the checklist to that service's claims and binds the
1564
- answers to this repository's commit, writing ONE attestation into a shared
1565
- \`schema: 2\` \`verification.yaml\`. Running it in the next repo adds that repo's
1566
- attestation and leaves the others standing. In a repo whose ./loam.json
1567
- declares a \`service\`, \`--service\` may be omitted — it is substituted, and the
1568
- run still federates. \`--results report.json\` alone is legal when the
1569
- checklist is all scenarios.
1570
-
1571
- ⚠ **Never run \`loam verify $1 --record answers.json\` without \`--service\`.**
1572
- That is the legacy all-at-once form: it writes the WHOLE record from one place,
1573
- and over a record that already carries other services' attestations it is
1574
- refused (\`record-federated\`, naming them) precisely because it would erase
1575
- them. If you meet that refusal, re-run with \`--service\`; do not work around it.
1576
-
1577
- The other refusals: \`answers-mismatch\` (an id
1578
- nobody asked about, a claim you left out — re-run step 1, the feature moved —
1579
- or an answers entry for a \`scenario.tested\` claim: the runner owns those),
1580
- \`answers-unevidenced\` (a \`confirmed\` with nothing behind it),
1581
- \`answers-unreadable\` (a report or answers file it cannot read),
1582
- \`record-unreadable\` (a \`verification.yaml\` that exists but cannot be read as a
1583
- record — it is never overwritten; a human repairs it, and
1584
- \`verify.record-miscounted\` is the reason when its \`summary\` block contradicts
1585
- its own \`claims[]\`: neither half can be believed, so re-record rather than
1586
- editing the counts),
1587
- \`service-mismatch\` (\`--service\` names a service this repo does not declare) and
1588
- \`repository-unavailable\` (\`--service\` from a repo that declares none, or whose
1589
- commit cannot be read — an attestation must be bound to the code it is about).
1590
- 6. Report every \`unconfirmed\` claim and what it would take to close it, and read
1591
- \`discarded\` in the payload: it names every previous answer the new record does
1592
- not carry, and each one is either evidence that went stale or evidence that was
1593
- just dropped. A scenario claim the report failed or missed closes by fixing the
1594
- code and re-running the suite — never by editing the generated .feature.
1595
- 7. Reading is safe from anywhere: \`loam verify $1 --service <id> --json\` without
1596
- \`--record\` writes nothing and works from the docs repo too. \`services\` lists
1597
- every subject of the checklist, so it is how you find out which repos still
1598
- owe an attestation, and \`loam status $1 --json\` says the same thing in the
1599
- shape of a next step.
1600
-
1601
- Anything you cannot show is \`unconfirmed\` with a note. That is a successful record,
1602
- and it is the useful one: nothing gates on this file, so the only thing it is for is
1603
- telling the next reader the truth. \`loam archive\` will ship the feature either way —
1604
- which is exactly why a \`confirmed\` you cannot back up costs nothing now and misleads
1605
- everyone later.
1606
- `,
1607
- },
1608
- {
1609
- name: "loam-ship",
1610
- description: "Archive a finished loam feature — merge its deltas into the living docs",
1611
- argumentHint: "<FEAT-id>",
1612
- purpose: "Merge a shipped feature into the living specs, API and landscape. This is the one command that rewrites the source of truth, so every step before it exists to make the merge reviewable before it runs.",
1613
- invocation: "loam instructions loam-ship $1",
1614
- placeholders: ["feature"],
1615
- spine: [
1616
- "confirm the code is actually built and merged — archiving early makes the docs claim something that does not exist",
1617
- "`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first",
1618
- "`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything",
1619
- "`loam archive $1` for real, and read what it reports it did",
1620
- "`loam unarchive $1` is the undo if the plan turns out to have been wrong",
1621
- ],
1622
- body: `Archive a shipped feature.
1623
-
1624
- 1. Confirm the code is actually built and merged. Archiving folds the delta into the
1625
- living docs; doing it early makes the docs claim something that does not exist.
1626
- \`loam status $1 --json\` answers "is this actually finished" in one call, and its
1627
- \`next[]\` names what is outstanding if it is not.
1628
- 2. \`loam validate --feature $1 --json\` — must come back \`valid: true\`.
1629
- If it reports \`delta.baseline-missing\` or \`openapi.baseline-missing\`, run
1630
- \`loam rebase $1\` and re-validate BEFORE going on: those two say nothing has
1631
- pinned what this delta was written against, so the merge cannot tell the text
1632
- you EDITED from the text you merely restated, and it upserts all of it —
1633
- reverting whatever another feature landed in between, silently and with exit 0.
1634
- 3. \`loam archive $1 --dry-run --json\` first. \`plan[]\` is every file the merge would
1635
- write (\`create\` / \`update\`, then the final \`move\` into \`features/archive/\`), and
1636
- none of them are written — read it before letting the merge touch the source of
1637
- truth, and stop if a file you did not expect is on it. \`warnings[]\` is what the
1638
- merge will do that is legal but lossy — advisory warnings never block, which is
1639
- exactly why you read them now: \`openapi.op-modified\` means an operation the living
1640
- contract already defines gets overwritten wholesale (\`openapi.component-modified\`
1641
- is the same story for a component the merged operations carry),
1642
- \`delta.baseline-missing\` / \`openapi.baseline-missing\` mean the pins are still not
1643
- there and this merge may revert somebody (go back to step 2),
1644
- \`service.no-model\` means a service arrives with no C4 centre behind it, and
1645
- \`delta.added-conflict\`
1646
- means another feature in flight adds the same requirement — the first to archive
1647
- lands it, and the other's archive is then refused (\`delta.added-duplicate\`: its
1648
- ADDED now collides with the living spec) unless a human \`--approve\`s the
1649
- replacement.
1650
- 4. \`loam archive $1 --json\`. It merges three axes into the living state — requirements
1651
- into \`services/<svc>/spec.md\`, endpoints into \`services/<svc>/openapi.yaml\`,
1652
- elements and edges into \`architecture/landscape.likec4\` — then moves the feature
1653
- under \`features/archive/\`. Success is \`ok: true\`; on \`ok: false\`, branch on
1654
- \`error.code\`:
1655
- - \`not-coherent\` — gating coherence issues; \`issues[]\` in the envelope lists them,
1656
- each with \`gates\` resolved (advisory warnings do not block). Fix the breaches.
1657
- \`--approve\` overrides the gating issues and is a human's call to make, not
1658
- yours: report the breach and stop. \`openapi.remove-marker-path-level\` arrives
1659
- here too: an \`x-loam-remove\` written beside the methods instead of inside the
1660
- operation retires nothing.
1661
- - \`living-outside-requirements\` — the LIVING spec holds a requirement outside
1662
- \`## Requirements\`, and the merge would duplicate it. Re-home it, then re-run.
1663
- \`--approve\` does not move this one.
1664
- - \`archive-exists\` — \`features/archive/\` already has that directory; a human
1665
- decides what it is.
1666
- - \`merge-failed\` — the merge could not be computed, or failed and was rolled
1667
- back. Either way the living docs are unchanged; fix the reported cause, re-run.
1668
- A file loam cannot decode as UTF-8 refuses here by name rather than being
1669
- rewritten with replacement characters. So does a living document that still
1670
- holds git conflict markers — the \`spec.merge-conflict\` /
1671
- \`landscape.merge-conflict\` findings ride in \`findings[]\`, and \`--approve\`
1672
- does not override them: the loss is mechanical, not a judgment call.
1673
- - \`docs-busy\` — another \`loam archive\` or \`loam unarchive\` holds the docs
1674
- repo's lock. Nothing was read or written; wait for it and re-run once.
1675
- - \`commit-interrupted\` — a previous archive or unarchive was killed mid-commit
1676
- and this run cannot repair it on its own: a half-written file was edited
1677
- since, its pre-image is gone or altered, or \`.loam-commit\` cannot be read.
1678
- Run \`loam doctor\` (\`doctor.commit-interrupted\` / \`doctor.commit-unreadable\`
1679
- name the same state) and hand it to a human — re-running does not help.
1680
- - \`rollback-incomplete\` — the merge failed AND some files could not be restored.
1681
- Stop and hand it to a human; the message lists the files to check.
1682
-
1683
- Archived by mistake? \`loam unarchive $1\` restores the living docs from the snapshot
1684
- archive left in \`features/archive/$1*/.loam-before/\` and re-opens the feature. Do not
1685
- hand-edit the living docs back instead: the previous text of a \`MODIFIED\` requirement
1686
- is in that snapshot and nowhere else, so anything you write by hand is a guess. It
1687
- refuses under \`snapshot-corrupt\` when a pre-image's bytes no longer match the digest
1688
- the archive recorded — \`--force\` does not override that one, because the damage is
1689
- to the undo itself — and under \`snapshot-missing\` for an archive older than the
1690
- current snapshot format. Both mean version control is the way back.
1691
- `,
1692
- },
1693
- ];
1694
- /**
1695
- * The loam verbs a generated file pre-approves, so an agent that honors the
1696
- * field stops asking permission for each call. Everything else a command needs
1697
- * (Read, Write, the service's own test runner) stays under the user's normal
1698
- * permission settings, and a tool that does not know the field ignores it.
1699
- *
1700
- * Enumerated rather than spelled `Bash(loam:*)`, for one verb's sake. `vouch`
1701
- * is the only command in loam whose output is a claim about a HUMAN act — a
1702
- * person read the code and says the document matches it — and a blanket
1703
- * allowlist pre-approved it, which handed the agent that wrote a draft the
1704
- * power to promote its own draft to `verified` without anybody being asked.
1705
- * That inverts the argument loam makes everywhere else: it holds test evidence
1706
- * to "an agent must not be able to SAY a scenario is tested", and then let one
1707
- * say a spec matches the code. `vouch` now refuses without a terminal or
1708
- * `--yes` (commands/vouch.ts) — this list is the other half, so the refusal is
1709
- * something a person sees rather than something an agent routes around.
1710
- *
1711
- * A verb missing from this list is not forbidden, only unapproved: the agent
1712
- * asks, and the user says yes. That is the correct cost for this one.
1713
- */
1714
- const LOAM_ALLOWED_VERBS = [
1715
- "adopt",
1716
- "archive",
1717
- "delta",
1718
- "dependencies",
1719
- "doctor",
1720
- "explore",
1721
- "gherkin",
1722
- "init",
1723
- "instructions",
1724
- "list",
1725
- "new",
1726
- "rebase",
1727
- "show",
1728
- "status",
1729
- "unarchive",
1730
- "validate",
1731
- "verify",
1732
- ];
1733
- const LOAM_ALLOWED_TOOLS = LOAM_ALLOWED_VERBS.map((v) => `Bash(loam ${v}:*)`).join(", ");
1734
- /**
1735
- * The Agent Skills convention, which every tool in the registry that reads
1736
- * skills at all spells the same way: `<tool-dir>/skills/<name>/SKILL.md`, YAML
1737
- * frontmatter, then the shared body verbatim.
1738
- *
1739
- * `description` is load-bearing in a way no command file's is — it is the only
1740
- * thing the model sees before deciding whether to load the skill — so it comes
1741
- * from the same `CommandContent.description` the command wrappers use rather
1742
- * than a second, skill-flavoured copy that could drift into a better pitch for
1743
- * a worse protocol.
1744
- */
1745
- const skillsIn = (dir) => ({
1746
- path: (name) => [dir, "skills", name, "SKILL.md"],
1747
- format: (c) => `---\nname: ${c.name}\ndescription: ${c.description}\n` +
1748
- `allowed-tools: ${LOAM_ALLOWED_TOOLS}\n---\n\n${c.body}`,
1749
- });
1750
- /**
1751
- * TOML basic-string escapes for the one non-markdown format. Today's bodies
1752
- * carry neither backslashes nor `"""`, but the escape is what keeps a future
1753
- * body edit from silently corrupting the generated TOML.
1754
- */
1755
- const tomlLine = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1756
- const tomlBlock = (s) => s.replace(/\\/g, "\\\\").replace(/"""/g, '""\\"');
1757
- /* The four markdown command dialects the registry actually needs. Shared so a
1758
- * tool's entry is its PATH and its dialect's name — the part that differs —
1759
- * instead of a wall of near-identical template literals. `claude` deliberately
1760
- * spells its own wrapper inline even though `hintedMd` matches it byte for
1761
- * byte: an edit made here for some other tool must not be able to re-spell the
1762
- * one format that is frozen. */
1763
- /** Description-only frontmatter — the most common dialect by a wide margin. */
1764
- const describedMd = (c) => `---\ndescription: ${c.description}\n---\n\n${c.body}`;
1765
- /** Description plus Claude's `argument-hint`, for the tools that copied it. */
1766
- const hintedMd = (c) => `---\ndescription: ${c.description}\nargument-hint: ${c.argumentHint}\n---\n\n${c.body}`;
1767
- /** Name plus description — the Cascade-style workflow frontmatter. */
1768
- const namedMd = (c) => `---\nname: ${c.name}\ndescription: ${c.description}\n---\n\n${c.body}`;
1769
- /** No frontmatter at all: a title line is the whole wrapper. */
1770
- const titledMd = (c) => `# ${c.name}\n\n${c.body}`;
1771
- /** The `loam-` prefix a namespace directory carries instead of the file name. */
1772
- const unprefixed = (name) => name.replace(/^loam-/, "");
1773
- /**
1774
- * What a generated file actually contains: the purpose, the spine, and the
1775
- * command that prints the rest.
1776
- *
1777
- * The protocol itself stays in `body` and ships inside the binary, reachable as
1778
- * `loam instructions <name>`. The file on disk gets a pointer to it, because
1779
- * these two things go stale in opposite directions and only one of them can be
1780
- * fixed. A generated file is written once, never regenerated (that is the
1781
- * never-overwrite contract, and it is deliberate — your edits outrank the
1782
- * template), so a repo scaffolded a year ago holds a year-old protocol: exact
1783
- * flags, exact finding codes, exact step order, all of it asserted with total
1784
- * confidence by a file whose reader has no way to know it is describing a
1785
- * different release. That is not a hypothetical failure mode. It is the one
1786
- * every project shipping generated agent instructions has had, and the fix
1787
- * everybody reaches for — regenerate on upgrade — trades it for silently
1788
- * overwriting what a human wrote.
1789
- *
1790
- * Thinning the file is the third option. What remains is what does not move
1791
- * between releases: what this workflow is for, and the verbs in order. What
1792
- * leaves is everything version-shaped. `doctor.agent-files-stale` still reports
1793
- * a file whose stamp has fallen behind, and now has almost nothing to be right
1794
- * about — which is the point.
1795
- *
1796
- * The trade is real and worth stating: an agent that cannot execute `loam` now
1797
- * has a spine instead of a protocol. That is the correct failure. Every step
1798
- * below the spine is a loam invocation, so an agent that cannot run loam was
1799
- * never going to complete this workflow from the file either — it was going to
1800
- * try, using a year-old flag.
1801
- */
1802
- function stubBody(c) {
1803
- const steps = c.spine.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
1804
- return `${c.purpose}
1805
-
1806
- **Run this first.** It is the protocol, and it ships with the binary you are about
1807
- to call — so it names this loam's flags, its finding codes and its fix tables,
1808
- not the ones that were current when this repository was scaffolded:
1809
-
1810
- ${c.invocation}
1811
-
1812
- The spine it fills in, so you can tell a run that went sideways from one that did not:
1813
-
1814
- ${steps}
1815
-
1816
- Every step above is a \`loam\` invocation, and each command's own \`--json\` output
1817
- carries what to do next: findings have stable codes, and \`loam status --json\` puts
1818
- the ordered \`next[]\` — each entry a code and the literal command — in one place.
1819
- Branch on the codes, never on the prose.
1820
-
1821
- This file is a pointer, not the protocol. loam wrote it once and will never
1822
- rewrite it, so your edits here outrank the template and nothing will quietly
1823
- undo them. Where this file and \`loam instructions\` disagree, the command is right.
1824
- `;
1825
- }
1826
- /**
1827
- * The command body a file actually gets: the pointer, under the same version
1828
- * stamp AGENTS.md carries.
1829
- *
1830
- * The stamp is applied once, here, rather than in twenty wrappers — it is a
1831
- * fact about loam, not about a tool's dialect, and a per-wrapper copy is twenty
1832
- * chances to forget it. It rides at the top of the BODY (not the file) because
1833
- * every dialect puts something of its own first: YAML frontmatter, a TOML key,
1834
- * a title line. An HTML comment is invisible in all of them, and
1835
- * `agentsStampVersion` matches it wherever in the file it lands.
1836
- *
1837
- * A repo initialized before stamping existed has unstamped files, which is
1838
- * exactly what `doctor.agent-files-stale` is for: nobody has confirmed that
1839
- * those instructions still describe this binary.
1840
- */
1841
- const stubbed = (c) => ({
1842
- ...c,
1843
- body: `${agentsStampLine(LOAM_VERSION)}\n\n${stubBody(c)}`,
1844
- });
1845
- /**
1846
- * The original target and the default. This WRAPPER must stay byte-identical
1847
- * to what loam has always written — SLASH_COMMANDS below derives through it —
1848
- * and an already-initialized repo must still read as fully scaffolded
1849
- * (`skipped` is existsSync, never content). What the wrapper wraps now carries
1850
- * a version stamp, which is the one thing about a generated file loam will
1851
- * report on; see `stubbed`.
1852
- *
1853
- * It stands outside the registry object because SLASH_COMMANDS reaches for it
1854
- * by name. That lookup was `AGENT_TOOLS["claude"]!.format!` — two assertions on
1855
- * an exported const evaluated at import time, so renaming the key, or letting
1856
- * this entry lose `format` the way `codex` legitimately has, would have thrown
1857
- * a TypeError while the CLI entry point was still being loaded: before
1858
- * `program.exitOverride()` exists, and therefore with no envelope around it.
1859
- * Named and typed, both mistakes are typecheck failures instead. The registry
1860
- * still lists it first, and the emitted bytes are unchanged.
1861
- */
1862
- const CLAUDE = {
1863
- path: (name) => [".claude", "commands", `${name}.md`],
1864
- format: (c) => `---\ndescription: ${c.description}\nargument-hint: ${c.argumentHint}\n---\n\n${c.body}`,
1865
- skill: skillsIn(".claude"),
1866
- detect: [".claude"],
1867
- };
1868
- export const AGENT_TOOLS = {
1869
- claude: CLAUDE,
1870
- // Cursor invokes by flat file name (`/loam-check`); its frontmatter `name`
1871
- // field spells that invocation form.
1872
- cursor: {
1873
- path: (name) => [".cursor", "commands", `${name}.md`],
1874
- format: (c) => `---\nname: /${c.name}\n---\n\n${c.body}`,
1875
- skill: skillsIn(".cursor"),
1876
- detect: [".cursor"],
1877
- },
1878
- // Copilot prompt files: `.prompt.md` extension, description-only frontmatter.
1879
- // Detection cannot use `.github/` — almost every repo has one and almost none
1880
- // of them means Copilot — so it looks for the files Copilot itself reads.
1881
- "github-copilot": {
1882
- path: (name) => [".github", "prompts", `${name}.prompt.md`],
1883
- format: describedMd,
1884
- skill: skillsIn(".github"),
1885
- detect: [
1886
- ".github/copilot-instructions.md",
1887
- ".github/instructions",
1888
- ".github/prompts",
1889
- ".github/agents",
1890
- ".github/skills",
1891
- ".github/workflows/copilot-setup-steps.yml",
1892
- ".github/.mcp.json",
1893
- ],
1894
- },
1895
- // Gemini: TOML, and a namespace DIRECTORY — `.gemini/commands/loam/check.toml`
1896
- // is invoked as `/loam:check`, so the file name drops the `loam-` prefix the
1897
- // flat-named tools carry.
1898
- gemini: {
1899
- path: (name) => [".gemini", "commands", "loam", `${unprefixed(name)}.toml`],
1900
- format: (c) => `description = "${tomlLine(c.description)}"\nprompt = """\n${tomlBlock(c.body)}"""`,
1901
- skill: skillsIn(".gemini"),
1902
- detect: [".gemini"],
1903
- },
1904
- opencode: {
1905
- path: (name) => [".opencode", "commands", `${name}.md`],
1906
- format: describedMd,
1907
- skill: skillsIn(".opencode"),
1908
- detect: [".opencode"],
1909
- },
1910
- // Cline reads workflows from `.clinerules/` — plain markdown, no frontmatter —
1911
- // but its skills from `.cline/`. The two directories are unrelated, so both
1912
- // count as a sighting.
1913
- cline: {
1914
- path: (name) => [".clinerules", "workflows", `${name}.md`],
1915
- format: (c) => `# ${c.name}\n\n${c.body}`,
1916
- skill: skillsIn(".cline"),
1917
- detect: [".cline", ".clinerules"],
1918
- },
1919
- // Amazon Q surfaces these as its PROMPT library, not as commands: the file is
1920
- // invoked `@loam-check`, never `/loam-check`. Same file shape regardless.
1921
- "amazon-q": {
1922
- path: (name) => [".amazonq", "prompts", `${name}.md`],
1923
- format: describedMd,
1924
- skill: skillsIn(".amazonq"),
1925
- detect: [".amazonq"],
1926
- },
1927
- // Antigravity's directory is `.agent/`, which is why the id and the directory
1928
- // do not match here as they do almost everywhere else.
1929
- antigravity: {
1930
- path: (name) => [".agent", "workflows", `${name}.md`],
1931
- format: describedMd,
1932
- skill: skillsIn(".agent"),
1933
- detect: [".agent"],
1934
- },
1935
- // Auggie is the Augment CLI: id `auggie`, directory `.augment/`.
1936
- auggie: {
1937
- path: (name) => [".augment", "commands", `${name}.md`],
1938
- format: hintedMd,
1939
- skill: skillsIn(".augment"),
1940
- detect: [".augment"],
1941
- },
1942
- // Skills-only, deliberately: Codex reads `.codex/skills/` and does not load
1943
- // custom command files, so it declares no command emitter at all rather than
1944
- // laying down files nothing will ever open.
1945
- codex: {
1946
- skill: skillsIn(".codex"),
1947
- detect: [".codex"],
1948
- },
1949
- // Continue's prompt files take a bare `.prompt` extension, and `invokable`
1950
- // is what promotes one from a template to something you can call.
1951
- continue: {
1952
- path: (name) => [".continue", "prompts", `${name}.prompt`],
1953
- format: (c) => `---\nname: ${c.name}\ndescription: ${c.description}\ninvokable: true\n---\n\n${c.body}`,
1954
- skill: skillsIn(".continue"),
1955
- detect: [".continue"],
1956
- },
1957
- // Crush namespaces like Gemini does — `.crush/commands/loam/check.md` is
1958
- // `/loam:check` — but stays markdown.
1959
- crush: {
1960
- path: (name) => [".crush", "commands", "loam", `${unprefixed(name)}.md`],
1961
- format: namedMd,
1962
- skill: skillsIn(".crush"),
1963
- detect: [".crush"],
1964
- },
1965
- // Devin Desktop, formerly Windsurf: Cascade-style workflows, and the rename
1966
- // moved the directory from `.windsurf/` to `.devin/`. New files go to the new
1967
- // directory; a repo still holding the old one is still a Devin repo, so both
1968
- // are sightings.
1969
- devin: {
1970
- path: (name) => [".devin", "workflows", `${name}.md`],
1971
- format: namedMd,
1972
- skill: skillsIn(".devin"),
1973
- detect: [".devin", ".windsurf"],
1974
- },
1975
- factory: {
1976
- path: (name) => [".factory", "commands", `${name}.md`],
1977
- format: hintedMd,
1978
- skill: skillsIn(".factory"),
1979
- detect: [".factory"],
1980
- },
1981
- junie: {
1982
- path: (name) => [".junie", "commands", `${name}.md`],
1983
- format: describedMd,
1984
- skill: skillsIn(".junie"),
1985
- detect: [".junie"],
1986
- },
1987
- // Kilo Code workflows carry no frontmatter — the title line is all the
1988
- // wrapper there is.
1989
- kilocode: {
1990
- path: (name) => [".kilocode", "workflows", `${name}.md`],
1991
- format: titledMd,
1992
- skill: skillsIn(".kilocode"),
1993
- detect: [".kilocode"],
1994
- },
1995
- // Kiro uses Copilot's `.prompt.md` extension under its own directory.
1996
- kiro: {
1997
- path: (name) => [".kiro", "prompts", `${name}.prompt.md`],
1998
- format: describedMd,
1999
- skill: skillsIn(".kiro"),
2000
- detect: [".kiro"],
2001
- },
2002
- // Qwen Code retired its TOML commands in favour of markdown + frontmatter,
2003
- // so this is markdown despite the Gemini lineage.
2004
- qwen: {
2005
- path: (name) => [".qwen", "commands", `${name}.md`],
2006
- format: describedMd,
2007
- skill: skillsIn(".qwen"),
2008
- detect: [".qwen"],
2009
- },
2010
- // Roo/Zoo Code: id `roocode`, directory `.roo/`, no frontmatter.
2011
- roocode: {
2012
- path: (name) => [".roo", "commands", `${name}.md`],
2013
- format: titledMd,
2014
- skill: skillsIn(".roo"),
2015
- detect: [".roo"],
2016
- },
2017
- trae: {
2018
- path: (name) => [".trae", "commands", `${name}.md`],
2019
- format: namedMd,
2020
- skill: skillsIn(".trae"),
2021
- detect: [".trae"],
2022
- },
2023
- };
2024
- /**
2025
- * The Claude-format files, keyed by command name: exactly what **this** binary
2026
- * would write, derived through the same adapter that writes them, so the export
2027
- * and a freshly scaffolded repo can never disagree.
2028
- *
2029
- * Read that scope literally. It used to be described as the on-disk contract of
2030
- * every repo initialized before `--tools` existed, and that is no longer true
2031
- * of anything but a fresh scaffold: a file holds the STUB now, every repo
2032
- * scaffolded before that holds the full protocol, and because loam never
2033
- * regenerates a generated file the two disagree permanently and on purpose. An
2034
- * export cannot describe files this binary did not write.
2035
- *
2036
- * The protocol those older files carry is `PROTOCOLS`. Anything asserting on
2037
- * protocol content wants that one — this answers "what would loam write here",
2038
- * not "what does loam instruct".
2039
- */
2040
- export const SLASH_COMMANDS = Object.fromEntries(COMMANDS.map((c) => [c.name, CLAUDE.format(stubbed(c))]));
2041
- /**
2042
- * The workflow protocols themselves, keyed by command name — what
2043
- * `loam instructions <name>` prints, and the corpus every check over loam's
2044
- * agent-facing prose reads.
2045
- *
2046
- * Separate from SLASH_COMMANDS since the file became a pointer: the protocol is
2047
- * still the thing that must document every stable code (`codes-drift`) and
2048
- * every `loam …` line it teaches must still parse against the real CLI
2049
- * (`agent-commands-runnable`). Those two properties are about the INSTRUCTIONS,
2050
- * and they did not move just because the delivery did.
2051
- */
2052
- export const PROTOCOLS = Object.fromEntries(COMMANDS.map((c) => [c.name, c.body]));
2053
- /** What each workflow's `$1`, `$2`, … denote — see {@link CommandContent.placeholders}. */
2054
- export const PLACEHOLDERS = Object.fromEntries(COMMANDS.map((c) => [c.name, c.placeholders]));
2055
- /**
2056
- * Why the arguments given to `name` cannot be substituted into its protocol, or
2057
- * null when they can. One sentence per bad argument, already naming the
2058
- * placeholder it was meant to fill.
2059
- *
2060
- * The grammars are `core/ids.ts`'s, never a second copy: a value this accepts
2061
- * and `loam adopt` refuses would be worse than no check, because the protocol
2062
- * would still be wrong and now something had approved it. An argument nobody
2063
- * supplied is not an error — `protocolFor` leaves that placeholder standing on
2064
- * purpose, so the page reads as "the service id goes here".
2065
- */
2066
- export function placeholderProblems(name, args) {
2067
- const kinds = PLACEHOLDERS[name];
2068
- if (kinds === undefined)
2069
- return null;
2070
- const problems = kinds.flatMap((kind, i) => {
2071
- const arg = args[i];
2072
- if (arg === undefined || arg === "" || kind === "free")
2073
- return [];
2074
- const problem = kind === "service"
2075
- ? serviceIdProblem(arg, `$${i + 1}`)
2076
- : featureIdProblem(arg, `feature id ($${i + 1})`);
2077
- return problem === null ? [] : [problem];
2078
- });
2079
- return problems.length === 0 ? null : problems;
2080
- }
2081
- /** The workflow names, in cycle order — `loam instructions` with no argument lists these. */
2082
- export const WORKFLOWS = COMMANDS.map(({ name, description, argumentHint }) => ({ name, description, argumentHint }));
2083
- /**
2084
- * One workflow's protocol with `$1`, `$2`, … replaced by the arguments given.
2085
- *
2086
- * An unsupplied placeholder is LEFT STANDING rather than blanked: the bodies
2087
- * are written so `$1` reads as "the feature id goes here", and a protocol that
2088
- * silently dropped it would hand an agent `loam validate --feature --json` — a
2089
- * command that parses, runs, and answers a different question. Extra arguments
2090
- * are ignored for the same reason: this substitutes, it does not validate.
2091
- *
2092
- * An EMPTY argument counts as unsupplied, and that is not pedantry. Several
2093
- * tool dialects expand an absent positional to the empty string, and
2094
- * `loam-feature`'s own invocation quotes its second one (`--title "$2"`), so
2095
- * `/loam-feature FEAT-101` with no title arrives here as `["FEAT-101", ""]`.
2096
- * Blanking it produced `loam new FEAT-101 --title ""` — which is exactly the
2097
- * failure the paragraph above describes, reached by the one path that paragraph
2098
- * did not cover: the command succeeds, and writes a feature whose title is the
2099
- * empty string and whose intent.md opens with a bare `#`.
2100
- */
2101
- export function protocolFor(name, args = []) {
2102
- const body = PROTOCOLS[name];
2103
- if (body === undefined)
2104
- return null;
2105
- return body.replace(/\$([1-9])/g, (whole, digit) => {
2106
- const arg = args[Number(digit) - 1];
2107
- return arg === undefined || arg === "" ? whole : arg;
2108
- });
2109
- }
2110
- /** The two ways a command body reaches an agent. Both are on by default. */
2111
- export const DELIVERIES = ["commands", "skills"];
2112
- /**
2113
- * Every file the selected tools would lay down, in creation order — the same
2114
- * list `init` probes for `skipped` BEFORE the scaffold runs, so created +
2115
- * skipped is the same list on every repo.
2116
- *
2117
- * Called with two arguments it returns EVERYTHING the binary would write under
2118
- * default delivery: commands and skills both. That is the contract callers
2119
- * outside this module depend on — `loam doctor` asks "what should be here?"
2120
- * and a half-answer would report a fully-scaffolded repo as fully scaffolded
2121
- * while ignoring every skill file. The name predates the second delivery and is
2122
- * kept because it is imported elsewhere; `delivery` narrows it.
2123
- */
2124
- export function plannedCommandFiles(cwd, toolIds, delivery = DELIVERIES) {
2125
- return toolIds.flatMap((id) => {
2126
- const tool = AGENT_TOOLS[id];
2127
- if (tool === undefined)
2128
- throw new Error(`unknown agent tool: ${id}`);
2129
- const emitters = [];
2130
- // A tool declares only the deliveries it actually reads, so an absent
2131
- // emitter is a fact about the tool, never a reason to fall back to some
2132
- // other tool's convention.
2133
- const { path, format, skill } = tool;
2134
- if (delivery.includes("commands") && path !== undefined && format !== undefined) {
2135
- emitters.push({ path, format });
2136
- }
2137
- if (delivery.includes("skills") && skill !== undefined)
2138
- emitters.push(skill);
2139
- return emitters.flatMap((e) => COMMANDS.map((c) => ({ path: join(cwd, ...e.path(c.name)), content: e.format(stubbed(c)) })));
2140
- });
2141
- }
2142
- /**
2143
- * Write the command and skill files for the selected tools into `cwd`. Existing
2144
- * files are left alone — the never-overwrite contract covers every tool and
2145
- * every delivery, not just the default one. Returns the paths created.
2146
- */
2147
- export async function scaffoldAgentCommands(cwd, toolIds = ["claude"], delivery = DELIVERIES) {
2148
- const created = [];
2149
- for (const { path, content } of plannedCommandFiles(cwd, toolIds, delivery)) {
2150
- if (existsSync(path))
2151
- continue;
2152
- await mkdir(dirname(path), { recursive: true });
2153
- await writeFile(path, content, "utf8");
2154
- created.push(path);
2155
- }
2156
- return created;
2157
- }
2158
- /**
2159
- * The registry ids whose marker paths are present in `cwd` — what `loam init`
2160
- * writes for when nobody passed `--tools`.
2161
- *
2162
- * A repo with `.cursor/` and no Claude Code used to get `.claude/commands/`
2163
- * and nothing it could actually run. Presence is the whole test, and existence
2164
- * (not directory-ness) is what is asked, because a marker is as often a file as
2165
- * a directory — `.github/copilot-instructions.md` is the honest signal for
2166
- * Copilot where `.github/` itself is noise.
2167
- */
2168
- export function detectAgentTools(cwd) {
2169
- return Object.entries(AGENT_TOOLS)
2170
- .filter(([, tool]) => tool.detect.some((marker) => existsSync(join(cwd, marker))))
2171
- .map(([id]) => id);
2172
- }
2173
- //# sourceMappingURL=agent.js.map