@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
@@ -3,10 +3,11 @@
3
3
  *
4
4
  * Every arm here keeps one property: a refusal means nothing was written. The
5
5
  * `--json` shapes carry the issues that caused it so an agent can act on the
6
- * list rather than on the sentence, and `ArchiveFailure` exists so a merge that
7
- * could not be computed reports as a merge failure rather than as `internal` —
8
- * a read-only directory or an unreadable pre-image is not a bug in loam, and
9
- * saying so sends the reader looking for one.
6
+ * list rather than on the sentence, and a refusal thrown from below is a
7
+ * `CodedFailure` (core/envelope/json.ts) so a merge that could not be computed
8
+ * reports as a merge failure rather than as `internal` — a read-only directory
9
+ * or an unreadable pre-image is not a bug in loam, and saying so sends the
10
+ * reader looking for one.
10
11
  */
11
12
  import { existsSync } from "node:fs";
12
13
  import { emitJsonError, repoPath } from "../../../core/envelope/json.js";
@@ -15,20 +16,9 @@ import {} from "../../../core/staging/interrupted.js";
15
16
  import {} from "../../../core/staging/writes.js";
16
17
  import { findingJson } from "../../../core/vocabulary/report.js";
17
18
  /**
18
- * A refusal or failure with its stable `--json` code attached. Thrown from the
19
- * plan phase (nothing written yet) and from the commit's rollback path (whose
20
- * message says whether the rollback held). Anything ELSE that escapes runArchive
21
- * is a bug, and the action handler reports it as `internal`.
22
- */
23
- export class ArchiveFailure extends Error {
24
- code;
25
- constructor(code, msg) {
26
- super(msg);
27
- this.code = code;
28
- }
29
- }
30
- /**
31
- * An Issue as the `--json` envelope spells it — the Finding shape, minus details.
19
+ * An Issue as the `--json` envelope spells it — the whole Finding shape, `details`
20
+ * included (`[]` when the issue carries none; the overwrite warnings list their
21
+ * current consumers there), plus `overridable`.
32
22
  * `gates` and `overridable` are always present and already resolved: a consumer
33
23
  * must not have to re-implement the severity default to know what blocks
34
24
  * archive, nor keep a code list to know what `--approve` can move.
@@ -1 +1 @@
1
- {"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/refusal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * How `loam archive` refuses, and what a refusal prints.\n *\n * Every arm here keeps one property: a refusal means nothing was written. The\n * `--json` shapes carry the issues that caused it so an agent can act on the\n * list rather than on the sentence, and `ArchiveFailure` exists so a merge that\n * could not be computed reports as a merge failure rather than as `internal` —\n * a read-only directory or an unreadable pre-image is not a bug in loam, and\n * saying so sends the reader looking for one.\n */\nimport { existsSync } from \"node:fs\";\nimport { emitJsonError, repoPath, type ErrorCode } from \"../../../core/envelope/json.js\";\nimport {\n approveOverrides,\n issueFinding,\n type Issue,\n} from \"../../../core/vocabulary/issue.js\";\nimport { type CommitRecovery } from \"../../../core/staging/interrupted.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { findingJson, type Finding } from \"../../../core/vocabulary/report.js\";\n\nexport interface ArchiveOptions {\n approve?: boolean;\n dryRun?: boolean;\n json?: boolean;\n}\n\n\n/**\n * A refusal or failure with its stable `--json` code attached. Thrown from the\n * plan phase (nothing written yet) and from the commit's rollback path (whose\n * message says whether the rollback held). Anything ELSE that escapes runArchive\n * is a bug, and the action handler reports it as `internal`.\n */\nexport class ArchiveFailure extends Error {\n constructor(\n readonly code: ErrorCode,\n msg: string,\n ) {\n super(msg);\n }\n}\n\n/**\n * An Issue as the `--json` envelope spells it — the Finding shape, minus details.\n * `gates` and `overridable` are always present and already resolved: a consumer\n * must not have to re-implement the severity default to know what blocks\n * archive, nor keep a code list to know what `--approve` can move.\n */\nexport function issueJson(i: Issue, scope?: string): Record<string, unknown> {\n return {\n ...findingJson(\n issueFinding(i),\n scope === undefined ? undefined : { path: scope, role: \"scope\" },\n ),\n overridable: approveOverrides(i),\n };\n}\n\n/** The failure envelope plus the issues that caused it, so a caller need not re-run validate. */\nexport function refuseJson(code: ErrorCode, msg: string, issues: Issue[], scope?: string): void {\n emitJsonError(code, msg, { issues: issues.map((issue) => issueJson(issue, scope)) });\n}\n\n/**\n * The same envelope for a refusal computed from `Finding`s rather than\n * `Issue`s — the plan-time checks whose codes are not coherence's (they belong\n * to the LIVING documents, not to the feature) and so are not `IssueCode`s.\n *\n * `gates: true` is asserted rather than derived: every finding listed in a\n * refusal is a reason archive stopped, which is what the field means. Nothing\n * advisory reaches here.\n */\nexport function refuseFindings(\n code: ErrorCode,\n msg: string,\n findings: Finding[],\n scope?: string,\n): void {\n emitJsonError(code, msg, {\n issues: findings.map((finding) => ({\n ...findingJson(\n finding,\n scope === undefined ? undefined : { path: scope, role: \"scope\" },\n ),\n gates: true,\n })),\n });\n}\n\n/**\n * Take the docs repo's advisory lock for the WHOLE plan+commit window, then\n * archive.\n *\n * The window is the point. Two archives that overlap do not fight over a\n * rename: they each read the living landscape, each splice their additions into\n * the bytes they read, and the second write replaces the first — both exit 0,\n * and `validate` stays green over a landscape that is missing one feature's\n * architecture, because a document with fewer elements is not an invalid one.\n * That is the only silent-loss path in loam that a later command cannot even\n * detect, so it is closed at the coarsest possible granularity: one writer per\n * docs repo, refusing rather than queueing, because a CLI that blocks for an\n * unknown time is worse for an agent than one that says `docs-busy`.\n */\n\n/**\n * What the recovery did, before this command's own output — a docs repo that\n * changed under the caller is the first thing they have to be told, not a\n * footnote. Shared by archive and unarchive through the same CommitRecovery.\n */\nexport function sayRecovery(r: CommitRecovery): void {\n const what = `an interrupted \\`loam ${r.command} ${r.feature}\\``;\n if (r.outcome === \"completed\") {\n console.log(`⚠ ${what} had in fact finished — cleared its commit record.\\n`);\n return;\n }\n if (r.outcome === \"consistent\") {\n console.log(`⚠ ${what} was rolled back before it wrote anything — cleared its commit record.\\n`);\n return;\n }\n // An interrupted archive is UNDONE and an interrupted unarchive is FINISHED —\n // the merged text a restore was replacing is written down nowhere, so there is\n // nothing to go back to. Say which happened; the paths are the same either way.\n const what_ = r.command === \"archive\" ? \"put them back from\" : \"finished them from\";\n console.log(`⚠ ${what} left ${r.repaired.length} file(s) half-written; ${what_} its snapshot:`);\n for (const p of r.repaired) console.log(` ↩ ${p}`);\n console.log(\"\");\n}\n\n/** The full plan, as files: what a dry run shows instead of doing. */\nexport function printPlan(docsDir: string, writes: PlannedWrite[], dirName: string): void {\n console.log(`\\n plan — ${writes.length} file(s):`);\n for (const w of writes) {\n const verb = existsSync(w.path) ? \"update\" : \"create\";\n console.log(` ${verb} ${repoPath(docsDir, w.path)}`);\n }\n console.log(` move features/${dirName} → features/archive/${dirName}`);\n console.log(\"\\n dry run — nothing was written.\");\n}\n"]}
1
+ {"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/refusal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * How `loam archive` refuses, and what a refusal prints.\n *\n * Every arm here keeps one property: a refusal means nothing was written. The\n * `--json` shapes carry the issues that caused it so an agent can act on the\n * list rather than on the sentence, and a refusal thrown from below is a\n * `CodedFailure` (core/envelope/json.ts) so a merge that could not be computed\n * reports as a merge failure rather than as `internal` — a read-only directory\n * or an unreadable pre-image is not a bug in loam, and saying so sends the\n * reader looking for one.\n */\nimport { existsSync } from \"node:fs\";\nimport { emitJsonError, repoPath, type ErrorCode } from \"../../../core/envelope/json.js\";\nimport {\n approveOverrides,\n issueFinding,\n type Issue,\n} from \"../../../core/vocabulary/issue.js\";\nimport { type CommitRecovery } from \"../../../core/staging/interrupted.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { findingJson, type Finding } from \"../../../core/vocabulary/report.js\";\n\nexport interface ArchiveOptions {\n approve?: boolean;\n dryRun?: boolean;\n json?: boolean;\n}\n\n/**\n * An Issue as the `--json` envelope spells it — the whole Finding shape, `details`\n * included (`[]` when the issue carries none; the overwrite warnings list their\n * current consumers there), plus `overridable`.\n * `gates` and `overridable` are always present and already resolved: a consumer\n * must not have to re-implement the severity default to know what blocks\n * archive, nor keep a code list to know what `--approve` can move.\n */\nexport function issueJson(i: Issue, scope?: string): Record<string, unknown> {\n return {\n ...findingJson(\n issueFinding(i),\n scope === undefined ? undefined : { path: scope, role: \"scope\" },\n ),\n overridable: approveOverrides(i),\n };\n}\n\n/** The failure envelope plus the issues that caused it, so a caller need not re-run validate. */\nexport function refuseJson(code: ErrorCode, msg: string, issues: Issue[], scope?: string): void {\n emitJsonError(code, msg, { issues: issues.map((issue) => issueJson(issue, scope)) });\n}\n\n/**\n * The same envelope for a refusal computed from `Finding`s rather than\n * `Issue`s — the plan-time checks whose codes are not coherence's (they belong\n * to the LIVING documents, not to the feature) and so are not `IssueCode`s.\n *\n * `gates: true` is asserted rather than derived: every finding listed in a\n * refusal is a reason archive stopped, which is what the field means. Nothing\n * advisory reaches here.\n */\nexport function refuseFindings(\n code: ErrorCode,\n msg: string,\n findings: Finding[],\n scope?: string,\n): void {\n emitJsonError(code, msg, {\n issues: findings.map((finding) => ({\n ...findingJson(\n finding,\n scope === undefined ? undefined : { path: scope, role: \"scope\" },\n ),\n gates: true,\n })),\n });\n}\n\n/**\n * Take the docs repo's advisory lock for the WHOLE plan+commit window, then\n * archive.\n *\n * The window is the point. Two archives that overlap do not fight over a\n * rename: they each read the living landscape, each splice their additions into\n * the bytes they read, and the second write replaces the first — both exit 0,\n * and `validate` stays green over a landscape that is missing one feature's\n * architecture, because a document with fewer elements is not an invalid one.\n * That is the only silent-loss path in loam that a later command cannot even\n * detect, so it is closed at the coarsest possible granularity: one writer per\n * docs repo, refusing rather than queueing, because a CLI that blocks for an\n * unknown time is worse for an agent than one that says `docs-busy`.\n */\n\n/**\n * What the recovery did, before this command's own output — a docs repo that\n * changed under the caller is the first thing they have to be told, not a\n * footnote. Shared by archive and unarchive through the same CommitRecovery.\n */\nexport function sayRecovery(r: CommitRecovery): void {\n const what = `an interrupted \\`loam ${r.command} ${r.feature}\\``;\n if (r.outcome === \"completed\") {\n console.log(`⚠ ${what} had in fact finished — cleared its commit record.\\n`);\n return;\n }\n if (r.outcome === \"consistent\") {\n console.log(`⚠ ${what} was rolled back before it wrote anything — cleared its commit record.\\n`);\n return;\n }\n // An interrupted archive is UNDONE and an interrupted unarchive is FINISHED —\n // the merged text a restore was replacing is written down nowhere, so there is\n // nothing to go back to. Say which happened; the paths are the same either way.\n const what_ = r.command === \"archive\" ? \"put them back from\" : \"finished them from\";\n console.log(`⚠ ${what} left ${r.repaired.length} file(s) half-written; ${what_} its snapshot:`);\n for (const p of r.repaired) console.log(` ↩ ${p}`);\n console.log(\"\");\n}\n\n/** The full plan, as files: what a dry run shows instead of doing. */\nexport function printPlan(docsDir: string, writes: PlannedWrite[], dirName: string): void {\n console.log(`\\n plan — ${writes.length} file(s):`);\n for (const w of writes) {\n const verb = existsSync(w.path) ? \"update\" : \"create\";\n console.log(` ${verb} ${repoPath(docsDir, w.path)}`);\n }\n console.log(` move features/${dirName} → features/archive/${dirName}`);\n console.log(\"\\n dry run — nothing was written.\");\n}\n"]}
@@ -23,8 +23,8 @@ import { locateServicePaths } from "../../../core/repo/service-target.js";
23
23
  import { isRequirementsHeading, parseRequirements, sectionHeadings, splitRequirementsSection, } from "../../../core/document/parse.js";
