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

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 (1026) hide show
  1. package/CHANGELOG.md +2548 -86
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +494 -0
  8. package/SCHEMA.md +2355 -256
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +317 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +161 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +251 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +72 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +194 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +236 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +250 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +219 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +334 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +47 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +282 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +88 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  398. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  399. package/dist/core/c4/parsed/specification.js +78 -0
  400. package/dist/core/c4/parsed/specification.js.map +1 -0
  401. package/dist/core/c4/parsed/values.js +57 -0
  402. package/dist/core/c4/parsed/values.js.map +1 -0
  403. package/dist/core/c4/parsed/view-ids.js +75 -0
  404. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  405. package/dist/core/c4/project/architecture.js +45 -0
  406. package/dist/core/c4/project/architecture.js.map +1 -0
  407. package/dist/core/c4/project/documents.js +58 -0
  408. package/dist/core/c4/project/documents.js.map +1 -0
  409. package/dist/core/c4/project/load.js +183 -0
  410. package/dist/core/c4/project/load.js.map +1 -0
  411. package/dist/core/c4/resolve/service.js +113 -0
  412. package/dist/core/c4/resolve/service.js.map +1 -0
  413. package/dist/core/c4/seed/fleet-file.js +201 -0
  414. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  415. package/dist/core/c4/seed/items.js +91 -0
  416. package/dist/core/c4/seed/items.js.map +1 -0
  417. package/dist/core/c4/seed/stamp.js +48 -0
  418. package/dist/core/c4/seed/stamp.js.map +1 -0
  419. package/dist/core/c4/seed/template.js +109 -0
  420. package/dist/core/c4/seed/template.js.map +1 -0
  421. package/dist/core/c4/source-mask.js +82 -0
  422. package/dist/core/c4/source-mask.js.map +1 -0
  423. package/dist/core/c4/source-scan.js +227 -0
  424. package/dist/core/c4/source-scan.js.map +1 -0
  425. package/dist/core/c4/splice/authored-source.js +101 -0
  426. package/dist/core/c4/splice/authored-source.js.map +1 -0
  427. package/dist/core/c4/splice/contract.js +10 -0
  428. package/dist/core/c4/splice/contract.js.map +1 -0
  429. package/dist/core/c4/splice/delta-blocks.js +126 -0
  430. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  431. package/dist/core/c4/splice/identity/edges.js +36 -0
  432. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  433. package/dist/core/c4/splice/landscape-merge.js +269 -0
  434. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  435. package/dist/core/c4/splice/placement.js +194 -0
  436. package/dist/core/c4/splice/placement.js.map +1 -0
  437. package/dist/core/c4/workspace.js +172 -0
  438. package/dist/core/c4/workspace.js.map +1 -0
  439. package/dist/core/capabilities/authoring/notes.js +141 -0
  440. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  441. package/dist/core/capabilities/capabilities.js +124 -0
  442. package/dist/core/capabilities/capabilities.js.map +1 -0
  443. package/dist/core/capabilities/delta/doc.js +50 -0
  444. package/dist/core/capabilities/delta/doc.js.map +1 -0
  445. package/dist/core/capabilities/delta/overlay.js +129 -0
  446. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  447. package/dist/core/capabilities/delta/summary.js +57 -0
  448. package/dist/core/capabilities/delta/summary.js.map +1 -0
  449. package/dist/core/capabilities/delta/tree.js +43 -0
  450. package/dist/core/capabilities/delta/tree.js.map +1 -0
  451. package/dist/core/capabilities/delta/uncovered.js +222 -0
  452. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  453. package/dist/core/capabilities/findings.js +342 -0
  454. package/dist/core/capabilities/findings.js.map +1 -0
  455. package/dist/core/capabilities/realizes/findings.js +181 -0
  456. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  457. package/dist/core/capabilities/realizes/join.js +143 -0
  458. package/dist/core/capabilities/realizes/join.js.map +1 -0
  459. package/dist/core/capabilities/rollup.js +199 -0
  460. package/dist/core/capabilities/rollup.js.map +1 -0
  461. package/dist/core/capabilities/tree.js +126 -0
  462. package/dist/core/capabilities/tree.js.map +1 -0
  463. package/dist/core/capabilities/usecase-join.js +277 -0
  464. package/dist/core/capabilities/usecase-join.js.map +1 -0
  465. package/dist/core/coherence/authoring/scaffold.js +177 -0
  466. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  467. package/dist/core/coherence/authoring/sentinels.js +74 -0
  468. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  469. package/dist/core/coherence/coherence.js +251 -0
  470. package/dist/core/coherence/coherence.js.map +1 -0
  471. package/dist/core/coherence/declared.js +269 -0
  472. package/dist/core/coherence/declared.js.map +1 -0
  473. package/dist/core/coherence/events/declared.js +206 -0
  474. package/dist/core/coherence/events/declared.js.map +1 -0
  475. package/dist/core/coherence/events/events.js +204 -0
  476. package/dist/core/coherence/events/events.js.map +1 -0
  477. package/dist/core/coherence/events/grades.js +166 -0
  478. package/dist/core/coherence/events/grades.js.map +1 -0
  479. package/dist/core/coherence/events/lookups.js +122 -0
  480. package/dist/core/coherence/events/lookups.js.map +1 -0
  481. package/dist/core/coherence/events/merged.js +40 -0
  482. package/dist/core/coherence/events/merged.js.map +1 -0
  483. package/dist/core/coherence/living.js +198 -0
  484. package/dist/core/coherence/living.js.map +1 -0
  485. package/dist/core/coherence/lookups.js +155 -0
  486. package/dist/core/coherence/lookups.js.map +1 -0
  487. package/dist/core/coherence/pending.js +39 -0
  488. package/dist/core/coherence/pending.js.map +1 -0
  489. package/dist/core/conflict-markers.js +60 -0
  490. package/dist/core/conflict-markers.js.map +1 -0
  491. package/dist/core/delta/claims.js +73 -0
  492. package/dist/core/delta/claims.js.map +1 -0
  493. package/dist/core/delta/delta.js +296 -0
  494. package/dist/core/delta/delta.js.map +1 -0
  495. package/dist/core/delta/document.js +177 -0
  496. package/dist/core/delta/document.js.map +1 -0
  497. package/dist/core/delta/scope.js +48 -0
  498. package/dist/core/delta/scope.js.map +1 -0
  499. package/dist/core/delta/select.js +237 -0
  500. package/dist/core/delta/select.js.map +1 -0
  501. package/dist/core/dependencies/dependencies.js +141 -0
  502. package/dist/core/dependencies/dependencies.js.map +1 -0
  503. package/dist/core/dependencies/facts.js +165 -0
  504. package/dist/core/dependencies/facts.js.map +1 -0
  505. package/dist/core/dependencies/fanin.js +88 -0
  506. package/dist/core/dependencies/fanin.js.map +1 -0
  507. package/dist/core/dependencies/graph.js +106 -0
  508. package/dist/core/dependencies/graph.js.map +1 -0
  509. package/dist/core/diff/base-git.js +148 -0
  510. package/dist/core/diff/base-git.js.map +1 -0
  511. package/dist/core/diff/base-state.js +209 -0
  512. package/dist/core/diff/base-state.js.map +1 -0
  513. package/dist/core/diff/current-state.js +96 -0
  514. package/dist/core/diff/current-state.js.map +1 -0
  515. package/dist/core/diff/scope/changed-paths.js +188 -0
  516. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  517. package/dist/core/diff/semantic.js +243 -0
  518. package/dist/core/diff/semantic.js.map +1 -0
  519. package/dist/core/diff/victims.js +247 -0
  520. package/dist/core/diff/victims.js.map +1 -0
  521. package/dist/core/docs.js +7 -64
  522. package/dist/core/docs.js.map +1 -1
  523. package/dist/core/doctor/agents.js +222 -0
  524. package/dist/core/doctor/agents.js.map +1 -0
  525. package/dist/core/doctor/config.js +135 -0
  526. package/dist/core/doctor/config.js.map +1 -0
  527. package/dist/core/doctor/doctor.js +275 -0
  528. package/dist/core/doctor/doctor.js.map +1 -0
  529. package/dist/core/doctor/report.js +13 -0
  530. package/dist/core/doctor/report.js.map +1 -0
  531. package/dist/core/doctor/residue.js +110 -0
  532. package/dist/core/doctor/residue.js.map +1 -0
  533. package/dist/core/document/apply.js +77 -0
  534. package/dist/core/document/apply.js.map +1 -0
  535. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  536. package/dist/core/document/frontmatter.js.map +1 -0
  537. package/dist/core/document/grammar/directives.js +143 -0
  538. package/dist/core/document/grammar/directives.js.map +1 -0
  539. package/dist/core/document/parse.js +263 -0
  540. package/dist/core/document/parse.js.map +1 -0
  541. package/dist/core/document/scenarios.js +221 -0
  542. package/dist/core/document/scenarios.js.map +1 -0
  543. package/dist/core/document/spec.js +241 -0
  544. package/dist/core/document/spec.js.map +1 -0
  545. package/dist/core/{config.js → envelope/config.js} +79 -18
  546. package/dist/core/envelope/config.js.map +1 -0
  547. package/dist/core/envelope/json.js +175 -0
  548. package/dist/core/envelope/json.js.map +1 -0
  549. package/dist/core/{version.js → envelope/version.js} +3 -3
  550. package/dist/core/envelope/version.js.map +1 -0
  551. package/dist/core/explain/families.js +175 -0
  552. package/dist/core/explain/families.js.map +1 -0
  553. package/dist/core/explain/fix-tables.js +178 -0
  554. package/dist/core/explain/fix-tables.js.map +1 -0
  555. package/dist/core/explain/lookup.js +181 -0
  556. package/dist/core/explain/lookup.js.map +1 -0
  557. package/dist/core/explain/refusals.js +49 -0
  558. package/dist/core/explain/refusals.js.map +1 -0
  559. package/dist/core/explain/terms.js +144 -0
  560. package/dist/core/explain/terms.js.map +1 -0
  561. package/dist/core/explore/describe.js +105 -0
  562. package/dist/core/explore/describe.js.map +1 -0
  563. package/dist/core/{explore.js → explore/explore.js} +50 -101
  564. package/dist/core/explore/explore.js.map +1 -0
  565. package/dist/core/fleet-context.js +120 -78
  566. package/dist/core/fleet-context.js.map +1 -1
  567. package/dist/core/gate/checks.js +201 -0
  568. package/dist/core/gate/checks.js.map +1 -0
  569. package/dist/core/gate/partners.js +205 -0
  570. package/dist/core/gate/partners.js.map +1 -0
  571. package/dist/core/gate/report.js +47 -0
  572. package/dist/core/gate/report.js.map +1 -0
  573. package/dist/core/gate/verification.js +113 -0
  574. package/dist/core/gate/verification.js.map +1 -0
  575. package/dist/core/gherkin/digest.js +60 -0
  576. package/dist/core/gherkin/digest.js.map +1 -0
  577. package/dist/core/gherkin/emit.js +177 -0
  578. package/dist/core/gherkin/emit.js.map +1 -0
  579. package/dist/core/gherkin/read.js +76 -0
  580. package/dist/core/gherkin/read.js.map +1 -0
  581. package/dist/core/gherkin/stale.js +234 -0
  582. package/dist/core/gherkin/stale.js.map +1 -0
  583. package/dist/core/gherkin/stamp.js +77 -0
  584. package/dist/core/gherkin/stamp.js.map +1 -0
  585. package/dist/core/gherkin/steps/catalogue.js +154 -0
  586. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  587. package/dist/core/gherkin/steps/inventory.js +98 -0
  588. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  589. package/dist/core/gherkin/steps/phrase.js +72 -0
  590. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  591. package/dist/core/glossary/delta.js +106 -0
  592. package/dist/core/glossary/delta.js.map +1 -0
  593. package/dist/core/glossary/tree.js +86 -0
  594. package/dist/core/glossary/tree.js.map +1 -0
  595. package/dist/core/kernel/concurrency.js.map +1 -0
  596. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  597. package/dist/core/kernel/document-bytes.js.map +1 -0
  598. package/dist/core/kernel/fences.js +42 -0
  599. package/dist/core/kernel/fences.js.map +1 -0
  600. package/dist/core/kernel/ids/capability.js +72 -0
  601. package/dist/core/kernel/ids/capability.js.map +1 -0
  602. package/dist/core/kernel/ids/dirs.js +33 -0
  603. package/dist/core/kernel/ids/dirs.js.map +1 -0
  604. package/dist/core/kernel/ids/feature.js +75 -0
  605. package/dist/core/kernel/ids/feature.js.map +1 -0
  606. package/dist/core/kernel/ids/service.js +156 -0
  607. package/dist/core/kernel/ids/service.js.map +1 -0
  608. package/dist/core/kernel/ids/subsystem.js +63 -0
  609. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  610. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  611. package/dist/core/kernel/path-safety.js.map +1 -0
  612. package/dist/core/kernel/records.js.map +1 -0
  613. package/dist/core/links/backlinks.js +68 -0
  614. package/dist/core/links/backlinks.js.map +1 -0
  615. package/dist/core/links/case.js +74 -0
  616. package/dist/core/links/case.js.map +1 -0
  617. package/dist/core/links/corpus.js +104 -0
  618. package/dist/core/links/corpus.js.map +1 -0
  619. package/dist/core/links/findings.js +110 -0
  620. package/dist/core/links/findings.js.map +1 -0
  621. package/dist/core/links/parse.js +236 -0
  622. package/dist/core/links/parse.js.map +1 -0
  623. package/dist/core/mcp/argv.js +111 -0
  624. package/dist/core/mcp/argv.js.map +1 -0
  625. package/dist/core/mcp/author-tools.js +72 -0
  626. package/dist/core/mcp/author-tools.js.map +1 -0
  627. package/dist/core/mcp/framing.js +46 -0
  628. package/dist/core/mcp/framing.js.map +1 -0
  629. package/dist/core/mcp/protocol.js +280 -0
  630. package/dist/core/mcp/protocol.js.map +1 -0
  631. package/dist/core/mcp/tools.js +314 -0
  632. package/dist/core/mcp/tools.js.map +1 -0
  633. package/dist/core/obligations/obligations.js +106 -0
  634. package/dist/core/obligations/obligations.js.map +1 -0
  635. package/dist/core/openapi/baseline/gate.js +166 -0
  636. package/dist/core/openapi/baseline/gate.js.map +1 -0
  637. package/dist/core/openapi/baseline/plan.js +112 -0
  638. package/dist/core/openapi/baseline/plan.js.map +1 -0
  639. package/dist/core/openapi/baseline/record.js +175 -0
  640. package/dist/core/openapi/baseline/record.js.map +1 -0
  641. package/dist/core/openapi/depth.js +122 -0
  642. package/dist/core/openapi/depth.js.map +1 -0
  643. package/dist/core/openapi/digest.js +189 -0
  644. package/dist/core/openapi/digest.js.map +1 -0
  645. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  646. package/dist/core/openapi/doc.js.map +1 -0
  647. package/dist/core/openapi/merge/components.js +234 -0
  648. package/dist/core/openapi/merge/components.js.map +1 -0
  649. package/dist/core/openapi/merge/error.js +26 -0
  650. package/dist/core/openapi/merge/error.js.map +1 -0
  651. package/dist/core/openapi/merge/markers.js +208 -0
  652. package/dist/core/openapi/merge/markers.js.map +1 -0
  653. package/dist/core/openapi/merge/merge.js +256 -0
  654. package/dist/core/openapi/merge/merge.js.map +1 -0
  655. package/dist/core/openapi/merge/pin.js +128 -0
  656. package/dist/core/openapi/merge/pin.js.map +1 -0
  657. package/dist/core/openspec/decide/capabilities.js +244 -0
  658. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  659. package/dist/core/openspec/decide/dispositions.js +63 -0
  660. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  661. package/dist/core/openspec/decide/renames.js +179 -0
  662. package/dist/core/openspec/decide/renames.js.map +1 -0
  663. package/dist/core/openspec/decide/verdict.js +84 -0
  664. package/dist/core/openspec/decide/verdict.js.map +1 -0
  665. package/dist/core/openspec/inventory.js +221 -0
  666. package/dist/core/openspec/inventory.js.map +1 -0
  667. package/dist/core/openspec/model/mapping.js +76 -0
  668. package/dist/core/openspec/model/mapping.js.map +1 -0
  669. package/dist/core/openspec/model/model.js +65 -0
  670. package/dist/core/openspec/model/model.js.map +1 -0
  671. package/dist/core/openspec/scan/changes.js +235 -0
  672. package/dist/core/openspec/scan/changes.js.map +1 -0
  673. package/dist/core/openspec/scan/config.js +200 -0
  674. package/dist/core/openspec/scan/config.js.map +1 -0
  675. package/dist/core/openspec/scan/shape.js +260 -0
  676. package/dist/core/openspec/scan/shape.js.map +1 -0
  677. package/dist/core/openspec/scan/walk.js +133 -0
  678. package/dist/core/openspec/scan/walk.js.map +1 -0
  679. package/dist/core/openspec/scan/workspace.js +71 -0
  680. package/dist/core/openspec/scan/workspace.js.map +1 -0
  681. package/dist/core/owners/codeowners.js +141 -0
  682. package/dist/core/owners/codeowners.js.map +1 -0
  683. package/dist/core/pack/features.js +120 -0
  684. package/dist/core/pack/features.js.map +1 -0
  685. package/dist/core/pack/joins.js +112 -0
  686. package/dist/core/pack/joins.js.map +1 -0
  687. package/dist/core/pack/living.js +118 -0
  688. package/dist/core/pack/living.js.map +1 -0
  689. package/dist/core/pack/pack.js +76 -0
  690. package/dist/core/pack/pack.js.map +1 -0
  691. package/dist/core/permissions/permissions.js +87 -0
  692. package/dist/core/permissions/permissions.js.map +1 -0
  693. package/dist/core/projection/api.js +84 -0
  694. package/dist/core/projection/api.js.map +1 -0
  695. package/dist/core/projection/arch-slice.js +63 -0
  696. package/dist/core/projection/arch-slice.js.map +1 -0
  697. package/dist/core/projection/events.js +34 -0
  698. package/dist/core/projection/events.js.map +1 -0
  699. package/dist/core/provenance/findings.js +208 -0
  700. package/dist/core/provenance/findings.js.map +1 -0
  701. package/dist/core/provenance/git.js +225 -0
  702. package/dist/core/provenance/git.js.map +1 -0
  703. package/dist/core/provenance/gitq/moves.js +182 -0
  704. package/dist/core/provenance/gitq/moves.js.map +1 -0
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  707. package/dist/core/provenance/sample/scope.js +238 -0
  708. package/dist/core/provenance/sample/scope.js.map +1 -0
  709. package/dist/core/provenance/sample/sections.js +61 -0
  710. package/dist/core/provenance/sample/sections.js.map +1 -0
  711. package/dist/core/provenance/sources.js +263 -0
  712. package/dist/core/provenance/sources.js.map +1 -0
  713. package/dist/core/provenance/stamp.js +232 -0
  714. package/dist/core/provenance/stamp.js.map +1 -0
  715. package/dist/core/provenance/walk.js +157 -0
  716. package/dist/core/provenance/walk.js.map +1 -0
  717. package/dist/core/repo/authored/paths.js +192 -0
  718. package/dist/core/repo/authored/paths.js.map +1 -0
  719. package/dist/core/repo/entries.js +126 -0
  720. package/dist/core/repo/entries.js.map +1 -0
  721. package/dist/core/repo/paths.js +236 -0
  722. package/dist/core/repo/paths.js.map +1 -0
  723. package/dist/core/repo/repo.js +248 -0
  724. package/dist/core/repo/repo.js.map +1 -0
  725. package/dist/core/repo/service-target.js +153 -0
  726. package/dist/core/repo/service-target.js.map +1 -0
  727. package/dist/core/repo/state.js +58 -0
  728. package/dist/core/repo/state.js.map +1 -0
  729. package/dist/core/repo/tree/find.js +52 -0
  730. package/dist/core/repo/tree/find.js.map +1 -0
  731. package/dist/core/repo/tree/fs.js +64 -0
  732. package/dist/core/repo/tree/fs.js.map +1 -0
  733. package/dist/core/repo/tree/marker.js +61 -0
  734. package/dist/core/repo/tree/marker.js.map +1 -0
  735. package/dist/core/repo/tree/views.js +172 -0
  736. package/dist/core/repo/tree/views.js.map +1 -0
  737. package/dist/core/repo/tree/walk.js +234 -0
  738. package/dist/core/repo/tree/walk.js.map +1 -0
  739. package/dist/core/results.js +99 -2
  740. package/dist/core/results.js.map +1 -1
  741. package/dist/core/scaffold/landscape.js +131 -0
  742. package/dist/core/scaffold/landscape.js.map +1 -0
  743. package/dist/core/scaffold/readme.js +84 -0
  744. package/dist/core/scaffold/readme.js.map +1 -0
  745. package/dist/core/staging/commit.js +241 -0
  746. package/dist/core/staging/commit.js.map +1 -0
  747. package/dist/core/staging/interrupted.js +96 -0
  748. package/dist/core/staging/interrupted.js.map +1 -0
  749. package/dist/core/staging/lock.js +206 -0
  750. package/dist/core/staging/lock.js.map +1 -0
  751. package/dist/core/staging/recovery/finding.js +35 -0
  752. package/dist/core/staging/recovery/finding.js.map +1 -0
  753. package/dist/core/staging/recovery/intent.js +98 -0
  754. package/dist/core/staging/recovery/intent.js.map +1 -0
  755. package/dist/core/staging/recovery/recover.js +126 -0
  756. package/dist/core/staging/recovery/recover.js.map +1 -0
  757. package/dist/core/staging/recovery/residue.js +62 -0
  758. package/dist/core/staging/recovery/residue.js.map +1 -0
  759. package/dist/core/staging/snapshot.js +202 -0
  760. package/dist/core/staging/snapshot.js.map +1 -0
  761. package/dist/core/staging/txn/forward.js +171 -0
  762. package/dist/core/staging/txn/forward.js.map +1 -0
  763. package/dist/core/staging/txn/journal.js +139 -0
  764. package/dist/core/staging/txn/journal.js.map +1 -0
  765. package/dist/core/staging/txn/transaction.js +75 -0
  766. package/dist/core/staging/txn/transaction.js.map +1 -0
  767. package/dist/core/staging/writes.js +113 -0
  768. package/dist/core/staging/writes.js.map +1 -0
  769. package/dist/core/status/actions/execution.js +49 -0
  770. package/dist/core/status/actions/execution.js.map +1 -0
  771. package/dist/core/status/contracts.js +102 -0
  772. package/dist/core/status/contracts.js.map +1 -0
  773. package/dist/core/status/feature/artifacts.js +171 -0
  774. package/dist/core/status/feature/artifacts.js.map +1 -0
  775. package/dist/core/status/feature/feature.js +207 -0
  776. package/dist/core/status/feature/feature.js.map +1 -0
  777. package/dist/core/status/feature/next.js +150 -0
  778. package/dist/core/status/feature/next.js.map +1 -0
  779. package/dist/core/status/feature/state.js +21 -0
  780. package/dist/core/status/feature/state.js.map +1 -0
  781. package/dist/core/status/fleet/fleet.js +187 -0
  782. package/dist/core/status/fleet/fleet.js.map +1 -0
  783. package/dist/core/status/fleet/next.js +207 -0
  784. package/dist/core/status/fleet/next.js.map +1 -0
  785. package/dist/core/status/interrupted.js +97 -0
  786. package/dist/core/status/interrupted.js.map +1 -0
  787. package/dist/core/status/report.js +31 -0
  788. package/dist/core/status/report.js.map +1 -0
  789. package/dist/core/status/scan.js +41 -0
  790. package/dist/core/status/scan.js.map +1 -0
  791. package/dist/core/status/verification.js +201 -0
  792. package/dist/core/status/verification.js.map +1 -0
  793. package/dist/core/usecases/capability.js +169 -0
  794. package/dist/core/usecases/capability.js.map +1 -0
  795. package/dist/core/usecases/delta/claims.js +116 -0
  796. package/dist/core/usecases/delta/claims.js.map +1 -0
  797. package/dist/core/usecases/delta/flows.js +103 -0
  798. package/dist/core/usecases/delta/flows.js.map +1 -0
  799. package/dist/core/usecases/delta/overlay.js +235 -0
  800. package/dist/core/usecases/delta/overlay.js.map +1 -0
  801. package/dist/core/usecases/fleet.js +137 -0
  802. package/dist/core/usecases/fleet.js.map +1 -0
  803. package/dist/core/usecases/operations.js +111 -0
  804. package/dist/core/usecases/operations.js.map +1 -0
  805. package/dist/core/usecases/place.js +44 -0
  806. package/dist/core/usecases/place.js.map +1 -0
  807. package/dist/core/usecases/touch.js +101 -0
  808. package/dist/core/usecases/touch.js.map +1 -0
  809. package/dist/core/verify/answers.js +173 -0
  810. package/dist/core/verify/answers.js.map +1 -0
  811. package/dist/core/verify/build.js +150 -0
  812. package/dist/core/verify/build.js.map +1 -0
  813. package/dist/core/verify/checklist.js +201 -0
  814. package/dist/core/verify/checklist.js.map +1 -0
  815. package/dist/core/verify/claims/identity.js +61 -0
  816. package/dist/core/verify/claims/identity.js.map +1 -0
  817. package/dist/core/verify/cross/diff.js +97 -0
  818. package/dist/core/verify/cross/diff.js.map +1 -0
  819. package/dist/core/verify/evidence/contract.js +169 -0
  820. package/dist/core/verify/evidence/contract.js.map +1 -0
  821. package/dist/core/verify/file.js +257 -0
  822. package/dist/core/verify/file.js.map +1 -0
  823. package/dist/core/verify/pins/lint.js +157 -0
  824. package/dist/core/verify/pins/lint.js.map +1 -0
  825. package/dist/core/verify/pins/pin.js +95 -0
  826. package/dist/core/verify/pins/pin.js.map +1 -0
  827. package/dist/core/verify/record.js +142 -0
  828. package/dist/core/verify/record.js.map +1 -0
  829. package/dist/core/verify/store/commit.js +89 -0
  830. package/dist/core/verify/store/commit.js.map +1 -0
  831. package/dist/core/verify/store/render.js +57 -0
  832. package/dist/core/verify/store/render.js.map +1 -0
  833. package/dist/core/vocabulary/health.js +91 -0
  834. package/dist/core/vocabulary/health.js.map +1 -0
  835. package/dist/core/vocabulary/issue.js +55 -0
  836. package/dist/core/vocabulary/issue.js.map +1 -0
  837. package/dist/core/vocabulary/maturity.js +145 -0
  838. package/dist/core/vocabulary/maturity.js.map +1 -0
  839. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  840. package/dist/core/vocabulary/report.js.map +1 -0
  841. package/dist/core/vocabulary/steps.js +266 -0
  842. package/dist/core/vocabulary/steps.js.map +1 -0
  843. package/dist/core/workspace/discover.js +239 -0
  844. package/dist/core/workspace/discover.js.map +1 -0
  845. package/dist/core/workspace/render.js +52 -0
  846. package/dist/core/workspace/render.js.map +1 -0
  847. package/examples/README.md +189 -0
  848. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  849. package/examples/docs/architecture/capabilities.yaml +31 -0
  850. package/examples/docs/architecture/landscape.likec4 +280 -0
  851. package/examples/docs/architecture/obligations.yaml +23 -0
  852. package/examples/docs/architecture/permissions.yaml +50 -0
  853. package/examples/docs/architecture/subsystems.likec4 +11 -0
  854. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  855. package/examples/docs/capabilities/checkout/spec.md +55 -0
  856. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  857. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  858. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  859. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  860. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  863. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  864. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  865. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  866. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  867. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  868. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  869. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  870. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  871. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  872. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  873. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  874. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  875. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  876. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  877. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  878. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  879. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  882. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  883. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  884. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  885. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  886. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  887. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  888. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  889. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  890. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  891. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  896. package/examples/docs/glossary/order.md +15 -0
  897. package/examples/docs/glossary/payments/authorization.md +14 -0
  898. package/examples/docs/likec4.config.json +9 -0
  899. package/examples/docs/loam.json +3 -0
  900. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  901. package/examples/docs/services/checkout-web/spec.md +91 -0
  902. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  903. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  904. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  905. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  906. package/examples/docs/services/order-service/arch.spec.md +127 -0
  907. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  908. package/examples/docs/services/order-service/health.yaml +57 -0
  909. package/examples/docs/services/order-service/model.likec4 +138 -0
  910. package/examples/docs/services/order-service/openapi.yaml +257 -0
  911. package/examples/docs/services/order-service/runbook.md +62 -0
  912. package/examples/docs/services/order-service/spec.md +134 -0
  913. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  914. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  915. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  916. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  917. package/examples/docs/services/payment-service/health.yaml +39 -0
  918. package/examples/docs/services/payment-service/model.likec4 +67 -0
  919. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  920. package/examples/docs/services/payment-service/runbook.md +37 -0
  921. package/examples/docs/services/payment-service/spec.md +88 -0
  922. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  923. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  924. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  925. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  926. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  927. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  928. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  929. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  930. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  931. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  932. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  933. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  934. package/examples/docs/services/platform/subsystem.yaml +2 -0
  935. package/examples/fleet.yaml +57 -0
  936. package/package.json +19 -6
  937. package/dist/commands/adopt.js +0 -240
  938. package/dist/commands/adopt.js.map +0 -1
  939. package/dist/commands/archive.js +0 -1313
  940. package/dist/commands/archive.js.map +0 -1
  941. package/dist/commands/delta.js +0 -388
  942. package/dist/commands/delta.js.map +0 -1
  943. package/dist/commands/docs-repo-gate.js.map +0 -1
  944. package/dist/commands/format.js +0 -17
  945. package/dist/commands/format.js.map +0 -1
  946. package/dist/commands/gherkin.js +0 -309
  947. package/dist/commands/gherkin.js.map +0 -1
  948. package/dist/commands/init.js.map +0 -1
  949. package/dist/commands/list.js +0 -295
  950. package/dist/commands/list.js.map +0 -1
  951. package/dist/commands/migrate-openspec.js +0 -950
  952. package/dist/commands/migrate-openspec.js.map +0 -1
  953. package/dist/commands/new.js +0 -413
  954. package/dist/commands/new.js.map +0 -1
  955. package/dist/commands/rebase.js +0 -356
  956. package/dist/commands/rebase.js.map +0 -1
  957. package/dist/commands/show.js.map +0 -1
  958. package/dist/commands/status.js +0 -262
  959. package/dist/commands/status.js.map +0 -1
  960. package/dist/commands/unarchive.js.map +0 -1
  961. package/dist/commands/validate.js +0 -1773
  962. package/dist/commands/validate.js.map +0 -1
  963. package/dist/commands/verify.js +0 -775
  964. package/dist/commands/verify.js.map +0 -1
  965. package/dist/commands/vouch.js +0 -421
  966. package/dist/commands/vouch.js.map +0 -1
  967. package/dist/core/agent.js +0 -2173
  968. package/dist/core/agent.js.map +0 -1
  969. package/dist/core/agents-stamp.js.map +0 -1
  970. package/dist/core/arch.js +0 -130
  971. package/dist/core/arch.js.map +0 -1
  972. package/dist/core/asyncapi.js.map +0 -1
  973. package/dist/core/brief.js +0 -755
  974. package/dist/core/brief.js.map +0 -1
  975. package/dist/core/coherence.js +0 -577
  976. package/dist/core/coherence.js.map +0 -1
  977. package/dist/core/concurrency.js.map +0 -1
  978. package/dist/core/config.js.map +0 -1
  979. package/dist/core/delta.js +0 -476
  980. package/dist/core/delta.js.map +0 -1
  981. package/dist/core/dependencies.js +0 -356
  982. package/dist/core/dependencies.js.map +0 -1
  983. package/dist/core/doctor.js +0 -596
  984. package/dist/core/doctor.js.map +0 -1
  985. package/dist/core/document-bytes.js.map +0 -1
  986. package/dist/core/explore.js.map +0 -1
  987. package/dist/core/frontmatter.js.map +0 -1
  988. package/dist/core/gherkin.js +0 -463
  989. package/dist/core/gherkin.js.map +0 -1
  990. package/dist/core/health.js +0 -67
  991. package/dist/core/health.js.map +0 -1
  992. package/dist/core/ids.js +0 -122
  993. package/dist/core/ids.js.map +0 -1
  994. package/dist/core/issue.js +0 -9
  995. package/dist/core/issue.js.map +0 -1
  996. package/dist/core/json.js +0 -74
  997. package/dist/core/json.js.map +0 -1
  998. package/dist/core/likec4.js +0 -489
  999. package/dist/core/likec4.js.map +0 -1
  1000. package/dist/core/maturity.js +0 -66
  1001. package/dist/core/maturity.js.map +0 -1
  1002. package/dist/core/openapi-merge.js +0 -552
  1003. package/dist/core/openapi-merge.js.map +0 -1
  1004. package/dist/core/openapi.js.map +0 -1
  1005. package/dist/core/openspec-inventory.js +0 -1440
  1006. package/dist/core/openspec-inventory.js.map +0 -1
  1007. package/dist/core/path-safety.js.map +0 -1
  1008. package/dist/core/provenance.js +0 -787
  1009. package/dist/core/provenance.js.map +0 -1
  1010. package/dist/core/records.js.map +0 -1
  1011. package/dist/core/repo.js +0 -438
  1012. package/dist/core/repo.js.map +0 -1
  1013. package/dist/core/report.js.map +0 -1
  1014. package/dist/core/spec.js +0 -505
  1015. package/dist/core/spec.js.map +0 -1
  1016. package/dist/core/staging.js +0 -890
  1017. package/dist/core/staging.js.map +0 -1
  1018. package/dist/core/status.js +0 -1132
  1019. package/dist/core/status.js.map +0 -1
  1020. package/dist/core/steps.js +0 -72
  1021. package/dist/core/steps.js.map +0 -1
  1022. package/dist/core/verify.js +0 -807
  1023. package/dist/core/verify.js.map +0 -1
  1024. package/dist/core/version.js.map +0 -1
  1025. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1026. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * What a requirement's scenarios are MISSING, and where they contradict
