@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/gherkin/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The emission's commit window: lock the owned root, recover a predecessor,\n * stage every write and delete, prove the tree still matches what reconcile\n * graded, and land it through the journaled transaction.\n *\n * Split from gherkin.ts so the lock's extent is visible in the code shape,\n * exactly as vouch's commitStamp is — and because this is the writer the\n * roadmap named: a kill inside the old write/delete loop left a half-old,\n * half-new suite with no marker, in the one repo (the service's own) where\n * nothing else of loam's ever looked.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { quietRmdir, rollbackStaged, stageWrites } from \"../../core/staging/commit.js\";\nimport { COMMIT_INTENT, type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { type ActionRow, type Scope } from \"./reconcile.js\";\n\nexport type EmissionCommit =\n | { ok: true; recovered: CommitRecovery | null }\n | { ok: false; code: \"docs-busy\" | \"commit-interrupted\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * Roll a predecessor's interrupted emission forward before ANYTHING reads the\n * suite — reconcile grades the very bytes a half-commit corrupts. Cheap when\n * there is nothing to do: no journal, no lock, no I/O beyond one existsSync.\n */\nexport async function recoverEmissionRoot(root: string): Promise<EmissionCommit> {\n if (!existsSync(join(root, COMMIT_INTENT))) return { ok: true, recovered: null };\n let release: () => Promise<void>;\n try {\n release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n return { ok: true, recovered: await recoverInterruptedCommit(root) };\n } catch (err) {\n if (err instanceof InterruptedCommitError) return { ok: false, code: \"commit-interrupted\", message: err.message };\n throw err;\n } finally {\n await release();\n }\n}\n\n/**\n * Stage, compare, journal, swap. The compare is against the bytes RECONCILE\n * read: a `.feature` that changed (or appeared) between the grading and this\n * commit is refused by name, never buried — the suite is code somebody may be\n * editing, and \"refused, not buried\" is the no-clobber rule the roadmap\n * spells for exactly this writer.\n */\nexport async function commitEmission(\n spec: { root: string; service: string; scope: Scope },\n plan: { writes: ActionRow[]; orphans: { path: string; raw: Buffer }[] },\n): Promise<EmissionCommit> {\n const { root, service, scope } = spec;\n // The lock file lives in the root, so the root must exist first. If the\n // commit then fails having created it, the empty directory is removed\n // again — an empty loam/ flips the repo into \"opted in\" and tells\n // `loam validate` the whole living suite is missing.\n const rootExisted = existsSync(root);\n await mkdir(root, { recursive: true });\n let release: () => Promise<void>;\n try {\n release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);\n } catch (err) {\n if (!rootExisted) await quietRmdir(root);\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n // A writer can have crashed between the command-start recovery and this\n // window; the re-check is one existsSync when nothing happened.\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(root);\n } catch (err) {\n if (err instanceof InterruptedCommitError) return { ok: false, code: \"commit-interrupted\", message: err.message };\n throw err;\n }\n\n const planned: PlannedWrite[] = [\n // A \"written\" row was graded against an ABSENT file, so its create is\n // exclusive: a file appearing after planning loses the link(2) race and\n // the run refuses rather than overwriting it.\n ...plan.writes.map((a): PlannedWrite =>\n a.action === \"written\" ? { path: a.path, content: a.content, exclusive: true } : { path: a.path, content: a.content },\n ),\n ...plan.orphans.map((o): PlannedWrite => ({ path: o.path, content: null })),\n ];\n const staged = await stageWrites(planned);\n\n // The bytes reconcile graded must be the bytes this commit replaces or\n // deletes. `swapStaged` re-checks against ITS read just before each\n // rename; this compare closes the other half of the window, from the\n // grading to the staging.\n const graded = new Map<string, Buffer>();\n for (const a of plan.writes) if (a.action === \"replaced\") graded.set(a.path, a.raw);\n for (const o of plan.orphans) graded.set(o.path, o.raw);\n const moved = staged\n .filter((s) => {\n const raw = graded.get(s.write.path);\n return raw !== undefined && (s.before === null || !s.before.equals(raw));\n })\n .map((s) => s.write.path);\n if (moved.length > 0) {\n await rollbackStaged(staged);\n return {\n ok: false,\n code: \"merge-failed\",\n message:\n `the suite changed while this emission was being planned — ${moved.join(\", \")} ` +\n `no longer holds the bytes the plan was graded against. Nothing was written: re-run.`,\n };\n }\n\n const committed = await commitStaged(\n {\n root,\n command: \"gherkin\",\n rerun: scope.mode === \"feature\" ? `loam gherkin ${scope.featureId}` : \"loam gherkin\",\n target: service,\n },\n staged,\n \"emitted\",\n );\n if (!committed.ok) {\n return committed.raced\n ? {\n ok: false,\n code: \"merge-failed\",\n message:\n \"a .feature file appeared after planning and the emission refused to overwrite it — \" +\n `${committed.message}. Nothing else was written: re-run to grade the new file.`,\n }\n : { ok: false, code: committed.code, message: committed.message };\n }\n return { ok: true, recovered };\n } finally {\n await release();\n if (!rootExisted && !existsSync(join(root, COMMIT_INTENT))) await quietRmdir(root);\n }\n}\n\n"]}
1
+ {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/gherkin/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The emission's commit window: lock the owned root, recover a predecessor,\n * stage every write and delete, prove the tree still matches what reconcile\n * graded, and land it through the journaled transaction.\n *\n * Split from gherkin.ts so the lock's extent is visible in the code shape,\n * exactly as vouch's commitStamp is — and because this is the writer the\n * roadmap named: a kill inside the old write/delete loop left a half-old,\n * half-new suite with no marker, in the one repo (the service's own) where\n * nothing else of loam's ever looked.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { quietRmdir, rollbackStaged, stageWrites } from \"../../core/staging/commit.js\";\nimport { COMMIT_INTENT, type CommitRecovery } from \"../../core/staging/interrupted.js\";\nimport { LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { openCommitWindow } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { type ActionRow, type Scope } from \"./reconcile.js\";\n\nexport type EmissionCommit =\n | { ok: true; recovered: CommitRecovery | null }\n | { ok: false; code: \"docs-busy\" | \"commit-interrupted\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * Stage, compare, journal, swap. The compare is against the bytes RECONCILE\n * read: a `.feature` that changed (or appeared) between the grading and this\n * commit is refused by name, never buried — the suite is code somebody may be\n * editing, and \"refused, not buried\" is the no-clobber rule the roadmap\n * spells for exactly this writer.\n */\nexport async function commitEmission(\n spec: { root: string; service: string; scope: Scope },\n plan: { writes: ActionRow[]; orphans: { path: string; raw: Buffer }[] },\n): Promise<EmissionCommit> {\n const { root, service, scope } = spec;\n // The lock file lives in the root, so the root must exist first. If the\n // commit then fails having created it, the empty directory is removed\n // again — an empty loam/ flips the repo into \"opted in\" and tells\n // `loam validate` the whole living suite is missing. A window that does not\n // open is such a failure too, so the removal sits in the one `finally`\n // every path out of here takes.\n const rootExisted = existsSync(root);\n await mkdir(root, { recursive: true });\n let release: (() => Promise<void>) | null = null;\n try {\n // A writer can have crashed between the command-start recovery and this\n // window; the re-check is one existsSync when nothing happened.\n const opened = await openCommitWindow(root, LOCK_WAIT_MS);\n if (!opened.ok) return opened;\n release = opened.release;\n const { recovered } = opened;\n\n const planned: PlannedWrite[] = [\n // A \"written\" row was graded against an ABSENT file, so its create is\n // exclusive: a file appearing after planning loses the link(2) race and\n // the run refuses rather than overwriting it.\n ...plan.writes.map((a): PlannedWrite =>\n a.action === \"written\" ? { path: a.path, content: a.content, exclusive: true } : { path: a.path, content: a.content },\n ),\n ...plan.orphans.map((o): PlannedWrite => ({ path: o.path, content: null })),\n ];\n const staged = await stageWrites(planned);\n\n // The bytes reconcile graded must be the bytes this commit replaces or\n // deletes. `swapStaged` re-checks against ITS read just before each\n // rename; this compare closes the other half of the window, from the\n // grading to the staging.\n const graded = new Map<string, Buffer>();\n for (const a of plan.writes) if (a.action === \"replaced\") graded.set(a.path, a.raw);\n for (const o of plan.orphans) graded.set(o.path, o.raw);\n const moved = staged\n .filter((s) => {\n const raw = graded.get(s.write.path);\n return raw !== undefined && (s.before === null || !s.before.equals(raw));\n })\n .map((s) => s.write.path);\n if (moved.length > 0) {\n await rollbackStaged(staged);\n return {\n ok: false,\n code: \"merge-failed\",\n message:\n `the suite changed while this emission was being planned — ${moved.join(\", \")} ` +\n `no longer holds the bytes the plan was graded against. Nothing was written: re-run.`,\n };\n }\n\n const committed = await commitStaged(\n {\n root,\n command: \"gherkin\",\n rerun: scope.mode === \"feature\" ? `loam gherkin ${scope.featureId}` : \"loam gherkin\",\n target: service,\n },\n staged,\n \"emitted\",\n );\n if (!committed.ok) {\n return committed.raced\n ? {\n ok: false,\n code: \"merge-failed\",\n message:\n \"a .feature file appeared after planning and the emission refused to overwrite it — \" +\n `${committed.message}. Nothing else was written: re-run to grade the new file.`,\n }\n : { ok: false, code: committed.code, message: committed.message };\n }\n return { ok: true, recovered };\n } finally {\n await release?.();\n if (!rootExisted && !existsSync(join(root, COMMIT_INTENT))) await quietRmdir(root);\n }\n}\n\n"]}
@@ -1,9 +1,8 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { relative } from "node:path";
4
- import { loadConfig } from "../../core/envelope/config.js";
5
4
  import { decodeDocument, NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
6
- import { emitJson, emitJsonError, fail, NO_SERVICE_MESSAGE, reportNoConfig, } from "../../core/envelope/json.js";
5
+ import { configOrRefuse, emitJson, emitJsonError, fail, NO_SERVICE_MESSAGE, } from "../../core/envelope/json.js";
7
6
  import { featureSpecPaths, SPEC_AXES } from "../../core/repo/paths.js";
8
7
  import { locateServicePaths } from "../../core/repo/service-target.js";
9
8
  import { listFeatures, missingFeatureMessage, resolveFeature } from "../../core/repo/repo.js";
@@ -13,7 +12,8 @@ import { axisLabel, planEmission } from "../../core/gherkin/emit.js";
13
12
  import { gherkinRoot } from "../../core/gherkin/stamp.js";
14
13
  import { LOAM_VERSION } from "../../core/envelope/version.js";
15
14
  import { UnsafePathError } from "../../core/kernel/path-safety.js";
16
- import { commitEmission, recoverEmissionRoot } from "./commit.js";
15
+ import { recoverIfJournaled } from "../../core/staging/recovery/recover.js";
16
+ import { commitEmission } from "./commit.js";
17
17
  import { reconcile } from "./reconcile.js";
18
18
  import { render } from "./render.js";
19
19
  import { sayRecovered } from "../policy/format.js";
@@ -28,12 +28,9 @@ export function registerGherkin(program) {
28
28
  .action(async (featureArg, opts) => {
29
29
  const json = opts.json === true;
30
30
  const dryRun = opts.dryRun === true;
31
- const loaded = await loadConfig();
32
- if (loaded.kind !== "loaded") {
33
- reportNoConfig(json, loaded);
31
+ const config = await configOrRefuse(json);
32
+ if (config === null)
34
33
  return;
35
- }
36
- const config = loaded.config;
37
34
  const service = opts.service ?? config.service;
38
35
  if (service === undefined) {
39
36
  return fail(json, "invalid-option", NO_SERVICE_MESSAGE);
@@ -83,8 +80,10 @@ export function registerGherkin(program) {
83
80
  // name a feature that has since ARCHIVED, whose recovery must still
84
81
  // work. `new` fixed the same ordering and documented it; refusing
85
82
  // unknown-target over a wedged root left doctor printing a repair that
86
- // could never run.
87
- const early = await recoverEmissionRoot(root);
83
+ // could never run. The lock is the emission root's own, given straight
84
+ // back: the slow half below runs unlocked, and `commitEmission` retakes
85
+ // it for the commit window.
86
+ const early = await recoverIfJournaled(root);
88
87
  if (!early.ok)
89
88
  return fail(json, early.code, early.message);
90
89
  let recovered = early.recovered;
@@ -146,11 +145,8 @@ export function registerGherkin(program) {
146
145
  featureTag: scope.mode === "feature" ? scope.featureId : undefined,
147
146
  version: LOAM_VERSION,
148
147
  });
149
- // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns
150
- // only its own emissions: stamped files carrying this feature's tag whose
151
- // file is no longer in the plan (a requirement renamed or dropped).
152
- // `Set<string>`, not the ids' own brand: this set answers membership
153
- // questions for tags read out of .feature files — document text.
148
+ // `Set<string>`, not the ids' own brand — core/gherkin/stale.ts's reason:
149
+ // it answers membership for tags read out of .feature files.
154
150
  const activeIds = new Set((await listFeatures(config.docsDir)).map((f) => f.id));
155
151
  let reconciled;
156
152
  try {
@@ -162,11 +158,8 @@ export function registerGherkin(program) {
162
158
  return fail(json, "invalid-option", `Cannot emit Gherkin: ${err.message}.`);
163
159
  }
164
160
  const { actions, orphans, conflicts } = reconciled;
165
- // All or nothing: one conflicting file refuses the whole emission, so a
166
- // half-written suite can never be the state an agent has to reason about.
167
- // Only a feature run can conflict — living mode keeps the in-flight file
168
- // rather than refusing — and standing inside that scope is also how the
169
- // message names the feature without asserting there is one.
161
+ // All or nothing, and only a feature run can conflict — `./reconcile.ts`
162
+ // records why, where the conflicts are collected.
170
163
  if (scope.mode === "feature" && conflicts.length > 0) {
171
164
  const detail = conflicts
172
165
  .map((c) => `${rel(c.path)} is @${c.owners.join(" @")} (requirement '${c.requirement.name}')`)
@@ -216,6 +209,29 @@ export function registerGherkin(program) {
216
209
  // emission), not the emission that was withheld.
217
210
  scenarios: a.action === "kept" ? a.kept.scenarios.length : a.digests.length,
218
211
  digests: a.action === "kept" ? a.kept.scenarios.map((s) => s.digest) : a.digests,
212
+ // `digests` is a bare array and `scenarios` a bare count, so the one
213
+ // thing a consumer actually needs — which stamp belongs to which
214
+ // scenario — was the one thing this payload could not say, and the
215
+ // only way to learn it was to go read the generated file back off
216
+ // disk. `stamped` is that pairing; `digests` stays exactly as it was
217
+ // beside it, because the envelope permits adding a key and never
218
+ // changing one (core/envelope/json.ts).
219
+ //
220
+ // The positional zip is sound because `renderFeature`
221
+ // (core/gherkin/emit.ts) pushes exactly one digest per scenario, in
222
+ // scenario order, inside the same loop that writes the `Scenario:`
223
+ // line — but it is written to FAIL CLOSED rather than to assert
224
+ // that: a scenario with no digest opposite it drops out of the
225
+ // pairing instead of being published with a null one. A kept row
226
+ // reads its pairs off the file that is really on disk, because its
227
+ // numbers describe the in-flight emission that stayed, not the one
228
+ // that was withheld.
229
+ stamped: a.action === "kept"
230
+ ? a.kept.scenarios.map((s) => ({ scenario: s.name, digest: s.digest }))
231
+ : a.requirement.scenarios.flatMap((s, i) => {
232
+ const digest = a.digests[i];
233
+ return digest === undefined ? [] : [{ scenario: s.name, digest }];
234
+ }),
219
235
  ...(a.action === "kept"
220
236
  ? { inFlight: a.kept.tags.filter((t) => activeIds.has(t)) }
221
237
  : {
@@ -1 +1 @@
1
- {"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../../../src/commands/gherkin/gherkin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gherkin` — the third writer in the CLI (after archive and vouch), and\n * the only one that writes into the SERVICE repo, never the docs repo: the\n * docs state what must be true, the tests live with the code that makes it so.\n *\n * Two scopes, one emitter. `loam gherkin <FEAT>` emits the feature's ADDED and\n * MODIFIED requirements for this service — both spec axes — as the acceptance\n * skeleton to implement against, each file tagged with the feature id.\n * `loam gherkin --service <id>` (no feature) emits the full living suite from\n * spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n * adoption. Either way the output is `<gherkinDir>/loam/` inside this repo,\n * a directory loam owns outright: the scope's files are rewritten, the scope's\n * orphans are deleted (and reported), and nothing outside `loam/` is touched —\n * except that a living run neither deletes NOR overwrites a file tagged with a\n * feature still in flight: it answers to that feature's delta until it\n * archives, and is reported as kept. A FEATURE run cannot keep — the file it\n * wants holds one feature's delta — so when the file it would write belongs to\n * a different feature still in flight, the whole run refuses (`gherkin-conflict`)\n * and names the owner rather than reverting it.\n *\n * It refuses to run anywhere but the service's own repo — vouch's discipline,\n * for vouch's reason: the output lands in the repo loam is standing in, and\n * from anywhere else that repo is somebody else's.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport {\n emitJson,\n emitJsonError,\n fail,\n NO_SERVICE_MESSAGE,\n reportNoConfig,\n} from \"../../core/envelope/json.js\";\nimport { featureSpecPaths, SPEC_AXES, type SpecAxis } from \"../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { listFeatures, missingFeatureMessage, resolveFeature } from \"../../core/repo/repo.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { type Requirement } from \"../../core/document/spec.js\";\nimport { axisLabel, planEmission } from \"../../core/gherkin/emit.js\";\nimport { gherkinRoot } from \"../../core/gherkin/stamp.js\";\nimport { LOAM_VERSION } from \"../../core/envelope/version.js\";\nimport { UnsafePathError } from \"../../core/kernel/path-safety.js\";\nimport { commitEmission, recoverEmissionRoot } from \"./commit.js\";\nimport { reconcile, type Scope } from \"./reconcile.js\";\nimport { render } from \"./render.js\";\nimport { sayRecovered } from \"../policy/format.js\";\n\ninterface GherkinOptions {\n service?: string;\n dryRun?: boolean;\n json?: boolean;\n}\n\nexport function registerGherkin(program: Command): void {\n program\n .command(\"gherkin\")\n .argument(\"[featureId]\", \"feature id — emit its ADDED/MODIFIED requirements; omit for the full living suite\")\n .description(\n \"Emit Gherkin .feature files from spec scenarios into this service repo's <gherkinDir>/loam/\",\n )\n .option(\"--service <id>\", \"service to emit for (defaults to the configured service)\")\n .option(\n \"--dry-run\",\n \"print the plan (writes, replacements, deletions) and write nothing — beyond first finishing a predecessor's interrupted commit, exactly as a real run would\",\n )\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureArg: string | undefined, opts: GherkinOptions) => {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const service = opts.service ?? config.service;\n if (service === undefined) {\n return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n }\n // The emission writes into the repo loam is standing in, so it can only\n // run where that repo is this service's — vouch's refusal, verbatim logic.\n //\n // TWO conditions, two codes, and the split is the whole point. Both used\n // to answer `invalid-option`, whose `loam explain` row says the\n // invocation itself is wrong — contradictory flags, a mistyped one, a\n // value that cannot be right. Here the invocation is perfectly correct\n // and the REPOSITORY is the wrong one, so that code sent a user whose\n // flags were fine off to re-read their flags, and left CI and agents\n // unable to tell \"run this in the service repo\" from \"you typed the flag\n // wrong\". `loam verify --record --service` already draws exactly this\n // line for exactly this reason (see commands/verify/verify.ts); gherkin\n // and vouch now answer the same pair, so a caller branching on\n // `error.code` gets one answer from all three. The messages are\n // unchanged — only the code moved.\n if (config.service === undefined) {\n return fail(\n json,\n \"repository-unavailable\",\n `Cannot emit gherkin for '${service}' from here: this is not a service repo. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n if (config.service !== service) {\n return fail(\n json,\n \"service-mismatch\",\n `Cannot emit gherkin for '${service}' from here: this repo is '${config.service}'. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n\n // The repo root, not the cwd: `config.root` is the directory loam.json\n // was actually found in, and config discovery walks upward. Taken from\n // `process.cwd()` this emitted the whole suite into whatever directory\n // the caller happened to stand in — `src/api/features/loam/…` inside the\n // source tree, which the next run from the root neither finds, replaces\n // nor deletes, and which `loam validate` grades as a suite that was never\n // generated.\n const repoDir = config.root ?? process.cwd();\n let root: string;\n try {\n root = gherkinRoot(repoDir, config.gherkinDir);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit Gherkin: ${err.message}. The output directory must stay inside the service repo.`,\n );\n }\n const rel = (abs: string): string => relative(repoDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // A predecessor's interrupted emission is rolled forward before ANY\n // read — and before the feature argument resolves: reconcile grades the\n // very bytes a half-commit corrupts, and the journal's stored rerun may\n // name a feature that has since ARCHIVED, whose recovery must still\n // work. `new` fixed the same ordering and documented it; refusing\n // unknown-target over a wedged root left doctor printing a repair that\n // could never run.\n const early = await recoverEmissionRoot(root);\n if (!early.ok) return fail(json, early.code, early.message);\n let recovered = early.recovered;\n\n const feature =\n featureArg === undefined ? null : await resolveFeature(config.docsDir, featureArg, \"exclude\");\n if (featureArg !== undefined && feature === null) {\n return fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureArg));\n }\n const scope: Scope =\n feature === null ? { mode: \"living\" } : { mode: \"feature\", featureId: feature.id };\n\n // The requirement set, by scope. Feature mode takes ADDED and MODIFIED —\n // a BASE requirement is the living state quoted, a REMOVED one is being\n // retired, and neither is behaviour anybody is about to test. Living mode\n // takes everything the living files hold.\n //\n // Read through `decodeDocument`, not `readFile(…, \"utf8\")`, and refused\n // rather than degraded: a spec.md saved as UTF-16 decodes to a document\n // with no requirements in it, which here is not merely a wrong answer but\n // a destructive one — an empty plan makes every existing .feature under\n // loam/ an orphan, and a living run DELETES its orphans. The whole\n // generated suite would go because a docs file was written by PowerShell.\n const byAxis: Array<{ axis: SpecAxis; reqs: Requirement[] }> = [];\n const readSpec = async (path: string): Promise<Requirement[]> =>\n parseRequirements(decodeDocument(await readFile(path), path));\n try {\n if (feature !== null) {\n // `config.service`, not `service`: the guard above proved they are\n // the same id, and only the config's copy carries the load-time\n // parse, so it is the pathable spelling — same as the else arm.\n const paths = featureSpecPaths(feature.dir, config.service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path)\n ? (await readSpec(path)).filter((r) => r.kind === \"ADDED\" || r.kind === \"MODIFIED\")\n : [];\n byAxis.push({ axis, reqs });\n }\n } else {\n // The guard above proved `service` IS `config.service`; only the\n // latter carries the load-time parse, so it is the pathable spelling.\n const paths = await locateServicePaths(config.docsDir, config.service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path) ? (await readSpec(path)).filter((r) => r.kind !== \"REMOVED\") : [];\n byAxis.push({ axis, reqs });\n }\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot emit gherkin: ${err.message}`);\n }\n\n // `service` is not decoration: it salts every `@loam-digest-…` stamp, and\n // it is the same string `loam verify` files the matching claims under.\n const plan = planEmission(byAxis, {\n service,\n featureTag: scope.mode === \"feature\" ? scope.featureId : undefined,\n version: LOAM_VERSION,\n });\n\n // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns\n // only its own emissions: stamped files carrying this feature's tag whose\n // file is no longer in the plan (a requirement renamed or dropped).\n\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 activeIds = new Set<string>((await listFeatures(config.docsDir)).map((f) => f.id));\n let reconciled;\n try {\n reconciled = await reconcile(plan, { root, repoDir }, scope, activeIds);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", `Cannot emit Gherkin: ${err.message}.`);\n }\n const { actions, orphans, conflicts } = reconciled;\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n if (scope.mode === \"feature\" && conflicts.length > 0) {\n const detail = conflicts\n .map((c) => `${rel(c.path)} is @${c.owners.join(\" @\")} (requirement '${c.requirement.name}')`)\n .join(\"; \");\n const message =\n `Cannot emit gherkin for ${scope.featureId}: ${conflicts.length} file(s) belong to another feature still in flight — ${detail}. ` +\n `A .feature file carries one feature's delta; overwriting would revert that feature's wording and destroy the digest stamps \\`loam verify --results\\` matches on. ` +\n `Archive (or abandon) the owning feature first, or rename the requirement in ${scope.featureId}'s delta so the two stop sharing a file name.`;\n if (json) {\n emitJsonError(\"gherkin-conflict\", message, {\n conflicts: conflicts.map((c) => ({\n path: rel(c.path),\n action: \"conflict\",\n requirement: c.requirement.name,\n inFlight: c.owners,\n })),\n });\n return;\n }\n return fail(json, \"gherkin-conflict\", message);\n }\n\n const writes = actions.filter((a) => a.action !== \"kept\");\n\n if (!dryRun && (writes.length > 0 || orphans.length > 0)) {\n // The commit window: locked, journaled, compared against the bytes\n // reconcile graded. A zero-op run takes no lock and creates no root —\n // an emission with nothing to emit must not flip the repo into\n // \"opted in\", where an empty loam/ tells `loam validate` the whole\n // living suite is missing.\n const committed = await commitEmission({ root, service, scope }, { writes, orphans });\n if (!committed.ok) return fail(json, committed.code, committed.message);\n recovered = committed.recovered ?? recovered;\n }\n\n if (json) {\n emitJson({\n command: \"gherkin\",\n mode: scope.mode,\n ...(scope.mode === \"feature\" ? { feature: scope.featureId } : {}),\n service,\n root: rel(root),\n written: !dryRun,\n files: actions.map((a) => ({\n path: rel(a.path),\n action: a.action,\n axis: axisLabel(a.axis),\n requirement: a.requirement.name,\n // A kept file's numbers describe what STAYS on disk (the in-flight\n // emission), not the emission that was withheld.\n scenarios: a.action === \"kept\" ? a.kept.scenarios.length : a.digests.length,\n digests: a.action === \"kept\" ? a.kept.scenarios.map((s) => s.digest) : a.digests,\n ...(a.action === \"kept\"\n ? { inFlight: a.kept.tags.filter((t) => activeIds.has(t)) }\n : {\n stepless: a.stepless,\n malformedExamples: a.malformedExamples,\n strandedBlocks: a.strandedBlocks,\n }),\n })),\n deleted: orphans.map((o) => rel(o.path)),\n ...(recovered === null ? {} : { recovered }),\n });\n return;\n }\n\n if (recovered !== null) console.log(`${sayRecovered(recovered)}\\n`);\n render(actions, {\n service,\n root: rel(root),\n absRoot: root,\n featureId: scope.mode === \"feature\" ? scope.featureId : null,\n dryRun,\n activeIds,\n writes: writes.length,\n orphans: orphans.map((o) => o.path),\n });\n });\n}\n"]}
1
+ {"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../../../src/commands/gherkin/gherkin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gherkin` — the third writer in the CLI (after archive and vouch), and\n * the only one that writes into the SERVICE repo, never the docs repo: the\n * docs state what must be true, the tests live with the code that makes it so.\n *\n * Two scopes, one emitter. `loam gherkin <FEAT>` emits the feature's ADDED and\n * MODIFIED requirements for this service — both spec axes — as the acceptance\n * skeleton to implement against, each file tagged with the feature id.\n * `loam gherkin --service <id>` (no feature) emits the full living suite from\n * spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n * adoption. Either way the output is `<gherkinDir>/loam/` inside this repo,\n * a directory loam owns outright: the scope's files are rewritten, the scope's\n * orphans are deleted (and reported), and nothing outside `loam/` is touched —\n * except that a living run neither deletes NOR overwrites a file tagged with a\n * feature still in flight: it answers to that feature's delta until it\n * archives, and is reported as kept. A FEATURE run cannot keep — the file it\n * wants holds one feature's delta — so when the file it would write belongs to\n * a different feature still in flight, the whole run refuses (`gherkin-conflict`)\n * and names the owner rather than reverting it.\n *\n * It refuses to run anywhere but the service's own repo — vouch's discipline,\n * for vouch's reason: the output lands in the repo loam is standing in, and\n * from anywhere else that repo is somebody else's.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport {\n configOrRefuse,\n emitJson,\n emitJsonError,\n fail,\n NO_SERVICE_MESSAGE,\n} from \"../../core/envelope/json.js\";\nimport { featureSpecPaths, SPEC_AXES, type SpecAxis } from \"../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { listFeatures, missingFeatureMessage, resolveFeature } from \"../../core/repo/repo.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { type Requirement } from \"../../core/document/spec.js\";\nimport { axisLabel, planEmission } from \"../../core/gherkin/emit.js\";\nimport { gherkinRoot } from \"../../core/gherkin/stamp.js\";\nimport { LOAM_VERSION } from \"../../core/envelope/version.js\";\nimport { UnsafePathError } from \"../../core/kernel/path-safety.js\";\nimport { recoverIfJournaled } from \"../../core/staging/recovery/recover.js\";\nimport { commitEmission } from \"./commit.js\";\nimport { reconcile, type Scope } from \"./reconcile.js\";\nimport { render } from \"./render.js\";\nimport { sayRecovered } from \"../policy/format.js\";\n\ninterface GherkinOptions {\n service?: string;\n dryRun?: boolean;\n json?: boolean;\n}\n\nexport function registerGherkin(program: Command): void {\n program\n .command(\"gherkin\")\n .argument(\"[featureId]\", \"feature id — emit its ADDED/MODIFIED requirements; omit for the full living suite\")\n .description(\n \"Emit Gherkin .feature files from spec scenarios into this service repo's <gherkinDir>/loam/\",\n )\n .option(\"--service <id>\", \"service to emit for (defaults to the configured service)\")\n .option(\n \"--dry-run\",\n \"print the plan (writes, replacements, deletions) and write nothing — beyond first finishing a predecessor's interrupted commit, exactly as a real run would\",\n )\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureArg: string | undefined, opts: GherkinOptions) => {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n const service = opts.service ?? config.service;\n if (service === undefined) {\n return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n }\n // The emission writes into the repo loam is standing in, so it can only\n // run where that repo is this service's — vouch's refusal, verbatim logic.\n //\n // TWO conditions, two codes, and the split is the whole point. Both used\n // to answer `invalid-option`, whose `loam explain` row says the\n // invocation itself is wrong — contradictory flags, a mistyped one, a\n // value that cannot be right. Here the invocation is perfectly correct\n // and the REPOSITORY is the wrong one, so that code sent a user whose\n // flags were fine off to re-read their flags, and left CI and agents\n // unable to tell \"run this in the service repo\" from \"you typed the flag\n // wrong\". `loam verify --record --service` already draws exactly this\n // line for exactly this reason (see commands/verify/verify.ts); gherkin\n // and vouch now answer the same pair, so a caller branching on\n // `error.code` gets one answer from all three. The messages are\n // unchanged — only the code moved.\n if (config.service === undefined) {\n return fail(\n json,\n \"repository-unavailable\",\n `Cannot emit gherkin for '${service}' from here: this is not a service repo. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n if (config.service !== service) {\n return fail(\n json,\n \"service-mismatch\",\n `Cannot emit gherkin for '${service}' from here: this repo is '${config.service}'. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n\n // The repo root, not the cwd: `config.root` is the directory loam.json\n // was actually found in, and config discovery walks upward. Taken from\n // `process.cwd()` this emitted the whole suite into whatever directory\n // the caller happened to stand in — `src/api/features/loam/…` inside the\n // source tree, which the next run from the root neither finds, replaces\n // nor deletes, and which `loam validate` grades as a suite that was never\n // generated.\n const repoDir = config.root ?? process.cwd();\n let root: string;\n try {\n root = gherkinRoot(repoDir, config.gherkinDir);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit Gherkin: ${err.message}. The output directory must stay inside the service repo.`,\n );\n }\n const rel = (abs: string): string => relative(repoDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // A predecessor's interrupted emission is rolled forward before ANY\n // read — and before the feature argument resolves: reconcile grades the\n // very bytes a half-commit corrupts, and the journal's stored rerun may\n // name a feature that has since ARCHIVED, whose recovery must still\n // work. `new` fixed the same ordering and documented it; refusing\n // unknown-target over a wedged root left doctor printing a repair that\n // could never run. The lock is the emission root's own, given straight\n // back: the slow half below runs unlocked, and `commitEmission` retakes\n // it for the commit window.\n const early = await recoverIfJournaled(root);\n if (!early.ok) return fail(json, early.code, early.message);\n let recovered = early.recovered;\n\n const feature =\n featureArg === undefined ? null : await resolveFeature(config.docsDir, featureArg, \"exclude\");\n if (featureArg !== undefined && feature === null) {\n return fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureArg));\n }\n const scope: Scope =\n feature === null ? { mode: \"living\" } : { mode: \"feature\", featureId: feature.id };\n\n // The requirement set, by scope. Feature mode takes ADDED and MODIFIED —\n // a BASE requirement is the living state quoted, a REMOVED one is being\n // retired, and neither is behaviour anybody is about to test. Living mode\n // takes everything the living files hold.\n //\n // Read through `decodeDocument`, not `readFile(…, \"utf8\")`, and refused\n // rather than degraded: a spec.md saved as UTF-16 decodes to a document\n // with no requirements in it, which here is not merely a wrong answer but\n // a destructive one — an empty plan makes every existing .feature under\n // loam/ an orphan, and a living run DELETES its orphans. The whole\n // generated suite would go because a docs file was written by PowerShell.\n const byAxis: Array<{ axis: SpecAxis; reqs: Requirement[] }> = [];\n const readSpec = async (path: string): Promise<Requirement[]> =>\n parseRequirements(decodeDocument(await readFile(path), path));\n try {\n if (feature !== null) {\n // `config.service`, not `service`: the guard above proved they are\n // the same id, and only the config's copy carries the load-time\n // parse, so it is the pathable spelling — same as the else arm.\n const paths = featureSpecPaths(feature.dir, config.service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path)\n ? (await readSpec(path)).filter((r) => r.kind === \"ADDED\" || r.kind === \"MODIFIED\")\n : [];\n byAxis.push({ axis, reqs });\n }\n } else {\n // The guard above proved `service` IS `config.service`; only the\n // latter carries the load-time parse, so it is the pathable spelling.\n const paths = await locateServicePaths(config.docsDir, config.service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path) ? (await readSpec(path)).filter((r) => r.kind !== \"REMOVED\") : [];\n byAxis.push({ axis, reqs });\n }\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot emit gherkin: ${err.message}`);\n }\n\n // `service` is not decoration: it salts every `@loam-digest-…` stamp, and\n // it is the same string `loam verify` files the matching claims under.\n const plan = planEmission(byAxis, {\n service,\n featureTag: scope.mode === \"feature\" ? scope.featureId : undefined,\n version: LOAM_VERSION,\n });\n\n // `Set<string>`, not the ids' own brand — core/gherkin/stale.ts's reason:\n // it answers membership for tags read out of .feature files.\n const activeIds = new Set<string>((await listFeatures(config.docsDir)).map((f) => f.id));\n let reconciled;\n try {\n reconciled = await reconcile(plan, { root, repoDir }, scope, activeIds);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", `Cannot emit Gherkin: ${err.message}.`);\n }\n const { actions, orphans, conflicts } = reconciled;\n\n // All or nothing, and only a feature run can conflict — `./reconcile.ts`\n // records why, where the conflicts are collected.\n if (scope.mode === \"feature\" && conflicts.length > 0) {\n const detail = conflicts\n .map((c) => `${rel(c.path)} is @${c.owners.join(\" @\")} (requirement '${c.requirement.name}')`)\n .join(\"; \");\n const message =\n `Cannot emit gherkin for ${scope.featureId}: ${conflicts.length} file(s) belong to another feature still in flight — ${detail}. ` +\n `A .feature file carries one feature's delta; overwriting would revert that feature's wording and destroy the digest stamps \\`loam verify --results\\` matches on. ` +\n `Archive (or abandon) the owning feature first, or rename the requirement in ${scope.featureId}'s delta so the two stop sharing a file name.`;\n if (json) {\n emitJsonError(\"gherkin-conflict\", message, {\n conflicts: conflicts.map((c) => ({\n path: rel(c.path),\n action: \"conflict\",\n requirement: c.requirement.name,\n inFlight: c.owners,\n })),\n });\n return;\n }\n return fail(json, \"gherkin-conflict\", message);\n }\n\n const writes = actions.filter((a) => a.action !== \"kept\");\n\n if (!dryRun && (writes.length > 0 || orphans.length > 0)) {\n // The commit window: locked, journaled, compared against the bytes\n // reconcile graded. A zero-op run takes no lock and creates no root —\n // an emission with nothing to emit must not flip the repo into\n // \"opted in\", where an empty loam/ tells `loam validate` the whole\n // living suite is missing.\n const committed = await commitEmission({ root, service, scope }, { writes, orphans });\n if (!committed.ok) return fail(json, committed.code, committed.message);\n recovered = committed.recovered ?? recovered;\n }\n\n if (json) {\n emitJson({\n command: \"gherkin\",\n mode: scope.mode,\n ...(scope.mode === \"feature\" ? { feature: scope.featureId } : {}),\n service,\n root: rel(root),\n written: !dryRun,\n files: actions.map((a) => ({\n path: rel(a.path),\n action: a.action,\n axis: axisLabel(a.axis),\n requirement: a.requirement.name,\n // A kept file's numbers describe what STAYS on disk (the in-flight\n // emission), not the emission that was withheld.\n scenarios: a.action === \"kept\" ? a.kept.scenarios.length : a.digests.length,\n digests: a.action === \"kept\" ? a.kept.scenarios.map((s) => s.digest) : a.digests,\n // `digests` is a bare array and `scenarios` a bare count, so the one\n // thing a consumer actually needs — which stamp belongs to which\n // scenario — was the one thing this payload could not say, and the\n // only way to learn it was to go read the generated file back off\n // disk. `stamped` is that pairing; `digests` stays exactly as it was\n // beside it, because the envelope permits adding a key and never\n // changing one (core/envelope/json.ts).\n //\n // The positional zip is sound because `renderFeature`\n // (core/gherkin/emit.ts) pushes exactly one digest per scenario, in\n // scenario order, inside the same loop that writes the `Scenario:`\n // line — but it is written to FAIL CLOSED rather than to assert\n // that: a scenario with no digest opposite it drops out of the\n // pairing instead of being published with a null one. A kept row\n // reads its pairs off the file that is really on disk, because its\n // numbers describe the in-flight emission that stayed, not the one\n // that was withheld.\n stamped:\n a.action === \"kept\"\n ? a.kept.scenarios.map((s) => ({ scenario: s.name, digest: s.digest }))\n : a.requirement.scenarios.flatMap((s, i) => {\n const digest = a.digests[i];\n return digest === undefined ? [] : [{ scenario: s.name, digest }];\n }),\n ...(a.action === \"kept\"\n ? { inFlight: a.kept.tags.filter((t) => activeIds.has(t)) }\n : {\n stepless: a.stepless,\n malformedExamples: a.malformedExamples,\n strandedBlocks: a.strandedBlocks,\n }),\n })),\n deleted: orphans.map((o) => rel(o.path)),\n ...(recovered === null ? {} : { recovered }),\n });\n return;\n }\n\n if (recovered !== null) console.log(`${sayRecovered(recovered)}\\n`);\n render(actions, {\n service,\n root: rel(root),\n absRoot: root,\n featureId: scope.mode === \"feature\" ? scope.featureId : null,\n dryRun,\n activeIds,\n writes: writes.length,\n orphans: orphans.map((o) => o.path),\n });\n });\n}\n"]}
@@ -21,9 +21,12 @@ import { dirname, join, relative } from "node:path";
21
21
  import {} from "../../core/gherkin/emit.js";
22
22
  import { parseStampedFeature } from "../../core/gherkin/read.js";
23
23
  import { featureFilesUnder } from "../../core/gherkin/stale.js";
24
- import { isPathInside, resolveInside, UnsafePathError } from "../../core/kernel/path-safety.js";
24
+ import { isPathInside, resolveInside } from "../../core/kernel/path-safety.js";
25
25
  export async function reconcile(plan, where, scope, activeIds) {
26
26
  const { root, repoDir } = where;
27
+ // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns
28
+ // only its own emissions: stamped files carrying this feature's tag whose
29
+ // file is no longer in the plan (a requirement renamed or dropped).
27
30
  // Living mode owns the whole suite — any .feature not in the plan goes —
28
31
  // EXCEPT files tagged with a feature still in flight: those answer to
29
32
  // their feature's delta until it archives, and `loam gherkin <FEAT>` is
@@ -100,18 +103,12 @@ export async function reconcile(plan, where, scope, activeIds) {
100
103
  // or the requirement renamed.
101
104
  const actions = [];
102
105
  for (const f of plan) {
103
- let path;
104
- try {
105
- // Check the final file as well as the owned root: a pre-planted
106
- // `<slug>.feature` symlink must not turn writeFile into an overwrite
107
- // outside the repository.
108
- path = resolveInside(repoDir, relative(repoDir, join(root, f.fileName)), `gherkin file '${f.fileName}'`);
109
- }
110
- catch (err) {
111
- if (!(err instanceof UnsafePathError))
112
- throw err;
113
- throw err;
114
- }
106
+ // Check the final file as well as the owned root: a pre-planted
107
+ // `<slug>.feature` symlink must not turn writeFile into an overwrite
108
+ // outside the repository. The UnsafePathError is left to escape: the one
109
+ // caller (./gherkin.ts) refuses it as `invalid-option`, and a catch here
110
+ // could only rethrow it.
111
+ const path = resolveInside(repoDir, relative(repoDir, join(root, f.fileName)), `gherkin file '${f.fileName}'`);
115
112
  if (!existsSync(path)) {
116
113
  actions.push({ ...f, path, action: "written" });
117
114
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/commands/gherkin/reconcile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The plan against what is already on disk: which files are orphans, which are\n * written, replaced, kept, or owned by another feature.\n *\n * A module of its own because it is the only part of `loam gherkin` that\n * DELETES, and the reasoning that makes deletion safe is all here: a candidate\n * is an orphan only when its containing directory really resolves inside the\n * owned root, and a root that does not resolve proves nothing about what lives\n * beneath it, so nothing is collected. Leaving an orphan behind is\n * self-repairing — `loam validate` grades it and the next run removes it — and a\n * deletion taken on an unproven path is not.\n *\n * The in-flight exemption is here for the same reason: files are named by\n * requirement slug in both modes, so a MODIFIED requirement's living emission\n * always collides with the active feature's file, and replacing it reverted the\n * delta's wording mid-flight with its digest stamps destroyed.\n */\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { type PlannedFeature } from \"../../core/gherkin/emit.js\";\nimport { parseStampedFeature, type StampedFeature } from \"../../core/gherkin/read.js\";\nimport { featureFilesUnder } from \"../../core/gherkin/stale.js\";\nimport { isPathInside, resolveInside, UnsafePathError } from \"../../core/kernel/path-safety.js\";\n\n/**\n * The run's scope as ONE value, not a mode with an id lying beside it that only\n * one of the modes ever fills in: the emission tag, the orphan filter, the\n * overwrite rule and the conflict refusal all want the feature id exactly when\n * the run is a feature run. A union states that; two variables could only be\n * asserted to agree.\n */\nexport type Scope = { mode: \"living\" } | { mode: \"feature\"; featureId: string };\n\nexport type Emission = PlannedFeature & { path: string };\n\n/**\n * The row's shape follows its action instead of carrying every field any action\n * might want: only a kept or a conflicting row has read the file that is\n * already there, and only a conflicting one has owners. Held as one flat row\n * with optional fields, every reader had to assert what the writer three lines\n * up already knew.\n */\nexport type ActionRow =\n | (Emission & { action: \"written\" })\n // A replaced row carries the exact bytes it was graded against, so the\n // commit can prove the file did not move between the grading and the swap —\n // planning and committing must compare the same read, or a mismatch is\n // buried instead of refused.\n | (Emission & { action: \"replaced\"; raw: Buffer })\n | (Emission & { action: \"kept\"; kept: StampedFeature })\n | (Emission & { action: \"conflict\"; kept: StampedFeature; owners: string[] });\n\nexport type Action = ActionRow[\"action\"];\n\n/** Where the suite lives: the owned root, inside the service repository. */\nexport interface EmissionRoot {\n root: string;\n repoDir: string;\n}\n\nexport interface Reconciliation {\n /** Each orphan with the bytes its grading read — the commit compares them before deleting. */\n orphans: { path: string; raw: Buffer }[];\n actions: ActionRow[];\n /** Non-empty only in feature mode; one entry refuses the whole emission. */\n conflicts: Extract<ActionRow, { action: \"conflict\" }>[];\n}\n\nexport async function reconcile(\n plan: PlannedFeature[],\n where: EmissionRoot,\n scope: Scope,\n activeIds: ReadonlySet<string>,\n): Promise<Reconciliation> {\n const { root, repoDir } = where;\n // Living mode owns the whole suite — any .feature not in the plan goes —\n // EXCEPT files tagged with a feature still in flight: those answer to\n // their feature's delta until it archives, and `loam gherkin <FEAT>` is\n // their regeneration.\n const planned = new Set(plan.map((f) => f.fileName));\n // \"Inside loam/\" has to hold after the links are resolved, not merely in\n // the spelling of the path. `featureFilesUnder` FOLLOWS symlinks and\n // recurses with the LINK's path, so a planted `<gherkinDir>/loam/sub ->\n // /outside` hands back candidates that read as ours while `unlink`\n // resolves them and destroys files that were never in this repo. Planned\n // names are flat, so nothing nested is ever spared by `planned.has(...)`,\n // and deletion is the one irreversible thing this command does: a\n // candidate is an orphan only when its CONTAINING directory really\n // resolves inside the owned root. The write path below refuses the same\n // attack for the same reason.\n //\n // The candidate FILE is deliberately NOT resolved. A symlinked .feature\n // sitting directly in loam/ is loam's to remove — unlinking it takes the\n // link and leaves its target alone — so only the directory chain has to\n // be ours.\n const realOrNull = (p: string): string | null => {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n };\n // A root that does not resolve — absent, dangling, or vanished mid-run —\n // proves nothing about what lives beneath it, so nothing is collected.\n // Leaving an orphan behind is self-repairing (`loam validate` grades it\n // `gherkin.orphaned` and the next run removes it); a deletion taken on an\n // unproven path is not.\n const rootReal = realOrNull(root);\n const orphans: { path: string; raw: Buffer }[] = [];\n if (rootReal !== null) {\n for (const abs of await featureFilesUnder(root)) {\n if (planned.has(relative(root, abs).split(/[\\\\/]/).join(\"/\"))) continue;\n const holder = realOrNull(dirname(abs));\n if (holder === null || !isPathInside(rootReal, holder)) continue;\n const raw = await readFile(abs);\n const stamped = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"feature\") {\n if (stamped !== null && stamped.tags.includes(scope.featureId)) orphans.push({ path: abs, raw });\n } else {\n if (stamped !== null && stamped.tags.some((t) => activeIds.has(t))) continue;\n orphans.push({ path: abs, raw });\n }\n }\n }\n\n // The in-flight exemption guards the OVERWRITE path too, not only the\n // orphan scan above: files are named by requirement slug in both modes,\n // so a MODIFIED requirement's living emission always collides with the\n // active feature's file — and replacing it reverted the delta's wording\n // mid-flight, feature tag and new digest stamps destroyed, invisibly\n // (the reverted file grades current against the living spec). A planned\n // path whose existing content is stamped and tagged with a feature still\n // in flight is KEPT and reported as such; it answers to its feature's\n // delta until the feature archives, and then living regeneration\n // replaces it normally.\n //\n // FEATURE mode has the same collision and cannot solve it by keeping:\n // two features in flight against one requirement slug want the same\n // file, and whichever runs second used to `replace` — silently reverting\n // the other feature's wording, feature tag and digest stamps, so its\n // `verify --results` could never confirm a scenario again. Nothing loam\n // can write is right here (the file holds ONE feature's delta), so the\n // run refuses and names the owner: the two features have to be sequenced,\n // or the requirement renamed.\n const actions: ActionRow[] = [];\n for (const f of plan) {\n let path: string;\n try {\n // Check the final file as well as the owned root: a pre-planted\n // `<slug>.feature` symlink must not turn writeFile into an overwrite\n // outside the repository.\n path = resolveInside(\n repoDir,\n relative(repoDir, join(root, f.fileName)),\n `gherkin file '${f.fileName}'`,\n );\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n throw err;\n }\n if (!existsSync(path)) {\n actions.push({ ...f, path, action: \"written\" });\n continue;\n }\n const raw = await readFile(path);\n const existing = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"living\") {\n if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {\n actions.push({ ...f, path, action: \"kept\", kept: existing });\n continue;\n }\n } else if (existing !== null) {\n // An unstamped file is nobody's delta — it owns nothing, so it is\n // replaced like any other, which is what the empty owner list used\n // to say the long way round.\n const owners = existing.tags.filter((t) => t !== scope.featureId && activeIds.has(t));\n if (owners.length > 0) {\n actions.push({ ...f, path, action: \"conflict\", kept: existing, owners });\n continue;\n }\n }\n actions.push({ ...f, path, action: \"replaced\", raw });\n }\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n const conflicts = scope.mode === \"feature\"\n ? actions.filter((a): a is Extract<ActionRow, { action: \"conflict\" }> => a.action === \"conflict\")\n : [];\n\n return { orphans, actions, conflicts };\n}\n"]}
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/commands/gherkin/reconcile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The plan against what is already on disk: which files are orphans, which are\n * written, replaced, kept, or owned by another feature.\n *\n * A module of its own because it is the only part of `loam gherkin` that\n * DELETES, and the reasoning that makes deletion safe is all here: a candidate\n * is an orphan only when its containing directory really resolves inside the\n * owned root, and a root that does not resolve proves nothing about what lives\n * beneath it, so nothing is collected. Leaving an orphan behind is\n * self-repairing — `loam validate` grades it and the next run removes it — and a\n * deletion taken on an unproven path is not.\n *\n * The in-flight exemption is here for the same reason: files are named by\n * requirement slug in both modes, so a MODIFIED requirement's living emission\n * always collides with the active feature's file, and replacing it reverted the\n * delta's wording mid-flight with its digest stamps destroyed.\n */\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { type PlannedFeature } from \"../../core/gherkin/emit.js\";\nimport { parseStampedFeature, type StampedFeature } from \"../../core/gherkin/read.js\";\nimport { featureFilesUnder } from \"../../core/gherkin/stale.js\";\nimport { isPathInside, resolveInside } from \"../../core/kernel/path-safety.js\";\n\n/**\n * The run's scope as ONE value, not a mode with an id lying beside it that only\n * one of the modes ever fills in: the emission tag, the orphan filter, the\n * overwrite rule and the conflict refusal all want the feature id exactly when\n * the run is a feature run. A union states that; two variables could only be\n * asserted to agree.\n */\nexport type Scope = { mode: \"living\" } | { mode: \"feature\"; featureId: string };\n\nexport type Emission = PlannedFeature & { path: string };\n\n/**\n * The row's shape follows its action instead of carrying every field any action\n * might want: only a kept or a conflicting row has read the file that is\n * already there, and only a conflicting one has owners. Held as one flat row\n * with optional fields, every reader had to assert what the writer three lines\n * up already knew.\n */\nexport type ActionRow =\n | (Emission & { action: \"written\" })\n // A replaced row carries the exact bytes it was graded against, so the\n // commit can prove the file did not move between the grading and the swap —\n // planning and committing must compare the same read, or a mismatch is\n // buried instead of refused.\n | (Emission & { action: \"replaced\"; raw: Buffer })\n | (Emission & { action: \"kept\"; kept: StampedFeature })\n | (Emission & { action: \"conflict\"; kept: StampedFeature; owners: string[] });\n\nexport type Action = ActionRow[\"action\"];\n\n/** Where the suite lives: the owned root, inside the service repository. */\nexport interface EmissionRoot {\n root: string;\n repoDir: string;\n}\n\nexport interface Reconciliation {\n /** Each orphan with the bytes its grading read — the commit compares them before deleting. */\n orphans: { path: string; raw: Buffer }[];\n actions: ActionRow[];\n /** Non-empty only in feature mode; one entry refuses the whole emission. */\n conflicts: Extract<ActionRow, { action: \"conflict\" }>[];\n}\n\nexport async function reconcile(\n plan: PlannedFeature[],\n where: EmissionRoot,\n scope: Scope,\n activeIds: ReadonlySet<string>,\n): Promise<Reconciliation> {\n const { root, repoDir } = where;\n // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns\n // only its own emissions: stamped files carrying this feature's tag whose\n // file is no longer in the plan (a requirement renamed or dropped).\n // Living mode owns the whole suite — any .feature not in the plan goes —\n // EXCEPT files tagged with a feature still in flight: those answer to\n // their feature's delta until it archives, and `loam gherkin <FEAT>` is\n // their regeneration.\n const planned = new Set(plan.map((f) => f.fileName));\n // \"Inside loam/\" has to hold after the links are resolved, not merely in\n // the spelling of the path. `featureFilesUnder` FOLLOWS symlinks and\n // recurses with the LINK's path, so a planted `<gherkinDir>/loam/sub ->\n // /outside` hands back candidates that read as ours while `unlink`\n // resolves them and destroys files that were never in this repo. Planned\n // names are flat, so nothing nested is ever spared by `planned.has(...)`,\n // and deletion is the one irreversible thing this command does: a\n // candidate is an orphan only when its CONTAINING directory really\n // resolves inside the owned root. The write path below refuses the same\n // attack for the same reason.\n //\n // The candidate FILE is deliberately NOT resolved. A symlinked .feature\n // sitting directly in loam/ is loam's to remove — unlinking it takes the\n // link and leaves its target alone — so only the directory chain has to\n // be ours.\n const realOrNull = (p: string): string | null => {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n };\n // A root that does not resolve — absent, dangling, or vanished mid-run —\n // proves nothing about what lives beneath it, so nothing is collected.\n // Leaving an orphan behind is self-repairing (`loam validate` grades it\n // `gherkin.orphaned` and the next run removes it); a deletion taken on an\n // unproven path is not.\n const rootReal = realOrNull(root);\n const orphans: { path: string; raw: Buffer }[] = [];\n if (rootReal !== null) {\n for (const abs of await featureFilesUnder(root)) {\n if (planned.has(relative(root, abs).split(/[\\\\/]/).join(\"/\"))) continue;\n const holder = realOrNull(dirname(abs));\n if (holder === null || !isPathInside(rootReal, holder)) continue;\n const raw = await readFile(abs);\n const stamped = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"feature\") {\n if (stamped !== null && stamped.tags.includes(scope.featureId)) orphans.push({ path: abs, raw });\n } else {\n if (stamped !== null && stamped.tags.some((t) => activeIds.has(t))) continue;\n orphans.push({ path: abs, raw });\n }\n }\n }\n\n // The in-flight exemption guards the OVERWRITE path too, not only the\n // orphan scan above: files are named by requirement slug in both modes,\n // so a MODIFIED requirement's living emission always collides with the\n // active feature's file — and replacing it reverted the delta's wording\n // mid-flight, feature tag and new digest stamps destroyed, invisibly\n // (the reverted file grades current against the living spec). A planned\n // path whose existing content is stamped and tagged with a feature still\n // in flight is KEPT and reported as such; it answers to its feature's\n // delta until the feature archives, and then living regeneration\n // replaces it normally.\n //\n // FEATURE mode has the same collision and cannot solve it by keeping:\n // two features in flight against one requirement slug want the same\n // file, and whichever runs second used to `replace` — silently reverting\n // the other feature's wording, feature tag and digest stamps, so its\n // `verify --results` could never confirm a scenario again. Nothing loam\n // can write is right here (the file holds ONE feature's delta), so the\n // run refuses and names the owner: the two features have to be sequenced,\n // or the requirement renamed.\n const actions: ActionRow[] = [];\n for (const f of plan) {\n // Check the final file as well as the owned root: a pre-planted\n // `<slug>.feature` symlink must not turn writeFile into an overwrite\n // outside the repository. The UnsafePathError is left to escape: the one\n // caller (./gherkin.ts) refuses it as `invalid-option`, and a catch here\n // could only rethrow it.\n const path = resolveInside(\n repoDir,\n relative(repoDir, join(root, f.fileName)),\n `gherkin file '${f.fileName}'`,\n );\n if (!existsSync(path)) {\n actions.push({ ...f, path, action: \"written\" });\n continue;\n }\n const raw = await readFile(path);\n const existing = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"living\") {\n if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {\n actions.push({ ...f, path, action: \"kept\", kept: existing });\n continue;\n }\n } else if (existing !== null) {\n // An unstamped file is nobody's delta — it owns nothing, so it is\n // replaced like any other, which is what the empty owner list used\n // to say the long way round.\n const owners = existing.tags.filter((t) => t !== scope.featureId && activeIds.has(t));\n if (owners.length > 0) {\n actions.push({ ...f, path, action: \"conflict\", kept: existing, owners });\n continue;\n }\n }\n actions.push({ ...f, path, action: \"replaced\", raw });\n }\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n const conflicts = scope.mode === \"feature\"\n ? actions.filter((a): a is Extract<ActionRow, { action: \"conflict\" }> => a.action === \"conflict\")\n : [];\n\n return { orphans, actions, conflicts };\n}\n"]}
@@ -25,4 +25,48 @@ export function firstHour(service) {
25
25
  ["loam status", "what to do next, derived from the files"],
26
26
  ];
27
27
  }
28
+ /**
29
+ * The first hour of a docs repo created with NO service bound — WORKFLOW.md's
30
+ * Day zero steps 2–4, and the same rungs `loam status` answers on an empty
31
+ * fleet (`next.author-landscape`, `next.bind-service`, `next.adopt-first`).
32
+ *
33
+ * This run used to print nothing after the scaffold list: the single-repo
34
+ * table above needs a service id to name, and the guard that withheld it read
35
+ * "nothing is bound" as "nothing to say". But this is the Day zero composition
36
+ * itself — the person who just created the fleet's shared repo — and its next
37
+ * steps are as fixed as the trial's: state the map, bind each service's own
38
+ * repo, adopt. The ids are placeholders because nothing here knows them yet,
39
+ * which is also why the runnable-commands suite substitutes them like every
40
+ * other `<id>` it parses. Exported for that suite, by import, for the reason
41
+ * the header gives.
42
+ *
43
+ * The bind line names `--agent-profile service` because it is run in a
44
+ * SERVICE checkout of a fleet whose features are authored and archived here,
45
+ * in this docs repo: the profile defaults to `full`, and `full` puts
46
+ * `loam-feature` and `loam-ship` into every service repo — the outcome
47
+ * README's "Pass the profile explicitly" warns against. Agents type the
48
+ * command loam prints, not the README.
49
+ */
50
+ export function docsRepoFirstHour() {
51
+ return [
52
+ ["loam seed --from fleet.yaml", "the fleet map from a fleet.yaml you write, or draw it by hand"],
53
+ ["loam init --docs <docs-repo> --service <id> --agent-profile service",
54
+ "in each service's own repo: bind it, commit loam.json"],
55
+ ["loam adopt --service <id> --json", "there: the brief, an agent writes the baseline as draft"],
56
+ ["loam status", "what to do next, derived from the files"],
57
+ ];
58
+ }
59
+ /**
60
+ * The epilogue's printed lines — the heading and one aligned `command # why`
61
+ * row per step — for whichever first hour this run earned. Rendering only:
62
+ * init.ts decides WHETHER (never on a join), this decides which table and how
63
+ * it reads, so the two variants cannot drift into two layouts.
64
+ */
65
+ export function firstHourLines(service) {
66
+ const [heading, steps] = service === undefined
67
+ ? ["next — the docs repo's first hour:", docsRepoFirstHour()]
68
+ : ["next — the first hour:", firstHour(service)];
69
+ const width = Math.max(...steps.map(([command]) => command.length));
70
+ return [` ${heading}`, ...steps.map(([command, why]) => ` ${command.padEnd(width)} # ${why}`)];
71
+ }
28
72
  //# sourceMappingURL=first-hour.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"first-hour.js","sourceRoot":"","sources":["../../../src/commands/init/first-hour.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO;QACL,CAAC,wBAAwB,OAAO,SAAS;YACvC,kEAAkE,CAAC;QACrE,CAAC,2BAA2B,OAAO,EAAE,EAAE,+BAA+B,CAAC;QACvE,CAAC,wBAAwB,OAAO,EAAE,EAAE,uCAAuC,CAAC;QAC5E,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAC3D,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The four commands of the single-repo trial's first hour, each with the\n * reason it is there. `init` prints them as its epilogue when one run both\n * creates the docs repo and leaves a service bound (init.ts holds that guard\n * and the why).\n *\n * A module of its own for two reasons. The seam: init.ts is the wiring — the\n * flag guards and the scaffold sequence — while this table is what the trial\n * user is told, and the table alone pushed init.ts past the 300-line limit.\n * The export: test/agent-commands-runnable.test.ts feeds these commands\n * through the real commander program by IMPORT, because the printed lines are\n * plain text inside template literals — no markdown backticks, no\n * single-quoted `loam` forms — so that suite's literal scrape cannot see\n * them, and they are the first instruction a single-repo trial user receives:\n * the exact class its header records shipping broken once as `loam adopt\n * <id>`. README.md's Quick start reprints the same four lines, and\n * test/cli.test.ts pins that copy to this table.\n */\nexport function firstHour(service: string): Array<[command: string, why: string]> {\n return [\n [`loam adopt --service ${service} --json`,\n \"the brief: an agent reads the code, writes the baseline as draft\"],\n [`loam validate --service ${service}`, \"grade the result in this repo\"],\n [`loam vouch --service ${service}`, \"you, not the agent: draft -> verified\"],\n [\"loam status\", \"what to do next, derived from the files\"],\n ];\n}\n"]}
1
+ {"version":3,"file":"first-hour.js","sourceRoot":"","sources":["../../../src/commands/init/first-hour.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO;QACL,CAAC,wBAAwB,OAAO,SAAS;YACvC,kEAAkE,CAAC;QACrE,CAAC,2BAA2B,OAAO,EAAE,EAAE,+BAA+B,CAAC;QACvE,CAAC,wBAAwB,OAAO,EAAE,EAAE,uCAAuC,CAAC;QAC5E,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAC3D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,CAAC,6BAA6B,EAAE,+DAA+D,CAAC;QAChG,CAAC,qEAAqE;YACpE,uDAAuD,CAAC;QAC1D,CAAC,kCAAkC,EAAE,yDAAyD,CAAC;QAC/F,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAC3D,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAA2B;IACxD,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,OAAO,KAAK,SAAS;QAC5C,CAAC,CAAC,CAAC,oCAAoC,EAAE,iBAAiB,EAAE,CAAC;QAC7D,CAAC,CAAC,CAAC,wBAAwB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,KAAK,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC;AACvG,CAAC","sourcesContent":["/**\n * The four commands of the single-repo trial's first hour, each with the\n * reason it is there. `init` prints them as its epilogue when one run both\n * creates the docs repo and leaves a service bound (init.ts holds that guard\n * and the why).\n *\n * A module of its own for two reasons. The seam: init.ts is the wiring — the\n * flag guards and the scaffold sequence — while this table is what the trial\n * user is told, and the table alone pushed init.ts past the 300-line limit.\n * The export: test/agent-commands-runnable.test.ts feeds these commands\n * through the real commander program by IMPORT, because the printed lines are\n * plain text inside template literals — no markdown backticks, no\n * single-quoted `loam` forms — so that suite's literal scrape cannot see\n * them, and they are the first instruction a single-repo trial user receives:\n * the exact class its header records shipping broken once as `loam adopt\n * <id>`. README.md's Quick start reprints the same four lines, and\n * test/cli.test.ts pins that copy to this table.\n */\nexport function firstHour(service: string): Array<[command: string, why: string]> {\n return [\n [`loam adopt --service ${service} --json`,\n \"the brief: an agent reads the code, writes the baseline as draft\"],\n [`loam validate --service ${service}`, \"grade the result in this repo\"],\n [`loam vouch --service ${service}`, \"you, not the agent: draft -> verified\"],\n [\"loam status\", \"what to do next, derived from the files\"],\n ];\n}\n\n/**\n * The first hour of a docs repo created with NO service bound — WORKFLOW.md's\n * Day zero steps 2–4, and the same rungs `loam status` answers on an empty\n * fleet (`next.author-landscape`, `next.bind-service`, `next.adopt-first`).\n *\n * This run used to print nothing after the scaffold list: the single-repo\n * table above needs a service id to name, and the guard that withheld it read\n * \"nothing is bound\" as \"nothing to say\". But this is the Day zero composition\n * itself — the person who just created the fleet's shared repo — and its next\n * steps are as fixed as the trial's: state the map, bind each service's own\n * repo, adopt. The ids are placeholders because nothing here knows them yet,\n * which is also why the runnable-commands suite substitutes them like every\n * other `<id>` it parses. Exported for that suite, by import, for the reason\n * the header gives.\n *\n * The bind line names `--agent-profile service` because it is run in a\n * SERVICE checkout of a fleet whose features are authored and archived here,\n * in this docs repo: the profile defaults to `full`, and `full` puts\n * `loam-feature` and `loam-ship` into every service repo — the outcome\n * README's \"Pass the profile explicitly\" warns against. Agents type the\n * command loam prints, not the README.\n */\nexport function docsRepoFirstHour(): Array<[command: string, why: string]> {\n return [\n [\"loam seed --from fleet.yaml\", \"the fleet map from a fleet.yaml you write, or draw it by hand\"],\n [\"loam init --docs <docs-repo> --service <id> --agent-profile service\",\n \"in each service's own repo: bind it, commit loam.json\"],\n [\"loam adopt --service <id> --json\", \"there: the brief, an agent writes the baseline as draft\"],\n [\"loam status\", \"what to do next, derived from the files\"],\n ];\n}\n\n/**\n * The epilogue's printed lines — the heading and one aligned `command # why`\n * row per step — for whichever first hour this run earned. Rendering only:\n * init.ts decides WHETHER (never on a join), this decides which table and how\n * it reads, so the two variants cannot drift into two layouts.\n */\nexport function firstHourLines(service: string | undefined): string[] {\n const [heading, steps] = service === undefined\n ? [\"next — the docs repo's first hour:\", docsRepoFirstHour()]\n : [\"next — the first hour:\", firstHour(service)];\n const width = Math.max(...steps.map(([command]) => command.length));\n return [` ${heading}`, ...steps.map(([command, why]) => ` ${command.padEnd(width)} # ${why}`)];\n}\n"]}
@@ -10,7 +10,7 @@ import { InvalidIdError, assertServiceId } from "../../core/kernel/ids/service.j
10
10
  import { detectAgentTools, plannedCommandFiles, syncAgentCommands, updatedAgentFileManifest, } from "../../core/agent/scaffold.js";
11
11
  import { AGENT_TOOLS } from "../../core/agent/tools/registry.js";
12
12
  import { initExample } from "./example.js";
13
- import { firstHour } from "./first-hour.js";
13
+ import { firstHourLines } from "./first-hour.js";
14
14
  import { MCP_CONFIG_FILENAME, mcpConfigPath, mcpServerSnippet, writeMcpConfig } from "./mcp.js";
15
15
  import { isDocsRepo, resolveAgentProfile, resolveTools, storedDocsDir } from "./options.js";
16
16
  export function registerInit(program) {
@@ -299,27 +299,23 @@ export function registerInit(program) {
299
299
  for (const line of mcpServerSnippet(cwd, opts.mcpAuthor === true))
300
300
  console.log(` ${line}`);
301
301
  }
302
- // Printed ONLY for the single-repo trial composition: one run that both
303
- // created the docs repo and left a service bound (the binding may
304
- // predate the run — the config spread above carries an existing
305
- // loam.json's `service` forward). `loam status` is NOT mute here — its
306
- // first-hour ladder (core/status/fleet/next.ts) already answers
307
- // `next.adopt-bound` in exactly this state — but status has to be run
308
- // to say so, and no status rung ever names `loam vouch`, the one step
309
- // of this loop that is a person's; init's output is already on screen,
310
- // so it prints the whole hour once. A join stays silent on purpose: a
311
- // joined fleet has other repositories already through this loop, and
312
- // `loam status` in its docs repo is the working guide there. The --json
313
- // path returned above, so the envelope is byte-identical to before
314
- // this block existed.
315
- if (!joining && config.service !== undefined) {
316
- const steps = firstHour(config.service);
317
- const width = Math.max(...steps.map(([command]) => command.length));
318
- console.log(" next — the first hour:");
319
- for (const [command, why] of steps) {
320
- console.log(` ${command.padEnd(width)} # ${why}`);
321
- }
322
- }
302
+ // Printed ONLY when this run created the docs repo. With a service bound
303
+ // (the binding may predate the run — the config spread above carries an
304
+ // existing loam.json's `service` forward) it is the single-repo trial's
305
+ // hour. `loam status` is NOT mute here — its first-hour ladder
306
+ // (core/status/fleet/next.ts) already answers `next.adopt-bound` in
307
+ // exactly this state — but status has to be run to say so, and no status
308
+ // rung ever names `loam vouch`, the one step of this loop that is a
309
+ // person's; init's output is already on screen, so it prints the whole
310
+ // hour once. With none bound it is the docs repo's own hour (map, bind,
311
+ // adopt — first-hour.ts says why that used to be silence). A join stays
312
+ // silent on purpose: a joined fleet has other repositories already
313
+ // through this loop, and `loam status` in its docs repo is the working
314
+ // guide there. The --json path returned above, so the envelope is
315
+ // byte-identical to before this block existed: neither variant has a key.
316
+ if (!joining)
317
+ for (const line of firstHourLines(config.service))
318
+ console.log(line);
323
319
  });
324
320
  }
325
321
  //# sourceMappingURL=init.js.map