24
24
  import { applyRequirementDelta } from "../../../core/document/apply.js";
25
25
  import { serializeRequirements } from "../../../core/document/spec.js";
26
- import {} from "../../../core/document/spec.js";
27
- import { ArchiveFailure } from "./refusal.js";
26
+ import { movedUnderVouch, parseFrontmatter, stringField } from "../../../core/document/frontmatter.js";
27
+ import { CodedFailure } from "../../../core/envelope/json.js";
28
28
  import {} from "./state.js";
29
29
  export async function planSpecs(config, gated, plan, say) {
30
30
  const { featureDir, deltaServices } = gated;
@@ -71,10 +71,23 @@ export async function planSpecs(config, gated, plan, say) {
71
71
  // Mechanical, like a model-less landscape, so merge-failed, not --approve.
72
72
  const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));
73
73
  if (reqHeadings.length > 1) {
74
- throw new ArchiveFailure("merge-failed", `living ${axis.file} for ${svc} has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(", ")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`);
74
+ throw new CodedFailure("merge-failed", `living ${axis.file} for ${svc} has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(", ")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`);
75
+ }
76
+ const livingReqs = parseRequirements(livingText);
77
+ const merged = applyRequirementDelta(livingReqs, deltaReqs);
78
+ const next = rewriteRequirementsRun(livingText, livingReqs, merged);
79
+ writes.push(planWrite(livingPath, next));
80
+ // A merge is an edit to the document's body like any other, so a vouched
81
+ // spec it moves goes `content.stale` — and honestly: the merged text is
82
+ // words no person has yet read against the code. Archive does not soften
83
+ // that. It names the document and its owner, so the re-vouch is known to
84
+ // be owed while the merge is still a PR rather than discovered later as a
85
+ // red `--strict` gate. Asked of the bytes about to be written, through the
86
+ // one definition validate grades with.
87
+ const fm = parseFrontmatter(next);
88
+ if (movedUnderVouch(fm, next)) {
89
+ plan.revouchOwed.push({ service: svc, path: livingPath, owner: stringField(fm, "owner") ?? null });
75
90
  }
76
- const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);
77
- writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));
78
91
  const c = summarize(deltaReqs);
79
92
  say(` ${axis.label}: ${svc} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);
80
93
  }
@@ -144,10 +157,11 @@ export async function planCapabilities(config, gated, plan, say) {
144
157
  // reason it cannot be guessed at.
145
158
  const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));
146
159
  if (reqHeadings.length > 1) {
147
- throw new ArchiveFailure("merge-failed", `living capabilities/${doc.id}/spec.md has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(", ")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`);
160
+ throw new CodedFailure("merge-failed", `living capabilities/${doc.id}/spec.md has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(", ")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`);
148
161
  }
149
- const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);
150
- writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));
162
+ const livingReqs = parseRequirements(livingText);
163
+ const merged = applyRequirementDelta(livingReqs, deltaReqs);
164
+ writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, livingReqs, merged)));
151
165
  const c = summarize(deltaReqs);
152
166
  say(` capability: ${doc.id} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);
153
167
  }
@@ -156,32 +170,127 @@ export async function planCapabilities(config, gated, plan, say) {
156
170
  }
157
171
  }
158
172
  /**
159
- * Rewrite ONLY the requirements run of a living spec. Byte-for-byte preserved:
160
- * everything before the first requirement inside `## Requirements` (the intro,
161
- * the heading line, prose under the heading) and everything from the section's
162
- * end onward (the next `## ` heading to EOF) — the old cut was a substring
163
- * `indexOf("\n## Requirements")`, a prefix match that also hit
164
- * `## Requirements Extra` and silently destroyed every section after the
165
- * requirements. Prose BETWEEN requirements is body text of whatever is open
166
- * above it (parseRequirements attributes it to the previous requirement's last
167
- * scenario, or its text) and survives inside the re-serialized run, framing
168
- * normalized. `merged` must contain every living requirement — runArchive's
173
+ * Rewrite ONLY what the delta touched inside a living spec's requirements run.
174
+ * Byte-for-byte preserved: everything before the first requirement inside
175
+ * `## Requirements` (the intro, the heading line, prose under the heading) and
176
+ * everything from the section's end onward (the next `## ` heading to EOF) —
177
+ * the old cut was a substring `indexOf("\n## Requirements")`, a prefix match
178
+ * that also hit `## Requirements Extra` and silently destroyed every section
179
+ * after the requirements. `living` must be every living requirement — the gate's
169
180
  * stray guard refuses any document whose requirements sit outside the section,
170
181
  * using the same heading definition, before this output is ever written.
182
+ *
183
+ * Inside the run the unit is one requirement, and an untouched one is written
184
+ * back as its raw slice (`spliceRun`). The whole run used to be re-serialized,
185
+ * and `serializeRequirements` writes a blank line under every
186
+ * `### Requirement:` heading — so a living spec written without one, the shape
187
+ * the adopt brief's own examples taught, had that line inserted under every
188
+ * requirement the feature never mentioned. The diff, the vouch pack and the
189
+ * re-vouch all widened from the delta to the whole section.
190
+ *
191
+ * Exported for test/archive.test.ts alone: the slice-to-requirement join
192
+ * (`verbatimSlices`) can only be handed a disagreeing pair directly, because
193
+ * the gate refuses every document that would produce one through the CLI.
171
194
  */