3
+ * themselves — the coverage and shape questions `validate` asks of every spec.
4
+ *
5
+ * Separate from `./parse.ts` because these grade a parsed document rather than
6
+ * produce one, and separate from `./spec.ts` because they answer to
7
+ * `vocabulary/report.ts`: a stepless scenario is a Finding with a subject and a
8
+ * label, and that shape belongs to the report, not to the requirement.
9
+ */
10
+ import { scenarioGherkin } from "../vocabulary/steps.js";
11
+ import {} from "./spec.js";
12
+ /** Requirements with no scenario — the OpenSpec coverage rule (every requirement needs ≥1). */
13
+ export function requirementsMissingScenarios(reqs) {
14
+ return reqs.filter((r) => r.kind !== "REMOVED" && r.scenarios.length === 0);
15
+ }
16
+ /**
17
+ * Scenarios that satisfy the coverage rule above and test nothing.
18
+ *
19
+ * `requirementsMissingScenarios` counts HEADINGS, so a `#### Scenario:` with no
20
+ * recognizable Given/When/Then line satisfied the fleet gate's coverage claim
21
+ * outright — and it is not a hypothetical shape: it is what `loam new`
22
+ * scaffolds and what an agent writes when it puts the criteria in prose.
23
+ * Downstream the same emptiness is fatal twice over: cucumber runs a stepless
24
+ * scenario vacuously green, and `loam verify --results` can never confirm it.
25
+ *
26
+ * The detector is `loam gherkin`'s own — `scenarioGherkin` decides what a step
27
+ * IS, and a second opinion here would mean the gate and the emitted suite could
28
+ * disagree about whether a scenario has any. That import is the one place these
29
+ * two modules are circular (gherkin.ts parses requirements); both sides export
30
+ * only function declarations, which are hoisted, so neither evaluation order
31
+ * can observe a half-built module.
32
+ */
33
+ export function steplessScenarios(reqs) {
34
+ const out = [];
35
+ for (const r of reqs) {
36
+ if (r.kind === "REMOVED")
37
+ continue;
38
+ for (const s of r.scenarios) {
39
+ if (scenarioGherkin(s.lines).steps.length === 0)
40
+ out.push({ requirement: r.name, scenario: s.name });
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ /**
46
+ * The stepless half of the coverage check, as findings — the missing-scenario
47
+ * half stays where it is (`requirementsMissingScenarios`), because the two are
48
+ * different states with different fixes: one requirement owes acceptance
49
+ * criteria, the other owes STEPS in criteria somebody has already written.
50
+ *
51
+ * `label` is the coverage check's own label (`payment-service: requirements`),
52
+ * so both halves of one document name it the same way.
53
+ */
54
+ export function steplessFindings(label, subject, reqs) {
55
+ const bare = steplessScenarios(reqs);
56
+ if (bare.length === 0)
57
+ return [];
58
+ return [
59
+ {
60
+ severity: "error",
61
+ code: "requirements.stepless-scenario",
62
+ subject,
63
+ message: `${label}: ${bare.length} scenario(s) have a heading and no recognizable steps — ` +
64
+ `they satisfy the coverage rule and test nothing. Cucumber runs a stepless scenario vacuously green and ` +
65
+ `\`loam verify --results\` can never confirm it. Write the body as \`- **Given/When/Then**\` bullets.`,
66
+ details: bare.map((s) => `${s.requirement} — ${s.scenario}`),
67
+ text: { detailPrefix: "- " },
68
+ },
69
+ ];
70
+ }
71
+ /**
72
+ * Scenarios that have steps and assert NOTHING — a body of Given and When with
73
+ * no Then anywhere in it.
74
+ *
75
+ * The third state, and the one that survives both checks above: the requirement
76
+ * has a scenario, the scenario has steps, and the run can only ever confirm that
77
+ * the arrangement did not throw. `And`/`But` are continuations, so their meaning
78
+ * is decided by the keyword they follow — with no `Then` in the body there is
79
+ * nothing for them to continue but the setup.
80
+ *
81
+ * It matters more the more evidence rests on the suite. A stepless scenario is
82
+ * at least visible as an empty body; this one reads like a real test, emits real
83
+ * steps, runs green, and answers a `scenario.tested` claim to the same standard
84
+ * as a scenario that checks twenty things. Under a runner-neutral answer sheet —
85
+ * one `{digest, status}` entry, no steps to count — this document-side check is
86
+ * the only thing left that can tell the two apart.
87
+ */
88
+ export function assertionlessScenarios(reqs) {
89
+ const out = [];
90
+ for (const r of reqs) {
91
+ if (r.kind === "REMOVED")
92
+ continue;
93
+ for (const s of r.scenarios) {
94
+ const { steps } = scenarioGherkin(s.lines);
95
+ // Disjoint from stepless by construction: a body with no steps at all is
96
+ // that finding's, and reporting one scenario under both codes would offer
97
+ // two fixes for one defect.
98
+ if (steps.length > 0 && !steps.some((st) => /^Then\b/.test(st.text))) {
99
+ out.push({ requirement: r.name, scenario: s.name });
100
+ }
101
+ }
102
+ }
103
+ return out;
104
+ }
105
+ const PLACEHOLDER_RE = /<([^<>\s]+)>/g;
106
+ /**
107
+ * Every `<placeholder>` a scenario's steps reach for, arguments included.
108
+ *
109
+ * Docstrings and data tables are scanned too because cucumber substitutes into
110
+ * them: `<fee>` inside a request payload is a real reference, and a check that
111
+ * read only the step lines would call its column unreferenced and invite the
112
+ * author to delete the column the payload depends on.
113
+ */
114
+ function placeholdersUsed(steps) {
115
+ const used = new Set();
116
+ const scan = (text) => {
117
+ for (const m of text.matchAll(PLACEHOLDER_RE))
118
+ used.add(m[1]);
119
+ };
120
+ for (const st of steps) {
121
+ scan(st.text);
122
+ for (const l of st.docstring?.lines ?? [])
123
+ scan(l);
124
+ for (const row of st.table ?? [])
125
+ for (const cell of row)
126
+ scan(cell);
127
+ }
128
+ return used;
129
+ }
130
+ /**
131
+ * Scenarios whose `Examples` header and `<placeholders>` disagree.
132
+ *
133
+ * Only scenarios that HAVE an Examples table are considered, and that scoping is
134
+ * load-bearing twice over: a plain scenario's `<angle brackets>` are prose (a
135
+ * generic, a placeholder in a URL template, loam's own scaffold sentinels), and
136
+ * a fleet that never writes an outline must never earn a finding from this.
137
+ *
138
+ * The two halves are different defects. An UNBOUND placeholder is an error the
139
+ * runner cannot repair: cucumber substitutes nothing, so the step definition
140
+ * receives the literal text `<fee>` and either fails obscurely or — worse —
141
+ * matches and asserts against a string nobody meant. An UNREFERENCED column is a
142
+ * warning, because it is usually the fossil of a rename: the column still lists
143
+ * six cases, the step that used it now names something else, and the outline
144
+ * runs six identical passes that read as six cases of coverage.
145
+ */
146
+ export function examplesMismatches(reqs) {
147
+ const out = [];
148
+ for (const r of reqs) {
149
+ if (r.kind === "REMOVED")
150
+ continue;
151
+ for (const s of r.scenarios) {
152
+ const { steps, examples } = scenarioGherkin(s.lines);
153
+ if (examples === null)
154
+ continue;
155
+ const used = placeholdersUsed(steps);
156
+ const columns = new Set(examples.header);
157
+ const unbound = [...used].filter((p) => !columns.has(p)).sort();
158
+ const unreferenced = examples.header.filter((c) => !used.has(c));
159
+ if (unbound.length > 0 || unreferenced.length > 0) {
160
+ out.push({ requirement: r.name, scenario: s.name, unbound, unreferenced });
161
+ }
162
+ }
163
+ }
164
+ return out;
165
+ }
166
+ /**
167
+ * The Examples half, as findings — two codes rather than one with two
168
+ * severities, because a machine cannot branch on a severity that depends on the
169
+ * case, and the fixes differ: one adds a column, the other removes one or
170
+ * restores the reference that used to use it.
171
+ */
172
+ export function examplesFindings(label, subject, reqs) {
173
+ const all = examplesMismatches(reqs);
174
+ const findings = [];
175
+ const unbound = all.filter((m) => m.unbound.length > 0);
176
+ if (unbound.length > 0) {
177
+ findings.push({
178
+ severity: "error",
179
+ code: "requirements.examples-unbound",
180
+ subject,
181
+ message: `${label}: ${unbound.length} scenario(s) use a \`<placeholder>\` no \`Examples\` column supplies — ` +
182
+ `cucumber substitutes nothing, so the step receives the literal angle-bracket text. ` +
183
+ `Add the column, or correct the placeholder to one the header names.`,
184
+ details: unbound.map((m) => `${m.requirement} — ${m.scenario}: ${m.unbound.map((p) => `<${p}>`).join(", ")}`),
185
+ text: { detailPrefix: "- " },
186
+ });
187
+ }
188
+ const dead = all.filter((m) => m.unreferenced.length > 0);
189
+ if (dead.length > 0) {
190
+ findings.push({
191
+ severity: "warn",
192
+ code: "requirements.examples-unreferenced",
193
+ subject,
194
+ message: `${label}: ${dead.length} scenario(s) declare an \`Examples\` column no step refers to — ` +
195
+ `usually the fossil of a rename, and the outline then runs its rows as identical passes that read as cases. ` +
196
+ `Reference the column from a step, or drop it.`,
197
+ details: dead.map((m) => `${m.requirement} — ${m.scenario}: ${m.unreferenced.join(", ")}`),
198
+ text: { detailPrefix: "- " },
199
+ });
200
+ }
201
+ return findings;
202
+ }
203
+ /** The assertionless half, as findings — same label and subject as its two siblings. */
204
+ export function assertionlessFindings(label, subject, reqs) {
205
+ const mute = assertionlessScenarios(reqs);
206
+ if (mute.length === 0)
207
+ return [];
208
+ return [
209
+ {
210
+ severity: "error",
211
+ code: "requirements.assertionless-scenario",
212
+ subject,
213
+ message: `${label}: ${mute.length} scenario(s) have steps and no \`Then\` — ` +
214
+ `they arrange and act, and assert nothing. The run confirms only that the setup did not throw, ` +
215
+ `and the claim it answers is confirmed on that. Add the \`- **Then**\` the scenario was written for.`,
216
+ details: mute.map((s) => `${s.requirement} — ${s.scenario}`),
217
+ text: { detailPrefix: "- " },
218
+ },
219
+ ];
220
+ }
221
+ //# sourceMappingURL=scenarios.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenarios.js","sourceRoot":"","sources":["../../../src/core/document/scenarios.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a requirement's scenarios are MISSING, and where they contradict\n * themselves — the coverage and shape questions `validate` asks of every spec.\n *\n * Separate from `./parse.ts` because these grade a parsed document rather than\n * produce one, and separate from `./spec.ts` because they answer to\n * `vocabulary/report.ts`: a stepless scenario is a Finding with a subject and a\n * label, and that shape belongs to the report, not to the requirement.\n */\nimport { scenarioGherkin, type EmittedStep } from \"../vocabulary/steps.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport { type Requirement } from \"./spec.js\";\n\n/** Requirements with no scenario — the OpenSpec coverage rule (every requirement needs ≥1). */\nexport function requirementsMissingScenarios(reqs: Requirement[]): Requirement[] {\n return reqs.filter((r) => r.kind !== \"REMOVED\" && r.scenarios.length === 0);\n}\n\n/** A `#### Scenario:` heading whose body holds nothing a runner would execute. */\nexport interface SteplessScenario {\n requirement: string;\n scenario: string;\n}\n\n/**\n * Scenarios that satisfy the coverage rule above and test nothing.\n *\n * `requirementsMissingScenarios` counts HEADINGS, so a `#### Scenario:` with no\n * recognizable Given/When/Then line satisfied the fleet gate's coverage claim\n * outright — and it is not a hypothetical shape: it is what `loam new`\n * scaffolds and what an agent writes when it puts the criteria in prose.\n * Downstream the same emptiness is fatal twice over: cucumber runs a stepless\n * scenario vacuously green, and `loam verify --results` can never confirm it.\n *\n * The detector is `loam gherkin`'s own — `scenarioGherkin` decides what a step\n * IS, and a second opinion here would mean the gate and the emitted suite could\n * disagree about whether a scenario has any. That import is the one place these\n * two modules are circular (gherkin.ts parses requirements); both sides export\n * only function declarations, which are hoisted, so neither evaluation order\n * can observe a half-built module.\n */\nexport function steplessScenarios(reqs: Requirement[]): SteplessScenario[] {\n const out: SteplessScenario[] = [];\n for (const r of reqs) {\n if (r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n if (scenarioGherkin(s.lines).steps.length === 0) out.push({ requirement: r.name, scenario: s.name });\n }\n }\n return out;\n}\n\n/**\n * The stepless half of the coverage check, as findings — the missing-scenario\n * half stays where it is (`requirementsMissingScenarios`), because the two are\n * different states with different fixes: one requirement owes acceptance\n * criteria, the other owes STEPS in criteria somebody has already written.\n *\n * `label` is the coverage check's own label (`payment-service: requirements`),\n * so both halves of one document name it the same way.\n */\nexport function steplessFindings(label: string, subject: string, reqs: Requirement[]): Finding[] {\n const bare = steplessScenarios(reqs);\n if (bare.length === 0) return [];\n return [\n {\n severity: \"error\",\n code: \"requirements.stepless-scenario\",\n subject,\n message:\n `${label}: ${bare.length} scenario(s) have a heading and no recognizable steps — ` +\n `they satisfy the coverage rule and test nothing. Cucumber runs a stepless scenario vacuously green and ` +\n `\\`loam verify --results\\` can never confirm it. Write the body as \\`- **Given/When/Then**\\` bullets.`,\n details: bare.map((s) => `${s.requirement} — ${s.scenario}`),\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/**\n * Scenarios that have steps and assert NOTHING — a body of Given and When with\n * no Then anywhere in it.\n *\n * The third state, and the one that survives both checks above: the requirement\n * has a scenario, the scenario has steps, and the run can only ever confirm that\n * the arrangement did not throw. `And`/`But` are continuations, so their meaning\n * is decided by the keyword they follow — with no `Then` in the body there is\n * nothing for them to continue but the setup.\n *\n * It matters more the more evidence rests on the suite. A stepless scenario is\n * at least visible as an empty body; this one reads like a real test, emits real\n * steps, runs green, and answers a `scenario.tested` claim to the same standard\n * as a scenario that checks twenty things. Under a runner-neutral answer sheet —\n * one `{digest, status}` entry, no steps to count — this document-side check is\n * the only thing left that can tell the two apart.\n */\nexport function assertionlessScenarios(reqs: Requirement[]): SteplessScenario[] {\n const out: SteplessScenario[] = [];\n for (const r of reqs) {\n if (r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const { steps } = scenarioGherkin(s.lines);\n // Disjoint from stepless by construction: a body with no steps at all is\n // that finding's, and reporting one scenario under both codes would offer\n // two fixes for one defect.\n if (steps.length > 0 && !steps.some((st) => /^Then\\b/.test(st.text))) {\n out.push({ requirement: r.name, scenario: s.name });\n }\n }\n }\n return out;\n}\n\n/** One scenario's disagreement between its `Examples` columns and its `<placeholders>`. */\nexport interface ExamplesMismatch {\n requirement: string;\n scenario: string;\n /** `<placeholders>` a step uses that no column supplies. */\n unbound: string[];\n /** Columns no step, docstring or data-table cell refers to. */\n unreferenced: string[];\n}\n\nconst PLACEHOLDER_RE = /<([^<>\\s]+)>/g;\n\n/**\n * Every `<placeholder>` a scenario's steps reach for, arguments included.\n *\n * Docstrings and data tables are scanned too because cucumber substitutes into\n * them: `<fee>` inside a request payload is a real reference, and a check that\n * read only the step lines would call its column unreferenced and invite the\n * author to delete the column the payload depends on.\n */\nfunction placeholdersUsed(steps: EmittedStep[]): Set<string> {\n const used = new Set<string>();\n const scan = (text: string): void => {\n for (const m of text.matchAll(PLACEHOLDER_RE)) used.add(m[1]!);\n };\n for (const st of steps) {\n scan(st.text);\n for (const l of st.docstring?.lines ?? []) scan(l);\n for (const row of st.table ?? []) for (const cell of row) scan(cell);\n }\n return used;\n}\n\n/**\n * Scenarios whose `Examples` header and `<placeholders>` disagree.\n *\n * Only scenarios that HAVE an Examples table are considered, and that scoping is\n * load-bearing twice over: a plain scenario's `<angle brackets>` are prose (a\n * generic, a placeholder in a URL template, loam's own scaffold sentinels), and\n * a fleet that never writes an outline must never earn a finding from this.\n *\n * The two halves are different defects. An UNBOUND placeholder is an error the\n * runner cannot repair: cucumber substitutes nothing, so the step definition\n * receives the literal text `<fee>` and either fails obscurely or — worse —\n * matches and asserts against a string nobody meant. An UNREFERENCED column is a\n * warning, because it is usually the fossil of a rename: the column still lists\n * six cases, the step that used it now names something else, and the outline\n * runs six identical passes that read as six cases of coverage.\n */\nexport function examplesMismatches(reqs: Requirement[]): ExamplesMismatch[] {\n const out: ExamplesMismatch[] = [];\n for (const r of reqs) {\n if (r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const { steps, examples } = scenarioGherkin(s.lines);\n if (examples === null) continue;\n const used = placeholdersUsed(steps);\n const columns = new Set(examples.header);\n const unbound = [...used].filter((p) => !columns.has(p)).sort();\n const unreferenced = examples.header.filter((c) => !used.has(c));\n if (unbound.length > 0 || unreferenced.length > 0) {\n out.push({ requirement: r.name, scenario: s.name, unbound, unreferenced });\n }\n }\n }\n return out;\n}\n\n/**\n * The Examples half, as findings — two codes rather than one with two\n * severities, because a machine cannot branch on a severity that depends on the\n * case, and the fixes differ: one adds a column, the other removes one or\n * restores the reference that used to use it.\n */\nexport function examplesFindings(label: string, subject: string, reqs: Requirement[]): Finding[] {\n const all = examplesMismatches(reqs);\n const findings: Finding[] = [];\n const unbound = all.filter((m) => m.unbound.length > 0);\n if (unbound.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"requirements.examples-unbound\",\n subject,\n message:\n `${label}: ${unbound.length} scenario(s) use a \\`<placeholder>\\` no \\`Examples\\` column supplies — ` +\n `cucumber substitutes nothing, so the step receives the literal angle-bracket text. ` +\n `Add the column, or correct the placeholder to one the header names.`,\n details: unbound.map((m) => `${m.requirement} — ${m.scenario}: ${m.unbound.map((p) => `<${p}>`).join(\", \")}`),\n text: { detailPrefix: \"- \" },\n });\n }\n const dead = all.filter((m) => m.unreferenced.length > 0);\n if (dead.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"requirements.examples-unreferenced\",\n subject,\n message:\n `${label}: ${dead.length} scenario(s) declare an \\`Examples\\` column no step refers to — ` +\n `usually the fossil of a rename, and the outline then runs its rows as identical passes that read as cases. ` +\n `Reference the column from a step, or drop it.`,\n details: dead.map((m) => `${m.requirement} — ${m.scenario}: ${m.unreferenced.join(\", \")}`),\n text: { detailPrefix: \"- \" },\n });\n }\n return findings;\n}\n\n/** The assertionless half, as findings — same label and subject as its two siblings. */\nexport function assertionlessFindings(label: string, subject: string, reqs: Requirement[]): Finding[] {\n const mute = assertionlessScenarios(reqs);\n if (mute.length === 0) return [];\n return [\n {\n severity: \"error\",\n code: \"requirements.assertionless-scenario\",\n subject,\n message:\n `${label}: ${mute.length} scenario(s) have steps and no \\`Then\\` — ` +\n `they arrange and act, and assert nothing. The run confirms only that the setup did not throw, ` +\n `and the claim it answers is confirmed on that. Add the \\`- **Then**\\` the scenario was written for.`,\n details: mute.map((s) => `${s.requirement} — ${s.scenario}`),\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n"]}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * What a requirement IS, what makes one the same requirement twice, and the one
3
+ * canonical way it is written back out.
4
+ *
5
+ * Living specs use `## Requirements` with `### Requirement:` + `#### Scenario:`.
6
+ * Feature deltas group requirements under `## ADDED|MODIFIED|REMOVED
7
+ * Requirements`. Scenarios are the acceptance criteria (Given/When/Then) and
8
+ * the source for tests.
9
+ *
10
+ * Identity and serialization cannot be separated here: `requirementDigest` is
11
+ * defined AS the sha256 of the serialized requirement without its baseline, so
12
+ * a second module owning the writer would mean the digest and the bytes it
13
+ * claims to describe could disagree. `./parse.ts` turns markdown into these,
14
+ * `./apply.ts` folds a delta into a living set of them.
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ export const KIND_RE = /^##\s+(ADDED|MODIFIED|REMOVED)\s+Requirements\s*$/i;
18
+ export const REQUIREMENT_ID_LINE_RE = /^\s*Requirement-ID:\s*(.*?)\s*$/i;
19
+ export const BASED_ON_LINE_RE = /^\s*Based-On:\s*(.*?)\s*$/i;
20
+ /**
21
+ * The `Capability:` body line (also accepted: `Capabilities:`), exactly as the
22
+ * parser reads it — `./parse.ts` consumes the capture, migrate-openspec's
23
+ * materializer tests presence with it before appending its own line. One
24
+ * spelling on purpose: two copies of a grammar is how a "presence" test and
25
+ * the parse quietly stop agreeing about what counts as the line.
26
+ */
27
+ export const CAPABILITY_LINE_RE = /^\s*Capabilit(?:y|ies):\s*(.+?)\s*$/i;
28
+ /**
29
+ * The `Realizes:` body line. One spelling only — no `Realize:` singular
30
+ * alternative, unlike `Operations?:` and the rest — because the verb already
31
+ * reads correctly for one entry and for six, and the plural forms in the older
32
+ * lines exist to forgive a NOUN that changes shape. A grammar with an
33
+ * alternative nobody needs is one more thing two readers can disagree about.
34
+ */
35
+ export const REALIZES_LINE_RE = /^\s*Realizes:\s*(.+?)\s*$/i;
36
+ /**
37
+ * The separator between a `Realizes:` target and the digest of the capability
38
+ * requirement it was written against — `checkout#CHK-1@9f2c1a4b`.
39
+ *
40
+ * `@` and not a second `#`, because the two halves are read by different rules:
41
+ * `splitRealizesEntry` takes the LAST `#` to find the requirement half, so a
42
+ * second one would move that boundary and re-parse every existing entry. `@`
43
+ * is also excluded from `REQUIREMENT_ID_RE`, which is what makes the split
44
+ * unambiguous rather than a convention — an id can never contain one.
45
+ *
46
+ * NOBODY TYPES THIS. `loam rebase --living` writes it and rewrites it; a human
47
+ * writes `Realizes: checkout#CHK-1` and is never asked for more. An entry
48
+ * without a pin is not a defect and never becomes one — it is a claim that has
49
+ * not been pinned yet, and it grades exactly as it did before pins existed.
50
+ */
51
+ export const REALIZES_PIN_SEPARATOR = "@";
52
+ /**
53
+ * One `Realizes:` entry split into the target it names and the digest it was
54
+ * written against, or `pin: null` when it carries none.
55
+ *
56
+ * A suffix that is not a well-formed digest is NOT a pin and NOT a refusal:
57
+ * the whole entry stays the target, so `checkout#CHK-1@nonsense` resolves — or
58
+ * fails to resolve — exactly as it would have before, under
59
+ * `capability.realizes-unknown`, whose message names the entry the author
60
+ * actually typed. Inventing a second refusal for one mistake is the failure
61
+ * `splitRealizesEntry` already refuses two lines up.
62
+ */
63
+ export function splitRealizesPin(entry) {
64
+ const at = entry.lastIndexOf(REALIZES_PIN_SEPARATOR);
65
+ if (at <= 0)
66
+ return { target: entry, pin: null };
67
+ const suffix = entry.slice(at + 1);
68
+ if (!REQUIREMENT_DIGEST_RE.test(suffix))
69
+ return { target: entry, pin: null };
70
+ return { target: entry.slice(0, at), pin: suffix };
71
+ }
72
+ /** Portable, review-friendly stable IDs. Case-sensitive by design. */
73
+ export const REQUIREMENT_ID_RE = /^[A-Za-z][A-Za-z0-9._-]{0,127}$/;
74
+ /**
75
+ * How much of the sha256 a `Based-On:` line carries — the length
76
+ * `sources_digest` already uses (core/provenance/stamp.ts), for one reason a reader
77
+ * can hold: every digest loam writes into a document reads the same.
78
+ */
79
+ export const REQUIREMENT_DIGEST_LENGTH = 16;
80
+ /** The value shape of a `Based-On:` line, exactly as `requirementDigest` writes it. */
81
+ export const REQUIREMENT_DIGEST_RE = new RegExp(`^[0-9a-f]{${REQUIREMENT_DIGEST_LENGTH}}$`);
82
+ /** Every authored Requirement-ID value in a requirement body, including blanks. */
83
+ export function requirementIdDeclarations(requirement) {
84
+ return requirement.text.flatMap((line) => {
85
+ const match = REQUIREMENT_ID_LINE_RE.exec(line);
86
+ return match === null ? [] : [match[1].trim()];
87
+ });
88
+ }
89
+ /** Structural ID problems within one spec/delta document. */
90
+ export function requirementIdProblems(reqs) {
91
+ const problems = [];
92
+ const owners = new Map();
93
+ for (const requirement of reqs) {
94
+ const declarations = requirementIdDeclarations(requirement);
95
+ const values = declarations.length > 0
96
+ ? declarations
97
+ : requirement.id === undefined
98
+ ? []
99
+ : [requirement.id];
100
+ if (declarations.length > 1) {
101
+ problems.push({ kind: "repeated", requirement: requirement.name, values: declarations });
102
+ }
103
+ for (const value of values) {
104
+ if (!REQUIREMENT_ID_RE.test(value)) {
105
+ problems.push({ kind: "invalid", requirement: requirement.name, value });
106
+ }
107
+ }
108
+ // A repeated declaration is already an error. Do not also pretend its
109
+ // keep-last parse result is an unambiguous document identity.
110
+ if (values.length !== 1 || !REQUIREMENT_ID_RE.test(values[0]))
111
+ continue;
112
+ const names = owners.get(values[0]) ?? [];
113
+ names.push(requirement.name);
114
+ owners.set(values[0], names);
115
+ }
116
+ for (const [id, requirements] of owners) {
117
+ if (requirements.length > 1)
118
+ problems.push({ kind: "duplicate", id, requirements });
119
+ }
120
+ return problems;
121
+ }
122
+ /** Every authored `Based-On:` value in a requirement body, including blanks. */
123
+ export function basedOnDeclarations(requirement) {
124
+ return requirement.text.flatMap((line) => {
125
+ const match = BASED_ON_LINE_RE.exec(line);
126
+ return match === null ? [] : [match[1].trim()];
127
+ });
128
+ }
129
+ /**
130
+ * The requirement without its baseline marker — what a digest is taken over,
131
+ * and what archive merges into the living document.
132
+ *
133
+ * `Based-On:` is a statement ABOUT a delta ("this was written against that"),
134
+ * never part of the requirement it describes, and both consequences of
135
+ * forgetting that are bad. Left in the digest input, a requirement's digest
136
+ * would depend on the pin pointing at it, so no baseline could ever be
137
+ * self-consistent. Left in the merged text — `applyRequirementDelta` copies the
138
+ * delta's body into living wholesale — the living document would grow a pin to
139
+ * a version of itself, and the NEXT feature's baseline would be taken over a
140
+ * document containing the previous feature's bookkeeping.
141
+ */
142
+ export function withoutBaseline(requirement) {
143
+ const { basedOn: _dropped, ...rest } = requirement;
144
+ return { ...rest, text: requirement.text.filter((line) => !BASED_ON_LINE_RE.test(line)) };
145
+ }
146
+ /**
147
+ * The requirement with every `Realizes:` pin stripped back to its bare target —
148
+ * the second thing a digest is taken over, for the same reason as the first.
149
+ *
150
+ * `withoutBaseline`'s argument, one axis over: a pin is a statement ABOUT a
151
+ * join ("this claim was written against that version of the promise"), never
152
+ * part of the requirement making the claim. Left in the digest input it would
153
+ * make re-pinning a content change, so every requirement realizing a capability
154
+ * would go stale against ITS OWN consumers the moment the capability moved —
155
+ * one edit cascading through the corpus, which is the failure the pin exists to
156
+ * report rather than to cause.
157
+ *
158
+ * The line is kept and only the suffix removed, unlike `withoutBaseline` which
159
+ * drops the whole line: the target ids ARE content, and a requirement that
160
+ * stops naming the promise it keeps is a different requirement.
161
+ */
162
+ export function withoutRealizesPins(requirement) {
163
+ return {
164
+ ...requirement,
165
+ text: requirement.text.map((line) => {
166
+ const match = REALIZES_LINE_RE.exec(line);
167
+ if (match === null)
168
+ return line;
169
+ const bare = match[1]
170
+ .split(",")
171
+ .map((entry) => splitRealizesPin(entry.trim()).target)
172
+ .join(", ");
173
+ return line.replace(match[1], bare);
174
+ }),
175
+ };
176
+ }
177
+ /**
178
+ * The identity of a requirement's CONTENT: sha256 over its canonical
179
+ * serialization, truncated like every other digest loam stamps.
180
+ *
181
+ * Canonical, not raw bytes, and deliberately: a living spec is rewritten by
182
+ * `serializeRequirements` on every archive, so hashing the file's bytes would
183
+ * make a baseline go stale over reflowed blank lines — a false alarm on the one
184
+ * check whose whole value is that it only fires when something real moved.
185
+ * What the hash therefore covers is exactly what survives a round trip: the
186
+ * heading, the body (`Requirement-ID:` and `Operations:`/`Covers:` lines
187
+ * included), and every scenario with its Given/When/Then lines. `kind` and
188
+ * `section` are not serialized and so are not hashed — a requirement does not
189
+ * change because the document quoting it moved it under another heading.
190
+ *
191
+ * The two normalizations are the two kinds of bookkeeping a requirement can
192
+ * carry about its own joins — the baseline pointing AT it, and the `Realizes:`
193
+ * pins pointing OUT of it. Both are stripped for the same reason: a digest that
194
+ * moved when bookkeeping moved would make every pin self-invalidating.
195
+ */
196
+ export function requirementDigest(requirement) {
197
+ return createHash("sha256")
198
+ .update(serializeRequirements([withoutRealizesPins(withoutBaseline(requirement))]), "utf8")
199
+ .digest("hex")
200
+ .slice(0, REQUIREMENT_DIGEST_LENGTH);
201
+ }
202
+ /** Serialize requirements back to OpenSpec markdown (`### Requirement:` + `#### Scenario:`). */
203
+ export function serializeRequirements(reqs) {
204
+ // Framing (blank lines between sections) is normalized here; body content is
205
+ // only edge-trimmed, never collapsed — blank lines inside a scenario (e.g. in
206
+ // fenced code blocks) are verbatim content.
207
+ const chunks = [];
208
+ for (const r of reqs) {
209
+ const chunk = [`### Requirement: ${r.name}`];
210
+ const body = [...r.text];
211
+ // Parsed documents already carry the authored line in `text`. This branch
212
+ // also makes programmatically constructed Requirements serialize fully.
213
+ if (r.id !== undefined && !body.some((line) => REQUIREMENT_ID_LINE_RE.test(line))) {
214
+ body.unshift(`Requirement-ID: ${r.id}`);
215
+ }
216
+ // Directly under the identity it pins, which is where `loam rebase` writes
217
+ // it too — the two lines are read together, so they are never apart.
218
+ if (r.basedOn !== undefined && !body.some((line) => BASED_ON_LINE_RE.test(line))) {
219
+ body.splice(body.findIndex((line) => REQUIREMENT_ID_LINE_RE.test(line)) + 1, 0, `Based-On: ${r.basedOn}`);
220
+ }
221
+ const text = body.join("\n").trim();
222
+ if (text)
223
+ chunk.push("", text);
224
+ for (const s of r.scenarios) {
225
+ chunk.push("", `#### Scenario: ${s.name}`);
226
+ const body = s.lines.join("\n").trim();
227
+ if (body)
228
+ chunk.push(body);
229
+ }
230
+ chunks.push(chunk.join("\n"));
231
+ }
232
+ return chunks.join("\n\n").trim() + "\n";
233
+ }
234
+ /**
235
+ * A delta requirement as it lands in the living document, with the bookkeeping
236
+ * that belongs to the DELTA left behind: the `Based-On:` pin (a claim about
237
+ * which living version this was written against — meaningless once it IS the
238
+ * living version, and poison for the next feature's baseline, which would then
239
+ * hash the previous feature's pin) and the line it was parsed from.
240
+ */
241
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../../src/core/document/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkJzC,MAAM,CAAC,MAAM,OAAO,GAAG,oDAAoD,CAAC;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,kCAAkC,CAAC;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,sCAAsC,CAAC;AAEzE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;IACrD,IAAI,EAAE,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AACrD,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,iCAAiC,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAE5C,uFAAuF;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,MAAM,CAAC,aAAa,yBAAyB,IAAI,CAAC,CAAC;AAO5F,mFAAmF;AACnF,MAAM,UAAU,yBAAyB,CAAC,WAAwB;IAChE,OAAO,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACvC,MAAM,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,qBAAqB,CAAC,IAAmB;IACvD,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,WAAW,IAAI,IAAI,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,yBAAyB,CAAC,WAAW,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC;YACpC,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,SAAS;gBAC5B,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;QACD,sEAAsE;QACtE,8DAA8D;QAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC;YAAE,SAAS;QACzE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,KAAK,MAAM,CAAC,EAAE,EAAE,YAAY,CAAC,IAAI,MAAM,EAAE,CAAC;QACxC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,mBAAmB,CAAC,WAAwB;IAC1D,OAAO,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAAC,WAAwB;IACtD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,WAAW,CAAC;IACnD,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AAC5F,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAwB;IAC1D,OAAO;QACL,GAAG,WAAW;QACd,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE;iBACnB,KAAK,CAAC,GAAG,CAAC;iBACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;iBACrD,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC;QACvC,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAwB;IACxD,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,qBAAqB,CAAC,CAAC,mBAAmB,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;SAC1F,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,CAAC;AACzC,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,qBAAqB,CAAC,IAAmB;IACvD,6EAA6E;IAC7E,8EAA8E;IAC9E,4CAA4C;IAC5C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,KAAK,GAAa,CAAC,oBAAoB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QACzB,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAClF,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,2EAA2E;QAC3E,qEAAqE;QACrE,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACjF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,kBAAkB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3C,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YACvC,IAAI,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG","sourcesContent":["/**\n * What a requirement IS, what makes one the same requirement twice, and the one\n * canonical way it is written back out.\n *\n * Living specs use `## Requirements` with `### Requirement:` + `#### Scenario:`.\n * Feature deltas group requirements under `## ADDED|MODIFIED|REMOVED\n * Requirements`. Scenarios are the acceptance criteria (Given/When/Then) and\n * the source for tests.\n *\n * Identity and serialization cannot be separated here: `requirementDigest` is\n * defined AS the sha256 of the serialized requirement without its baseline, so\n * a second module owning the writer would mean the digest and the bytes it\n * claims to describe could disagree. `./parse.ts` turns markdown into these,\n * `./apply.ts` folds a delta into a living set of them.\n */\nimport { createHash } from \"node:crypto\";\n\nexport type DeltaKind = \"ADDED\" | \"MODIFIED\" | \"REMOVED\" | \"BASE\";\n\nexport interface Scenario {\n name: string;\n lines: string[];\n}\n\nexport interface Requirement {\n /** BASE for a living spec; ADDED/MODIFIED/REMOVED inside a feature delta. */\n kind: DeltaKind;\n /**\n * Stable identity from a `Requirement-ID:` body line. Optional so existing\n * OpenSpec-compatible documents continue to use their heading as identity.\n */\n id?: string;\n /**\n * The living requirement this delta was WRITTEN AGAINST, from a `Based-On:`\n * body line — `requirementDigest` of that requirement at authoring time.\n *\n * Delta-only, and meaningful on MODIFIED/REMOVED alone: those two address a\n * requirement that already exists, and a MODIFIED carries its full new text,\n * so archive does not merge the living wording — it REPLACES it. Two features\n * rewriting one requirement therefore lose the loser's text outright, and the\n * in-flight warning (`delta.modified-conflict`) cannot see the case that\n * actually happens: once the first archives it leaves `changes/`, the second\n * goes green again, and its stale text lands on top without a word. This is\n * the pin that closes that window — the same trick as `sources_digest`\n * (core/provenance/stamp.ts), one requirement wide.\n *\n * Absent on requirements adopted from OpenSpec, which never had the line;\n * those keep the older, weaker protection and are told so once (warn).\n */\n basedOn?: string;\n name: string;\n text: string[];\n /** OpenAPI operationIds this requirement governs, from an `Operations:` line. */\n operations: string[];\n /**\n * What this requirement's scenarios exercise, from a `Covers:` line — the\n * architecture analog of `Operations:`. Entries are C4 element ids, edges\n * (`source -> target`), or health signals (`alert:<id>` / `sli:<id>`);\n * core/c4/arch.ts owns the grammar and the resolution. Parsed everywhere for one\n * grammar's sake, meaningful in arch.spec.md.\n */\n covers: string[];\n /**\n * Authorization this requirement is gated on, from a `Requires:` line —\n * `<subject>/<permission>` entries declared in `architecture/permissions.yaml`.\n *\n * A third join beside `Operations:` and `Covers:`, and it exists because the\n * other two cannot carry it. A permission is not an operation (one operation\n * is gated by several, and the same permission gates several operations) and\n * it is not a C4 element (it is a domain fact, not a structural one). The\n * subject half is what the OpenAPI security model cannot express at all:\n * `security` names what the CALLER must hold, while a permission is as often\n * checked on an entity inside the request — the profile a message is sent\n * from, the org a resource belongs to.\n */\n requires: string[];\n /**\n * Fleet capabilities this requirement realizes part of, from a `Capability:`\n * (also accepted: `Capabilities:`) line — ids declared in\n * `architecture/capabilities.yaml`.\n *\n * A fourth join beside `Operations:`, `Covers:` and `Requires:`, and a LIST\n * because the relation is many-to-many in both directions: one requirement\n * commonly closes part of two capabilities, and a capability is realized by\n * many requirements across several services — a single-valued field would\n * force authors to pick a lie. Neither existing line can carry it: a\n * capability is not an operation (it is behaviour above any one endpoint)\n * and not a C4 element (it is a business fact, not a structural one).\n * The parse is additive exactly as `requires` is — the line rides in\n * `req.text` and therefore inside `requirementDigest`, so no living\n * document's digest moves merely because loam learned to read it.\n */\n capabilities: string[];\n /**\n * Capability REQUIREMENTS this requirement realizes part of, from a\n * `Realizes:` line — entries spelled `<capability-id>#<Requirement-ID>`,\n * resolving against `capabilities/<cap>/spec.md`.\n *\n * A fifth join, and the one that makes the business tree checkable rather\n * than merely present. `Capability:` beside it answers a DIFFERENT question\n * and neither replaces the other: `Capability: checkout` says this\n * requirement is part of that capability, which is a claim about theme;\n * `Realizes: checkout#CHECKOUT-CHARGE-ONCE` says it is part of what makes one\n * named promise true, which is a claim loam can grade in both directions —\n * an entry that resolves to nothing, and a capability requirement nothing\n * realizes.\n *\n * WHY A COMPOSITE ENTRY RATHER THAN TWO LINES. A `Requirement-ID` is only\n * unique inside its own document, so the capability half is not decoration —\n * it is what makes the target addressable at all. Two lines would let a\n * requirement name three capabilities and four ids with nothing saying which\n * belongs to which.\n *\n * WHY THE SEPARATOR IS THE LAST `#` AND NOT THE FIRST. The requirement half\n * has a strict grammar (`REQUIREMENT_ID_RE`) that excludes `#`, while the\n * capability half is a YAML key and a directory name and is not constrained\n * here at all. Splitting at the last `#` is therefore unambiguous for every\n * capability id there is; splitting at the first would mis-parse any id that\n * contained one, and would do it silently.\n *\n * Parsed additively exactly as `capabilities` is — the line rides in\n * `req.text` and therefore inside `requirementDigest`, so no living\n * document's digest moves merely because loam learned to read it.\n */\n realizes: string[];\n /**\n * AsyncAPI message names this requirement governs on the PRODUCING side, from\n * a `Publishes:` line — the event-axis analog of `Operations:`.\n *\n * Two lines rather than one, because the async spine is directional in a way\n * the HTTP one is not: `Publishes:` resolves against this service's OWN\n * contract, while `Consumes:` names a message another service declares, and\n * only a fleet-wide view can say whether anybody does. Grading them alike\n * would either excuse a typo in a producer or point a consumer's error at the\n * wrong repository.\n */\n publishes: string[];\n /** AsyncAPI message names this requirement consumes, from a `Consumes:` line. See `publishes`. */\n consumes: string[];\n scenarios: Scenario[];\n /**\n * The H2 heading of its SOURCE DOCUMENT this requirement was parsed under,\n * verbatim (`## Behavior`), or absent if it preceded every heading. Records where\n * the text came from, so it stays true after a merge re-homes the requirement.\n *\n * `kind` alone cannot explain why a requirement is BASE, and the two BASE cases\n * differ completely: under `## Requirements` a delta is legally quoting the\n * living state, while under `## Behavior` the author wrote a change that archive\n * will silently not merge. `delta.requirement-not-merged` tells them apart.\n */\n section?: string;\n /**\n * 1-based line of this requirement's `### Requirement:` heading in its source\n * document. Body line `text[i]` is therefore at line `line + 1 + i` — the\n * capture is contiguous from the heading to the first scenario — which is what\n * lets `loam rebase` rewrite one `Based-On:` line by surgery instead of\n * reserializing the document and flattening the author's sections and prose.\n */\n line?: number;\n}\n\nexport const KIND_RE = /^##\\s+(ADDED|MODIFIED|REMOVED)\\s+Requirements\\s*$/i;\nexport const REQUIREMENT_ID_LINE_RE = /^\\s*Requirement-ID:\\s*(.*?)\\s*$/i;\nexport const BASED_ON_LINE_RE = /^\\s*Based-On:\\s*(.*?)\\s*$/i;\n\n/**\n * The `Capability:` body line (also accepted: `Capabilities:`), exactly as the\n * parser reads it — `./parse.ts` consumes the capture, migrate-openspec's\n * materializer tests presence with it before appending its own line. One\n * spelling on purpose: two copies of a grammar is how a \"presence\" test and\n * the parse quietly stop agreeing about what counts as the line.\n */\nexport const CAPABILITY_LINE_RE = /^\\s*Capabilit(?:y|ies):\\s*(.+?)\\s*$/i;\n\n/**\n * The `Realizes:` body line. One spelling only — no `Realize:` singular\n * alternative, unlike `Operations?:` and the rest — because the verb already\n * reads correctly for one entry and for six, and the plural forms in the older\n * lines exist to forgive a NOUN that changes shape. A grammar with an\n * alternative nobody needs is one more thing two readers can disagree about.\n */\nexport const REALIZES_LINE_RE = /^\\s*Realizes:\\s*(.+?)\\s*$/i;\n\n/**\n * The separator between a `Realizes:` target and the digest of the capability\n * requirement it was written against — `checkout#CHK-1@9f2c1a4b`.\n *\n * `@` and not a second `#`, because the two halves are read by different rules:\n * `splitRealizesEntry` takes the LAST `#` to find the requirement half, so a\n * second one would move that boundary and re-parse every existing entry. `@`\n * is also excluded from `REQUIREMENT_ID_RE`, which is what makes the split\n * unambiguous rather than a convention — an id can never contain one.\n *\n * NOBODY TYPES THIS. `loam rebase --living` writes it and rewrites it; a human\n * writes `Realizes: checkout#CHK-1` and is never asked for more. An entry\n * without a pin is not a defect and never becomes one — it is a claim that has\n * not been pinned yet, and it grades exactly as it did before pins existed.\n */\nexport const REALIZES_PIN_SEPARATOR = \"@\";\n\n/**\n * One `Realizes:` entry split into the target it names and the digest it was\n * written against, or `pin: null` when it carries none.\n *\n * A suffix that is not a well-formed digest is NOT a pin and NOT a refusal:\n * the whole entry stays the target, so `checkout#CHK-1@nonsense` resolves — or\n * fails to resolve — exactly as it would have before, under\n * `capability.realizes-unknown`, whose message names the entry the author\n * actually typed. Inventing a second refusal for one mistake is the failure\n * `splitRealizesEntry` already refuses two lines up.\n */\nexport function splitRealizesPin(entry: string): { target: string; pin: string | null } {\n const at = entry.lastIndexOf(REALIZES_PIN_SEPARATOR);\n if (at <= 0) return { target: entry, pin: null };\n const suffix = entry.slice(at + 1);\n if (!REQUIREMENT_DIGEST_RE.test(suffix)) return { target: entry, pin: null };\n return { target: entry.slice(0, at), pin: suffix };\n}\n\n/** Portable, review-friendly stable IDs. Case-sensitive by design. */\nexport const REQUIREMENT_ID_RE = /^[A-Za-z][A-Za-z0-9._-]{0,127}$/;\n\n/**\n * How much of the sha256 a `Based-On:` line carries — the length\n * `sources_digest` already uses (core/provenance/stamp.ts), for one reason a reader\n * can hold: every digest loam writes into a document reads the same.\n */\nexport const REQUIREMENT_DIGEST_LENGTH = 16;\n\n/** The value shape of a `Based-On:` line, exactly as `requirementDigest` writes it. */\nexport const REQUIREMENT_DIGEST_RE = new RegExp(`^[0-9a-f]{${REQUIREMENT_DIGEST_LENGTH}}$`);\n\nexport type RequirementIdProblem =\n | { kind: \"invalid\"; requirement: string; value: string }\n | { kind: \"repeated\"; requirement: string; values: string[] }\n | { kind: \"duplicate\"; id: string; requirements: string[] };\n\n/** Every authored Requirement-ID value in a requirement body, including blanks. */\nexport function requirementIdDeclarations(requirement: Requirement): string[] {\n return requirement.text.flatMap((line) => {\n const match = REQUIREMENT_ID_LINE_RE.exec(line);\n return match === null ? [] : [match[1]!.trim()];\n });\n}\n\n/** Structural ID problems within one spec/delta document. */\nexport function requirementIdProblems(reqs: Requirement[]): RequirementIdProblem[] {\n const problems: RequirementIdProblem[] = [];\n const owners = new Map<string, string[]>();\n for (const requirement of reqs) {\n const declarations = requirementIdDeclarations(requirement);\n const values = declarations.length > 0\n ? declarations\n : requirement.id === undefined\n ? []\n : [requirement.id];\n if (declarations.length > 1) {\n problems.push({ kind: \"repeated\", requirement: requirement.name, values: declarations });\n }\n for (const value of values) {\n if (!REQUIREMENT_ID_RE.test(value)) {\n problems.push({ kind: \"invalid\", requirement: requirement.name, value });\n }\n }\n // A repeated declaration is already an error. Do not also pretend its\n // keep-last parse result is an unambiguous document identity.\n if (values.length !== 1 || !REQUIREMENT_ID_RE.test(values[0]!)) continue;\n const names = owners.get(values[0]!) ?? [];\n names.push(requirement.name);\n owners.set(values[0]!, names);\n }\n for (const [id, requirements] of owners) {\n if (requirements.length > 1) problems.push({ kind: \"duplicate\", id, requirements });\n }\n return problems;\n}\n\n/** Every authored `Based-On:` value in a requirement body, including blanks. */\nexport function basedOnDeclarations(requirement: Requirement): string[] {\n return requirement.text.flatMap((line) => {\n const match = BASED_ON_LINE_RE.exec(line);\n return match === null ? [] : [match[1]!.trim()];\n });\n}\n\n/**\n * The requirement without its baseline marker — what a digest is taken over,\n * and what archive merges into the living document.\n *\n * `Based-On:` is a statement ABOUT a delta (\"this was written against that\"),\n * never part of the requirement it describes, and both consequences of\n * forgetting that are bad. Left in the digest input, a requirement's digest\n * would depend on the pin pointing at it, so no baseline could ever be\n * self-consistent. Left in the merged text — `applyRequirementDelta` copies the\n * delta's body into living wholesale — the living document would grow a pin to\n * a version of itself, and the NEXT feature's baseline would be taken over a\n * document containing the previous feature's bookkeeping.\n */\nexport function withoutBaseline(requirement: Requirement): Requirement {\n const { basedOn: _dropped, ...rest } = requirement;\n return { ...rest, text: requirement.text.filter((line) => !BASED_ON_LINE_RE.test(line)) };\n}\n\n/**\n * The requirement with every `Realizes:` pin stripped back to its bare target —\n * the second thing a digest is taken over, for the same reason as the first.\n *\n * `withoutBaseline`'s argument, one axis over: a pin is a statement ABOUT a\n * join (\"this claim was written against that version of the promise\"), never\n * part of the requirement making the claim. Left in the digest input it would\n * make re-pinning a content change, so every requirement realizing a capability\n * would go stale against ITS OWN consumers the moment the capability moved —\n * one edit cascading through the corpus, which is the failure the pin exists to\n * report rather than to cause.\n *\n * The line is kept and only the suffix removed, unlike `withoutBaseline` which\n * drops the whole line: the target ids ARE content, and a requirement that\n * stops naming the promise it keeps is a different requirement.\n */\nexport function withoutRealizesPins(requirement: Requirement): Requirement {\n return {\n ...requirement,\n text: requirement.text.map((line) => {\n const match = REALIZES_LINE_RE.exec(line);\n if (match === null) return line;\n const bare = match[1]!\n .split(\",\")\n .map((entry) => splitRealizesPin(entry.trim()).target)\n .join(\", \");\n return line.replace(match[1]!, bare);\n }),\n };\n}\n\n/**\n * The identity of a requirement's CONTENT: sha256 over its canonical\n * serialization, truncated like every other digest loam stamps.\n *\n * Canonical, not raw bytes, and deliberately: a living spec is rewritten by\n * `serializeRequirements` on every archive, so hashing the file's bytes would\n * make a baseline go stale over reflowed blank lines — a false alarm on the one\n * check whose whole value is that it only fires when something real moved.\n * What the hash therefore covers is exactly what survives a round trip: the\n * heading, the body (`Requirement-ID:` and `Operations:`/`Covers:` lines\n * included), and every scenario with its Given/When/Then lines. `kind` and\n * `section` are not serialized and so are not hashed — a requirement does not\n * change because the document quoting it moved it under another heading.\n *\n * The two normalizations are the two kinds of bookkeeping a requirement can\n * carry about its own joins — the baseline pointing AT it, and the `Realizes:`\n * pins pointing OUT of it. Both are stripped for the same reason: a digest that\n * moved when bookkeeping moved would make every pin self-invalidating.\n */\nexport function requirementDigest(requirement: Requirement): string {\n return createHash(\"sha256\")\n .update(serializeRequirements([withoutRealizesPins(withoutBaseline(requirement))]), \"utf8\")\n .digest(\"hex\")\n .slice(0, REQUIREMENT_DIGEST_LENGTH);\n}\n\n/** Serialize requirements back to OpenSpec markdown (`### Requirement:` + `#### Scenario:`). */\nexport function serializeRequirements(reqs: Requirement[]): string {\n // Framing (blank lines between sections) is normalized here; body content is\n // only edge-trimmed, never collapsed — blank lines inside a scenario (e.g. in\n // fenced code blocks) are verbatim content.\n const chunks: string[] = [];\n for (const r of reqs) {\n const chunk: string[] = [`### Requirement: ${r.name}`];\n const body = [...r.text];\n // Parsed documents already carry the authored line in `text`. This branch\n // also makes programmatically constructed Requirements serialize fully.\n if (r.id !== undefined && !body.some((line) => REQUIREMENT_ID_LINE_RE.test(line))) {\n body.unshift(`Requirement-ID: ${r.id}`);\n }\n // Directly under the identity it pins, which is where `loam rebase` writes\n // it too — the two lines are read together, so they are never apart.\n if (r.basedOn !== undefined && !body.some((line) => BASED_ON_LINE_RE.test(line))) {\n body.splice(body.findIndex((line) => REQUIREMENT_ID_LINE_RE.test(line)) + 1, 0, `Based-On: ${r.basedOn}`);\n }\n const text = body.join(\"\\n\").trim();\n if (text) chunk.push(\"\", text);\n for (const s of r.scenarios) {\n chunk.push(\"\", `#### Scenario: ${s.name}`);\n const body = s.lines.join(\"\\n\").trim();\n if (body) chunk.push(body);\n }\n chunks.push(chunk.join(\"\\n\"));\n }\n return chunks.join(\"\\n\\n\").trim() + \"\\n\";\n}\n\n/**\n * A delta requirement as it lands in the living document, with the bookkeeping\n * that belongs to the DELTA left behind: the `Based-On:` pin (a claim about\n * which living version this was written against — meaningless once it IS the\n * living version, and poison for the next feature's baseline, which would then\n * hash the previous feature's pin) and the line it was parsed from.\n */\n"]}