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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (648) hide show
  1. package/CHANGELOG.md +1315 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +83 -7
  8. package/SCHEMA.md +569 -98
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +33 -11
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -37,10 +37,14 @@ reads the service, so a verdict is worth exactly what its evidence is worth.
37
37
  must change.
38
38
  2. The \`scenario.tested\` claims are the runner's to answer, not yours. In the
39
39
  service's repo, run the generated suite with a JSON report —
40
- \`npx cucumber-js --format json:report.json\` (cucumber-jvm, behave and
41
- SpecFlow emit the same format). The generated scenarios carry
42
- \`@loam-digest-…\` tags that ride into the report; that digest is the match,
43
- so never retitle or hand-edit a generated scenario to make one pass.
40
+ \`npx cucumber-js --format json:report.json\` (cucumber-jvm, behave,
41
+ SpecFlow/Reqnroll, pytest-bdd \`--cucumberjson\` and godog emit the same
42
+ format). The generated scenarios carry \`@loam-digest-…\` tags that ride into
43
+ the report; that digest is the match, and the run must have executed every
44
+ step of the stamped scenario, so never retitle or hand-edit a generated
45
+ scenario to make one pass. A runner with no cucumber formatter answers
46
+ through loam's runner-neutral shape instead, keyed by the same digests:
47
+ \`{"loamScenarioReport": 1, "results": [{"digest": "…", "status": "passed"}]}\`.
44
48
  If there is no runnable suite yet you MAY answer them yourself, and the record
45
49
  will say so: \`answered_by: agent\` makes the feature **attested**, not verified
46
50
  (\`verified: false\`, \`verdict: "attested"\`, the \`verify.scenario-attested\`
@@ -105,29 +109,36 @@ reads the service, so a verdict is worth exactly what its evidence is worth.
105
109
  \`\`\`
106
110
 
107
111
  \`--service <id>\` narrows the checklist to that service's claims and binds the
108
- answers to this repository's commit, writing ONE attestation into a shared
109
- \`schema: 2\` \`verification.yaml\`. Running it in the next repo adds that repo's
110
- attestation and leaves the others standing. In a repo whose ./loam.json
112
+ answers to this repository's commit, writing that service's attestation to
113
+ its OWN file, \`features/$1/verification/<id>.yaml\` — the directory is the
114
+ record, and loam adds the files up when it reads. Running it in the next repo
115
+ writes that repo's file and touches no other, so two teams' docs PRs never
116
+ conflict over the record. In a repo whose ./loam.json
111
117
  declares a \`service\`, \`--service\` may be omitted — it is substituted, and the
112
118
  run still federates. \`--results report.json\` alone is legal when the
113
119
  checklist is all scenarios.
114
120
 
115
121
  ⚠ **Never run \`loam verify $1 --record answers.json\` without \`--service\`.**
116
122
  That is the legacy all-at-once form: it writes the WHOLE record from one place,
117
- and over a record that already carries other services' attestations it is
118
- refused (\`record-federated\`, naming them) precisely because it would erase
119
- them. If you meet that refusal, re-run with \`--service\`; do not work around it.
123
+ into the one file \`verification.yaml\`, and over a record that already carries
124
+ other services' attestations it is refused (\`record-federated\`, naming them)
125
+ precisely because it would put one unattested answer set over them. If you
126
+ meet that refusal, re-run with \`--service\`; do not work around it.
120
127
 
121
128
  The other refusals: \`answers-mismatch\` (an id
122
129
  nobody asked about, a claim you left out — re-run step 1, the feature moved —
123
130
  or an answers entry for a \`scenario.tested\` claim: the runner owns those),
124
131
  \`answers-unevidenced\` (a \`confirmed\` with nothing behind it),
125
132
  \`answers-unreadable\` (a report or answers file it cannot read),
126
- \`record-unreadable\` (a \`verification.yaml\` that exists but cannot be read as a
127
- record — it is never overwritten; a human repairs it, and
128
- \`verify.record-miscounted\` is the reason when its \`summary\` block contradicts
129
- its own \`claims[]\`: neither half can be believed, so re-record rather than
130
- editing the counts),
133
+ \`record-unreadable\` (a record file that exists but cannot be read — broken
134
+ YAML, git conflict markers a merge left, or \`verify.record-miscounted\` when
135
+ its \`summary\` block contradicts its own \`claims[]\`. It is never overwritten,
136
+ and the message names the file and the safe repair: a service's own
137
+ \`verification/<id>.yaml\` holds only that service's word, so that service
138
+ may delete it and record again; the one-file \`verification.yaml\` may hold
139
+ other services' attestations, so it is repaired or restored from version
140
+ control — after a merge, the copy on the branch you merged into — and
141
+ **never deleted**. Never edit the counts to make them agree),
131
142
  \`service-mismatch\` (\`--service\` names a service this repo does not declare) and
132
143
  \`repository-unavailable\` (\`--service\` from a repo that declares none, or whose
133
144
  commit cannot be read — an attestation must be bound to the code it is about).
@@ -161,8 +172,8 @@ gathered from the steps above; branch on the code, never the prose:
161
172
  | \`verify.scenario-attested\` (warn) | scenario claims were answered on an agent's word (\`answered_by: agent\`), so the verdict is **attested**, not verified — nothing gates on it, but say so when you hand back | run the generated suite with a JSON report and re-record with \`--results report.json\` the moment a suite exists |
162
173
  | \`verify.digest-contested\` (warn) | two or more services word a scenario identically, so they share one \`@loam-digest-…\` tag and no single cucumber report can say whose suite ran it — those scenario claims stay unconfirmed | each service records its own claims from its own repository with \`--service <id>\` |
163
174
  | \`verify.operation-contested\` (warn) | two services on the checklist expose the same operationId, so a contract report — which names no service — cannot say whose suite exercised it; those claims stay unconfirmed | each service records its own with \`--service <id>\`, passing its own contract report |
164
- | \`verify.checklist-forked\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \`checklist\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale | re-record the services on the older version from their own repositories (\`loam verify <FEAT> --service <id> --record\`), or accept the split knowingly — nothing gates on it |
165
- | \`verify.record-miscounted\` (error) | a verification.yaml whose \`summary\` block contradicts its own \`claims[]\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | re-record from the service's repo; never edit the counts by hand |
175
+ | \`verify.checklist-forked\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \`checklist\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale. In a per-service record the answers of the services named do not count until they record again | re-record the services on the older version from their own repositories (\`loam verify <FEAT> --service <id> --record\`) — nothing gates on it |
176
+ | \`verify.record-miscounted\` (error) | a record file whose \`summary\` block contradicts its own \`claims[]\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | a service's own \`verification/<id>.yaml\`: delete it and re-record from that service's repo; the one-file \`verification.yaml\`: restore it from version control, then re-record — never delete it, and never edit the counts by hand |
166
177
  | \`verify.evidence-token-missing\` (warn) | a just-confirmed citation's file at the attested commit does not contain the claim's own token (the operationId, message name or edge op) — the evidence may not show what the answer says | re-read that evidence while the answer can still be corrected; verdict-neutral, it gates nothing |
167
178
  | \`cross.agree-confirmed\` / \`cross.agree-unconfirmed\` (ok) | a \`--diff-answers\` row where both blind answer sets reach the same verdict — the cheap-to-skip review queue, though same-model agreement is correlated and proves nothing | nothing on its own; record only the answer set you stand behind, in a separate run |
168
179
  | \`cross.disagree\` (the rows a human reads first) | a \`--diff-answers\` row where the two blind answer sets contradict each other — one of them is wrong about the code | read both cited evidences and decide; the diff writes nothing and never moves a verdict |
@@ -181,6 +192,7 @@ export const LOAM_SHIP = {
181
192
  "`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first",
182
193
  "`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything",
183
194
  "`loam archive $1` for real, and read what it reports it did",
195
+ "every vouched spec on `revouchOwed[]` owes its owner's re-vouch — tell them; it lands in this same PR",
184
196
  "`loam unarchive $1` is the undo if the plan turns out to have been wrong",
185
197
  ],