172
- function rewriteRequirementsRun(text, merged) {
195
+ export function rewriteRequirementsRun(text, living, merged) {
173
196
  const s = splitRequirementsSection(text);
174
197
  // No `## Requirements` at all: the stray guard has already refused any doc
175
198
  // whose requirements live elsewhere, so this one has none — open the section.
176
199
  if (s === null)
177
200
  return `${text.trimEnd()}\n\n## Requirements\n\n${serializeRequirements(merged)}`;
178
- const body = serializeRequirements(merged);
179
- // head/tail are raw slices; only the run's own framing is normalized. When
180
- // the section held no requirements yet, the glue supplies the blank line the
181
- // author never had reason to write.
182
- const headGlue = s.run !== "" || s.head.endsWith("\n\n") ? "" : s.head.endsWith("\n") ? "\n" : "\n\n";
201
+ if (s.run !== "")
202
+ return s.head + spliceRun(s, living, merged) + s.tail;
203
+ // The section held no requirements yet: head and tail are raw slices, and the
204
+ // glue supplies the blank lines the author never had reason to write.
205
+ const headGlue = s.head.endsWith("\n\n") ? "" : s.head.endsWith("\n") ? "\n" : "\n\n";
183
206
  const tailGlue = s.tail === "" ? "" : "\n";
184
- return s.head + headGlue + body + tailGlue + s.tail;
207
+ return s.head + headGlue + serializeRequirements(merged) + tailGlue + s.tail;
208
+ }
209
+ /** The line breaks (and blank lines) a slice ends with — what separates it from whatever follows. */
210
+ const TRAILING_BREAKS = /(?:\r?\n[ \t]*)*$/;
211
+ /**
212
+ * The merged run: every living requirement the delta left alone as its raw
213
+ * slice, every one it added or replaced serialized, in `merged`'s order.
214
+ *
215
+ * Keyed on object identity, which `applyRequirementDelta` keeps for exactly the
216
+ * requirements it did not touch — so there is no second reading here of what
217
+ * the delta changed, and no way for the splice and the algebra to disagree.
218
+ * Which slice belongs to which living requirement is `verbatimSlices`' join.
219
+ *
220
+ * Prose BETWEEN requirements rides in the slice of the requirement above it,
221
+ * which is where parseRequirements attributes it too: it survives verbatim
222
+ * beside an untouched requirement and goes with a replaced one, as it always
223
+ * did — a MODIFIED carries its full new text.
224
+ */
225
+ function spliceRun(section, living, merged) {
226
+ const verbatim = verbatimSlices(section.requirements, living);
227
+ let out = "";
228
+ let previousVerbatim = true;
229
+ for (const requirement of merged) {
230
+ const raw = verbatim.get(requirement);
231
+ // Two untouched neighbours keep whatever the author put between them. Next
232
+ // to a serialized requirement the canonical blank line is ADDED after the
233
+ // untouched text, never by rewriting it.
234
+ if (out !== "" && (raw === undefined || !previousVerbatim))
235
+ out = withBlankLine(out);
236
+ out += raw ?? serializeRequirements([requirement]);
237
+ previousVerbatim = raw !== undefined;
238
+ }
239
+ if (out === "")
240
+ return "";
241
+ // The run ends as it ended: whatever separated the old last requirement from
242
+ // the next heading (or EOF) now follows the new last one — and at least a
243
+ // newline when a merged requirement lands where the file used to stop without one.
244
+ const ending = section.run.slice(section.run.search(TRAILING_BREAKS));
245
+ const body = out.slice(0, out.search(TRAILING_BREAKS));
246
+ return body + (ending === "" && merged.at(-1) !== living.at(-1) ? "\n" : ending);
247
+ }
248
+ /** A requirement's identity as the merge selects it: `Requirement-ID:` when authored, the exact heading otherwise. */
249
+ const identity = (r) => (r.id === undefined ? `name:${r.name}` : `id:${r.id}`);
250
+ /**
251
+ * Each living requirement's raw slice, joined on its IDENTITY — the slice read
252
+ * back through the same parser that produced `living`, so a slice can only
253
+ * land in place of the requirement it actually is.
254
+ *
255
+ * The join used to be by position, guarded by a count. The two walks agree in
256
+ * order today (one heading-and-fence definition, core/document/parse.ts, and
257
+ * the gate's stray guard refusing any requirement outside the section), but
258
+ * nothing declared that, and equal counts paired differently would have written
259
+ * one requirement's bytes where another belongs — under its vouch stamp, with
260
+ * nothing to say so.
261
+ *
262
+ * Fails closed to the empty map, which serializes every requirement — the old
263
+ * whole-run rewrite, still a correct merge and only a wider diff: when the
264
+ * counts differ, when a slice does not read as exactly one requirement, when
265
+ * two slices claim one identity, or when a living requirement finds no slice
266
+ * of its own.
267
+ */
268
+ function verbatimSlices(slices, living) {
269
+ const none = new Map();
270
+ if (slices.length !== living.length)
271
+ return none;
272
+ const byIdentity = new Map();
273
+ for (const slice of slices) {
274
+ const [only, ...more] = parseRequirements(slice);
275
+ if (only === undefined || more.length > 0 || byIdentity.has(identity(only)))
276
+ return none;
277
+ byIdentity.set(identity(only), slice);
278
+ }
279
+ const verbatim = new Map();
280
+ for (const requirement of living) {
281
+ const slice = byIdentity.get(identity(requirement));
282
+ if (slice === undefined)
283
+ return none;
284
+ // Taken once: a second living requirement of the same identity finds nothing.
285
+ byIdentity.delete(identity(requirement));
286
+ verbatim.set(requirement, slice);
287
+ }
288
+ return verbatim;
289
+ }
290
+ /** `text` ending in a blank line: line breaks appended to make one, nothing rewritten. */
291
+ function withBlankLine(text) {
292
+ const breaks = text.slice(text.search(TRAILING_BREAKS)).split("\n").length - 1;
293
+ return breaks >= 2 ? text : text + "\n".repeat(2 - breaks);
185
294
  }
186
295
  function summarize(reqs) {
187
296
  const c = { ADDED: 0, MODIFIED: 0, REMOVED: 0 };
@@ -1 +1 @@
1
- {"version":3,"file":"specs.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/specs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The requirements merge: the feature's ADDED/MODIFIED/REMOVED requirements\n * applied into each living service spec. The contract merges live beside it in\n * `./contracts/` — one file per axis, split out when the OpenAPI half made\n * this one carry two merges.\n *\n * ONE code path for the pair of requirement-carrying files — the business\n * spec.md and the architecture arch.spec.md ride the same delta algebra, the\n * same prose-preserving rewrite and the same guards, parameterized by filename.\n * A fork here would be two places the merge could disagree.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { rebaseLinks } from \"../../../core/links/parse.js\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { featureSpecPaths, SPEC_AXES } from \"../../../core/repo/paths.js\";\nimport { capabilityDocsDir, glossaryDir, livingCapabilityPaths } from \"../../../core/repo/authored/paths.js\";\nimport { featureGlossary, livingTermPath } from \"../../../core/glossary/delta.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport {\n isRequirementsHeading,\n parseRequirements,\n sectionHeadings,\n splitRequirementsSection,\n} from \"../../../core/document/parse.js\";\nimport { applyRequirementDelta } from \"../../../core/document/apply.js\";\nimport { serializeRequirements } from \"../../../core/document/spec.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { ArchiveFailure } from \"./refusal.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function planSpecs(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes } = plan;\n // Requirements merge — apply ADDED/MODIFIED/REMOVED into each living service\n // spec. ONE code path for the pair of requirement-carrying files: the business\n // spec.md and the architecture arch.spec.md ride the same delta algebra, the\n // same prose-preserving rewrite and the same guards, parameterized by filename\n // (SPEC_AXES) — a fork here would be two places the merge could disagree.\n for (const svc of deltaServices) {\n for (const axis of SPEC_AXES) {\n const deltaPath = featureSpecPaths(featureDir, svc)[axis.key];\n if (!existsSync(deltaPath)) continue;\n\n // The shared context: this is a deltaServices × SPEC_AXES loop, and a\n // context-less locate is a full fleet walk per iteration.\n const livingPath = (await locateServicePaths(config.docsDir, svc, config.fleet))[axis.key];\n // Resolved BEFORE the delta is parsed, because the parse now depends on\n // it: a requirement's markdown links are relative to the file they were\n // written in, and this merge moves the text two directories up the tree.\n // `core/links/parse.ts`'s `rebaseLinks` states the defect; the ordering\n // here is what lets the fix see both ends of the move, including a\n // service filed under a subsystem, whose living directory is deeper still.\n const deltaReqs = parseRequirements(\n rebaseLinks(await readUtf8(deltaPath), { from: dirname(deltaPath), to: dirname(livingPath) }),\n );\n if (!existsSync(livingPath)) {\n // New service (or first arch spec) — create the living file from the\n // ADDED/MODIFIED requirements.\n const created = applyRequirementDelta([], deltaReqs);\n if (created.length === 0) {\n say(` ${axis.label}: ${svc} — nothing to merge (delta leaves no requirements), no living ${axis.file} created`);\n continue;\n }\n const heading = axis.key === \"spec\" ? svc : `${svc} — architecture`;\n const frontmatter = `---\\nservice: ${svc}\\nstatus: draft\\n---\\n\\n# ${heading}\\n\\n`;\n writes.push(planWrite(livingPath, `${frontmatter}## Requirements\\n\\n${serializeRequirements(created)}`));\n say(` ${axis.label}: ${svc} — created living ${axis.file} (${created.length} requirement(s))`);\n continue;\n }\n const livingText = await readUtf8(livingPath);\n // TWO `## Requirements` headings would put the rewrite's one-section\n // invariant to a choice it must not make: the run of the first would be\n // rewritten while the second survived verbatim in the tail — and its\n // requirements, collected by parseRequirements, would land in the run TOO.\n // Mechanical, like a model-less landscape, so merge-failed, not --approve.\n const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));\n if (reqHeadings.length > 1) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `living ${axis.file} for ${svc} has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(\", \")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`,\n );\n }\n const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);\n writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));\n\n const c = summarize(deltaReqs);\n say(` ${axis.label}: ${svc} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);\n }\n }\n}\n\n/**\n * The BUSINESS corpus's merge: a feature's `capabilities/<id>/spec.md` deltas\n * applied into the living `capabilities/<id>/spec.md`.\n *\n * Here rather than in a sixth file in this package because this module already\n * owns \"requirements merged into a living document\" — it owns\n * `rewriteRequirementsRun`, the two-headings refusal and the `applyRequirementDelta`\n * call, and the business corpus needs all three unchanged. What differs from\n * `planSpecs` above is only where the living document lives and what a CREATED\n * one says at the top.\n *\n * ARCHIVE CREATES THE LIVING DOCUMENT, by exact analogy with the new-service\n * branch above. The directory IS the list on this axis (`core/capabilities/tree.ts`),\n * so an archive that merged into a file it refused to create would make the\n * whole delta merge nothing — which is the `delta.no-delta-sections` failure\n * class, arriving through the merge instead of through the grammar.\n *\n * NO FRONTMATTER on a created capability document, and that is a decision\n * rather than an omission. Nothing reads a capability document's frontmatter:\n * `readCapabilityTree` classifies by the file's PRESENCE and\n * `readCapabilityVocabulary` takes `description`/`owner` from the YAML side. A\n * `status:` field invented here would be the \"second list nothing keeps\n * current\" this axis was designed against.\n *\n * AND IT SAYS SO OUT LOUD. Creating the first `capabilities/<id>/spec.md`\n * creates `capabilities/`, which is one of the two ways a fleet opts INTO the\n * capability axis — from the next command on, `loam validate --all` grades a\n * corpus that did not exist before. A merge that changes what the whole fleet\n * is graded on must not do it silently.\n */\nexport async function planCapabilities(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { writes } = plan;\n // Whether `capabilities/` exists is asked BEFORE the loop, because the loop\n // is what would create it; `creating` records whether this run actually\n // materialises a document, so the adoption line below is not printed by a\n // run whose every capability delta merged into a document already there.\n const adopting = !existsSync(capabilityDocsDir(config.docsDir));\n let creating = false;\n for (const doc of gated.capabilityDeltas) {\n const livingPath = livingCapabilityPaths(config.docsDir, doc.id).spec;\n // Same move, same rewrite: `features/<FEAT>/capabilities/<id>/` is two\n // directories deeper than `capabilities/<id>/`, so a citation of a glossary\n // term or an ADR would land pointing above the repository.\n const deltaReqs = parseRequirements(\n rebaseLinks(await readUtf8(doc.spec), { from: dirname(doc.spec), to: dirname(livingPath) }),\n );\n if (!existsSync(livingPath)) {\n const created = applyRequirementDelta([], deltaReqs);\n if (created.length === 0) {\n say(` capability: ${doc.id} — nothing to merge (delta leaves no requirements), no living spec.md created`);\n continue;\n }\n // The heading is the id and nothing else. loam merges requirements and\n // never prose, so it must not fabricate the narrative that is the whole\n // point of the document — the line below tells the author it is theirs.\n writes.push(planWrite(livingPath, `# ${doc.id}\\n\\n## Requirements\\n\\n${serializeRequirements(created)}`));\n creating = true;\n say(` capability: ${doc.id} — created capabilities/${doc.id}/spec.md (${created.length} requirement(s))`);\n say(` write the narrative above '## Requirements' — loam merges requirements, not prose`);\n continue;\n }\n const livingText = await readUtf8(livingPath);\n // The same one-section invariant as the service axis above, and the same\n // reason it cannot be guessed at.\n const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));\n if (reqHeadings.length > 1) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `living capabilities/${doc.id}/spec.md has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(\", \")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`,\n );\n }\n const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);\n writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));\n const c = summarize(deltaReqs);\n say(` capability: ${doc.id} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);\n }\n if (adopting && creating) {\n say(` capabilities/ did not exist — this archive opts the fleet into the business axis, and \\`loam validate --all\\` grades it from now on`);\n }\n}\n\n/**\n * Rewrite ONLY the requirements run of a living spec. Byte-for-byte preserved:\n * everything before the first requirement inside `## Requirements` (the intro,\n * the heading line, prose under the heading) and everything from the section's\n * end onward (the next `## ` heading to EOF) — the old cut was a substring\n * `indexOf(\"\\n## Requirements\")`, a prefix match that also hit\n * `## Requirements Extra` and silently destroyed every section after the\n * requirements. Prose BETWEEN requirements is body text of whatever is open\n * above it (parseRequirements attributes it to the previous requirement's last\n * scenario, or its text) and survives inside the re-serialized run, framing\n * normalized. `merged` must contain every living requirement — runArchive's\n * stray guard refuses any document whose requirements sit outside the section,\n * using the same heading definition, before this output is ever written.\n */\nfunction rewriteRequirementsRun(text: string, merged: Requirement[]): string {\n const s = splitRequirementsSection(text);\n // No `## Requirements` at all: the stray guard has already refused any doc\n // whose requirements live elsewhere, so this one has none — open the section.\n if (s === null) return `${text.trimEnd()}\\n\\n## Requirements\\n\\n${serializeRequirements(merged)}`;\n const body = serializeRequirements(merged);\n // head/tail are raw slices; only the run's own framing is normalized. When\n // the section held no requirements yet, the glue supplies the blank line the\n // author never had reason to write.\n const headGlue = s.run !== \"\" || s.head.endsWith(\"\\n\\n\") ? \"\" : s.head.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const tailGlue = s.tail === \"\" ? \"\" : \"\\n\";\n return s.head + headGlue + body + tailGlue + s.tail;\n}\n\nfunction summarize(reqs: Requirement[]): { ADDED: number; MODIFIED: number; REMOVED: number } {\n const c = { ADDED: 0, MODIFIED: 0, REMOVED: 0 };\n for (const r of reqs) {\n if (r.kind === \"ADDED\") c.ADDED += 1;\n else if (r.kind === \"MODIFIED\") c.MODIFIED += 1;\n else if (r.kind === \"REMOVED\") c.REMOVED += 1;\n }\n return c;\n}\n\n/**\n * The vocabulary merge: each `features/<FEAT>/glossary/<term>.md` copied to\n * `glossary/<term>.md`.\n *\n * A WHOLE-FILE COPY, and there is nothing else it could be. A capability delta\n * is folded into a living document by the requirement algebra because the two\n * documents share identified parts; a term document has one part, and merging\n * two definitions of one word is not something a machine may attempt. So the\n * definition the author wrote in the feature IS the definition that lands,\n * verbatim — loam re-encodes it as UTF-8 through `readUtf8` and changes nothing\n * else, not even the trailing newline.\n *\n * CREATE-ONLY, which is why this loop has no other branch. A term the living\n * glossary already defines was refused at the gate (`glossary.term-exists`,\n * `core/glossary/delta.ts` has the reasoning), so by the time the plan runs\n * every path here is a file that does not exist. `planWrite` marks it\n * `exclusive`, so a race that created it between the gate and the commit fails\n * the write rather than overwriting somebody.\n *\n * AND IT SAYS SO OUT LOUD when it creates `glossary/`, exactly as\n * `planCapabilities` does for `capabilities/`: the directory's existence is the\n * axis's opt-in, so this merge changes what `loam validate --all` grades from\n * the next command on, and a merge that widens the fleet's own gate must not do\n * it silently.\n */\nexport async function planGlossary(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const glossary = await featureGlossary(gated.featureDir);\n if (!glossary.present || glossary.terms.length === 0) return;\n // Asked BEFORE the loop, because the loop is what would create it.\n const adopting = !existsSync(glossaryDir(config.docsDir));\n for (const term of glossary.terms) {\n plan.writes.push(planWrite(livingTermPath(config.docsDir, term.id), await readUtf8(term.path)));\n say(` glossary: ${term.id} — created glossary/${term.id}.md`);\n }\n if (adopting) {\n say(` glossary/ did not exist — this archive opts the fleet into the domain vocabulary, and \\`loam validate --all\\` grades it from now on`);\n }\n}\n"]}
1
+ {"version":3,"file":"specs.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/specs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The requirements merge: the feature's ADDED/MODIFIED/REMOVED requirements\n * applied into each living service spec. The contract merges live beside it in\n * `./contracts/` — one file per axis, split out when the OpenAPI half made\n * this one carry two merges.\n *\n * ONE code path for the pair of requirement-carrying files — the business\n * spec.md and the architecture arch.spec.md ride the same delta algebra, the\n * same prose-preserving rewrite and the same guards, parameterized by filename.\n * A fork here would be two places the merge could disagree.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { rebaseLinks } from \"../../../core/links/parse.js\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { featureSpecPaths, SPEC_AXES } from \"../../../core/repo/paths.js\";\nimport { capabilityDocsDir, glossaryDir, livingCapabilityPaths } from \"../../../core/repo/authored/paths.js\";\nimport { featureGlossary, livingTermPath } from \"../../../core/glossary/delta.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport {\n isRequirementsHeading,\n parseRequirements,\n sectionHeadings,\n splitRequirementsSection,\n} from \"../../../core/document/parse.js\";\nimport { applyRequirementDelta } from \"../../../core/document/apply.js\";\nimport { serializeRequirements, type Requirement } from \"../../../core/document/spec.js\";\nimport { movedUnderVouch, parseFrontmatter, stringField } from \"../../../core/document/frontmatter.js\";\nimport { CodedFailure } from \"../../../core/envelope/json.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function planSpecs(\n config: { docsDir: DocsDir; fleet: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes } = plan;\n // Requirements merge — apply ADDED/MODIFIED/REMOVED into each living service\n // spec. ONE code path for the pair of requirement-carrying files: the business\n // spec.md and the architecture arch.spec.md ride the same delta algebra, the\n // same prose-preserving rewrite and the same guards, parameterized by filename\n // (SPEC_AXES) — a fork here would be two places the merge could disagree.\n for (const svc of deltaServices) {\n for (const axis of SPEC_AXES) {\n const deltaPath = featureSpecPaths(featureDir, svc)[axis.key];\n if (!existsSync(deltaPath)) continue;\n\n // The shared context: this is a deltaServices × SPEC_AXES loop, and a\n // context-less locate is a full fleet walk per iteration.\n const livingPath = (await locateServicePaths(config.docsDir, svc, config.fleet))[axis.key];\n // Resolved BEFORE the delta is parsed, because the parse now depends on\n // it: a requirement's markdown links are relative to the file they were\n // written in, and this merge moves the text two directories up the tree.\n // `core/links/parse.ts`'s `rebaseLinks` states the defect; the ordering\n // here is what lets the fix see both ends of the move, including a\n // service filed under a subsystem, whose living directory is deeper still.\n const deltaReqs = parseRequirements(\n rebaseLinks(await readUtf8(deltaPath), { from: dirname(deltaPath), to: dirname(livingPath) }),\n );\n if (!existsSync(livingPath)) {\n // New service (or first arch spec) — create the living file from the\n // ADDED/MODIFIED requirements.\n const created = applyRequirementDelta([], deltaReqs);\n if (created.length === 0) {\n say(` ${axis.label}: ${svc} — nothing to merge (delta leaves no requirements), no living ${axis.file} created`);\n continue;\n }\n const heading = axis.key === \"spec\" ? svc : `${svc} — architecture`;\n const frontmatter = `---\\nservice: ${svc}\\nstatus: draft\\n---\\n\\n# ${heading}\\n\\n`;\n writes.push(planWrite(livingPath, `${frontmatter}## Requirements\\n\\n${serializeRequirements(created)}`));\n say(` ${axis.label}: ${svc} — created living ${axis.file} (${created.length} requirement(s))`);\n continue;\n }\n const livingText = await readUtf8(livingPath);\n // TWO `## Requirements` headings would put the rewrite's one-section\n // invariant to a choice it must not make: the run of the first would be\n // rewritten while the second survived verbatim in the tail — and its\n // requirements, collected by parseRequirements, would land in the run TOO.\n // Mechanical, like a model-less landscape, so merge-failed, not --approve.\n const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));\n if (reqHeadings.length > 1) {\n throw new CodedFailure(\n \"merge-failed\",\n `living ${axis.file} for ${svc} has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(\", \")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`,\n );\n }\n const livingReqs = parseRequirements(livingText);\n const merged = applyRequirementDelta(livingReqs, deltaReqs);\n const next = rewriteRequirementsRun(livingText, livingReqs, merged);\n writes.push(planWrite(livingPath, next));\n // A merge is an edit to the document's body like any other, so a vouched\n // spec it moves goes `content.stale` — and honestly: the merged text is\n // words no person has yet read against the code. Archive does not soften\n // that. It names the document and its owner, so the re-vouch is known to\n // be owed while the merge is still a PR rather than discovered later as a\n // red `--strict` gate. Asked of the bytes about to be written, through the\n // one definition validate grades with.\n const fm = parseFrontmatter(next);\n if (movedUnderVouch(fm, next)) {\n plan.revouchOwed.push({ service: svc, path: livingPath, owner: stringField(fm, \"owner\") ?? null });\n }\n\n const c = summarize(deltaReqs);\n say(` ${axis.label}: ${svc} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);\n }\n }\n}\n\n/**\n * The BUSINESS corpus's merge: a feature's `capabilities/<id>/spec.md` deltas\n * applied into the living `capabilities/<id>/spec.md`.\n *\n * Here rather than in a sixth file in this package because this module already\n * owns \"requirements merged into a living document\" — it owns\n * `rewriteRequirementsRun`, the two-headings refusal and the `applyRequirementDelta`\n * call, and the business corpus needs all three unchanged. What differs from\n * `planSpecs` above is only where the living document lives and what a CREATED\n * one says at the top.\n *\n * ARCHIVE CREATES THE LIVING DOCUMENT, by exact analogy with the new-service\n * branch above. The directory IS the list on this axis (`core/capabilities/tree.ts`),\n * so an archive that merged into a file it refused to create would make the\n * whole delta merge nothing — which is the `delta.no-delta-sections` failure\n * class, arriving through the merge instead of through the grammar.\n *\n * NO FRONTMATTER on a created capability document, and that is a decision\n * rather than an omission. Nothing reads a capability document's frontmatter:\n * `readCapabilityTree` classifies by the file's PRESENCE and\n * `readCapabilityVocabulary` takes `description`/`owner` from the YAML side. A\n * `status:` field invented here would be the \"second list nothing keeps\n * current\" this axis was designed against.\n *\n * AND IT SAYS SO OUT LOUD. Creating the first `capabilities/<id>/spec.md`\n * creates `capabilities/`, which is one of the two ways a fleet opts INTO the\n * capability axis — from the next command on, `loam validate --all` grades a\n * corpus that did not exist before. A merge that changes what the whole fleet\n * is graded on must not do it silently.\n */\nexport async function planCapabilities(\n config: { docsDir: DocsDir; fleet: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { writes } = plan;\n // Whether `capabilities/` exists is asked BEFORE the loop, because the loop\n // is what would create it; `creating` records whether this run actually\n // materialises a document, so the adoption line below is not printed by a\n // run whose every capability delta merged into a document already there.\n const adopting = !existsSync(capabilityDocsDir(config.docsDir));\n let creating = false;\n for (const doc of gated.capabilityDeltas) {\n const livingPath = livingCapabilityPaths(config.docsDir, doc.id).spec;\n // Same move, same rewrite: `features/<FEAT>/capabilities/<id>/` is two\n // directories deeper than `capabilities/<id>/`, so a citation of a glossary\n // term or an ADR would land pointing above the repository.\n const deltaReqs = parseRequirements(\n rebaseLinks(await readUtf8(doc.spec), { from: dirname(doc.spec), to: dirname(livingPath) }),\n );\n if (!existsSync(livingPath)) {\n const created = applyRequirementDelta([], deltaReqs);\n if (created.length === 0) {\n say(` capability: ${doc.id} — nothing to merge (delta leaves no requirements), no living spec.md created`);\n continue;\n }\n // The heading is the id and nothing else. loam merges requirements and\n // never prose, so it must not fabricate the narrative that is the whole\n // point of the document — the line below tells the author it is theirs.\n writes.push(planWrite(livingPath, `# ${doc.id}\\n\\n## Requirements\\n\\n${serializeRequirements(created)}`));\n creating = true;\n say(` capability: ${doc.id} — created capabilities/${doc.id}/spec.md (${created.length} requirement(s))`);\n say(` write the narrative above '## Requirements' — loam merges requirements, not prose`);\n continue;\n }\n const livingText = await readUtf8(livingPath);\n // The same one-section invariant as the service axis above, and the same\n // reason it cannot be guessed at.\n const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));\n if (reqHeadings.length > 1) {\n throw new CodedFailure(\n \"merge-failed\",\n `living capabilities/${doc.id}/spec.md has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(\", \")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`,\n );\n }\n const livingReqs = parseRequirements(livingText);\n const merged = applyRequirementDelta(livingReqs, deltaReqs);\n writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, livingReqs, merged)));\n const c = summarize(deltaReqs);\n say(` capability: ${doc.id} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);\n }\n if (adopting && creating) {\n say(` capabilities/ did not exist — this archive opts the fleet into the business axis, and \\`loam validate --all\\` grades it from now on`);\n }\n}\n\n/**\n * Rewrite ONLY what the delta touched inside a living spec's requirements run.\n * Byte-for-byte preserved: everything before the first requirement inside\n * `## Requirements` (the intro, the heading line, prose under the heading) and\n * everything from the section's end onward (the next `## ` heading to EOF) —\n * the old cut was a substring `indexOf(\"\\n## Requirements\")`, a prefix match\n * that also hit `## Requirements Extra` and silently destroyed every section\n * after the requirements. `living` must be every living requirement — the gate's\n * stray guard refuses any document whose requirements sit outside the section,\n * using the same heading definition, before this output is ever written.\n *\n * Inside the run the unit is one requirement, and an untouched one is written\n * back as its raw slice (`spliceRun`). The whole run used to be re-serialized,\n * and `serializeRequirements` writes a blank line under every\n * `### Requirement:` heading — so a living spec written without one, the shape\n * the adopt brief's own examples taught, had that line inserted under every\n * requirement the feature never mentioned. The diff, the vouch pack and the\n * re-vouch all widened from the delta to the whole section.\n *\n * Exported for test/archive.test.ts alone: the slice-to-requirement join\n * (`verbatimSlices`) can only be handed a disagreeing pair directly, because\n * the gate refuses every document that would produce one through the CLI.\n */\nexport function rewriteRequirementsRun(text: string, living: Requirement[], merged: Requirement[]): string {\n const s = splitRequirementsSection(text);\n // No `## Requirements` at all: the stray guard has already refused any doc\n // whose requirements live elsewhere, so this one has none — open the section.\n if (s === null) return `${text.trimEnd()}\\n\\n## Requirements\\n\\n${serializeRequirements(merged)}`;\n if (s.run !== \"\") return s.head + spliceRun(s, living, merged) + s.tail;\n // The section held no requirements yet: head and tail are raw slices, and the\n // glue supplies the blank lines the author never had reason to write.\n const headGlue = s.head.endsWith(\"\\n\\n\") ? \"\" : s.head.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const tailGlue = s.tail === \"\" ? \"\" : \"\\n\";\n return s.head + headGlue + serializeRequirements(merged) + tailGlue + s.tail;\n}\n\n/** The line breaks (and blank lines) a slice ends with — what separates it from whatever follows. */\nconst TRAILING_BREAKS = /(?:\\r?\\n[ \\t]*)*$/;\n\n/**\n * The merged run: every living requirement the delta left alone as its raw\n * slice, every one it added or replaced serialized, in `merged`'s order.\n *\n * Keyed on object identity, which `applyRequirementDelta` keeps for exactly the\n * requirements it did not touch — so there is no second reading here of what\n * the delta changed, and no way for the splice and the algebra to disagree.\n * Which slice belongs to which living requirement is `verbatimSlices`' join.\n *\n * Prose BETWEEN requirements rides in the slice of the requirement above it,\n * which is where parseRequirements attributes it too: it survives verbatim\n * beside an untouched requirement and goes with a replaced one, as it always\n * did — a MODIFIED carries its full new text.\n */\nfunction spliceRun(\n section: { run: string; requirements: string[] },\n living: Requirement[],\n merged: Requirement[],\n): string {\n const verbatim = verbatimSlices(section.requirements, living);\n let out = \"\";\n let previousVerbatim = true;\n for (const requirement of merged) {\n const raw = verbatim.get(requirement);\n // Two untouched neighbours keep whatever the author put between them. Next\n // to a serialized requirement the canonical blank line is ADDED after the\n // untouched text, never by rewriting it.\n if (out !== \"\" && (raw === undefined || !previousVerbatim)) out = withBlankLine(out);\n out += raw ?? serializeRequirements([requirement]);\n previousVerbatim = raw !== undefined;\n }\n if (out === \"\") return \"\";\n // The run ends as it ended: whatever separated the old last requirement from\n // the next heading (or EOF) now follows the new last one — and at least a\n // newline when a merged requirement lands where the file used to stop without one.\n const ending = section.run.slice(section.run.search(TRAILING_BREAKS));\n const body = out.slice(0, out.search(TRAILING_BREAKS));\n return body + (ending === \"\" && merged.at(-1) !== living.at(-1) ? \"\\n\" : ending);\n}\n\n/** A requirement's identity as the merge selects it: `Requirement-ID:` when authored, the exact heading otherwise. */\nconst identity = (r: Pick<Requirement, \"id\" | \"name\">): string => (r.id === undefined ? `name:${r.name}` : `id:${r.id}`);\n\n/**\n * Each living requirement's raw slice, joined on its IDENTITY — the slice read\n * back through the same parser that produced `living`, so a slice can only\n * land in place of the requirement it actually is.\n *\n * The join used to be by position, guarded by a count. The two walks agree in\n * order today (one heading-and-fence definition, core/document/parse.ts, and\n * the gate's stray guard refusing any requirement outside the section), but\n * nothing declared that, and equal counts paired differently would have written\n * one requirement's bytes where another belongs — under its vouch stamp, with\n * nothing to say so.\n *\n * Fails closed to the empty map, which serializes every requirement — the old\n * whole-run rewrite, still a correct merge and only a wider diff: when the\n * counts differ, when a slice does not read as exactly one requirement, when\n * two slices claim one identity, or when a living requirement finds no slice\n * of its own.\n */\nfunction verbatimSlices(slices: string[], living: Requirement[]): Map<Requirement, string> {\n const none = new Map<Requirement, string>();\n if (slices.length !== living.length) return none;\n const byIdentity = new Map<string, string>();\n for (const slice of slices) {\n const [only, ...more] = parseRequirements(slice);\n if (only === undefined || more.length > 0 || byIdentity.has(identity(only))) return none;\n byIdentity.set(identity(only), slice);\n }\n const verbatim = new Map<Requirement, string>();\n for (const requirement of living) {\n const slice = byIdentity.get(identity(requirement));\n if (slice === undefined) return none;\n // Taken once: a second living requirement of the same identity finds nothing.\n byIdentity.delete(identity(requirement));\n verbatim.set(requirement, slice);\n }\n return verbatim;\n}\n\n/** `text` ending in a blank line: line breaks appended to make one, nothing rewritten. */\nfunction withBlankLine(text: string): string {\n const breaks = text.slice(text.search(TRAILING_BREAKS)).split(\"\\n\").length - 1;\n return breaks >= 2 ? text : text + \"\\n\".repeat(2 - breaks);\n}\n\nfunction summarize(reqs: Requirement[]): { ADDED: number; MODIFIED: number; REMOVED: number } {\n const c = { ADDED: 0, MODIFIED: 0, REMOVED: 0 };\n for (const r of reqs) {\n if (r.kind === \"ADDED\") c.ADDED += 1;\n else if (r.kind === \"MODIFIED\") c.MODIFIED += 1;\n else if (r.kind === \"REMOVED\") c.REMOVED += 1;\n }\n return c;\n}\n\n/**\n * The vocabulary merge: each `features/<FEAT>/glossary/<term>.md` copied to\n * `glossary/<term>.md`.\n *\n * A WHOLE-FILE COPY, and there is nothing else it could be. A capability delta\n * is folded into a living document by the requirement algebra because the two\n * documents share identified parts; a term document has one part, and merging\n * two definitions of one word is not something a machine may attempt. So the\n * definition the author wrote in the feature IS the definition that lands,\n * verbatim — loam re-encodes it as UTF-8 through `readUtf8` and changes nothing\n * else, not even the trailing newline.\n *\n * CREATE-ONLY, which is why this loop has no other branch. A term the living\n * glossary already defines was refused at the gate (`glossary.term-exists`,\n * `core/glossary/delta.ts` has the reasoning), so by the time the plan runs\n * every path here is a file that does not exist. `planWrite` marks it\n * `exclusive`, so a race that created it between the gate and the commit fails\n * the write rather than overwriting somebody.\n *\n * AND IT SAYS SO OUT LOUD when it creates `glossary/`, exactly as\n * `planCapabilities` does for `capabilities/`: the directory's existence is the\n * axis's opt-in, so this merge changes what `loam validate --all` grades from\n * the next command on, and a merge that widens the fleet's own gate must not do\n * it silently.\n */\nexport async function planGlossary(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const glossary = await featureGlossary(gated.featureDir);\n if (!glossary.present || glossary.terms.length === 0) return;\n // Asked BEFORE the loop, because the loop is what would create it.\n const adopting = !existsSync(glossaryDir(config.docsDir));\n for (const term of glossary.terms) {\n plan.writes.push(planWrite(livingTermPath(config.docsDir, term.id), await readUtf8(term.path)));\n say(` glossary: ${term.id} — created glossary/${term.id}.md`);\n }\n if (adopting) {\n say(` glossary/ did not exist — this archive opts the fleet into the domain vocabulary, and \\`loam validate --all\\` grades it from now on`);\n }\n}\n"]}
@@ -22,6 +22,7 @@ export function emptyPlan() {
22
22
  openapiRemovals: [],
23
23
  asyncapiRemovals: [],
24
24
  architectureServices: new Set(),
25
+ revouchOwed: [],
25
26
  };
