@ybotok/loam 0.1.0-beta.2 → 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 +2570 -61
  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 +2362 -245
  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 +82 -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/agent/agents-stamp.js +155 -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/read.js +239 -0
  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 +59 -55
  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 +132 -77
  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 -291
  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 -254
  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 -1591
  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 -343
  966. package/dist/commands/vouch.js.map +0 -1
  967. package/dist/core/agent.js +0 -2065
  968. package/dist/core/agent.js.map +0 -1
  969. package/dist/core/agents-stamp.js +0 -68
  970. package/dist/core/agents-stamp.js.map +0 -1
  971. package/dist/core/arch.js +0 -130
  972. package/dist/core/arch.js.map +0 -1
  973. package/dist/core/brief.js +0 -733
  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 -559
  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 -479
  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 -720
  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 -436
  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 -481
  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 -1104
  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,234 @@
1
+ /**
2
+ * What `loam validate` reports about a generated suite: which scenarios moved
3
+ * out from under it, and which were never generated at all.
4
+ *
5
+ * Service-repo-scoped, and silent until a suite exists — a service that never
6
+ * ran `loam gherkin` has not opted into the axis and is not failing it. The
7
+ * whole comparison is by digest rather than by name, so a reworded scenario
8
+ * reads as stale rather than as missing plus new.
9
+ */
10
+ import { existsSync, statSync } from "node:fs";
11
+ import { readdir, readFile, realpath } from "node:fs/promises";
12
+ import { join, relative, sep } from "node:path";
13
+ import { parseRequirements } from "../document/parse.js";
14
+ import { SPEC_AXES } from "../repo/paths.js";
15
+ import { locateServicePaths } from "../repo/service-target.js";
16
+ import { listFeatures } from "../repo/repo.js";
17
+ import { UnsafePathError } from "../kernel/path-safety.js";
18
+ import { gherkinRoot, scenarioDigest } from "./stamp.js";
19
+ import { axisLabel } from "./emit.js";
20
+ import { parseStampedFeature } from "./read.js";
21
+ /**
22
+ * The gherkin freshness chain, service-repo-scoped like `sources.*`: it needs
23
+ * the repo (the suite lives there), and it stays quiet until
24
+ * `<gherkinDir>/loam/` exists — a service that never generated has not opted
25
+ * in, and silence there is honest, not vacuous.
26
+ *
27
+ * The LIVING specs are the reference, with one carve-out. A stamped file
28
+ * tagged with a feature still in flight answers to that feature's delta, not
29
+ * to the living spec its requirements have not merged into yet — so it is
30
+ * skipped here (its regeneration is `loam gherkin <FEAT>`), and the moment the
31
+ * feature archives, its requirements ARE living requirements, digests
32
+ * unchanged, and the same file grades current with no rewrite. A tag naming
33
+ * no active feature (the feature was archived, or abandoned) gets no
34
+ * exemption: the living spec is all there is to answer to.
35
+ *
36
+ * Three warnings, one per way the suite and the spec can disagree, judged
37
+ * digest-first and by requirement name second:
38
+ *
39
+ * - `gherkin.missing` — a living scenario whose digest no stamped scenario of
40
+ * its axis carries: the suite has no test for these words.
41
+ * - `gherkin.stale` — a stamped scenario whose digest matches no living
42
+ * scenario while its file's requirement still exists: the spec moved under
43
+ * the generated file. (A reworded scenario therefore reports both: the old
44
+ * words are stale, the new words are missing — one regeneration clears
45
+ * both.)
46
+ * - `gherkin.orphaned` — a whole file whose requirement no longer exists in
47
+ * its axis's living spec; reported once per file, because every scenario in
48
+ * it is moot together.
49
+ *
50
+ * Digests are content identity within one service and one axis, deliberately:
51
+ * two identically-worded scenarios of one service's one axis share one digest,
52
+ * so one stamped copy covers both — while the axes stay separate namespaces
53
+ * twice over (every comparison here is axis-scoped, and the recipe salts the
54
+ * arch axis), and the services stay separate because the recipe salts the
55
+ * service too. That last salt is why a stamped file copied out of another
56
+ * service's repository grades `gherkin.orphaned`/`gherkin.stale` here instead
57
+ * of quietly counting as this service's coverage.
58
+ *
59
+ * All warnings — `--strict` is the CI escalation — plus one `ok` confirmation
60
+ * (`gherkin.current`, the `sources.current` pattern) when a generated suite
61
+ * exists and nothing disagrees.
62
+ */
63
+ export async function gherkinFindings(ctx) {
64
+ if (ctx.repoDir === undefined)
65
+ return [];
66
+ let root;
67
+ try {
68
+ root = gherkinRoot(ctx.repoDir, ctx.gherkinDir);
69
+ }
70
+ catch (err) {
71
+ if (!(err instanceof UnsafePathError))
72
+ throw err;
73
+ return [
74
+ {
75
+ severity: "error",
76
+ code: "gherkin.path-outside",
77
+ subject: ctx.service,
78
+ message: `The configured Gherkin output is unsafe: ${err.message}. It must stay inside the service repo.`,
79
+ },
80
+ ];
81
+ }
82
+ if (!existsSync(root))
83
+ return [];
84
+ const paths = await locateServicePaths(ctx.docsDir, ctx.service, ctx.fleet);
85
+ const axes = new Map();
86
+ for (const axis of SPEC_AXES) {
87
+ const label = axisLabel(axis);
88
+ const state = { file: axis.file, reqNames: new Set(), digests: new Set(), scenarios: [] };
89
+ const path = paths[axis.key];
90
+ if (existsSync(path)) {
91
+ const reqs = ctx.fleet === undefined
92
+ ? parseRequirements(await readFile(path, "utf8"))
93
+ : await ctx.fleet.readRequirements(path);
94
+ for (const r of reqs) {
95
+ if (r.kind === "REMOVED")
96
+ continue;
97
+ state.reqNames.add(r.name);
98
+ for (const s of r.scenarios) {
99
+ const digest = scenarioDigest(ctx.service, s.lines, label);
100
+ state.digests.add(digest);
101
+ state.scenarios.push({ req: r.name, name: s.name, digest });
102
+ }
103
+ }
104
+ }
105
+ axes.set(label, state);
106
+ }
107
+ // The stamped side. Every stamped digest counts toward coverage — an
108
+ // in-flight file whose scenario matches the living words IS a test for them.
109
+ // `Set<string>`, not the ids' own brand: this set answers membership
110
+ // questions for tags read out of .feature files — document text.
111
+ const active = new Set((await listFeatures(ctx.docsDir, {}, ctx.fleet)).map((f) => f.id));
112
+ const stampedDigests = { business: new Set(), arch: new Set() };
113
+ const parsed = [];
114
+ let stampedScenarios = 0;
115
+ for (const abs of await featureFilesUnder(root)) {
116
+ const file = parseStampedFeature(await readFile(abs, "utf8"));
117
+ if (file === null)
118
+ continue;
119
+ const axis = file.tags.includes("architecture") ? "arch" : "business";
120
+ for (const s of file.scenarios)
121
+ stampedDigests[axis].add(s.digest);
122
+ stampedScenarios += file.scenarios.length;
123
+ parsed.push({ rel: relative(ctx.repoDir, abs).split(sep).join("/"), axis, file });
124
+ }
125
+ const findings = [];
126
+ const regen = `\`loam gherkin --service ${ctx.service}\``;
127
+ for (const [axis, state] of axes) {
128
+ for (const sc of state.scenarios) {
129
+ if (stampedDigests[axis].has(sc.digest))
130
+ continue;
131
+ findings.push({
132
+ severity: "warn",
133
+ code: "gherkin.missing",
134
+ subject: ctx.service,
135
+ message: `${ctx.service}: scenario '${sc.name}' of requirement '${sc.req}' (${state.file}) has no generated .feature scenario — the suite has no test for these words; ${regen} regenerates it`,
136
+ });
137
+ }
138
+ }
139
+ for (const { rel, axis, file } of parsed) {
140
+ if (file.tags.some((t) => active.has(t)))
141
+ continue;
142
+ const state = axes.get(axis);
143
+ if (file.featureName === null || !state.reqNames.has(file.featureName)) {
144
+ findings.push({
145
+ severity: "warn",
146
+ code: "gherkin.orphaned",
147
+ subject: ctx.service,
148
+ message: `${ctx.service}: ${rel}: requirement '${file.featureName ?? "(unnamed)"}' no longer exists in the living ${state.file} — the whole file is orphaned; ${regen} removes it`,
149
+ });
150
+ continue;
151
+ }
152
+ for (const s of file.scenarios) {
153
+ if (state.digests.has(s.digest))
154
+ continue;
155
+ findings.push({
156
+ severity: "warn",
157
+ code: "gherkin.stale",
158
+ subject: ctx.service,
159
+ message: `${ctx.service}: ${rel}: scenario '${s.name}' matches no scenario in the living ${state.file} — the spec moved under the generated file; ${regen} regenerates it`,
160
+ });
161
+ }
162
+ }
163
+ if (findings.length === 0) {
164
+ findings.push({
165
+ severity: "ok",
166
+ code: "gherkin.current",
167
+ message: `${ctx.service}: generated gherkin current (${parsed.length} file(s), ${stampedScenarios} stamped scenario(s) agree with the living specs)`,
168
+ });
169
+ }
170
+ return findings;
171
+ }
172
+ /**
173
+ * Every `.feature` file at or under `root`, sorted by repo-relative path —
174
+ * enumeration is part of the output contract, so the order cannot depend on
175
+ * the filesystem's. Dot-entries are skipped, like every other walk in loam.
176
+ *
177
+ * Symlinks are FOLLOWED — repo.ts's `entryIs` and brief/brief.ts's `hasMarkdown` are
178
+ * the same rule for the same reason. A `Dirent` from `readdir(withFileTypes)`
179
+ * never follows one, so `isFile()` and `isDirectory()` are both false for a
180
+ * symlinked `.feature`: it was invisible to the orphan scan (`loam gherkin`
181
+ * left it behind forever) and invisible to the staleness scan (it graded as no
182
+ * coverage at all). Composing a suite out of links is legitimate, so they are
183
+ * followed rather than refused, and a dangling one is skipped exactly as an
184
+ * absent file is.
185
+ */
186
+ export async function featureFilesUnder(root) {
187
+ const out = [];
188
+ // Following links means a cycle is now possible; every directory is entered
189
+ // once by its resolved path, so a loop terminates instead of hanging.
190
+ const entered = new Set();
191
+ const walk = async (dir) => {
192
+ if (!existsSync(dir))
193
+ return;
194
+ let resolved;
195
+ try {
196
+ resolved = await realpath(dir);
197
+ }
198
+ catch {
199
+ return;
200
+ }
201
+ if (entered.has(resolved))
202
+ return;
203
+ entered.add(resolved);
204
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
205
+ if (entry.name.startsWith("."))
206
+ continue;
207
+ const abs = join(dir, entry.name);
208
+ const what = entryKind(abs, entry);
209
+ if (what === "dir")
210
+ await walk(abs);
211
+ else if (what === "file" && entry.name.endsWith(".feature"))
212
+ out.push(abs);
213
+ }
214
+ };
215
+ await walk(root);
216
+ return out.sort((a, b) => {
217
+ const ra = a.split(sep).join("/");
218
+ const rb = b.split(sep).join("/");
219
+ return ra < rb ? -1 : ra > rb ? 1 : 0;
220
+ });
221
+ }
222
+ /** What an entry IS, resolving a symlink to find out. Nothing when it dangles. */
223
+ function entryKind(abs, e) {
224
+ if (!e.isSymbolicLink())
225
+ return e.isDirectory() ? "dir" : e.isFile() ? "file" : null;
226
+ try {
227
+ const s = statSync(abs);
228
+ return s.isDirectory() ? "dir" : s.isFile() ? "file" : null;
229
+ }
230
+ catch {
231
+ return null;
232
+ }
233
+ }
234
+ //# sourceMappingURL=stale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale.js","sourceRoot":"","sources":["../../../src/core/gherkin/stale.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam validate` reports about a generated suite: which scenarios moved\n * out from under it, and which were never generated at all.\n *\n * Service-repo-scoped, and silent until a suite exists — a service that never\n * ran `loam gherkin` has not opted into the axis and is not failing it. The\n * whole comparison is by digest rather than by name, so a reworded scenario\n * reads as stale rather than as missing plus new.\n */\nimport { existsSync, statSync, type Dirent } from \"node:fs\";\nimport { readdir, readFile, realpath } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport { SPEC_AXES } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { listFeatures } from \"../repo/repo.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport { UnsafePathError } from \"../kernel/path-safety.js\";\nimport { gherkinRoot, scenarioDigest } from \"./stamp.js\";\nimport { axisLabel } from \"./emit.js\";\nimport { parseStampedFeature, type StampedFeature } from \"./read.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/**\n * The gherkin freshness chain, service-repo-scoped like `sources.*`: it needs\n * the repo (the suite lives there), and it stays quiet until\n * `<gherkinDir>/loam/` exists — a service that never generated has not opted\n * in, and silence there is honest, not vacuous.\n *\n * The LIVING specs are the reference, with one carve-out. A stamped file\n * tagged with a feature still in flight answers to that feature's delta, not\n * to the living spec its requirements have not merged into yet — so it is\n * skipped here (its regeneration is `loam gherkin <FEAT>`), and the moment the\n * feature archives, its requirements ARE living requirements, digests\n * unchanged, and the same file grades current with no rewrite. A tag naming\n * no active feature (the feature was archived, or abandoned) gets no\n * exemption: the living spec is all there is to answer to.\n *\n * Three warnings, one per way the suite and the spec can disagree, judged\n * digest-first and by requirement name second:\n *\n * - `gherkin.missing` — a living scenario whose digest no stamped scenario of\n * its axis carries: the suite has no test for these words.\n * - `gherkin.stale` — a stamped scenario whose digest matches no living\n * scenario while its file's requirement still exists: the spec moved under\n * the generated file. (A reworded scenario therefore reports both: the old\n * words are stale, the new words are missing — one regeneration clears\n * both.)\n * - `gherkin.orphaned` — a whole file whose requirement no longer exists in\n * its axis's living spec; reported once per file, because every scenario in\n * it is moot together.\n *\n * Digests are content identity within one service and one axis, deliberately:\n * two identically-worded scenarios of one service's one axis share one digest,\n * so one stamped copy covers both — while the axes stay separate namespaces\n * twice over (every comparison here is axis-scoped, and the recipe salts the\n * arch axis), and the services stay separate because the recipe salts the\n * service too. That last salt is why a stamped file copied out of another\n * service's repository grades `gherkin.orphaned`/`gherkin.stale` here instead\n * of quietly counting as this service's coverage.\n *\n * All warnings — `--strict` is the CI escalation — plus one `ok` confirmation\n * (`gherkin.current`, the `sources.current` pattern) when a generated suite\n * exists and nothing disagrees.\n */\nexport async function gherkinFindings(ctx: {\n docsDir: DocsDir;\n service: PathableService;\n /** The service repo, when loam is standing in it. Undefined disables the chain, like sources.*. */\n repoDir?: string;\n gherkinDir?: string;\n fleet?: FleetContext;\n}): Promise<Finding[]> {\n if (ctx.repoDir === undefined) return [];\n let root: string;\n try {\n root = gherkinRoot(ctx.repoDir, ctx.gherkinDir);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return [\n {\n severity: \"error\",\n code: \"gherkin.path-outside\",\n subject: ctx.service,\n message: `The configured Gherkin output is unsafe: ${err.message}. It must stay inside the service repo.`,\n },\n ];\n }\n if (!existsSync(root)) return [];\n\n // The living reference, per axis: requirement names, and scenario digests.\n interface AxisState {\n file: string;\n reqNames: Set<string>;\n digests: Set<string>;\n scenarios: Array<{ req: string; name: string; digest: string }>;\n }\n const paths = await locateServicePaths(ctx.docsDir, ctx.service, ctx.fleet);\n const axes = new Map<\"business\" | \"arch\", AxisState>();\n for (const axis of SPEC_AXES) {\n const label = axisLabel(axis);\n const state: AxisState = { file: axis.file, reqNames: new Set(), digests: new Set(), scenarios: [] };\n const path = paths[axis.key];\n if (existsSync(path)) {\n const reqs = ctx.fleet === undefined\n ? parseRequirements(await readFile(path, \"utf8\"))\n : await ctx.fleet.readRequirements(path);\n for (const r of reqs) {\n if (r.kind === \"REMOVED\") continue;\n state.reqNames.add(r.name);\n for (const s of r.scenarios) {\n const digest = scenarioDigest(ctx.service, s.lines, label);\n state.digests.add(digest);\n state.scenarios.push({ req: r.name, name: s.name, digest });\n }\n }\n }\n axes.set(label, state);\n }\n\n // The stamped side. Every stamped digest counts toward coverage — an\n // in-flight file whose scenario matches the living words IS a test for them.\n // `Set<string>`, not the ids' own brand: this set answers membership\n // questions for tags read out of .feature files — document text.\n const active = new Set<string>((await listFeatures(ctx.docsDir, {}, ctx.fleet)).map((f) => f.id));\n const stampedDigests = { business: new Set<string>(), arch: new Set<string>() };\n const parsed: Array<{ rel: string; axis: \"business\" | \"arch\"; file: StampedFeature }> = [];\n let stampedScenarios = 0;\n for (const abs of await featureFilesUnder(root)) {\n const file = parseStampedFeature(await readFile(abs, \"utf8\"));\n if (file === null) continue;\n const axis = file.tags.includes(\"architecture\") ? \"arch\" : \"business\";\n for (const s of file.scenarios) stampedDigests[axis].add(s.digest);\n stampedScenarios += file.scenarios.length;\n parsed.push({ rel: relative(ctx.repoDir, abs).split(sep).join(\"/\"), axis, file });\n }\n\n const findings: Finding[] = [];\n const regen = `\\`loam gherkin --service ${ctx.service}\\``;\n for (const [axis, state] of axes) {\n for (const sc of state.scenarios) {\n if (stampedDigests[axis].has(sc.digest)) continue;\n findings.push({\n severity: \"warn\",\n code: \"gherkin.missing\",\n subject: ctx.service,\n message: `${ctx.service}: scenario '${sc.name}' of requirement '${sc.req}' (${state.file}) has no generated .feature scenario — the suite has no test for these words; ${regen} regenerates it`,\n });\n }\n }\n for (const { rel, axis, file } of parsed) {\n if (file.tags.some((t) => active.has(t))) continue;\n const state = axes.get(axis)!;\n if (file.featureName === null || !state.reqNames.has(file.featureName)) {\n findings.push({\n severity: \"warn\",\n code: \"gherkin.orphaned\",\n subject: ctx.service,\n message: `${ctx.service}: ${rel}: requirement '${file.featureName ?? \"(unnamed)\"}' no longer exists in the living ${state.file} — the whole file is orphaned; ${regen} removes it`,\n });\n continue;\n }\n for (const s of file.scenarios) {\n if (state.digests.has(s.digest)) continue;\n findings.push({\n severity: \"warn\",\n code: \"gherkin.stale\",\n subject: ctx.service,\n message: `${ctx.service}: ${rel}: scenario '${s.name}' matches no scenario in the living ${state.file} — the spec moved under the generated file; ${regen} regenerates it`,\n });\n }\n }\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"gherkin.current\",\n message: `${ctx.service}: generated gherkin current (${parsed.length} file(s), ${stampedScenarios} stamped scenario(s) agree with the living specs)`,\n });\n }\n return findings;\n}\n\n/**\n * Every `.feature` file at or under `root`, sorted by repo-relative path —\n * enumeration is part of the output contract, so the order cannot depend on\n * the filesystem's. Dot-entries are skipped, like every other walk in loam.\n *\n * Symlinks are FOLLOWED — repo.ts's `entryIs` and brief/brief.ts's `hasMarkdown` are\n * the same rule for the same reason. A `Dirent` from `readdir(withFileTypes)`\n * never follows one, so `isFile()` and `isDirectory()` are both false for a\n * symlinked `.feature`: it was invisible to the orphan scan (`loam gherkin`\n * left it behind forever) and invisible to the staleness scan (it graded as no\n * coverage at all). Composing a suite out of links is legitimate, so they are\n * followed rather than refused, and a dangling one is skipped exactly as an\n * absent file is.\n */\nexport async function featureFilesUnder(root: string): Promise<string[]> {\n const out: string[] = [];\n // Following links means a cycle is now possible; every directory is entered\n // once by its resolved path, so a loop terminates instead of hanging.\n const entered = new Set<string>();\n const walk = async (dir: string): Promise<void> => {\n if (!existsSync(dir)) return;\n let resolved: string;\n try {\n resolved = await realpath(dir);\n } catch {\n return;\n }\n if (entered.has(resolved)) return;\n entered.add(resolved);\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\")) continue;\n const abs = join(dir, entry.name);\n const what = entryKind(abs, entry);\n if (what === \"dir\") await walk(abs);\n else if (what === \"file\" && entry.name.endsWith(\".feature\")) out.push(abs);\n }\n };\n await walk(root);\n return out.sort((a, b) => {\n const ra = a.split(sep).join(\"/\");\n const rb = b.split(sep).join(\"/\");\n return ra < rb ? -1 : ra > rb ? 1 : 0;\n });\n}\n\n/** What an entry IS, resolving a symlink to find out. Nothing when it dangles. */\nfunction entryKind(abs: string, e: Dirent): \"dir\" | \"file\" | null {\n if (!e.isSymbolicLink()) return e.isDirectory() ? \"dir\" : e.isFile() ? \"file\" : null;\n try {\n const s = statSync(abs);\n return s.isDirectory() ? \"dir\" : s.isFile() ? \"file\" : null;\n } catch {\n return null;\n }\n}\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The Gherkin emission: spec scenarios become real `.feature` files.
3
+ *
4
+ * Scenarios have been "the source for tests" since SCHEMA.md's first draft, but
5
+ * until now they only left the docs repo as projected markdown — prose a coding
6
+ * agent could paraphrase into something easier to pass. The emission closes the
7
+ * gap mechanically: one `.feature` file per requirement, steps taken from the
8
+ * scenario's own bullet lines, and a digest TAG (`@loam-digest-<16hex>`) on
9
+ * every scenario using the SAME body-hash recipe `loam verify` folds into its
10
+ * claim ids (`./digest.ts`'s scenarioBodyHash — salted by the owning service and
11
+ * by the spec axis, so a stamp belongs to one suite and one axis) — so the
12
+ * test skeleton, the claim and the spec can never quietly disagree about what
13
+ * a scenario says, and no two repositories can answer each other's. A tag
14
+ * rather than a comment because tags are what cucumber's JSON report carries
15
+ * per scenario: the stamp rides through the runner untouched, which is what
16
+ * lets `loam verify --results` match a report scenario back to its claim.
17
+ *
18
+ * The files land in the SERVICE repo — the docs repo states the questions, the
19
+ * tests live with the code — under `<gherkinDir>/loam/`. That `loam/`
20
+ * subdirectory is loam's own derived space: regeneration rewrites its scope and
21
+ * removes its orphans, and loam never writes or deletes a byte outside it.
22
+ * Everything here is deterministic on purpose: the same specs yield the same
23
+ * bytes, which is what lets `loam validate` grade staleness by digest instead
24
+ * of by guesswork.
25
+ */
26
+ import { join } from "node:path";
27
+ import { scenarioBodyHash } from "./digest.js";
28
+ import { resolveInside } from "../kernel/path-safety.js";
29
+ /** Where Gherkin lives in a service repo when loam.json does not say. */
30
+ const DEFAULT_GHERKIN_DIR = "features";
31
+ /** The subdirectory loam owns outright. Everything loam writes stays inside it. */
32
+ const GHERKIN_SUBDIR = "loam";
33
+ /** How much of the scenario body hash goes into a `@loam-digest-…` tag. */
34
+ export const GHERKIN_DIGEST_LENGTH = 16;
35
+ /** The one directory `loam gherkin` may touch: `<repo>/<gherkinDir>/loam/`. */
36
+ export function gherkinRoot(repoDir, gherkinDir) {
37
+ return resolveInside(repoDir, join(gherkinDir ?? DEFAULT_GHERKIN_DIR, GHERKIN_SUBDIR), "gherkin output directory");
38
+ }
39
+ /**
40
+ * The digest stamped onto every generated scenario: verify's scenario body
41
+ * hash, at the sources-digest length. Content identity WITHIN one service and
42
+ * one axis, not provenance — two identically-worded scenarios of one service's
43
+ * one axis share a digest, and that is the point: the stamp answers "does this
44
+ * suite still contain a test for THESE words".
45
+ *
46
+ * Both salts ride in the hash (see `scenarioBodyHash`), so the namespaces stay
47
+ * separate all the way into a cucumber report: a business run can never answer
48
+ * for an arch scenario, and one service's green run can never answer another
49
+ * service's claim just because the two specs spell the sentence the same way.
50
+ * `service` is required and never defaulted — a stamp that forgot it would be
51
+ * a tag no claim matches, and the fleet bug it exists to close would come back
52
+ * silently.
53
+ */
54
+ export function scenarioDigest(service, lines, axis = "business") {
55
+ return scenarioBodyHash(service, lines, axis).slice(0, GHERKIN_DIGEST_LENGTH);
56
+ }
57
+ /** The digest as the tag on the line above each `Scenario:`. */
58
+ export function scenarioDigestTag(digest) {
59
+ return `@loam-digest-${digest}`;
60
+ }
61
+ /**
62
+ * A tag token carrying a scenario digest — in a generated file, or in a
63
+ * cucumber JSON report. The leading `@` is optional because some runners
64
+ * (behave) strip it from the tags they report.
65
+ */
66
+ export const DIGEST_TAG_RE = /^@?loam-digest-([0-9a-f]{16})$/;
67
+ /**
68
+ * Line 1 of every generated file — the AGENTS.md stamp pattern, in Gherkin
69
+ * comment syntax. The version records which loam wrote the file; the rest is
70
+ * the ownership rule in the reader's face, because `loam/` files are rewritten
71
+ * wholesale on regeneration and a hand edit would be silently destroyed.
72
+ */
73
+ export function gherkinStampLine(version) {
74
+ return `# generated by loam v${version} — \`loam gherkin\`; do not hand-edit, regenerate instead`;
75
+ }
76
+ export const STAMP_RE = /^#\s*generated by loam v(\S+)/;
77
+ //# sourceMappingURL=stamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../src/core/gherkin/stamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAqB,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAEvC,mFAAmF;AACnF,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,2EAA2E;AAC3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,UAAmB;IAC9D,OAAO,aAAa,CAClB,OAAO,EACP,IAAI,CAAC,UAAU,IAAI,mBAAmB,EAAE,cAAc,CAAC,EACvD,0BAA0B,CAC3B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,KAAe,EACf,IAAI,GAAiB,UAAU;IAE/B,OAAO,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;AAChF,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,gBAAgB,MAAM,EAAE,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,wBAAwB,OAAO,2DAA2D,CAAC;AACpG,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,+BAA+B,CAAC","sourcesContent":["/**\n * The Gherkin emission: spec scenarios become real `.feature` files.\n *\n * Scenarios have been \"the source for tests\" since SCHEMA.md's first draft, but\n * until now they only left the docs repo as projected markdown — prose a coding\n * agent could paraphrase into something easier to pass. The emission closes the\n * gap mechanically: one `.feature` file per requirement, steps taken from the\n * scenario's own bullet lines, and a digest TAG (`@loam-digest-<16hex>`) on\n * every scenario using the SAME body-hash recipe `loam verify` folds into its\n * claim ids (`./digest.ts`'s scenarioBodyHash — salted by the owning service and\n * by the spec axis, so a stamp belongs to one suite and one axis) — so the\n * test skeleton, the claim and the spec can never quietly disagree about what\n * a scenario says, and no two repositories can answer each other's. A tag\n * rather than a comment because tags are what cucumber's JSON report carries\n * per scenario: the stamp rides through the runner untouched, which is what\n * lets `loam verify --results` match a report scenario back to its claim.\n *\n * The files land in the SERVICE repo — the docs repo states the questions, the\n * tests live with the code — under `<gherkinDir>/loam/`. That `loam/`\n * subdirectory is loam's own derived space: regeneration rewrites its scope and\n * removes its orphans, and loam never writes or deletes a byte outside it.\n * Everything here is deterministic on purpose: the same specs yield the same\n * bytes, which is what lets `loam validate` grade staleness by digest instead\n * of by guesswork.\n */\nimport { join } from \"node:path\";\nimport { scenarioBodyHash, type ScenarioAxis } from \"./digest.js\";\nimport { resolveInside } from \"../kernel/path-safety.js\";\n\n/** Where Gherkin lives in a service repo when loam.json does not say. */\nconst DEFAULT_GHERKIN_DIR = \"features\";\n\n/** The subdirectory loam owns outright. Everything loam writes stays inside it. */\nconst GHERKIN_SUBDIR = \"loam\";\n\n/** How much of the scenario body hash goes into a `@loam-digest-…` tag. */\nexport const GHERKIN_DIGEST_LENGTH = 16;\n\n/** The one directory `loam gherkin` may touch: `<repo>/<gherkinDir>/loam/`. */\nexport function gherkinRoot(repoDir: string, gherkinDir?: string): string {\n return resolveInside(\n repoDir,\n join(gherkinDir ?? DEFAULT_GHERKIN_DIR, GHERKIN_SUBDIR),\n \"gherkin output directory\",\n );\n}\n\n/**\n * The digest stamped onto every generated scenario: verify's scenario body\n * hash, at the sources-digest length. Content identity WITHIN one service and\n * one axis, not provenance — two identically-worded scenarios of one service's\n * one axis share a digest, and that is the point: the stamp answers \"does this\n * suite still contain a test for THESE words\".\n *\n * Both salts ride in the hash (see `scenarioBodyHash`), so the namespaces stay\n * separate all the way into a cucumber report: a business run can never answer\n * for an arch scenario, and one service's green run can never answer another\n * service's claim just because the two specs spell the sentence the same way.\n * `service` is required and never defaulted — a stamp that forgot it would be\n * a tag no claim matches, and the fleet bug it exists to close would come back\n * silently.\n */\nexport function scenarioDigest(\n service: string,\n lines: string[],\n axis: ScenarioAxis = \"business\",\n): string {\n return scenarioBodyHash(service, lines, axis).slice(0, GHERKIN_DIGEST_LENGTH);\n}\n\n/** The digest as the tag on the line above each `Scenario:`. */\nexport function scenarioDigestTag(digest: string): string {\n return `@loam-digest-${digest}`;\n}\n\n/**\n * A tag token carrying a scenario digest — in a generated file, or in a\n * cucumber JSON report. The leading `@` is optional because some runners\n * (behave) strip it from the tags they report.\n */\nexport const DIGEST_TAG_RE = /^@?loam-digest-([0-9a-f]{16})$/;\n\n/**\n * Line 1 of every generated file — the AGENTS.md stamp pattern, in Gherkin\n * comment syntax. The version records which loam wrote the file; the rest is\n * the ownership rule in the reader's face, because `loam/` files are rewritten\n * wholesale on regeneration and a hand edit would be silently destroyed.\n */\nexport function gherkinStampLine(version: string): string {\n return `# generated by loam v${version} — \\`loam gherkin\\`; do not hand-edit, regenerate instead`;\n}\n\nexport const STAMP_RE = /^#\\s*generated by loam v(\\S+)/;\n\n"]}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The step catalogue: which phrases somebody has DECIDED the suite defines.
3
+ *
4
+ * ## The decision this file records, because it was the open question
5
+ *
6
+ * `./inventory.ts` answers how many definitions a suite needs. It stops there on
7
+ * purpose — no file, no finding, no stable code — and the gap it leaves is the
8
+ * next ARTIFACT, not the next warning: a team reads the report, writes the glue
9
+ * by hand, and nothing anywhere records which phrases now have definitions, so
10
+ * the report is recomputed from scratch every time instead of diffed against a
11
+ * decision somebody made.
12
+ *
13
+ * The catalogue is **AUTHORED**, not emitted, and it lives in the DOCS repo at
14
+ * `services/<svc>/steps.yaml`. Four things decided it:
15
+ *
16
+ * - It is a DECISION, not an observation. loam does not read code and must
17
+ * never present a derived thing as truth (the non-goal is written down); what
18
+ * a team can say is "these are the phrases we have agreed to define", and
19
+ * that sentence is an authored document by construction. An emitted
20
+ * catalogue would have to be derived from a step registry loam cannot read.
21
+ * - `loam steps` NEEDS NO SERVICE REPO to stand in — that is its own stated
22
+ * design, and it reads the living specs rather than the emitted `.feature`
23
+ * files precisely so it answers the same way in a repo that has never run
24
+ * `loam gherkin`. A catalogue in the service repo would make the comparison
25
+ * require one, which is a different command.
26
+ * - The shape rule the glossary and capability trees already settled: an entry
27
+ * with prose gets a file, an entry WITHOUT prose stays a line in YAML. A
28
+ * catalogue entry is a phrase and nothing else, so it is a line in YAML — the
29
+ * same side of that rule `architecture/capabilities.yaml` and
30
+ * `permissions.yaml` sit on.
31
+ * - Nothing generated may land inside `<gherkinDir>/loam/`, which `loam
32
+ * gherkin` owns and overwrites wholesale. Living in the docs repo satisfies
33
+ * that by construction rather than by a rule somebody has to remember.
34
+ *
35
+ * SERVICE-SCOPED, beside `health.yaml`, because a step registry belongs to one
36
+ * suite and a suite belongs to one service. A fleet-level catalogue would claim
37
+ * one registry across services that do not share a test runner.
38
+ *
39
+ * ## What it does NOT do, and this is the other half of the decision
40
+ *
41
+ * **It gains no stable code, and `loam validate` never reads it.** A phrase
42
+ * written but not catalogued is a work-list row, not a breach: writing a spec
43
+ * ahead of the glue is the normal order, and a check that fires on it is the
44
+ * kind a fleet turns off — at which point the real findings beside it are turned
45
+ * off too. The near-duplicate groups stay a report for the same reason. So this
46
+ * axis costs one document kind and zero codes, and everything it produces is
47
+ * columns in `loam steps`.
48
+ *
49
+ * **It claims nothing about whether the glue exists.** A catalogued phrase is a
50
+ * phrase somebody said they would define; loam cannot see a step registry and
51
+ * does not pretend to. The two columns are named for what they actually mean —
52
+ * written-not-catalogued and catalogued-not-written — and neither says "missing
53
+ * definition".
54
+ *
55
+ * ## The grammar
56
+ *
57
+ * ```yaml
58
+ * steps:
59
+ * - a payment of 100.00
60
+ * - Given it is split 60/40
61
+ * ```
62
+ *
63
+ * A list of step TEXTS, not of keys. The key is loam's own spelling — `{n}`,
64
+ * `{s}`, `{p}` collapse, keyword stripped (`./phrase.ts`) — and asking an author
65
+ * to write it would make an internal normalisation a hand-typed contract, where
66
+ * one wrong brace silently catalogues nothing. So an entry is written the way a
67
+ * step is written, with or without its keyword, and loam keys it the same way it
68
+ * keys the written steps it is being compared against. Two entries that collapse
69
+ * onto one key are reported as such rather than merged: that is the author
70
+ * cataloguing one definition twice, which is exactly what the near-duplicate
71
+ * report exists to surface.
72
+ */
73
+ import { existsSync } from "node:fs";
74
+ import { readFile } from "node:fs/promises";
75
+ import { parse as parseYaml } from "yaml";
76
+ import { isRecord } from "../../kernel/records.js";
77
+ import { compareIds } from "../../repo/entries.js";
78
+ import { phraseOf } from "./phrase.js";
79
+ import {} from "./inventory.js";
80
+ const ABSENT = { present: false, entries: [] };
81
+ /**
82
+ * Read one service's catalogue.
83
+ *
84
+ * A missing file is silence, not an error: the axis's opt-in is the file's
85
+ * existence, exactly as `capabilities/`'s is a directory's. Anything present and
86
+ * unparseable is `unreadable` with the reason — never an empty catalogue, which
87
+ * would report every written phrase as uncatalogued and read as a work-list
88
+ * somebody has to do.
89
+ */
90
+ export async function readStepCatalogue(path) {
91
+ if (!existsSync(path))
92
+ return ABSENT;
93
+ let doc;
94
+ try {
95
+ doc = parseYaml(await readFile(path, "utf8"));
96
+ }
97
+ catch (e) {
98
+ return { present: true, entries: [], unreadable: e instanceof Error ? e.message : String(e) };
99
+ }
100
+ if (doc === null || doc === undefined)
101
+ return { present: true, entries: [] };
102
+ if (!isRecord(doc))
103
+ return { present: true, entries: [], unreadable: "the document is not a mapping" };
104
+ const block = doc["steps"];
105
+ if (block === undefined)
106
+ return { present: true, entries: [] };
107
+ if (!Array.isArray(block)) {
108
+ return { present: true, entries: [], unreadable: "`steps` is not a list of step texts" };
109
+ }
110
+ const entries = [];
111
+ for (const item of block) {
112
+ // A non-string entry refuses the whole file rather than being skipped. A
113
+ // skipped entry is a phrase silently absent from the catalogue, which prints
114
+ // as "written but not catalogued" — a work-list row invented by a shape
115
+ // error, which is the wrong answer this fails closed to avoid.
116
+ if (typeof item !== "string") {
117
+ return { present: true, entries: [], unreadable: "every `steps` entry must be a step text (a string)" };
118
+ }
119
+ entries.push({ text: item, key: phraseOf(item).key });
120
+ }
121
+ return { present: true, entries };
122
+ }
123
+ /**
124
+ * Written phrases against catalogued ones.
125
+ *
126
+ * ORDER IS THE INVENTORY'S for `uncatalogued` — `inv.phrases` is already sorted
127
+ * count-descending then key-ascending, so the work-list comes out with the
128
+ * phrase that pays back most first, and stays diffable. `unwritten` and
129
+ * `duplicated` have no count to sort on and are sorted by key.
130
+ *
131
+ * The catalogue's own `present`/`unreadable` state is NOT consulted here: this
132
+ * is the comparison, and whether it was worth making is the caller's question.
133
+ * Handing it an unreadable catalogue would produce a full `uncatalogued` list
134
+ * that reads exactly like a real work-list, which is why the caller checks first.
135
+ */
136
+ export function compareToCatalogue(inv, catalogue) {
137
+ const catalogued = new Set(catalogue.entries.map((e) => e.key));
138
+ const written = new Set(inv.phrases.map((p) => p.key));
139
+ const bySpelling = new Map();
140
+ for (const entry of catalogue.entries) {
141
+ const texts = bySpelling.get(entry.key) ?? [];
142
+ texts.push(entry.text);
143
+ bySpelling.set(entry.key, texts);
144
+ }
145
+ return {
146
+ uncatalogued: inv.phrases.filter((p) => !catalogued.has(p.key)).map((p) => p.key),
147
+ unwritten: [...catalogued].filter((key) => !written.has(key)).sort(compareIds),
148
+ duplicated: [...bySpelling]
149
+ .filter(([, texts]) => texts.length > 1)
150
+ .map(([key, texts]) => ({ key, texts }))
151
+ .sort((a, b) => compareIds(a.key, b.key)),
152
+ };
153
+ }
154
+ //# sourceMappingURL=catalogue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue.js","sourceRoot":"","sources":["../../../../src/core/gherkin/steps/catalogue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The step catalogue: which phrases somebody has DECIDED the suite defines.\n *\n * ## The decision this file records, because it was the open question\n *\n * `./inventory.ts` answers how many definitions a suite needs. It stops there on\n * purpose — no file, no finding, no stable code — and the gap it leaves is the\n * next ARTIFACT, not the next warning: a team reads the report, writes the glue\n * by hand, and nothing anywhere records which phrases now have definitions, so\n * the report is recomputed from scratch every time instead of diffed against a\n * decision somebody made.\n *\n * The catalogue is **AUTHORED**, not emitted, and it lives in the DOCS repo at\n * `services/<svc>/steps.yaml`. Four things decided it:\n *\n * - It is a DECISION, not an observation. loam does not read code and must\n * never present a derived thing as truth (the non-goal is written down); what\n * a team can say is \"these are the phrases we have agreed to define\", and\n * that sentence is an authored document by construction. An emitted\n * catalogue would have to be derived from a step registry loam cannot read.\n * - `loam steps` NEEDS NO SERVICE REPO to stand in — that is its own stated\n * design, and it reads the living specs rather than the emitted `.feature`\n * files precisely so it answers the same way in a repo that has never run\n * `loam gherkin`. A catalogue in the service repo would make the comparison\n * require one, which is a different command.\n * - The shape rule the glossary and capability trees already settled: an entry\n * with prose gets a file, an entry WITHOUT prose stays a line in YAML. A\n * catalogue entry is a phrase and nothing else, so it is a line in YAML — the\n * same side of that rule `architecture/capabilities.yaml` and\n * `permissions.yaml` sit on.\n * - Nothing generated may land inside `<gherkinDir>/loam/`, which `loam\n * gherkin` owns and overwrites wholesale. Living in the docs repo satisfies\n * that by construction rather than by a rule somebody has to remember.\n *\n * SERVICE-SCOPED, beside `health.yaml`, because a step registry belongs to one\n * suite and a suite belongs to one service. A fleet-level catalogue would claim\n * one registry across services that do not share a test runner.\n *\n * ## What it does NOT do, and this is the other half of the decision\n *\n * **It gains no stable code, and `loam validate` never reads it.** A phrase\n * written but not catalogued is a work-list row, not a breach: writing a spec\n * ahead of the glue is the normal order, and a check that fires on it is the\n * kind a fleet turns off — at which point the real findings beside it are turned\n * off too. The near-duplicate groups stay a report for the same reason. So this\n * axis costs one document kind and zero codes, and everything it produces is\n * columns in `loam steps`.\n *\n * **It claims nothing about whether the glue exists.** A catalogued phrase is a\n * phrase somebody said they would define; loam cannot see a step registry and\n * does not pretend to. The two columns are named for what they actually mean —\n * written-not-catalogued and catalogued-not-written — and neither says \"missing\n * definition\".\n *\n * ## The grammar\n *\n * ```yaml\n * steps:\n * - a payment of 100.00\n * - Given it is split 60/40\n * ```\n *\n * A list of step TEXTS, not of keys. The key is loam's own spelling — `{n}`,\n * `{s}`, `{p}` collapse, keyword stripped (`./phrase.ts`) — and asking an author\n * to write it would make an internal normalisation a hand-typed contract, where\n * one wrong brace silently catalogues nothing. So an entry is written the way a\n * step is written, with or without its keyword, and loam keys it the same way it\n * keys the written steps it is being compared against. Two entries that collapse\n * onto one key are reported as such rather than merged: that is the author\n * cataloguing one definition twice, which is exactly what the near-duplicate\n * report exists to surface.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseYaml } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport { phraseOf } from \"./phrase.js\";\nimport { type StepInventory } from \"./inventory.js\";\n\n/** One catalogued phrase: what the author wrote, and the key loam derived from it. */\nexport interface CatalogueEntry {\n /** The entry exactly as `steps.yaml` spells it. */\n text: string;\n /** The normalized phrase — the same key `./inventory.ts` gives a written step. */\n key: string;\n}\n\n/** What `services/<svc>/steps.yaml` says, or the honest refusal to say. */\nexport interface StepCatalogue {\n /** False when the service has no `steps.yaml` — which is every service until one opts in. */\n present: boolean;\n /**\n * Why the file could not be read, when it exists and does not parse as the\n * grammar above. A REASON rather than a boolean, because the report prints it:\n * \"could not look\" is only useful if it says what stopped it.\n */\n unreadable?: string;\n /** The catalogued entries, in the order the file lists them. */\n entries: CatalogueEntry[];\n}\n\nconst ABSENT: StepCatalogue = { present: false, entries: [] };\n\n/**\n * Read one service's catalogue.\n *\n * A missing file is silence, not an error: the axis's opt-in is the file's\n * existence, exactly as `capabilities/`'s is a directory's. Anything present and\n * unparseable is `unreadable` with the reason — never an empty catalogue, which\n * would report every written phrase as uncatalogued and read as a work-list\n * somebody has to do.\n */\nexport async function readStepCatalogue(path: string): Promise<StepCatalogue> {\n if (!existsSync(path)) return ABSENT;\n let doc: unknown;\n try {\n doc = parseYaml(await readFile(path, \"utf8\"));\n } catch (e) {\n return { present: true, entries: [], unreadable: e instanceof Error ? e.message : String(e) };\n }\n if (doc === null || doc === undefined) return { present: true, entries: [] };\n if (!isRecord(doc)) return { present: true, entries: [], unreadable: \"the document is not a mapping\" };\n const block = doc[\"steps\"];\n if (block === undefined) return { present: true, entries: [] };\n if (!Array.isArray(block)) {\n return { present: true, entries: [], unreadable: \"`steps` is not a list of step texts\" };\n }\n const entries: CatalogueEntry[] = [];\n for (const item of block) {\n // A non-string entry refuses the whole file rather than being skipped. A\n // skipped entry is a phrase silently absent from the catalogue, which prints\n // as \"written but not catalogued\" — a work-list row invented by a shape\n // error, which is the wrong answer this fails closed to avoid.\n if (typeof item !== \"string\") {\n return { present: true, entries: [], unreadable: \"every `steps` entry must be a step text (a string)\" };\n }\n entries.push({ text: item, key: phraseOf(item).key });\n }\n return { present: true, entries };\n}\n\n/** The two lists a report needs, plus the entries an author catalogued twice. */\nexport interface CatalogueComparison {\n /** Written phrase keys the catalogue does not list, most-used first — the work-list. */\n uncatalogued: string[];\n /** Catalogued keys nothing in the living specs writes — a definition with nothing to match. */\n unwritten: string[];\n /** Keys two or more catalogue entries collapse onto, with every spelling. */\n duplicated: Array<{ key: string; texts: string[] }>;\n}\n\n/**\n * Written phrases against catalogued ones.\n *\n * ORDER IS THE INVENTORY'S for `uncatalogued` — `inv.phrases` is already sorted\n * count-descending then key-ascending, so the work-list comes out with the\n * phrase that pays back most first, and stays diffable. `unwritten` and\n * `duplicated` have no count to sort on and are sorted by key.\n *\n * The catalogue's own `present`/`unreadable` state is NOT consulted here: this\n * is the comparison, and whether it was worth making is the caller's question.\n * Handing it an unreadable catalogue would produce a full `uncatalogued` list\n * that reads exactly like a real work-list, which is why the caller checks first.\n */\nexport function compareToCatalogue(inv: StepInventory, catalogue: StepCatalogue): CatalogueComparison {\n const catalogued = new Set(catalogue.entries.map((e) => e.key));\n const written = new Set(inv.phrases.map((p) => p.key));\n const bySpelling = new Map<string, string[]>();\n for (const entry of catalogue.entries) {\n const texts = bySpelling.get(entry.key) ?? [];\n texts.push(entry.text);\n bySpelling.set(entry.key, texts);\n }\n return {\n uncatalogued: inv.phrases.filter((p) => !catalogued.has(p.key)).map((p) => p.key),\n unwritten: [...catalogued].filter((key) => !written.has(key)).sort(compareIds),\n duplicated: [...bySpelling]\n .filter(([, texts]) => texts.length > 1)\n .map(([key, texts]) => ({ key, texts }))\n .sort((a, b) => compareIds(a.key, b.key)),\n };\n}\n"]}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The step-phrase inventory: how many DEFINITIONS a service's suite actually
3
+ * needs, and which written steps collapse onto each one.
4
+ *
5
+ * ## What this is for
6
+ *
7
+ * Generated `.feature` files are only a suite if somebody writes the glue, and
8
+ * the glue is where a generated suite dies: the step text is free prose from a
9
+ * spec bullet, so the same precondition gets three spellings across three
10
+ * requirements and the registry grows without bound until people quietly go
11
+ * back to hand-written tests. Nothing in loam could previously say how bad that
12
+ * was, which meant nobody could tell a suite that needs forty definitions from
13
+ * one that needs four hundred.
14
+ *
15
+ * Measured on a real twelve-scenario component-test file, 324 written steps
16
+ * collapse to 97 phrases, and 34 of those cover 82% of every step — so the
17
+ * report is not an audit, it is the work-list a team writes glue from, and the
18
+ * draft of any step catalogue that comes later.
19
+ *
20
+ * ## What it deliberately is NOT
21
+ *
22
+ * It emits no finding and no stable code. A near-duplicate is a judgement about
23
+ * writing, and a phrase-similarity WARNING is the kind of check a fleet turns
24
+ * off — at which point the real findings beside it are turned off too. It reads
25
+ * the LIVING specs rather than the emitted files, so it answers the same way in
26
+ * a repo that has never run `loam gherkin` and needs no service repo to stand
27
+ * in.
28
+ */
29
+ import { scenarioGherkin } from "../../vocabulary/steps.js";
30
+ import {} from "../../document/spec.js";
31
+ import { keywordOf, phraseOf } from "./phrase.js";
32
+ /**
33
+ * Count the phrases across both axes of one service.
34
+ *
35
+ * REMOVED requirements are skipped, exactly as every other scenario check skips
36
+ * them: a step nobody will run is not glue anybody owes.
37
+ *
38
+ * Ordering is count-descending then key-ascending, and that second key is what
39
+ * makes the report diffable — two phrases used once each must not swap places
40
+ * between runs because a Map happened to iterate differently.
41
+ */
42
+ export function stepInventory(axes) {
43
+ const byKey = new Map();
44
+ const families = new Map();
45
+ let steps = 0;
46
+ for (const { axis, reqs } of axes) {
47
+ for (const r of reqs) {
48
+ if (r.kind === "REMOVED")
49
+ continue;
50
+ for (const s of r.scenarios) {
51
+ for (const st of scenarioGherkin(s.lines).steps) {
52
+ steps += 1;
53
+ const { key, family } = phraseOf(st.text);
54
+ const row = byKey.get(key) ?? { key, count: 0, keywords: [], uses: [] };
55
+ row.count += 1;
56
+ const kw = keywordOf(st.text);
57
+ if (kw.length > 0 && !row.keywords.includes(kw))
58
+ row.keywords.push(kw);
59
+ row.uses.push({ requirement: r.name, scenario: s.name, axis, text: st.text });
60
+ byKey.set(key, row);
61
+ const seen = families.get(family) ?? new Set();
62
+ seen.add(key);
63
+ families.set(family, seen);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ const phrases = [...byKey.values()]
69
+ .map((row) => ({ ...row, keywords: [...row.keywords].sort() }))
70
+ .sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
71
+ const nearDuplicates = [...families.entries()]
72
+ .filter(([, keys]) => keys.size > 1)
73
+ .map(([family, keys]) => ({ family, keys: [...keys].sort() }))
74
+ .sort((a, b) => (a.family < b.family ? -1 : a.family > b.family ? 1 : 0));
75
+ return { steps, phrases, nearDuplicates };
76
+ }
77
+ /**
78
+ * How many phrases cover `share` of every written step — the one number that
79
+ * says whether the glue is a morning's work or a quarter's.
80
+ *
81
+ * Returns zero for an empty suite rather than dividing by it, because "0 of 0
82
+ * steps" is the honest reading of a service that has written no scenarios yet.
83
+ */
84
+ export function coveringPhrases(inv, share = 0.8) {
85
+ if (inv.steps === 0)
86
+ return 0;
87
+ const target = inv.steps * share;
88
+ let running = 0;
89
+ let n = 0;
90
+ for (const row of inv.phrases) {
91
+ if (running >= target)
92
+ break;
93
+ running += row.count;
94
+ n += 1;
95
+ }
96
+ return n;
97
+ }
98
+ //# sourceMappingURL=inventory.js.map