186
198
  body: `Archive a shipped feature.
@@ -202,8 +214,10 @@ export const LOAM_SHIP = {
202
214
  truth, and stop if a file you did not expect is on it. \`warnings[]\` is what the
203
215
  merge will do that is legal but lossy — advisory warnings never block, which is
204
216
  exactly why you read them now: \`openapi.op-modified\` means an operation the living
205
- contract already defines gets overwritten wholesale (\`openapi.component-modified\`
206
- is the same story for a component the merged operations carry),
217
+ contract already defines gets overwritten wholesale, and its \`details\` name the
218
+ current consumers that overwrite lands on (\`asyncapi.message-modified\` likewise for
219
+ a message; \`openapi.component-modified\` is the same story for a component the
220
+ merged operations carry, with no consumer list),
207
221
  \`service.no-model\` means a service arrives with no C4 centre behind it, and
208
222
  \`delta.added-conflict\`
209
223
  means another feature in flight adds the same requirement — the first to archive
@@ -215,7 +229,11 @@ export const LOAM_SHIP = {
215
229
  elements and edges into \`architecture/landscape.likec4\` and, for an addition nested
216
230
  under a service whose model EXTENDS the map, into that
217
231
  \`services/<…>/<svc>/model.likec4\` instead — then moves the feature
218
- under \`features/archive/\`. Success is \`ok: true\`; on \`ok: false\`, branch on
232
+ under \`features/archive/\`, whole: its verification record travels with it,
233
+ the per-service \`verification/<id>.yaml\` files and any one-file
234
+ \`verification.yaml\` alike, and \`loam verify $1\` reads it back afterwards as
235
+ frozen history. Verification never gates the archive — an open or missing
236
+ record ships as exactly that. Success is \`ok: true\`; on \`ok: false\`, branch on
219
237
  \`error.code\`:
220
238
  - \`not-coherent\` — gating coherence issues; \`issues[]\` in the envelope lists them,
221
239
  each with \`gates\` and \`overridable\` resolved (advisory warnings do not block).
@@ -228,7 +246,12 @@ export const LOAM_SHIP = {
228
246
  \`overridable: false\`, and the fix is a rename, never the flag.
229
247
  \`openapi.remove-marker-path-level\` arrives
230
248
  here too: an \`x-loam-remove\` written beside the methods instead of inside the
231
- operation retires nothing.
249
+ operation retires nothing. So do \`openapi.op-revived\` and
250
+ \`asyncapi.slot-revived\`: the delta spells an operation, slot or message an
251
+ ARCHIVED feature retired, and the merge would put it back. If the delta only
252
+ quoted it, delete the quote; whether the feature brings it back on purpose is
253
+ the human's \`--approve\`, and the archive output then names what came back
254
+ and whose retirement it reverses.
232
255
  - \`living-outside-requirements\` — the LIVING spec holds a requirement outside
233
256
  \`## Requirements\`, and the merge would duplicate it. Re-home it, then re-run.
234
257
  \`--approve\` does not move this one.
@@ -250,6 +273,16 @@ export const LOAM_SHIP = {
250
273
  name the same state) and hand it to a human — re-running does not help.
251
274
  - \`rollback-incomplete\` — the merge failed AND some files could not be restored.
252
275
  Stop and hand it to a human; the message lists the files to check.
276
+ 5. Re-vouches are owed. \`revouchOwed[]\` — on the dry run and the real merge alike —
277
+ names every VOUCHED spec the merge moves under its vouch stamp, with the
278
+ document's \`owner\`. \`content.stale\` follows on each, and honestly: the merged
279
+ text is words nobody has yet read against the code. You cannot clear it — a
280
+ vouch is a person's, run in that service's own repo (\`loam vouch --service <id>\`,
281
+ after \`loam vouch --pack --service <id>\` for what to re-read). Name the
282
+ documents and owners in your hand-back, and say the re-vouches belong in the
283
+ SAME PR as this archive: a docs repo gated on
284
+ \`loam validate --all --base <ref> --strict\` keeps the archive's PR red until they
285
+ land. Never edit a stamp by hand to turn it green.
253
286
 
254
287
  Archived by mistake? \`loam unarchive $1\` restores the living docs from the snapshot
255
288
  archive left in \`features/archive/$1*/.loam-before/\` and re-opens the feature. Do not
@@ -1 +1 @@
1
- {"version":3,"file":"closing.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/closing.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAmB;IACzC,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,qFAAqF;IACvF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,6NAA6N;IAC/N,UAAU,EAAE,kCAAkC;IAC9C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,yEAAyE;QACzE,uGAAuG;QACvG,sFAAsF;QACtF,2EAA2E;QAC3E,6FAA6F;KAC9F;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoJP;CACA,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,yEAAyE;IACtF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,0MAA0M;IAC5M,UAAU,EAAE,gCAAgC;IAC5C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,oHAAoH;QACpH,sHAAsH;QACtH,uGAAuG;QACvG,6DAA6D;QAC7D,0EAA0E;KAC3E;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4EP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/**\n * The two post-build workflows, paired: the done-check (`loam-verify`) and the\n * merge it precedes (`loam-ship`). One file for two commands because six\n * workflows meet the five-file package cap somewhere, and this is the seam the\n * cycle already has — both describe what happens after the code is built, and\n * each body names the other's refusals (`record-federated`, `not-coherent`).\n */\nexport const LOAM_VERIFY: CommandContent = {\n name: \"loam-verify\",\n description:\n \"Verify a built loam feature against its own promises, claim by claim, with evidence\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"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.\",\n invocation: \"loam instructions loam-verify $1\",\n placeholders: [\"feature\"],\n spine: [\n \"`loam verify` — the checklist, derived from the feature's own artifacts\",\n \"run the generated suite with a JSON report: the scenario claims are the runner's to answer, not yours\",\n \"answer the remaining claims yourself, every one carrying a `file:line` that resolves\",\n \"record from each affected service's own repository, bound to that service\",\n \"read the verdict honestly — a scenario confirmed on your word is `attested`, not `verified`\",\n ],\n body: `Check that the code somebody just built is the feature that was designed.\n\nloam derives the questions; the test runner and you answer them. loam never\nreads the service, so a verdict is worth exactly what its evidence is worth.\n\n1. \\`loam verify $1 --json\\`. \\`claims[]\\` is the checklist, derived from the feature's\n own artifacts — one claim per new service, per operation its openapi delta adds,\n per (direction, message) its asyncapi delta newly declares against the living\n contract's send/receive sets (quotes and removals are never questions),\n per tagged edge that names an operation, and per scenario of every changed\n requirement, arch.spec.md deltas included. Each has a stable \\`id\\` and a\n \\`subject\\` (the service whose code answers it); a claim that says arch.spec.md\n wants an integration/ops test, not an acceptance test. An ARCHIVED feature returns its record as frozen history instead\n (\\`frozen: true\\`) and \\`--record\\` / \\`--results\\` / \\`--contract-results\\` /\n \\`--diff-answers\\` refuse it — \\`loam unarchive\\` first if the answers really\n must change.\n2. The \\`scenario.tested\\` claims are the runner's to answer, not yours. In the\n service's repo, run the generated suite with a JSON report —\n \\`npx cucumber-js --format json:report.json\\` (cucumber-jvm, behave and\n SpecFlow emit the same format). The generated scenarios carry\n \\`@loam-digest-…\\` tags that ride into the report; that digest is the match,\n so never retitle or hand-edit a generated scenario to make one pass.\n If there is no runnable suite yet you MAY answer them yourself, and the record\n will say so: \\`answered_by: agent\\` makes the feature **attested**, not verified\n (\\`verified: false\\`, \\`verdict: \"attested\"\\`, the \\`verify.scenario-attested\\`\n notice, and a \\`next.verify-attested\\` step in \\`loam status\\`). Nothing gates on\n it — a legacy service with no suite must still be able to ship — but say so\n when you hand back, and close it with \\`--results\\` the moment a suite runs.\n \\`verify.digest-contested\\` (warn) is the case where two services word a\n scenario identically: they share one digest, so no single report can say whose\n suite ran it, and each service must record its own with \\`--service\\`.\n The \\`api.exposes\\` claims have a mechanical channel too, when an API\n contract-test suite exists: emit loam's contract-results shape from its\n report — \\`{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"…\",\n \"status\": \"passed\"}]}\\`, a one-line transform for Specmatic, Pact, Dredd or a\n property harness — and pass \\`--contract-results contract.json\\` alongside.\n Only a status of exactly \\`passed\\` confirms the named operation\n (\\`answered_by: external-runner\\`); anything else, and an operation the report\n never exercised, stays \\`unconfirmed\\` with the reason, and under the flag the\n report owns every api.exposes claim, so take those out of the answers file.\n3. Answer every OTHER claim by finding it in the code. Not by reasoning that it\n must be there — by opening the file:\n - \\`service.exists\\` — the service is deployable: its build, its entry point.\n - \\`api.exposes\\` — the route handler serving that operationId, not the spec that\n declares it — unless a contract-test report already answered it in step 2.\n - \\`event.declares\\` — the code that puts the message on the wire (sends) or\n handles it off the wire (receives), not the contract that names it.\n - \\`c4.calls\\` — the call site in the CALLER.\n4. Write those answers as JSON — evidence is \\`file:line\\`, one entry per place it can\n be seen:\n \\`\\`\\`json\n { \"answers\": [\n { \"id\": \"<claim id>\", \"verdict\": \"confirmed\", \"evidence\": [\"src/split/Api.ts:42\"] },\n { \"id\": \"<claim id>\", \"verdict\": \"unconfirmed\", \"note\": \"the split service has no entry point yet\" }\n ] }\n \\`\\`\\`\n5. Optional, before recording: cross-examine. Have a SECOND agent answer the\n same checklist blind — a fresh context, given only \\`loam verify $1 --json\\`\n and the code, never your answers file — then compare the two sets:\n\n \\`\\`\\`sh\n loam verify $1 --diff-answers yours.json theirs.json --json\n \\`\\`\\`\n\n Both files must answer EVERY claim in the compared scope,\n \\`scenario.tested\\` included: the diff consults no test report, so this is a\n DIFFERENT file from the one step 6 records under \\`--results\\` (that one\n must omit the runner's claims; a step-6-shaped file here refuses\n \\`answers-mismatch\\`). Narrowing with \\`--service <id>\\` compares one\n service's claims — give the second agent the same lens, or its file will\n not match the compared scope. An archived feature refuses the diff too:\n frozen history has no current checklist. It writes nothing and changes no\n verdict: \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` rows are the\n cheap-to-skip queue, \\`disagreements[]\\` (\\`cross.disagree\\`) is what a human\n reads first, and the \\`cross.evidence-disjoint\\` notice flags agreements\n citing non-overlapping files — read those second. Agreement between two\n same-model agents is correlated, so it ranks review; it never upgrades\n \\`attested\\` to \\`verified\\`. Record only the answer set you stand behind, in\n a separate run.\n6. Record, **in each affected service's own repository**, one service at a time:\n\n \\`\\`\\`sh\n loam verify $1 --service <id> --results report.json [--contract-results contract.json] --record answers.json --json\n \\`\\`\\`\n\n \\`--service <id>\\` narrows the checklist to that service's claims and binds the\n answers to this repository's commit, writing ONE attestation into a shared\n \\`schema: 2\\` \\`verification.yaml\\`. Running it in the next repo adds that repo's\n attestation and leaves the others standing. In a repo whose ./loam.json\n declares a \\`service\\`, \\`--service\\` may be omitted — it is substituted, and the\n run still federates. \\`--results report.json\\` alone is legal when the\n checklist is all scenarios.\n\n ⚠ **Never run \\`loam verify $1 --record answers.json\\` without \\`--service\\`.**\n That is the legacy all-at-once form: it writes the WHOLE record from one place,\n and over a record that already carries other services' attestations it is\n refused (\\`record-federated\\`, naming them) precisely because it would erase\n them. If you meet that refusal, re-run with \\`--service\\`; do not work around it.\n\n The other refusals: \\`answers-mismatch\\` (an id\n nobody asked about, a claim you left out — re-run step 1, the feature moved —\n or an answers entry for a \\`scenario.tested\\` claim: the runner owns those),\n \\`answers-unevidenced\\` (a \\`confirmed\\` with nothing behind it),\n \\`answers-unreadable\\` (a report or answers file it cannot read),\n \\`record-unreadable\\` (a \\`verification.yaml\\` that exists but cannot be read as a\n record — it is never overwritten; a human repairs it, and\n \\`verify.record-miscounted\\` is the reason when its \\`summary\\` block contradicts\n its own \\`claims[]\\`: neither half can be believed, so re-record rather than\n editing the counts),\n \\`service-mismatch\\` (\\`--service\\` names a service this repo does not declare) and\n \\`repository-unavailable\\` (\\`--service\\` from a repo that declares none, or whose\n commit cannot be read — an attestation must be bound to the code it is about).\n7. Report every \\`unconfirmed\\` claim and what it would take to close it, and read\n \\`discarded\\` in the payload: it names every previous answer the new record does\n not carry, and each one is either evidence that went stale or evidence that was\n just dropped. A scenario claim the report failed or missed closes by fixing the\n code and re-running the suite — never by editing the generated .feature. While\n any claim stays unconfirmed or unanswered, \\`notices[]\\` carries\n \\`verify.claims-open\\` (warn, gating nothing): the one-line \"not a clean\n result\" summary with all four counts, so a partial record can never read as a\n finished one from the notice lines alone.\n8. Reading is safe from anywhere: \\`loam verify $1 --service <id> --json\\` without\n \\`--record\\` writes nothing and works from the docs repo too. \\`services\\` lists\n every subject of the checklist, so it is how you find out which repos still\n owe an attestation, and \\`loam status $1 --json\\` says the same thing in the\n shape of a next step.\n\nAnything you cannot show is \\`unconfirmed\\` with a note. That is a successful record,\nand it is the useful one: nothing gates on this file, so the only thing it is for is\ntelling the next reader the truth. \\`loam archive\\` will ship the feature either way —\nwhich is exactly why a \\`confirmed\\` you cannot back up costs nothing now and misleads\neveryone later.\n\n\\`loam verify\\` — the notice and record codes its reports carry in \\`notices[]\\`,\ngathered from the steps above; branch on the code, never the prose:\n\n| code | what it means | what to do |\n|---|---|---|\n| \\`verify.claims-open\\` (warn) | a recorded feature still has unconfirmed or unanswered claims — the one-line \"not a clean result\" summary with all four counts, so a partial record can never read as finished from the notice lines alone; a feature with no record at all never carries it | close each open claim: fix the code and re-run the suite for a failed scenario, re-answer the rest with evidence, then re-record |\n| \\`verify.scenario-attested\\` (warn) | scenario claims were answered on an agent's word (\\`answered_by: agent\\`), so the verdict is **attested**, not verified — nothing gates on it, but say so when you hand back | run the generated suite with a JSON report and re-record with \\`--results report.json\\` the moment a suite exists |\n| \\`verify.digest-contested\\` (warn) | two or more services word a scenario identically, so they share one \\`@loam-digest-…\\` tag and no single cucumber report can say whose suite ran it — those scenario claims stay unconfirmed | each service records its own claims from its own repository with \\`--service <id>\\` |\n| \\`verify.operation-contested\\` (warn) | two services on the checklist expose the same operationId, so a contract report — which names no service — cannot say whose suite exercised it; those claims stay unconfirmed | each service records its own with \\`--service <id>\\`, passing its own contract report |\n| \\`verify.checklist-forked\\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \\`checklist\\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale | re-record the services on the older version from their own repositories (\\`loam verify <FEAT> --service <id> --record\\`), or accept the split knowingly — nothing gates on it |\n| \\`verify.record-miscounted\\` (error) | a verification.yaml whose \\`summary\\` block contradicts its own \\`claims[]\\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | re-record from the service's repo; never edit the counts by hand |\n| \\`verify.evidence-token-missing\\` (warn) | a just-confirmed citation's file at the attested commit does not contain the claim's own token (the operationId, message name or edge op) — the evidence may not show what the answer says | re-read that evidence while the answer can still be corrected; verdict-neutral, it gates nothing |\n| \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` (ok) | a \\`--diff-answers\\` row where both blind answer sets reach the same verdict — the cheap-to-skip review queue, though same-model agreement is correlated and proves nothing | nothing on its own; record only the answer set you stand behind, in a separate run |\n| \\`cross.disagree\\` (the rows a human reads first) | a \\`--diff-answers\\` row where the two blind answer sets contradict each other — one of them is wrong about the code | read both cited evidences and decide; the diff writes nothing and never moves a verdict |\n| \\`cross.evidence-disjoint\\` (warn) | a \\`--diff-answers\\` notice: claims BOTH sets confirm while citing non-overlapping files — two yeses that looked at different evidence | read those agreements second, after the disagreements; agreement over disjoint evidence is two guesses, not one proof |\n`,\n};\n\nexport const LOAM_SHIP: CommandContent = {\n name: \"loam-ship\",\n description: \"Archive a finished loam feature — merge its deltas into the living docs\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"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.\",\n invocation: \"loam instructions loam-ship $1\",\n placeholders: [\"feature\"],\n spine: [\n \"confirm the code is actually built and merged — archiving early makes the docs claim something that does not exist\",\n \"`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first\",\n \"`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything\",\n \"`loam archive $1` for real, and read what it reports it did\",\n \"`loam unarchive $1` is the undo if the plan turns out to have been wrong\",\n ],\n body: `Archive a shipped feature.\n\n1. Confirm the code is actually built and merged. Archiving folds the delta into the\n living docs; doing it early makes the docs claim something that does not exist.\n \\`loam status $1 --json\\` answers \"is this actually finished\" in one call, and its\n \\`next[]\\` names what is outstanding if it is not.\n2. \\`loam validate --feature $1 --json\\` — must come back \\`valid: true\\`.\n If it reports \\`delta.baseline-missing\\` or \\`openapi.baseline-missing\\`, run\n \\`loam rebase $1\\` and re-validate BEFORE going on: those two say nothing has\n pinned what this delta was written against, so the merge cannot tell the text\n you EDITED from the text you merely restated. Both gate the archive — the\n dry run in step 3 refuses on them too — and the only way past without the\n pins is a human's \\`--approve\\`.\n3. \\`loam archive $1 --dry-run --json\\` first. \\`plan[]\\` is every file the merge would\n write (\\`create\\` / \\`update\\`, then the final \\`move\\` into \\`features/archive/\\`), and\n none of them are written — read it before letting the merge touch the source of\n truth, and stop if a file you did not expect is on it. \\`warnings[]\\` is what the\n merge will do that is legal but lossy — advisory warnings never block, which is\n exactly why you read them now: \\`openapi.op-modified\\` means an operation the living\n contract already defines gets overwritten wholesale (\\`openapi.component-modified\\`\n is the same story for a component the merged operations carry),\n \\`service.no-model\\` means a service arrives with no C4 centre behind it, and\n \\`delta.added-conflict\\`\n means another feature in flight adds the same requirement — the first to archive\n lands it, and the other's archive is then refused (\\`delta.added-duplicate\\`: its\n ADDED now collides with the living spec) unless a human \\`--approve\\`s the\n replacement.\n4. \\`loam archive $1 --json\\`. It merges three axes into the living state — requirements\n into \\`services/<svc>/spec.md\\`, endpoints into \\`services/<svc>/openapi.yaml\\`,\n elements and edges into \\`architecture/landscape.likec4\\` and, for an addition nested\n under a service whose model EXTENDS the map, into that\n \\`services/<…>/<svc>/model.likec4\\` instead — then moves the feature\n under \\`features/archive/\\`. Success is \\`ok: true\\`; on \\`ok: false\\`, branch on\n \\`error.code\\`:\n - \\`not-coherent\\` — gating coherence issues; \\`issues[]\\` in the envelope lists them,\n each with \\`gates\\` and \\`overridable\\` resolved (advisory warnings do not block).\n Fix the breaches. \\`--approve\\` overrides the gating issues and is a human's\n call to make, not yours: report the breach and stop. Two codes it does not\n override even then — \\`delta.service-id-invalid\\` (a \\`specs/<svc>/\\` name the\n id grammar refuses) and \\`c4.service-binding-invalid\\` (a \\`metadata { service }\\`\n binding it refuses, anywhere inside a tagged element's block): each names a\n path the merge would write mechanically, they arrive with\n \\`overridable: false\\`, and the fix is a rename, never the flag.\n \\`openapi.remove-marker-path-level\\` arrives\n here too: an \\`x-loam-remove\\` written beside the methods instead of inside the\n operation retires nothing.\n - \\`living-outside-requirements\\` — the LIVING spec holds a requirement outside\n \\`## Requirements\\`, and the merge would duplicate it. Re-home it, then re-run.\n \\`--approve\\` does not move this one.\n - \\`archive-exists\\` — \\`features/archive/\\` already has that directory; a human\n decides what it is.\n - \\`merge-failed\\` — the merge could not be computed, or failed and was rolled\n back. Either way the living docs are unchanged; fix the reported cause, re-run.\n A file loam cannot decode as UTF-8 refuses here by name rather than being\n rewritten with replacement characters. So does a living document that still\n holds git conflict markers — the \\`spec.merge-conflict\\` /\n \\`landscape.merge-conflict\\` findings ride in \\`findings[]\\`, and \\`--approve\\`\n does not override them: the loss is mechanical, not a judgment call.\n - \\`docs-busy\\` — another \\`loam archive\\` or \\`loam unarchive\\` holds the docs\n repo's lock. Nothing was read or written; wait for it and re-run once.\n - \\`commit-interrupted\\` — a previous archive or unarchive was killed mid-commit\n and this run cannot repair it on its own: a half-written file was edited\n since, its pre-image is gone or altered, or \\`.loam-commit\\` cannot be read.\n Run \\`loam doctor\\` (\\`doctor.commit-interrupted\\` / \\`doctor.commit-unreadable\\`\n name the same state) and hand it to a human — re-running does not help.\n - \\`rollback-incomplete\\` — the merge failed AND some files could not be restored.\n Stop and hand it to a human; the message lists the files to check.\n\nArchived by mistake? \\`loam unarchive $1\\` restores the living docs from the snapshot\narchive left in \\`features/archive/$1*/.loam-before/\\` and re-opens the feature. Do not\nhand-edit the living docs back instead: the previous text of a \\`MODIFIED\\` requirement\nis in that snapshot and nowhere else, so anything you write by hand is a guess. It\nrefuses under \\`snapshot-corrupt\\` when a pre-image's bytes no longer match the digest\nthe archive recorded — \\`--force\\` does not override that one, because the damage is\nto the undo itself — and under \\`snapshot-missing\\` for an archive older than the\ncurrent snapshot format. Both mean version control is the way back.\n`,\n};\n"]}
1
+ {"version":3,"file":"closing.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/closing.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAmB;IACzC,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,qFAAqF;IACvF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,6NAA6N;IAC/N,UAAU,EAAE,kCAAkC;IAC9C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,yEAAyE;QACzE,uGAAuG;QACvG,sFAAsF;QACtF,2EAA2E;QAC3E,6FAA6F;KAC9F;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+JP;CACA,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,yEAAyE;IACtF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,0MAA0M;IAC5M,UAAU,EAAE,gCAAgC;IAC5C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,oHAAoH;QACpH,sHAAsH;QACtH,uGAAuG;QACvG,6DAA6D;QAC7D,uGAAuG;QACvG,0EAA0E;KAC3E;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiGP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/**\n * The two post-build workflows, paired: the done-check (`loam-verify`) and the\n * merge it precedes (`loam-ship`). One file for two commands because six\n * workflows meet the five-file package cap somewhere, and this is the seam the\n * cycle already has — both describe what happens after the code is built, and\n * each body names the other's refusals (`record-federated`, `not-coherent`).\n */\nexport const LOAM_VERIFY: CommandContent = {\n name: \"loam-verify\",\n description:\n \"Verify a built loam feature against its own promises, claim by claim, with evidence\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"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.\",\n invocation: \"loam instructions loam-verify $1\",\n placeholders: [\"feature\"],\n spine: [\n \"`loam verify` — the checklist, derived from the feature's own artifacts\",\n \"run the generated suite with a JSON report: the scenario claims are the runner's to answer, not yours\",\n \"answer the remaining claims yourself, every one carrying a `file:line` that resolves\",\n \"record from each affected service's own repository, bound to that service\",\n \"read the verdict honestly — a scenario confirmed on your word is `attested`, not `verified`\",\n ],\n body: `Check that the code somebody just built is the feature that was designed.\n\nloam derives the questions; the test runner and you answer them. loam never\nreads the service, so a verdict is worth exactly what its evidence is worth.\n\n1. \\`loam verify $1 --json\\`. \\`claims[]\\` is the checklist, derived from the feature's\n own artifacts — one claim per new service, per operation its openapi delta adds,\n per (direction, message) its asyncapi delta newly declares against the living\n contract's send/receive sets (quotes and removals are never questions),\n per tagged edge that names an operation, and per scenario of every changed\n requirement, arch.spec.md deltas included. Each has a stable \\`id\\` and a\n \\`subject\\` (the service whose code answers it); a claim that says arch.spec.md\n wants an integration/ops test, not an acceptance test. An ARCHIVED feature returns its record as frozen history instead\n (\\`frozen: true\\`) and \\`--record\\` / \\`--results\\` / \\`--contract-results\\` /\n \\`--diff-answers\\` refuse it — \\`loam unarchive\\` first if the answers really\n must change.\n2. The \\`scenario.tested\\` claims are the runner's to answer, not yours. In the\n service's repo, run the generated suite with a JSON report —\n \\`npx cucumber-js --format json:report.json\\` (cucumber-jvm, behave,\n SpecFlow/Reqnroll, pytest-bdd \\`--cucumberjson\\` and godog emit the same\n format). The generated scenarios carry \\`@loam-digest-…\\` tags that ride into\n the report; that digest is the match, and the run must have executed every\n step of the stamped scenario, so never retitle or hand-edit a generated\n scenario to make one pass. A runner with no cucumber formatter answers\n through loam's runner-neutral shape instead, keyed by the same digests:\n \\`{\"loamScenarioReport\": 1, \"results\": [{\"digest\": \"…\", \"status\": \"passed\"}]}\\`.\n If there is no runnable suite yet you MAY answer them yourself, and the record\n will say so: \\`answered_by: agent\\` makes the feature **attested**, not verified\n (\\`verified: false\\`, \\`verdict: \"attested\"\\`, the \\`verify.scenario-attested\\`\n notice, and a \\`next.verify-attested\\` step in \\`loam status\\`). Nothing gates on\n it — a legacy service with no suite must still be able to ship — but say so\n when you hand back, and close it with \\`--results\\` the moment a suite runs.\n \\`verify.digest-contested\\` (warn) is the case where two services word a\n scenario identically: they share one digest, so no single report can say whose\n suite ran it, and each service must record its own with \\`--service\\`.\n The \\`api.exposes\\` claims have a mechanical channel too, when an API\n contract-test suite exists: emit loam's contract-results shape from its\n report — \\`{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"…\",\n \"status\": \"passed\"}]}\\`, a one-line transform for Specmatic, Pact, Dredd or a\n property harness — and pass \\`--contract-results contract.json\\` alongside.\n Only a status of exactly \\`passed\\` confirms the named operation\n (\\`answered_by: external-runner\\`); anything else, and an operation the report\n never exercised, stays \\`unconfirmed\\` with the reason, and under the flag the\n report owns every api.exposes claim, so take those out of the answers file.\n3. Answer every OTHER claim by finding it in the code. Not by reasoning that it\n must be there — by opening the file:\n - \\`service.exists\\` — the service is deployable: its build, its entry point.\n - \\`api.exposes\\` — the route handler serving that operationId, not the spec that\n declares it — unless a contract-test report already answered it in step 2.\n - \\`event.declares\\` — the code that puts the message on the wire (sends) or\n handles it off the wire (receives), not the contract that names it.\n - \\`c4.calls\\` — the call site in the CALLER.\n4. Write those answers as JSON — evidence is \\`file:line\\`, one entry per place it can\n be seen:\n \\`\\`\\`json\n { \"answers\": [\n { \"id\": \"<claim id>\", \"verdict\": \"confirmed\", \"evidence\": [\"src/split/Api.ts:42\"] },\n { \"id\": \"<claim id>\", \"verdict\": \"unconfirmed\", \"note\": \"the split service has no entry point yet\" }\n ] }\n \\`\\`\\`\n5. Optional, before recording: cross-examine. Have a SECOND agent answer the\n same checklist blind — a fresh context, given only \\`loam verify $1 --json\\`\n and the code, never your answers file — then compare the two sets:\n\n \\`\\`\\`sh\n loam verify $1 --diff-answers yours.json theirs.json --json\n \\`\\`\\`\n\n Both files must answer EVERY claim in the compared scope,\n \\`scenario.tested\\` included: the diff consults no test report, so this is a\n DIFFERENT file from the one step 6 records under \\`--results\\` (that one\n must omit the runner's claims; a step-6-shaped file here refuses\n \\`answers-mismatch\\`). Narrowing with \\`--service <id>\\` compares one\n service's claims — give the second agent the same lens, or its file will\n not match the compared scope. An archived feature refuses the diff too:\n frozen history has no current checklist. It writes nothing and changes no\n verdict: \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` rows are the\n cheap-to-skip queue, \\`disagreements[]\\` (\\`cross.disagree\\`) is what a human\n reads first, and the \\`cross.evidence-disjoint\\` notice flags agreements\n citing non-overlapping files — read those second. Agreement between two\n same-model agents is correlated, so it ranks review; it never upgrades\n \\`attested\\` to \\`verified\\`. Record only the answer set you stand behind, in\n a separate run.\n6. Record, **in each affected service's own repository**, one service at a time:\n\n \\`\\`\\`sh\n loam verify $1 --service <id> --results report.json [--contract-results contract.json] --record answers.json --json\n \\`\\`\\`\n\n \\`--service <id>\\` narrows the checklist to that service's claims and binds the\n answers to this repository's commit, writing that service's attestation to\n its OWN file, \\`features/$1/verification/<id>.yaml\\` — the directory is the\n record, and loam adds the files up when it reads. Running it in the next repo\n writes that repo's file and touches no other, so two teams' docs PRs never\n conflict over the record. In a repo whose ./loam.json\n declares a \\`service\\`, \\`--service\\` may be omitted — it is substituted, and the\n run still federates. \\`--results report.json\\` alone is legal when the\n checklist is all scenarios.\n\n ⚠ **Never run \\`loam verify $1 --record answers.json\\` without \\`--service\\`.**\n That is the legacy all-at-once form: it writes the WHOLE record from one place,\n into the one file \\`verification.yaml\\`, and over a record that already carries\n other services' attestations it is refused (\\`record-federated\\`, naming them)\n precisely because it would put one unattested answer set over them. If you\n meet that refusal, re-run with \\`--service\\`; do not work around it.\n\n The other refusals: \\`answers-mismatch\\` (an id\n nobody asked about, a claim you left out — re-run step 1, the feature moved —\n or an answers entry for a \\`scenario.tested\\` claim: the runner owns those),\n \\`answers-unevidenced\\` (a \\`confirmed\\` with nothing behind it),\n \\`answers-unreadable\\` (a report or answers file it cannot read),\n \\`record-unreadable\\` (a record file that exists but cannot be read — broken\n YAML, git conflict markers a merge left, or \\`verify.record-miscounted\\` when\n its \\`summary\\` block contradicts its own \\`claims[]\\`. It is never overwritten,\n and the message names the file and the safe repair: a service's own\n \\`verification/<id>.yaml\\` holds only that service's word, so that service\n may delete it and record again; the one-file \\`verification.yaml\\` may hold\n other services' attestations, so it is repaired or restored from version\n control — after a merge, the copy on the branch you merged into — and\n **never deleted**. Never edit the counts to make them agree),\n \\`service-mismatch\\` (\\`--service\\` names a service this repo does not declare) and\n \\`repository-unavailable\\` (\\`--service\\` from a repo that declares none, or whose\n commit cannot be read — an attestation must be bound to the code it is about).\n7. Report every \\`unconfirmed\\` claim and what it would take to close it, and read\n \\`discarded\\` in the payload: it names every previous answer the new record does\n not carry, and each one is either evidence that went stale or evidence that was\n just dropped. A scenario claim the report failed or missed closes by fixing the\n code and re-running the suite — never by editing the generated .feature. While\n any claim stays unconfirmed or unanswered, \\`notices[]\\` carries\n \\`verify.claims-open\\` (warn, gating nothing): the one-line \"not a clean\n result\" summary with all four counts, so a partial record can never read as a\n finished one from the notice lines alone.\n8. Reading is safe from anywhere: \\`loam verify $1 --service <id> --json\\` without\n \\`--record\\` writes nothing and works from the docs repo too. \\`services\\` lists\n every subject of the checklist, so it is how you find out which repos still\n owe an attestation, and \\`loam status $1 --json\\` says the same thing in the\n shape of a next step.\n\nAnything you cannot show is \\`unconfirmed\\` with a note. That is a successful record,\nand it is the useful one: nothing gates on this file, so the only thing it is for is\ntelling the next reader the truth. \\`loam archive\\` will ship the feature either way —\nwhich is exactly why a \\`confirmed\\` you cannot back up costs nothing now and misleads\neveryone later.\n\n\\`loam verify\\` — the notice and record codes its reports carry in \\`notices[]\\`,\ngathered from the steps above; branch on the code, never the prose:\n\n| code | what it means | what to do |\n|---|---|---|\n| \\`verify.claims-open\\` (warn) | a recorded feature still has unconfirmed or unanswered claims — the one-line \"not a clean result\" summary with all four counts, so a partial record can never read as finished from the notice lines alone; a feature with no record at all never carries it | close each open claim: fix the code and re-run the suite for a failed scenario, re-answer the rest with evidence, then re-record |\n| \\`verify.scenario-attested\\` (warn) | scenario claims were answered on an agent's word (\\`answered_by: agent\\`), so the verdict is **attested**, not verified — nothing gates on it, but say so when you hand back | run the generated suite with a JSON report and re-record with \\`--results report.json\\` the moment a suite exists |\n| \\`verify.digest-contested\\` (warn) | two or more services word a scenario identically, so they share one \\`@loam-digest-…\\` tag and no single cucumber report can say whose suite ran it — those scenario claims stay unconfirmed | each service records its own claims from its own repository with \\`--service <id>\\` |\n| \\`verify.operation-contested\\` (warn) | two services on the checklist expose the same operationId, so a contract report — which names no service — cannot say whose suite exercised it; those claims stay unconfirmed | each service records its own with \\`--service <id>\\`, passing its own contract report |\n| \\`verify.checklist-forked\\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \\`checklist\\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale. In a per-service record the answers of the services named do not count until they record again | re-record the services on the older version from their own repositories (\\`loam verify <FEAT> --service <id> --record\\`) — nothing gates on it |\n| \\`verify.record-miscounted\\` (error) | a record file whose \\`summary\\` block contradicts its own \\`claims[]\\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | a service's own \\`verification/<id>.yaml\\`: delete it and re-record from that service's repo; the one-file \\`verification.yaml\\`: restore it from version control, then re-record — never delete it, and never edit the counts by hand |\n| \\`verify.evidence-token-missing\\` (warn) | a just-confirmed citation's file at the attested commit does not contain the claim's own token (the operationId, message name or edge op) — the evidence may not show what the answer says | re-read that evidence while the answer can still be corrected; verdict-neutral, it gates nothing |\n| \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` (ok) | a \\`--diff-answers\\` row where both blind answer sets reach the same verdict — the cheap-to-skip review queue, though same-model agreement is correlated and proves nothing | nothing on its own; record only the answer set you stand behind, in a separate run |\n| \\`cross.disagree\\` (the rows a human reads first) | a \\`--diff-answers\\` row where the two blind answer sets contradict each other — one of them is wrong about the code | read both cited evidences and decide; the diff writes nothing and never moves a verdict |\n| \\`cross.evidence-disjoint\\` (warn) | a \\`--diff-answers\\` notice: claims BOTH sets confirm while citing non-overlapping files — two yeses that looked at different evidence | read those agreements second, after the disagreements; agreement over disjoint evidence is two guesses, not one proof |\n`,\n};\n\nexport const LOAM_SHIP: CommandContent = {\n name: \"loam-ship\",\n description: \"Archive a finished loam feature — merge its deltas into the living docs\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"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.\",\n invocation: \"loam instructions loam-ship $1\",\n placeholders: [\"feature\"],\n spine: [\n \"confirm the code is actually built and merged — archiving early makes the docs claim something that does not exist\",\n \"`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first\",\n \"`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything\",\n \"`loam archive $1` for real, and read what it reports it did\",\n \"every vouched spec on `revouchOwed[]` owes its owner's re-vouch — tell them; it lands in this same PR\",\n \"`loam unarchive $1` is the undo if the plan turns out to have been wrong\",\n ],\n body: `Archive a shipped feature.\n\n1. Confirm the code is actually built and merged. Archiving folds the delta into the\n living docs; doing it early makes the docs claim something that does not exist.\n \\`loam status $1 --json\\` answers \"is this actually finished\" in one call, and its\n \\`next[]\\` names what is outstanding if it is not.\n2. \\`loam validate --feature $1 --json\\` — must come back \\`valid: true\\`.\n If it reports \\`delta.baseline-missing\\` or \\`openapi.baseline-missing\\`, run\n \\`loam rebase $1\\` and re-validate BEFORE going on: those two say nothing has\n pinned what this delta was written against, so the merge cannot tell the text\n you EDITED from the text you merely restated. Both gate the archive — the\n dry run in step 3 refuses on them too — and the only way past without the\n pins is a human's \\`--approve\\`.\n3. \\`loam archive $1 --dry-run --json\\` first. \\`plan[]\\` is every file the merge would\n write (\\`create\\` / \\`update\\`, then the final \\`move\\` into \\`features/archive/\\`), and\n none of them are written — read it before letting the merge touch the source of\n truth, and stop if a file you did not expect is on it. \\`warnings[]\\` is what the\n merge will do that is legal but lossy — advisory warnings never block, which is\n exactly why you read them now: \\`openapi.op-modified\\` means an operation the living\n contract already defines gets overwritten wholesale, and its \\`details\\` name the\n current consumers that overwrite lands on (\\`asyncapi.message-modified\\` likewise for\n a message; \\`openapi.component-modified\\` is the same story for a component the\n merged operations carry, with no consumer list),\n \\`service.no-model\\` means a service arrives with no C4 centre behind it, and\n \\`delta.added-conflict\\`\n means another feature in flight adds the same requirement — the first to archive\n lands it, and the other's archive is then refused (\\`delta.added-duplicate\\`: its\n ADDED now collides with the living spec) unless a human \\`--approve\\`s the\n replacement.\n4. \\`loam archive $1 --json\\`. It merges three axes into the living state — requirements\n into \\`services/<svc>/spec.md\\`, endpoints into \\`services/<svc>/openapi.yaml\\`,\n elements and edges into \\`architecture/landscape.likec4\\` and, for an addition nested\n under a service whose model EXTENDS the map, into that\n \\`services/<…>/<svc>/model.likec4\\` instead — then moves the feature\n under \\`features/archive/\\`, whole: its verification record travels with it,\n the per-service \\`verification/<id>.yaml\\` files and any one-file\n \\`verification.yaml\\` alike, and \\`loam verify $1\\` reads it back afterwards as\n frozen history. Verification never gates the archive — an open or missing\n record ships as exactly that. Success is \\`ok: true\\`; on \\`ok: false\\`, branch on\n \\`error.code\\`:\n - \\`not-coherent\\` — gating coherence issues; \\`issues[]\\` in the envelope lists them,\n each with \\`gates\\` and \\`overridable\\` resolved (advisory warnings do not block).\n Fix the breaches. \\`--approve\\` overrides the gating issues and is a human's\n call to make, not yours: report the breach and stop. Two codes it does not\n override even then — \\`delta.service-id-invalid\\` (a \\`specs/<svc>/\\` name the\n id grammar refuses) and \\`c4.service-binding-invalid\\` (a \\`metadata { service }\\`\n binding it refuses, anywhere inside a tagged element's block): each names a\n path the merge would write mechanically, they arrive with\n \\`overridable: false\\`, and the fix is a rename, never the flag.\n \\`openapi.remove-marker-path-level\\` arrives\n here too: an \\`x-loam-remove\\` written beside the methods instead of inside the\n operation retires nothing. So do \\`openapi.op-revived\\` and\n \\`asyncapi.slot-revived\\`: the delta spells an operation, slot or message an\n ARCHIVED feature retired, and the merge would put it back. If the delta only\n quoted it, delete the quote; whether the feature brings it back on purpose is\n the human's \\`--approve\\`, and the archive output then names what came back\n and whose retirement it reverses.\n - \\`living-outside-requirements\\` — the LIVING spec holds a requirement outside\n \\`## Requirements\\`, and the merge would duplicate it. Re-home it, then re-run.\n \\`--approve\\` does not move this one.\n - \\`archive-exists\\` — \\`features/archive/\\` already has that directory; a human\n decides what it is.\n - \\`merge-failed\\` — the merge could not be computed, or failed and was rolled\n back. Either way the living docs are unchanged; fix the reported cause, re-run.\n A file loam cannot decode as UTF-8 refuses here by name rather than being\n rewritten with replacement characters. So does a living document that still\n holds git conflict markers — the \\`spec.merge-conflict\\` /\n \\`landscape.merge-conflict\\` findings ride in \\`findings[]\\`, and \\`--approve\\`\n does not override them: the loss is mechanical, not a judgment call.\n - \\`docs-busy\\` — another \\`loam archive\\` or \\`loam unarchive\\` holds the docs\n repo's lock. Nothing was read or written; wait for it and re-run once.\n - \\`commit-interrupted\\` — a previous archive or unarchive was killed mid-commit\n and this run cannot repair it on its own: a half-written file was edited\n since, its pre-image is gone or altered, or \\`.loam-commit\\` cannot be read.\n Run \\`loam doctor\\` (\\`doctor.commit-interrupted\\` / \\`doctor.commit-unreadable\\`\n name the same state) and hand it to a human — re-running does not help.\n - \\`rollback-incomplete\\` — the merge failed AND some files could not be restored.\n Stop and hand it to a human; the message lists the files to check.\n5. Re-vouches are owed. \\`revouchOwed[]\\` — on the dry run and the real merge alike —\n names every VOUCHED spec the merge moves under its vouch stamp, with the\n document's \\`owner\\`. \\`content.stale\\` follows on each, and honestly: the merged\n text is words nobody has yet read against the code. You cannot clear it — a\n vouch is a person's, run in that service's own repo (\\`loam vouch --service <id>\\`,\n after \\`loam vouch --pack --service <id>\\` for what to re-read). Name the\n documents and owners in your hand-back, and say the re-vouches belong in the\n SAME PR as this archive: a docs repo gated on\n \\`loam validate --all --base <ref> --strict\\` keeps the archive's PR red until they\n land. Never edit a stamp by hand to turn it green.\n\nArchived by mistake? \\`loam unarchive $1\\` restores the living docs from the snapshot\narchive left in \\`features/archive/$1*/.loam-before/\\` and re-opens the feature. Do not\nhand-edit the living docs back instead: the previous text of a \\`MODIFIED\\` requirement\nis in that snapshot and nowhere else, so anything you write by hand is a guess. It\nrefuses under \\`snapshot-corrupt\\` when a pre-image's bytes no longer match the digest\nthe archive recorded — \\`--force\\` does not override that one, because the damage is\nto the undo itself — and under \\`snapshot-missing\\` for an archive older than the\ncurrent snapshot format. Both mean version control is the way back.\n`,\n};\n"]}
@@ -8,16 +8,41 @@ export const LOAM_FEATURE = {
8
8
  // $2 is the feature's human title: any string is a legal one.
9
9
  placeholders: ["feature", "free"],
10
10
  spine: [
11
+ "decide whether this is a feature at all — a change that touches no requirement, operation or edge needs none",
11
12
  "read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on",
12
13
  "`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground",
13
14
  "`loam status` / `loam list` / `loam show` — what exists and what is already owed",
14
15
  "`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces",
15
16
  "author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi",
16
- "`loam rebase` — pin every MODIFIED requirement and every operation to the living version you wrote against",
17
+ "analyze before you pin — write what the documents leave open under `## Open questions` in intent.md, for a human to answer",
18
+ "`loam rebase` — pin every MODIFIED requirement and every living operation you edited to the version you wrote against",
17
19
  "validate the feature, then read `loam dependencies` before anyone starts building",
18
20
  ],
21
+ // Step 0 and step 4b are the two this protocol was missing, and neither is
22
+ // graded — on purpose. Step 0 exists because the lightest lane loam has (no
23
+ // feature at all, for a change that moves no requirement, operation or edge)
24
+ // was true and written down nowhere, so the work that is most of a legacy
25
+ // fleet's week either walked the whole feature cycle or routed around loam.
26
+ // Step 4b exists because every check after it is a join between exact
27
+ // tokens: two services that agree on every operationId and disagree on what
28
+ // it means (one confirms a split the other answers 422 for) validate, archive
29
+ // and ship green, and a `Realizes:` whose requirement does not deliver the
30
+ // promise reads as a clean join. Nothing in the binary can read meaning, and
31
+ // a check pretending to would be the false green this tool exists to refuse,
32
+ // so the step asks for questions and leaves the answers to a person.
19
33
  body: `Start a new feature in the loam docs repo (its path is \`docsDir\` in ./loam.json).
20
34
 
35
+ 0. First decide whether this needs a feature at all. A change that touches no
36
+ requirement, no operation and no fleet edge — a refactor, a dependency bump,
37
+ a fix that brings the code back to behaviour the living spec already
38
+ documents — needs NONE: nothing in the docs repo changes, so there is nothing
39
+ to scaffold, pin or archive. It is a code PR in the service repo, then
40
+ \`loam validate --service <id>\` there (the service repo is the only place
41
+ \`sources.stale\` is visible), then \`loam vouch --pack --service <id>\` to
42
+ prepare the re-read the moved sources owe — the vouch itself is a human's.
43
+ If the change alters what a requirement promises, it IS a feature, however
44
+ small: one \`--touches\`, a Why, the ADDED/MODIFIED requirement, and
45
+ \`loam rebase\` for a MODIFIED one — the rest of this page still applies.
21
46
  1. Read \`AGENTS.md\` at the root of the docs repo first — it defines the ID spine
22
47
  everything here depends on.
23
48
  2. Understand the current state before proposing a change:
@@ -71,20 +96,63 @@ export const LOAM_FEATURE = {
71
96
  - \`specs/<svc>/arch.spec.md\` — the architectural obligations (outbox, retries,
72
97
  idempotency, alerts), same grammar; a \`Covers:\` line per requirement naming the
73
98
  tagged elements/edges it accounts for, or \`c4.uncovered\` says nothing does
74
- - \`specs/<svc>/openapi.yaml\` — define every operationId the edges reference
99
+ - \`specs/<svc>/openapi.yaml\` — define every operationId the edges reference.
100
+ A self-contained document holding ONLY the operations this feature adds,
101
+ changes or removes — never a copy of the living contract. An operation you
102
+ leave out stays exactly as the living contract has it, and a local \`$ref\`
103
+ (\`#/components/schemas/Order\`) resolves into the living document, so there
104
+ is nothing to restate. Every operation you quote costs a pin, and once a
105
+ parallel feature retires it — or moves it, and you quoted the old
106
+ address — a pinned quote is \`openapi.baseline-invalid\` and
107
+ \`openapi.op-revived\` both, an unpinned one \`openapi.op-revived\` alone
108
+ (\`asyncapi.slot-revived\` on the event axis) — archive refuses either.
109
+ Delete the quote; only a feature that MEANS to bring a retired operation
110
+ back keeps it, and archives past the finding with a human's \`--approve\`
75
111
  - \`specs/<svc>/asyncapi.yaml\` — the event axis, only when the feature changes
76
- what a service puts on or takes off the bus: a complete AsyncAPI 3.0
77
- document restating the living contract around the slots you change, new
78
- messages under \`components.messages\`, and a \`Publishes:\`/\`Consumes:\` line
79
- on each requirement that names them. \`loam new\` scaffolds none — an event
112
+ what a service puts on or takes off the bus: an AsyncAPI 3.0 document
113
+ holding only the channel, operation and message slots you add, change or
114
+ remove (an edited slot is written out whole), new messages under
115
+ \`components.messages\`, and a \`Publishes:\`/\`Consumes:\` line on each
116
+ requirement that names them. \`loam new\` scaffolds none — an event
80
117
  contract is genuinely optional
118
+ 4b. **Analyze before you pin.** Nothing grades this step, which is why it is here:
119
+ every check after it is a join between exact tokens, so two services that
120
+ agree on every operationId and disagree on what it means validate, archive
121
+ and ship green. Read the feature as its reviewer will, before rebase freezes it:
122
+ - \`loam show $1 --json\` — its \`review\` object joins the intent, the C4
123
+ objects, the API/event slices and the dependencies
124
+ - \`loam context <service> --feature $1 --json\` for every service it touches —
125
+ the living requirements verbatim, the edges one hop out, and this delta
126
+ - the \`neighbours\` step 2b's \`loam explore\` reported
127
+ Then write what the documents leave open under \`## Open questions\` in
128
+ \`intent.md\` (the scaffold already carries the heading), one line per question,
129
+ naming the requirement id or edge it is about:
130
+ - a SHALL two engineers could build two ways — an unstated unit, bound,
131
+ ordering, or an "eventually" with no window
132
+ - a value, limit or window two services state differently (a refund window,
133
+ a rounding rule, a retry budget), and which side refuses when they
134
+ disagree — a caller that accepts what its provider answers 4xx for is a
135
+ contradiction no check can see
136
+ - a \`Realizes:\` line whose requirement does not actually deliver the
137
+ capability promise it names, or a promise this feature changes that no
138
+ realizing requirement keeps
139
+ - per call edge in the delta: the timeout, the retry and what makes it safe
140
+ to repeat, what a partial failure leaves behind, and what compensates it —
141
+ an edge no requirement answers these for is a question, not a default
142
+ - each \`explore\` neighbour outside \`--touches\`: why it is untouched, or that
143
+ it is a candidate — adding one means a \`specs/<svc>/\` delta in this feature,
144
+ because that directory IS the feature's service list
145
+ Ask; do not answer for the author. A human answers by editing the file —
146
+ replacing a question with the decision, or with a change to the delta.
147
+ Nothing is graded: an empty section is legal, and so is an answered one.
81
148
  5. **\`loam rebase $1\`** — the step between authoring and checking, and the one that
82
149
  stops two features in flight from silently deleting each other's work. It pins
83
- every MODIFIED/REMOVED requirement (\`Based-On:\`), every operation in the
84
- contract delta and every asyncapi channel/operation/message slot
85
- (\`x-loam-based-on\`) to the living version you wrote against.
86
- Run it on the contract axes even if you changed exactly one endpoint: that is
87
- what marks the REST of the document as quotation the merge must not write back.
150
+ every MODIFIED/REMOVED requirement (\`Based-On:\`), and every operation and
151
+ asyncapi channel/operation/message slot the living contract already holds
152
+ (\`x-loam-based-on\`), to the living version you wrote against. What the
153
+ feature ADDS has no living version and takes no pin, so an add-only contract
154
+ delta needs no rebase. Any living operation or slot the delta spells is one
155
+ the merge upserts, so it needs its pin even when you changed one line of it.
88
156
  Without the pins, \`delta.baseline-missing\` / \`openapi.baseline-missing\` /
89
157
  \`asyncapi.baseline-missing\` GATE
90
158
  the archive: before they did, the second feature to archive reverted the
@@ -1 +1 @@
1
- {"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/feature.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,CAAC,MAAM,YAAY,GAAmB;IAC1C,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,yFAAyF;IAC3F,YAAY,EAAE,qBAAqB;IACnC,OAAO,EACL,uQAAuQ;IACzQ,UAAU,EAAE,wCAAwC;IACpD,8DAA8D;IAC9D,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IACjC,KAAK,EAAE;QACL,6FAA6F;QAC7F,qIAAqI;QACrI,kFAAkF;QAClF,sGAAsG;QACtG,0GAA0G;QAC1G,4GAA4G;QAC5G,mFAAmF;KACpF;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** Starting a feature: explore, scaffold, author the three axes, rebase, validate. */\nexport const LOAM_FEATURE: CommandContent = {\n name: \"loam-feature\",\n description:\n \"Start a new loam feature — scaffold it, then author the C4 delta and requirement deltas\",\n argumentHint: `<FEAT-id> \"<title>\"`,\n purpose:\n \"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.\",\n invocation: 'loam instructions loam-feature $1 \"$2\"',\n // $2 is the feature's human title: any string is a legal one.\n placeholders: [\"feature\", \"free\"],\n spine: [\n \"read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on\",\n \"`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground\",\n \"`loam status` / `loam list` / `loam show` — what exists and what is already owed\",\n \"`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces\",\n \"author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi\",\n \"`loam rebase` — pin every MODIFIED requirement and every operation to the living version you wrote against\",\n \"validate the feature, then read `loam dependencies` before anyone starts building\",\n ],\n body: `Start a new feature in the loam docs repo (its path is \\`docsDir\\` in ./loam.json).\n\n1. Read \\`AGENTS.md\\` at the root of the docs repo first — it defines the ID spine\n everything here depends on.\n2. Understand the current state before proposing a change:\n - \\`loam status --json\\` — what is already in flight and what it is waiting on.\n Run this FIRST if you joined this repository halfway or lost the session:\n \\`next[]\\` is ordered and each entry carries the literal command to run, so it\n tells you whether the work is to start $1 at all. \\`loam status $1 --json\\`\n narrows it to one feature once $1 exists\n - \\`loam list --json\\` — what services exist, and what documentation they are missing\n - \\`loam show <service> --json\\` — what a service owns, exposes, and who already calls it\n2b. \\`loam explore <service> --json --as $1\\` around every service you believe this\n feature touches. This is the step that decides step 3's \\`--touches\\` list, and it\n is the only decision in the cycle nothing downstream catches — a list short by one\n service produces a feature that validates, archives and ships with a consumer\n nobody updated. Read four fields:\n - \\`neighbours\\` — the services one hop out in the fleet map. Weigh each one. loam\n knows they are connected, not whether you change them, and it deliberately\n leaves them OUT of the suggested command line rather than guessing for you\n - \\`services[].maturity\\` — a service at \\`empty\\` or \\`partial\\` has no baseline to\n write a delta against, so the work starts with \\`loam adopt --service <id>\\`, not\n with this feature\n - \\`services[].operations\\` — what each already exposes, so an operation you are\n about to \"add\" that is already there becomes a MODIFIED requirement instead\n - \\`overlaps\\` — active features already carrying a delta for these services.\n \\`loam dependencies --json\\` (step 7) then says which must archive first\n \\`--op <operationId>\\` seeds from an operation when you know the call but not who\n owns it. A seed naming no \\`services/<id>/\\` is reported in \\`unknown\\` with the\n closest real ids rather than refused — the feature may be introducing it, and a\n typo looks identical until you read that list. \\`scaffold\\` is the literal step-3\n command line the seeds imply, with \\`--new-service\\` where the service does not\n exist yet.\n3. Scaffold it: \\`loam new $1 --title \"$2\"\\`, adding \\`--touches <id>\\` for every service the\n feature touches and \\`--new-service <id>\\` for every one it introduces. Every id goes\n through the \\`services/<id>/\\` grammar, so a typo is refused (\\`invalid-option\\`)\n rather than scaffolded; a \\`--touches\\` that matches nothing existing comes back as\n a \\`note\\` with the close ids, not as a failure — read it, because a \\`--touches\\`\n naming a service that does not exist is \\`delta.service-unknown\\` at validate time\n and a phantom \\`services/<typo>/\\` directory if it ever archives.\n4. Author what the templates left as TODO. This step is gated, not advisory:\n \\`loam archive\\` refuses a scaffold nobody edited (\\`scaffold.placeholder\\`,\n \\`intent.empty\\`), so the placeholder text cannot reach the living docs.\n - \\`intent.md\\` — the problem in business terms, in prose; the scaffold's\n comment-only Why/Scope count as unwritten\n - \\`delta.likec4\\` — new elements and edges, each tagged \\`#$1\\`; every call edge carries\n \\`metadata { op '<operationId>' }\\`; replace the scaffolded TODO description on each\n new service. For a requirements-only feature DELETE this\n file: an empty delta is legal, but a scaffold left half-filled is not\n - \\`specs/<svc>/spec.md\\` — the example requirement ships INSIDE the template's\n comment: copy it out, unindent, one behaviour per requirement, SHALL, at\n least one Given/When/Then scenario; add \\`Operations:\\` once the operation exists\n - \\`specs/<svc>/arch.spec.md\\` — the architectural obligations (outbox, retries,\n idempotency, alerts), same grammar; a \\`Covers:\\` line per requirement naming the\n tagged elements/edges it accounts for, or \\`c4.uncovered\\` says nothing does\n - \\`specs/<svc>/openapi.yaml\\` — define every operationId the edges reference\n - \\`specs/<svc>/asyncapi.yaml\\` — the event axis, only when the feature changes\n what a service puts on or takes off the bus: a complete AsyncAPI 3.0\n document restating the living contract around the slots you change, new\n messages under \\`components.messages\\`, and a \\`Publishes:\\`/\\`Consumes:\\` line\n on each requirement that names them. \\`loam new\\` scaffolds none — an event\n contract is genuinely optional\n5. **\\`loam rebase $1\\`** — the step between authoring and checking, and the one that\n stops two features in flight from silently deleting each other's work. It pins\n every MODIFIED/REMOVED requirement (\\`Based-On:\\`), every operation in the\n contract delta and every asyncapi channel/operation/message slot\n (\\`x-loam-based-on\\`) to the living version you wrote against.\n Run it on the contract axes even if you changed exactly one endpoint: that is\n what marks the REST of the document as quotation the merge must not write back.\n Without the pins, \\`delta.baseline-missing\\` / \\`openapi.baseline-missing\\` /\n \\`asyncapi.baseline-missing\\` GATE\n the archive: before they did, the second feature to archive reverted the\n first — with \\`+0 ~1 -0\\`, exit 0, and nobody told. Rebase clears them; a\n human's \\`--approve\\` is the only other way past.\n Restamping is not resolving: if rebase reports a requirement as MOVED, re-read\n the living text and fold in what you still mean BEFORE you ship.\n6. \\`loam validate --feature $1 --json\\`. Do not stop while \\`valid\\` is false.\n7. \\`loam dependencies $1 --json\\` before anyone starts building: it says which\n features in flight have to archive first (\\`order\\`) and which collisions no\n ordering fixes (\\`conflicts\\`, \\`cycles\\`). Every conflict it names is one \\`loam rebase\\`\n cannot fix for you — two intentions have to be merged by people.\n\nThe operation's three spellings — the edge's \\`op\\`, the requirement's \\`Operations:\\`\nline, and the OpenAPI \\`operationId\\` — must match exactly.\n`,\n};\n"]}
1
+ {"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/feature.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,CAAC,MAAM,YAAY,GAAmB;IAC1C,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,yFAAyF;IAC3F,YAAY,EAAE,qBAAqB;IACnC,OAAO,EACL,uQAAuQ;IACzQ,UAAU,EAAE,wCAAwC;IACpD,8DAA8D;IAC9D,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IACjC,KAAK,EAAE;QACL,8GAA8G;QAC9G,6FAA6F;QAC7F,qIAAqI;QACrI,kFAAkF;QAClF,sGAAsG;QACtG,0GAA0G;QAC1G,4HAA4H;QAC5H,uHAAuH;QACvH,mFAAmF;KACpF;IACD,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,4EAA4E;IAC5E,sEAAsE;IACtE,4EAA4E;IAC5E,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,qEAAqE;IACrE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0IP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** Starting a feature: explore, scaffold, author the three axes, rebase, validate. */\nexport const LOAM_FEATURE: CommandContent = {\n name: \"loam-feature\",\n description:\n \"Start a new loam feature — scaffold it, then author the C4 delta and requirement deltas\",\n argumentHint: `<FEAT-id> \"<title>\"`,\n purpose:\n \"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.\",\n invocation: 'loam instructions loam-feature $1 \"$2\"',\n // $2 is the feature's human title: any string is a legal one.\n placeholders: [\"feature\", \"free\"],\n spine: [\n \"decide whether this is a feature at all — a change that touches no requirement, operation or edge needs none\",\n \"read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on\",\n \"`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground\",\n \"`loam status` / `loam list` / `loam show` — what exists and what is already owed\",\n \"`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces\",\n \"author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi\",\n \"analyze before you pin — write what the documents leave open under `## Open questions` in intent.md, for a human to answer\",\n \"`loam rebase` — pin every MODIFIED requirement and every living operation you edited to the version you wrote against\",\n \"validate the feature, then read `loam dependencies` before anyone starts building\",\n ],\n // Step 0 and step 4b are the two this protocol was missing, and neither is\n // graded — on purpose. Step 0 exists because the lightest lane loam has (no\n // feature at all, for a change that moves no requirement, operation or edge)\n // was true and written down nowhere, so the work that is most of a legacy\n // fleet's week either walked the whole feature cycle or routed around loam.\n // Step 4b exists because every check after it is a join between exact\n // tokens: two services that agree on every operationId and disagree on what\n // it means (one confirms a split the other answers 422 for) validate, archive\n // and ship green, and a `Realizes:` whose requirement does not deliver the\n // promise reads as a clean join. Nothing in the binary can read meaning, and\n // a check pretending to would be the false green this tool exists to refuse,\n // so the step asks for questions and leaves the answers to a person.\n body: `Start a new feature in the loam docs repo (its path is \\`docsDir\\` in ./loam.json).\n\n0. First decide whether this needs a feature at all. A change that touches no\n requirement, no operation and no fleet edge — a refactor, a dependency bump,\n a fix that brings the code back to behaviour the living spec already\n documents — needs NONE: nothing in the docs repo changes, so there is nothing\n to scaffold, pin or archive. It is a code PR in the service repo, then\n \\`loam validate --service <id>\\` there (the service repo is the only place\n \\`sources.stale\\` is visible), then \\`loam vouch --pack --service <id>\\` to\n prepare the re-read the moved sources owe — the vouch itself is a human's.\n If the change alters what a requirement promises, it IS a feature, however\n small: one \\`--touches\\`, a Why, the ADDED/MODIFIED requirement, and\n \\`loam rebase\\` for a MODIFIED one — the rest of this page still applies.\n1. Read \\`AGENTS.md\\` at the root of the docs repo first — it defines the ID spine\n everything here depends on.\n2. Understand the current state before proposing a change:\n - \\`loam status --json\\` — what is already in flight and what it is waiting on.\n Run this FIRST if you joined this repository halfway or lost the session:\n \\`next[]\\` is ordered and each entry carries the literal command to run, so it\n tells you whether the work is to start $1 at all. \\`loam status $1 --json\\`\n narrows it to one feature once $1 exists\n - \\`loam list --json\\` — what services exist, and what documentation they are missing\n - \\`loam show <service> --json\\` — what a service owns, exposes, and who already calls it\n2b. \\`loam explore <service> --json --as $1\\` around every service you believe this\n feature touches. This is the step that decides step 3's \\`--touches\\` list, and it\n is the only decision in the cycle nothing downstream catches — a list short by one\n service produces a feature that validates, archives and ships with a consumer\n nobody updated. Read four fields:\n - \\`neighbours\\` — the services one hop out in the fleet map. Weigh each one. loam\n knows they are connected, not whether you change them, and it deliberately\n leaves them OUT of the suggested command line rather than guessing for you\n - \\`services[].maturity\\` — a service at \\`empty\\` or \\`partial\\` has no baseline to\n write a delta against, so the work starts with \\`loam adopt --service <id>\\`, not\n with this feature\n - \\`services[].operations\\` — what each already exposes, so an operation you are\n about to \"add\" that is already there becomes a MODIFIED requirement instead\n - \\`overlaps\\` — active features already carrying a delta for these services.\n \\`loam dependencies --json\\` (step 7) then says which must archive first\n \\`--op <operationId>\\` seeds from an operation when you know the call but not who\n owns it. A seed naming no \\`services/<id>/\\` is reported in \\`unknown\\` with the\n closest real ids rather than refused — the feature may be introducing it, and a\n typo looks identical until you read that list. \\`scaffold\\` is the literal step-3\n command line the seeds imply, with \\`--new-service\\` where the service does not\n exist yet.\n3. Scaffold it: \\`loam new $1 --title \"$2\"\\`, adding \\`--touches <id>\\` for every service the\n feature touches and \\`--new-service <id>\\` for every one it introduces. Every id goes\n through the \\`services/<id>/\\` grammar, so a typo is refused (\\`invalid-option\\`)\n rather than scaffolded; a \\`--touches\\` that matches nothing existing comes back as\n a \\`note\\` with the close ids, not as a failure — read it, because a \\`--touches\\`\n naming a service that does not exist is \\`delta.service-unknown\\` at validate time\n and a phantom \\`services/<typo>/\\` directory if it ever archives.\n4. Author what the templates left as TODO. This step is gated, not advisory:\n \\`loam archive\\` refuses a scaffold nobody edited (\\`scaffold.placeholder\\`,\n \\`intent.empty\\`), so the placeholder text cannot reach the living docs.\n - \\`intent.md\\` — the problem in business terms, in prose; the scaffold's\n comment-only Why/Scope count as unwritten\n - \\`delta.likec4\\` — new elements and edges, each tagged \\`#$1\\`; every call edge carries\n \\`metadata { op '<operationId>' }\\`; replace the scaffolded TODO description on each\n new service. For a requirements-only feature DELETE this\n file: an empty delta is legal, but a scaffold left half-filled is not\n - \\`specs/<svc>/spec.md\\` — the example requirement ships INSIDE the template's\n comment: copy it out, unindent, one behaviour per requirement, SHALL, at\n least one Given/When/Then scenario; add \\`Operations:\\` once the operation exists\n - \\`specs/<svc>/arch.spec.md\\` — the architectural obligations (outbox, retries,\n idempotency, alerts), same grammar; a \\`Covers:\\` line per requirement naming the\n tagged elements/edges it accounts for, or \\`c4.uncovered\\` says nothing does\n - \\`specs/<svc>/openapi.yaml\\` — define every operationId the edges reference.\n A self-contained document holding ONLY the operations this feature adds,\n changes or removes — never a copy of the living contract. An operation you\n leave out stays exactly as the living contract has it, and a local \\`$ref\\`\n (\\`#/components/schemas/Order\\`) resolves into the living document, so there\n is nothing to restate. Every operation you quote costs a pin, and once a\n parallel feature retires it — or moves it, and you quoted the old\n address — a pinned quote is \\`openapi.baseline-invalid\\` and\n \\`openapi.op-revived\\` both, an unpinned one \\`openapi.op-revived\\` alone\n (\\`asyncapi.slot-revived\\` on the event axis) — archive refuses either.\n Delete the quote; only a feature that MEANS to bring a retired operation\n back keeps it, and archives past the finding with a human's \\`--approve\\`\n - \\`specs/<svc>/asyncapi.yaml\\` — the event axis, only when the feature changes\n what a service puts on or takes off the bus: an AsyncAPI 3.0 document\n holding only the channel, operation and message slots you add, change or\n remove (an edited slot is written out whole), new messages under\n \\`components.messages\\`, and a \\`Publishes:\\`/\\`Consumes:\\` line on each\n requirement that names them. \\`loam new\\` scaffolds none — an event\n contract is genuinely optional\n4b. **Analyze before you pin.** Nothing grades this step, which is why it is here:\n every check after it is a join between exact tokens, so two services that\n agree on every operationId and disagree on what it means validate, archive\n and ship green. Read the feature as its reviewer will, before rebase freezes it:\n - \\`loam show $1 --json\\` — its \\`review\\` object joins the intent, the C4\n objects, the API/event slices and the dependencies\n - \\`loam context <service> --feature $1 --json\\` for every service it touches —\n the living requirements verbatim, the edges one hop out, and this delta\n - the \\`neighbours\\` step 2b's \\`loam explore\\` reported\n Then write what the documents leave open under \\`## Open questions\\` in\n \\`intent.md\\` (the scaffold already carries the heading), one line per question,\n naming the requirement id or edge it is about:\n - a SHALL two engineers could build two ways — an unstated unit, bound,\n ordering, or an \"eventually\" with no window\n - a value, limit or window two services state differently (a refund window,\n a rounding rule, a retry budget), and which side refuses when they\n disagree — a caller that accepts what its provider answers 4xx for is a\n contradiction no check can see\n - a \\`Realizes:\\` line whose requirement does not actually deliver the\n capability promise it names, or a promise this feature changes that no\n realizing requirement keeps\n - per call edge in the delta: the timeout, the retry and what makes it safe\n to repeat, what a partial failure leaves behind, and what compensates it —\n an edge no requirement answers these for is a question, not a default\n - each \\`explore\\` neighbour outside \\`--touches\\`: why it is untouched, or that\n it is a candidate — adding one means a \\`specs/<svc>/\\` delta in this feature,\n because that directory IS the feature's service list\n Ask; do not answer for the author. A human answers by editing the file —\n replacing a question with the decision, or with a change to the delta.\n Nothing is graded: an empty section is legal, and so is an answered one.\n5. **\\`loam rebase $1\\`** — the step between authoring and checking, and the one that\n stops two features in flight from silently deleting each other's work. It pins\n every MODIFIED/REMOVED requirement (\\`Based-On:\\`), and every operation and\n asyncapi channel/operation/message slot the living contract already holds\n (\\`x-loam-based-on\\`), to the living version you wrote against. What the\n feature ADDS has no living version and takes no pin, so an add-only contract\n delta needs no rebase. Any living operation or slot the delta spells is one\n the merge upserts, so it needs its pin even when you changed one line of it.\n Without the pins, \\`delta.baseline-missing\\` / \\`openapi.baseline-missing\\` /\n \\`asyncapi.baseline-missing\\` GATE\n the archive: before they did, the second feature to archive reverted the\n first — with \\`+0 ~1 -0\\`, exit 0, and nobody told. Rebase clears them; a\n human's \\`--approve\\` is the only other way past.\n Restamping is not resolving: if rebase reports a requirement as MOVED, re-read\n the living text and fold in what you still mean BEFORE you ship.\n6. \\`loam validate --feature $1 --json\\`. Do not stop while \\`valid\\` is false.\n7. \\`loam dependencies $1 --json\\` before anyone starts building: it says which\n features in flight have to archive first (\\`order\\`) and which collisions no\n ordering fixes (\\`conflicts\\`, \\`cycles\\`). Every conflict it names is one \\`loam rebase\\`\n cannot fix for you — two intentions have to be merged by people.\n\nThe operation's three spellings — the edge's \\`op\\`, the requirement's \\`Operations:\\`\nline, and the OpenAPI \\`operationId\\` — must match exactly.\n`,\n};\n"]}
@@ -1,4 +1,18 @@
1
- /** Building one service's slice: the delta brief, generated gherkin, steps, code, verify. */
1
+ /**
2
+ * Building one service's slice: the delta brief, then a unit at a time —
3
+ * generated gherkin, steps, code, verify.
4
+ *
5
+ * Steps 3-5 are a LOOP, not a sequence, and that is why `loam tasks` sits at
6
+ * the head of them. They used to be three things done in order — generate the
7
+ * suite, write the definitions, implement — which is only the right order for a
8
+ * slice whose every requirement already owns a scenario. Where one does not,
9
+ * the next thing owed is a sentence in the docs repo, and the body had no step
10
+ * that could say so; where the suite is half written, nothing here answered
11
+ * which half. `at` answers both: the projection names which of four states the
12
+ * next unit is in, and each state names exactly one action, so the numbering
13
+ * below is a table of destinations rather than an itinerary. An editor adding a
14
+ * step must therefore say which `at` value routes to it, or it is unreachable.
15
+ */
2
16
  export const LOAM_IMPLEMENT = {
3
17
  name: "loam-implement",
4
18
  description: "Implement a loam feature's slice for one service — generated gherkin first, tests from it, then code",
@@ -10,6 +24,7 @@ export const LOAM_IMPLEMENT = {
10
24
  "`loam status` — where this feature actually is, and what is owed before you start",
11
25
  "`loam context` — the briefing: the service's living slice, and every delta in flight over it",
12
26
  "`loam delta` — the task: the intent, every requirement verbatim, the endpoints, and the calls in and out",
27
+ "`loam tasks` — the open units, ordered; `--next` is the single next unit, with the scenario verbatim and the fact that closes it",
13
28
  "`loam gherkin` in the service's repo — the digest-stamped `.feature` files. Never edit them",
14
29
  "write step definitions for the generated scenarios first, outside the generated directory",
15
30
  "implement until the suite passes, running it with a JSON report",
@@ -18,7 +33,8 @@ export const LOAM_IMPLEMENT = {
18
33
  ],
19
34
  body: `Implement one service's part of a feature. Every step runs in the SERVICE'S own
20
35
  repository, which needs its own committed ./loam.json — if there is none,
21
- \`loam init --docs <path-to-docs-repo> --service $2\` then \`loam doctor\` first.
36
+ \`loam init --docs <path-to-docs-repo> --service $2 --agent-profile service\` then
37
+ \`loam doctor\` first.
22
38
 
23
39
  0. \`loam status $1 --json\` — where this feature actually is. Run it before
24
40
  anything else if you joined halfway or lost the session: it says which
@@ -59,12 +75,64 @@ repository, which needs its own committed ./loam.json — if there is none,
59
75
  document before building anything — for the delta, \`loam validate $1 --json\` names
60
76
  the error; the contract delta is not graded by validate, so read the parser message
61
77
  the payload carries.
62
- 3. In the service's repo, \`loam gherkin $1 --json\` — one \`.feature\` file per changed
63
- requirement lands under \`<gherkinDir>/loam/\` (default \`features/loam/\`), scenarios
64
- digest-stamped, arch requirements tagged \`@architecture\`. Those files ARE the
65
- acceptance criteria. Never edit them: regeneration rewrites the directory, and
66
- \`loam validate\` reports the suite stale by digest, not by your intentions.
67
- 4. Write step definitions for the generated scenarios FIRST — outside \`loam/\`.
78
+ 3. \`loam tasks $1 --service $2 --next --json\` — ONE unit of work, and the loop
79
+ every step below runs inside. Step 2 is the whole brief; this is the next thing
80
+ to do in it. It writes nothing: the projection is derived from the verify
81
+ checklist, the delta scans, the feature's verification record and — in this,
82
+ the bound service repo — the digest stamps in the generated \`.feature\` files.
83
+ Drop \`--next\` to see every open unit instead of only the head.
84
+ Each entry of \`units[]\` carries its \`id\` (the same id the verify checklist uses
85
+ for that claim), \`kind\` (\`service.exists\`, \`api.exposes\`, \`event.declares\`,
86
+ \`c4.calls\`, \`scenario.tested\` or \`requirement.described\`), \`subject\`, \`claim\`,
87
+ \`digest\`, \`dependsOn\`, \`source\` — the path, line, requirement and scenario it was
88
+ derived from, with the scenario's own \`lines\` verbatim — and \`done.check\`.
89
+
90
+ \`at\` is the instruction. It says what the unit NEEDS, and each of its four
91
+ values names exactly one action:
92
+ - \`describe\` — the requirement owns no scenarios yet. Write the Given/When/Then
93
+ in the DOCS repo, on the requirement \`source\` names, and nothing else. loam
94
+ does not author, and no amount of code closes a unit sitting here.
95
+ - \`generate\` — no stamped \`.feature\` carries this scenario's digest. The only
96
+ action is \`loam gherkin $1\` in this repo (step 4). Do not write code.
97
+ - \`implement\` — the stamped scenario exists and no recorded run has confirmed
98
+ it. Write step definitions outside \`loam/\`, implement until the suite passes,
99
+ then \`loam verify $1 --service $2 --results report.json\` (step 5).
100
+ - \`attested\` — the claim was closed on somebody's word rather than on a run.
101
+ Run the suite and re-record it so it stops resting on an assertion. An
102
+ agent-attested scenario stays visible at \`attested\` forever, deliberately: it
103
+ is the one state that cannot be made to go away by ignoring it.
104
+
105
+ \`done.check\` is the FACT that closes the unit, and the RUNNER decides whether
106
+ that fact holds — never your own reading of the code you just wrote. Done means
107
+ the unit no longer appears, and it stops appearing only once its claim is
108
+ recorded confirmed in a verification record that is not stale. So re-run
109
+ \`loam tasks $1 --service $2 --json\` after every recording instead of working
110
+ down a list you copied out: \`frontier\` (the first ten open unit ids, in order)
111
+ and \`counts\` (\`open\`, \`done\`, \`attested\`) are recomputed from the documents on
112
+ every run, which is what makes taking the head of it always safe.
113
+ \`checklist\` is the digest of the question set the whole projection derives from.
114
+ If it differs from the one you started against, the question set moved under
115
+ you — re-read the feature before continuing, because what you closed earlier may
116
+ not be what is being asked now.
117
+ \`record\` says whether the answers were found at all (\`absent\`, \`recorded\`,
118
+ \`stale\`, \`unreadable\`) and \`stamps\` whether this repo's generated suite could be
119
+ read (\`read\`, \`unknown\`); an \`unknown\` makes every \`generate\` verdict a guess,
120
+ so fix the binding before acting on one. \`contracts\` restates step 6 as data
121
+ (\`expose[]\` and \`call[]\`), \`size\` the shape of the packet, \`review[]\` what a
122
+ reviewer of it will read, and \`waitingOn\` — \`--service\` runs only — what another
123
+ service still owes before your frontier can move.
124
+ Exit 1 with \`ok: true\` is step 2's rule again: delta.likec4 or one of the
125
+ contract axes did not parse, so the projection is SHORT by everything that
126
+ document would have contributed. Fix the document; do not work the units that
127
+ happened to survive it.
128
+ 4. \`at: "generate"\` — in the service's repo, \`loam gherkin $1 --json\`. One
129
+ \`.feature\` file per changed requirement lands under \`<gherkinDir>/loam/\`
130
+ (default \`features/loam/\`), scenarios digest-stamped, arch requirements tagged
131
+ \`@architecture\`. Those files ARE the acceptance criteria. Never edit them:
132
+ regeneration rewrites the directory, and \`loam validate\` reports the suite stale
133
+ by digest, not by your intentions. Generating is the WHOLE action for a unit at
134
+ \`generate\`: go back to step 3, where the same unit now reads \`implement\`.
135
+ 5. \`at: "implement"\` — step definitions FIRST, outside \`loam/\`, then the code.
68
136
  Do not paraphrase a scenario into something easier to pass; it is the acceptance
69
137
  criterion someone else reviews against.
70
138
  Run \`loam steps --service $2 --json\` before you write any: it collapses every
@@ -75,12 +143,20 @@ repository, which needs its own committed ./loam.json — if there is none,
75
143
  and regenerate, rather than writing the second definition.
76
144
  A step's argument arrives as an argument: a \`"""\` docstring is the payload, an
77
145
  indented table is a DataTable. Assert against them; do not re-parse the step text.
78
- 5. Implement until the suite passes — run it with a JSON report
79
- (\`cucumber-js --format json:report.json\`):
146
+ Then implement until the suite passes — run it with a JSON report
147
+ (\`cucumber-js --format json:report.json\`; cucumber-jvm, behave, Reqnroll,
148
+ pytest-bdd \`--cucumberjson\` and godog write the same cucumber JSON):
80
149
  \`loam verify $1 --service $2 --results report.json\` consumes that report as the
81
- done-check's answer sheet. Record from THIS repo, with \`--service\` — the
150
+ done-check's answer sheet. A runner with no cucumber formatter answers through
151
+ loam's runner-neutral shape instead, keyed by each scenario's \`@loam-digest\`
152
+ tag — adapting the runner's report into it is a build step you own:
153
+ \`{"loamScenarioReport": 1, "results": [{"digest": "<16 hex>", "status": "passed"}]}\`.
154
+ A cucumber run must have run every step of the stamped scenario: a report
155
+ entry whose step count differs is not that scenario's run and stays
156
+ unconfirmed. Record from THIS repo, with \`--service\` — the
82
157
  \`--service\`-less \`--record\` form writes the whole record from one place and is
83
- refused (\`record-federated\`) once another service has attested.
158
+ refused (\`record-federated\`) once another service has attested. Recording is what
159
+ closes the unit, so step 3 is the command AFTER it, not the end of the run.
84
160
  6. Honour the contract: every operation in \`architecture.inbound\` must exist under
85
161
  exactly that operationId, and every one in \`architecture.outbound\` must be called.
86
162
  7. If building made you change the feature's documents at all — a requirement's
@@ -99,7 +175,9 @@ repository, which needs its own committed ./loam.json — if there is none,
99
175
  8. \`loam validate --feature $1 --json\` before handing back.
100
176
 
101
177
  If the requirement is ambiguous, say so and stop — do not invent behaviour and
102
- leave the spec disagreeing with the code.
178
+ leave the spec disagreeing with the code. Write the question under
179
+ \`## Open questions\` in the feature's \`intent.md\`, where loam-feature's analyze
180
+ step put the ones it found, so a person answers it once for every service.
103
181
  `,
104
182
  };
105
183
  //# sourceMappingURL=implement.js.map