26
27
  }
27
28
  //# sourceMappingURL=state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAc,MAAM,mCAAmC,CAAC;AAC/D,OAAO,EAAqB,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAmB,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAwB,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAkB,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAsB,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAuB,MAAM,sCAAsC,CAAC;AA6C3E,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,eAAe,EAAE,EAAE;QACnB,gBAAgB,EAAE,EAAE;QACpB,oBAAoB,EAAE,IAAI,GAAG,EAAmB;KACjD,CAAC;AACJ,CAAC","sourcesContent":["/**\n * What one phase of an archive hands the next.\n *\n * `Gated` is everything the refusals resolved on the way through — the\n * canonical id, the delta document parsed once, the issue lists. `Plan` is what\n * the merges accumulate. Neither is a bag of conveniences: a value is in here\n * exactly when two phases have to agree about it, and the alternative was\n * fifteen parameters threaded through three functions in the same order.\n */\nimport { type Issue } from \"../../../core/vocabulary/issue.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { type FeatureDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type CapabilityDoc } from \"../../../core/capabilities/tree.js\";\nimport { type CommitRecovery } from \"../../../core/staging/interrupted.js\";\n\nexport interface Gated {\n /** The canonical feature id. The raw argument does not survive the gate. */\n id: string;\n dirName: string;\n featureDir: FeatureDir;\n /** Parsed ONCE for the whole run — loading it spins up a fresh Langium workspace. */\n deltaDoc: LoadedDoc | undefined;\n deltaServices: PathableService[];\n /**\n * The capability documents this feature's delta carries, walked ONCE in the\n * gate. Three phases downstream ask about them — the conflict-marker scan,\n * the strayed-requirement scan and the merge — and a second walk is a second\n * chance to disagree about which capabilities the merge touches.\n */\n capabilityDeltas: CapabilityDoc[];\n gating: Issue[];\n advisory: Issue[];\n archiveDir: string;\n archiveDest: string;\n /** The interrupted commit this run repaired, if any — `--json` reports it. */\n recovered: CommitRecovery | null;\n /** Every coherence issue the gate read, gating and advisory together. */\n issues: Issue[];\n}\n\nexport interface Plan {\n writes: PlannedWrite[];\n /**\n * Warnings born in the plan itself (openapi.op-modified,\n * openapi.component-modified, the asyncapi.*-modified trio) — printed with\n * the plan and carried into the `--json` envelope beside the coherence\n * warnings.\n */\n planWarns: Issue[];\n /** Gating issues born in the plan itself (openapi.ref-unresolved, asyncapi.ref-unresolved). */\n planGates: Issue[];\n openapiRemovals: Array<{ service: string; operations: string[] }>;\n /** Living asyncapi slots the plan deletes on removal markers, by their labels. */\n asyncapiRemovals: Array<{ service: string; slots: string[] }>;\n /** Services this feature introduces on the architecture axis alone. */\n architectureServices: Set<PathableService>;\n}\n\nexport function emptyPlan(): Plan {\n return {\n writes: [],\n planWarns: [],\n planGates: [],\n openapiRemovals: [],\n asyncapiRemovals: [],\n architectureServices: new Set<PathableService>(),\n };\n}\n"]}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAc,MAAM,mCAAmC,CAAC;AAC/D,OAAO,EAAqB,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAmB,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAwB,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAkB,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAsB,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAuB,MAAM,sCAAsC,CAAC;AAqD3E,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,eAAe,EAAE,EAAE;QACnB,gBAAgB,EAAE,EAAE;QACpB,oBAAoB,EAAE,IAAI,GAAG,EAAmB;QAChD,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * What one phase of an archive hands the next.\n *\n * `Gated` is everything the refusals resolved on the way through — the\n * canonical id, the delta document parsed once, the issue lists. `Plan` is what\n * the merges accumulate. Neither is a bag of conveniences: a value is in here\n * exactly when two phases have to agree about it, and the alternative was\n * fifteen parameters threaded through three functions in the same order.\n */\nimport { type Issue } from \"../../../core/vocabulary/issue.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { type FeatureDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type CapabilityDoc } from \"../../../core/capabilities/tree.js\";\nimport { type CommitRecovery } from \"../../../core/staging/interrupted.js\";\n\nexport interface Gated {\n /** The canonical feature id. The raw argument does not survive the gate. */\n id: string;\n dirName: string;\n featureDir: FeatureDir;\n /** Parsed ONCE for the whole run — loading it spins up a fresh Langium workspace. */\n deltaDoc: LoadedDoc | undefined;\n deltaServices: PathableService[];\n /**\n * The capability documents this feature's delta carries, walked ONCE in the\n * gate. Three phases downstream ask about them — the conflict-marker scan,\n * the strayed-requirement scan and the merge — and a second walk is a second\n * chance to disagree about which capabilities the merge touches.\n */\n capabilityDeltas: CapabilityDoc[];\n gating: Issue[];\n advisory: Issue[];\n archiveDir: string;\n archiveDest: string;\n /** The interrupted commit this run repaired, if any — `--json` reports it. */\n recovered: CommitRecovery | null;\n /** Every coherence issue the gate read, gating and advisory together. */\n issues: Issue[];\n}\n\nexport interface Plan {\n writes: PlannedWrite[];\n /**\n * Warnings born in the plan itself (openapi.op-modified,\n * openapi.component-modified, the asyncapi.*-modified trio) — printed with\n * the plan and carried into the `--json` envelope beside the coherence\n * warnings.\n */\n planWarns: Issue[];\n /** Gating issues born in the plan itself (openapi.ref-unresolved, asyncapi.ref-unresolved). */\n planGates: Issue[];\n openapiRemovals: Array<{ service: string; operations: string[] }>;\n /** Living asyncapi slots the plan deletes on removal markers, by their labels. */\n asyncapiRemovals: Array<{ service: string; slots: string[] }>;\n /** Services this feature introduces on the architecture axis alone. */\n architectureServices: Set<PathableService>;\n /**\n * Every vouched living spec this merge leaves out of step with its vouch\n * stamp — `content.stale` follows on each until its owner re-vouches.\n * `path` is absolute, like every planned write's, and made docs-relative\n * where it is printed; `owner` is the frontmatter's, or null when the\n * document names none. The `--json` payload carries it as `revouchOwed`.\n */\n revouchOwed: Array<{ service: string; path: string; owner: string | null }>;\n}\n\nexport function emptyPlan(): Plan {\n return {\n writes: [],\n planWarns: [],\n planGates: [],\n openapiRemovals: [],\n asyncapiRemovals: [],\n architectureServices: new Set<PathableService>(),\n revouchOwed: [],\n };\n}\n"]}
@@ -10,10 +10,11 @@
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
12
  import { mkdir, rename } from "node:fs/promises";
13
- import { emitJson, repoPath, sayExplain } from "../../core/envelope/json.js";
13
+ import { CodedFailure, emitJson, repoPath, sayExplain } from "../../core/envelope/json.js";
14
14
  import { message, quietPruneEmptyParents, quietRm, rollbackError, rollbackStaged, stageWrites, swapStaged, } from "../../core/staging/commit.js";
15
15
  import { clearCommitIntent, writeCommitIntent } from "../../core/staging/recovery/intent.js";
16
- import { SNAPSHOT_DIR, SnapshotClobberError, snapshotDir, writeSnapshot, } from "../../core/staging/snapshot.js";
16
+ import { SNAPSHOT_DIR, snapshotDir } from "../../core/staging/snapshot/manifest.js";
17
+ import { SnapshotClobberError, writeSnapshot } from "../../core/staging/snapshot/snapshot.js";
17
18
  import { enumeratedServices } from "../../core/repo/service-target.js";
18
19
  import { listFleetTree } from "../../core/repo/repo.js";
19
20
  import { serviceViewsPath, subsystemViewsPath } from "../../core/repo/paths.js";
@@ -21,15 +22,14 @@ import { serviceViewsState, viewsState } from "../../core/repo/tree/render/stale
21
22
  import { loadArchitecture } from "../../core/c4/project/architecture.js";
22
23
  import { FleetContext } from "../../core/fleet-context.js";
23
24
  import { gate } from "./plan/gate.js";
24
- import {} from "./plan/refusal.js";
25
+ import { issueJson, printPlan, refuseJson } from "./plan/refusal.js";
25
26
  import { planCapabilities, planGlossary, planSpecs } from "./plan/specs.js";
26
27
  import { planOpenapiContracts } from "./plan/contracts/openapi.js";
27
28
  import { planAsyncapiContracts } from "./plan/contracts/asyncapi.js";
29
+ import { contractConsumers } from "./plan/contracts/consumers.js";
28
30
  import { planDeployments, planFlows, planLandscape } from "./plan/landscape.js";
29
31
  import { emptyPlan } from "./plan/state.js";
30
- import { issueJson, refuseJson } from "./plan/refusal.js";
31
- import { ArchiveFailure } from "./plan/refusal.js";
32
- import { printPlan } from "./plan/refusal.js";
32
+ import { checklistAtArchive } from "./shipped.js";
33
33
  export async function archiveLocked(config, featureId, opts) {
34
34
  const dryRun = opts.dryRun === true;
35
35
  const json = opts.json === true;
@@ -63,8 +63,12 @@ export async function archiveLocked(config, featureId, opts) {
63
63
  // a create-only whole-file copy, refused at the gate if the word already
64
64
  // lives (`core/glossary/delta.ts`).
65
65
  await planGlossary(read, gated, planned, say);
66
- await planOpenapiContracts(read, gated, planned, say);
67
- await planAsyncapiContracts(read, gated, planned, say);
66
+ // One consumer index for both contract planners, built only if one of them
67
+ // overwrites an operation or a message (./plan/contracts/consumers.ts) —
68
+ // and then over the living fleet as it stands before this merge.
69
+ const contracts = { ...read, consumers: contractConsumers(read) };
70
+ await planOpenapiContracts(contracts, gated, planned, say);
71
+ await planAsyncapiContracts(contracts, gated, planned, say);
68
72
  // `read`, not `config`: the C4 merge now asks which services own their own
69
73
  // interior, and that answer comes from the fleet enumeration this run has
70
74
  // already paid for.
@@ -77,6 +81,7 @@ export async function archiveLocked(config, featureId, opts) {
77
81
  await planFlows(config, gated, planned, say);
78
82
  await planDeployments(config, gated, planned, say);
79
83
  const { writes, planWarns, planGates, openapiRemovals, asyncapiRemovals } = planned;
84
+ const revouchOwed = planned.revouchOwed.map((r) => ({ ...r, path: repoPath(config.docsDir, r.path) }));
80
85
  // Gate on what only the plan could see: a merged operation pointing at a
81
86
  // component that exists nowhere, a removal marker addressing no operation.
82
87
  // Same doctrine as the coherence gate — a judgment call --approve overrides
@@ -102,6 +107,17 @@ export async function archiveLocked(config, featureId, opts) {
102
107
  for (const i of planGates)
103
108
  say(` ✗ ${i.message}`);
104
109
  }
110
+ // Printed by the dry run and the real merge alike, before either commits to
111
+ // anything: the merge is what moves these documents, and the merge's PR is
112
+ // the place their re-vouches belong. Not a finding and not a refusal — the
113
+ // staleness that follows is honest, and `content.stale` is what grades it.
114
+ if (revouchOwed.length > 0) {
115
+ say(`\n re-vouch owed — this merge moves ${revouchOwed.length} vouched spec(s) under the vouch stamp, so content.stale follows on each until its owner re-vouches:`);
116
+ for (const r of revouchOwed) {
117
+ say(` ${r.path} (owner: ${r.owner ?? "none recorded"}) — \`loam vouch --service ${r.service}\`, run in that service's repo`);
118
+ }
119
+ say(` land the re-vouches in this PR: \`loam validate --all --base <ref> --strict\` stays red on it until they do.`);
120
+ }
105
121
  // The plan as data, verb decided now — after the commit everything would read
106
122
  // as an update. The `--json` payload is identical for a dry run and the real
107
123
  // thing except for `archived`; what WOULD happen is what DOES happen.
@@ -122,6 +138,7 @@ export async function archiveLocked(config, featureId, opts) {
122
138
  overridden: overridden.map((issue) => issueJson(issue, repoPath(config.docsDir, archiveDest))),
123
139
  openapiRemovals,
124
140
  asyncapiRemovals,
141
+ revouchOwed,
125
142
  // Present only when this run found an interrupted commit and dealt with it:
126
143
  // an agent that sees the docs change under it deserves to be told why, and
127
144
  // the absent field is the ordinary case.
@@ -134,6 +151,10 @@ export async function archiveLocked(config, featureId, opts) {
134
151
  printPlan(config.docsDir, writes, dirName);
135
152
  return;
136
153
  }
154
+ // The question the feature asks as it ships, captured before anything below
155
+ // moves the living docs it is derived from — see ./shipped.ts. It rides in
156
+ // the snapshot manifest; a dry run writes no snapshot, so it pays nothing.
157
+ const checklist = await checklistAtArchive(read, gated);
137
158
  // COMMIT — the whole plan computed cleanly. Stage every new version beside its
138
159
  // target, snapshot what is about to be overwritten, then swap them into place.
139
160
  // Staging touches the filesystem — a read-only `architecture/`, a full disk,
@@ -145,7 +166,7 @@ export async function archiveLocked(config, featureId, opts) {
145
166
  staged = await stageWrites(writes);
146
167
  }
147
168
  catch (err) {
148
- throw new ArchiveFailure("merge-failed", `${message(err)} — nothing was written`);
169
+ throw new CodedFailure("merge-failed", `${message(err)} — nothing was written`);
149
170
  }
150
171
  let snapshot = false;
151
172
  let createdArchiveDir;
@@ -181,6 +202,7 @@ export async function archiveLocked(config, featureId, opts) {
181
202
  staged,
182
203
  serviceKeyOf,
183
204
  serviceDirOf,
205
+ ...(checklist === undefined ? {} : { checklist }),
184
206
  });
185
207
  snapshot = true;
186
208
  // The journal, fsynced, BEFORE the first rename: swapStaged is N renames and
@@ -202,7 +224,7 @@ export async function archiveLocked(config, featureId, opts) {
202
224
  // the answer is not "re-run": a previous archive of this feature is still
203
225
  // sitting in the living docs.
204
226
  await rollbackStaged(staged);
205
- throw new ArchiveFailure("commit-interrupted", err.message);
227
+ throw new CodedFailure("commit-interrupted", err.message);
206
228
  }
207
229
  // Everything this run made, unmade: the swapped files, the snapshot inside the
208
230
  // feature that is staying put, and features/archive/ if we are the ones who
@@ -232,7 +254,7 @@ export async function archiveLocked(config, featureId, opts) {
232
254
  const kept = snapshot && failures.length > 0
233
255
  ? ` Pre-images of the overwritten files are kept in features/${dirName}/${SNAPSHOT_DIR}/files/.`
234
256
  : "";
235
- throw new ArchiveFailure(failures.length > 0 ? "rollback-incomplete" : "merge-failed", wrapped.message + kept);
257
+ throw new CodedFailure(failures.length > 0 ? "rollback-incomplete" : "merge-failed", wrapped.message + kept);
236
258
  }
237
259
  // This merge just changed the map, and both generated views files are a
238
260
  // function of the tree AND the map: a delta whose element lands inside a