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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../../../src/commands/migrate-openspec/openspec/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAwB,MAAM,yCAAyC,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,KAAa;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAChF,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,GAAG,KAAK,+CAA+C,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,WAAW,CAAC,KAAc,EAAE,KAAa;IAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACjD,IAAI,OAAO,KAAK,IAAI;YAAE,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,gCAAgC,IAAI,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9G,CAAC;IACD,6EAA6E;IAC7E,uEAAuE;IACvE,uCAAuC;IACvC,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAC1D,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,qBAAqB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClH,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,0CAA0C,CAAC,CAAC;IACvH,CAAC;IACD,MAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,UAAU,KAAK,IAAI;WAC7B,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ;WACnC,OAAO,UAAU,CAAC,eAAe,KAAK,QAAQ;QACjD,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAC,eAAe,EAAE;QACxE,CAAC,CAAC,IAAI,CAAC;IACT,MAAM,gBAAgB,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACzD,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,uCAAuC,CAAC,CAAC;IACpH,CAAC;IACD,MAAM,YAAY,GAAG,UAAU,EAA2C,CAAC;IAC3E,KAAK,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACnE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,QAAQ,IAAI,KAAK,EAAE,gBAAgB,UAAU,WAAW,CAAC,CAAC;QAC/F,MAAM,mBAAmB,GAAG,UAAU,EAAY,CAAC;QACnD,IAAI,MAAM,EAAE,mBAAmB,KAAK,SAAS,EAAE,CAAC;YAC9C,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;YACzD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;gBACzB,MAAM,IAAI,oBAAoB,CAC5B,gBAAgB,EAChB,gBAAgB,UAAU,yCAAyC,CACpE,CAAC;YACJ,CAAC;YACD,KAAK,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClE,mBAAmB,CAAC,WAAW,CAAC,GAAG,WAAW,CAC5C,QAAQ,EACR,gBAAgB,UAAU,wBAAwB,WAAW,EAAE,CAChE,CAAC;YACJ,CAAC;QACH,CAAC;QACD,YAAY,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,kCAAkC,CAAC,CAAC;IAC/G,CAAC;IACD,MAAM,OAAO,GAAG,UAAU,EAAsC,CAAC;IACjE,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,oBAAoB,CAC5B,gBAAgB,EAChB,WAAW,MAAM,4DAA4D,CAC9E,CAAC;QACJ,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC;QACpC,IAAI,YAAY,KAAK,IAAI,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC9D,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,WAAW,MAAM,oCAAoC,CAAC,CAAC;QAC1G,CAAC;QACD,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrC,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,WAAW,MAAM,0BAA0B,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,GAAG;YAChB,OAAO,EAAE,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;YACpG,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,EAAU,CAAC;IACrC,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,6BAA6B,CAAC,CAAC;QAC1G,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC/B,MAAM,aAAa,GAAG,MAAM,KAAK,IAAI,IAAI,eAAe,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC;YAClG,oEAAoE;YACpE,mEAAmE;YACnE,IAAI,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,EAAE;gBAAE,SAAS;YAC7D,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;gBACtC,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,WAAW,GAAG,0CAA0C,CAAC,CAAC;YAC7G,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QACtC,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,+EAA+E;IAC/E,2EAA2E;IAC3E,gCAAgC;IAChC,MAAM,SAAS,GAAG,UAAU,EAAU,CAAC;IACvC,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YAC3B,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,+BAA+B,CAAC,CAAC;QAC5G,CAAC;QACD,KAAK,MAAM,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YAClE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC/B,MAAM,WAAW,GAAG,MAAM,KAAK,IAAI,IAAI,aAAa,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC;YAC5F,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;gBAAE,SAAS;YACzD,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;gBACpC,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,aAAa,YAAY,wCAAwC,CAAC,CAAC;YACtH,CAAC;YACD,SAAS,CAAC,YAAY,CAAC,GAAG,WAAW,CAAC;QACxC,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * The mapping document on the way in: YAML a human edited, graded into an\n * `OpenSpecMapping` or refused with the line to fix.\n *\n * Split from `./decisions.ts`, which is the same document on the way out — the\n * blank skeleton, the normalized record written beside a migration, and the\n * routing query. Reading is where every refusal lives and writing is where none\n * do, so the two halves have almost no vocabulary in common beyond the type.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseYaml } from \"yaml\";\nimport { serviceIdProblem } from \"../../../core/kernel/ids/service.js\";\nimport { asRecord, dictionary } from \"../../../core/kernel/records.js\";\nimport { type OpenSpecMapping } from \"../../../core/openspec/model/mapping.js\";\nimport { message } from \"../../../core/staging/commit.js\";\nimport { OpenSpecCommandError } from \"./error.js\";\nimport { decodeSource } from \"./read.js\";\n\n/**\n * `Array.isArray` narrows an `unknown` to `any[]`, so the element check below it\n * proved nothing to tsc and the elements had to be asserted back to `string` at\n * the point of use. One predicate makes the narrowing carry the check.\n */\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction stringList(value: unknown, label: string): string[] {\n if (typeof value === \"string\") return value.trim() === \"\" ? [] : [value.trim()];\n if (!isStringArray(value)) {\n throw new OpenSpecCommandError(\"invalid-option\", `${label} must be a string or an array of service ids.`);\n }\n return [...new Set(value.map((item) => item.trim()).filter(Boolean))];\n}\n\n/**\n * The service ids a mapping may name, checked against the ONE grammar.\n *\n * This used to carry its own copy of the rule — the same alphabet regex, the\n * `.`/`..` tests, the trailing dot and the Windows device names — which is how\n * the two spellings drifted apart to begin with: for a while migrate was the\n * STRICTER of the two, so the primary authoring path accepted ids a migration\n * refused. ids.ts owns the grammar now, so a mapping can only ever name a\n * `services/<id>/` the authoring commands can still address.\n *\n * The refusal keeps migrate's own shape — `OpenSpecCommandError` with\n * `invalid-option`, not an `Issue` — and `serviceIdProblem`'s label carries the\n * mapping key, so the message still points at the line of the YAML to edit.\n */\nfunction serviceList(value: unknown, label: string): string[] {\n const services = stringList(value, label);\n for (const service of services) {\n const problem = serviceIdProblem(service, label);\n if (problem !== null) throw new OpenSpecCommandError(\"invalid-option\", problem);\n }\n return services;\n}\n\nexport async function readMapping(path: string): Promise<OpenSpecMapping> {\n let bytes: Buffer;\n try {\n bytes = await readFile(path);\n } catch (error) {\n throw new OpenSpecCommandError(\"unknown-target\", `Cannot read OpenSpec mapping ${path}: ${message(error)}`);\n }\n // Decoded rather than assumed: a mapping saved as UTF-16 parses as YAML with\n // no capabilities block, and the refusal would name a missing decision\n // instead of the encoding that hid it.\n const raw = decodeSource(bytes, path, \"OpenSpec mapping\");\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (error) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} is invalid YAML: ${message(error)}`);\n }\n const document = asRecord(parsed);\n if (document === null || document.version !== 1) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} must be a YAML mapping with version: 1.`);\n }\n const sourceNode = asRecord(document.source);\n const source = sourceNode !== null\n && typeof sourceNode.root === \"string\"\n && typeof sourceNode.inventoryDigest === \"string\"\n ? { root: sourceNode.root, inventoryDigest: sourceNode.inventoryDigest }\n : null;\n const capabilitiesNode = asRecord(document.capabilities);\n if (capabilitiesNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} must contain a capabilities mapping.`);\n }\n const capabilities = dictionary<OpenSpecMapping[\"capabilities\"][string]>();\n for (const [capability, value] of Object.entries(capabilitiesNode)) {\n const record = asRecord(value);\n const services = serviceList(record?.services ?? value, `capabilities.${capability}.services`);\n const requirementServices = dictionary<string[]>();\n if (record?.requirementServices !== undefined) {\n const allocations = asRecord(record.requirementServices);\n if (allocations === null) {\n throw new OpenSpecCommandError(\n \"invalid-option\",\n `capabilities.${capability}.requirementServices must be a mapping.`,\n );\n }\n for (const [requirement, selected] of Object.entries(allocations)) {\n requirementServices[requirement] = serviceList(\n selected,\n `capabilities.${capability}.requirementServices.${requirement}`,\n );\n }\n }\n capabilities[capability] = { services, requirementServices };\n }\n\n const changesNode = asRecord(document.changes);\n if (changesNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} must contain a changes mapping.`);\n }\n const changes = dictionary<OpenSpecMapping[\"changes\"][string]>();\n for (const [change, value] of Object.entries(changesNode)) {\n const record = asRecord(value);\n if (record === null) {\n throw new OpenSpecCommandError(\n \"invalid-option\",\n `changes.${change} must be a mapping with explicit feature and title fields.`,\n );\n }\n const featureValue = record.feature;\n if (featureValue !== null && typeof featureValue !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `changes.${change}.feature must be a string or null.`);\n }\n if (typeof record.title !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `changes.${change}.title must be a string.`);\n }\n changes[change] = {\n feature: typeof featureValue === \"string\" && featureValue.trim() !== \"\" ? featureValue.trim() : null,\n title: record.title,\n };\n }\n\n const renames = dictionary<string>();\n if (document.renames !== undefined) {\n const renamesNode = asRecord(document.renames);\n if (renamesNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} renames must be a mapping.`);\n }\n for (const [key, value] of Object.entries(renamesNode)) {\n const record = asRecord(value);\n const requirementId = record !== null && \"requirementId\" in record ? record.requirementId : value;\n // A freshly generated skeleton deliberately uses null: it remains a\n // needsIdentity decision rather than becoming a malformed mapping.\n if (requirementId === null || requirementId === \"\") continue;\n if (typeof requirementId !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `renames.${key}.requirementId must be a string or null.`);\n }\n renames[key] = requirementId.trim();\n }\n }\n // Recorded as written. Naming the disposition union here only meant asserting\n // an unreviewed string into it: `mapping.invalid-artifact-disposition` is what\n // actually grades this value, and it has to see what the human typed to be\n // able to name it back to them.\n const artifacts = dictionary<string>();\n if (document.artifacts !== undefined) {\n const artifactsNode = asRecord(document.artifacts);\n if (artifactsNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} artifacts must be a mapping.`);\n }\n for (const [artifactPath, value] of Object.entries(artifactsNode)) {\n const record = asRecord(value);\n const disposition = record !== null && \"disposition\" in record ? record.disposition : value;\n if (disposition === null || disposition === \"\") continue;\n if (typeof disposition !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `artifacts.${artifactPath}.disposition must be a string or null.`);\n }\n artifacts[artifactPath] = disposition;\n }\n }\n return { source, capabilities, changes, renames, artifacts };\n}\n"]}
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../../../src/commands/migrate-openspec/openspec/mapping.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The mapping document on the way in: YAML a human edited, graded into an\n * `OpenSpecMapping` or refused with the line to fix.\n *\n * Split from `./decisions.ts`, which is the same document on the way out — the\n * blank skeleton, the normalized record written beside a migration, and the\n * routing query. Reading is where every refusal lives and writing is where none\n * do, so the two halves have almost no vocabulary in common beyond the type.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseYaml } from \"yaml\";\nimport { serviceIdProblem } from \"../../../core/kernel/ids/service.js\";\nimport { asRecord, dictionary, isStringArray } from \"../../../core/kernel/records.js\";\nimport { type OpenSpecMapping } from \"../../../core/openspec/model/mapping.js\";\nimport { message } from \"../../../core/staging/commit.js\";\nimport { OpenSpecCommandError } from \"./error.js\";\nimport { decodeSource } from \"./read.js\";\n\nfunction stringList(value: unknown, label: string): string[] {\n if (typeof value === \"string\") return value.trim() === \"\" ? [] : [value.trim()];\n if (!isStringArray(value)) {\n throw new OpenSpecCommandError(\"invalid-option\", `${label} must be a string or an array of service ids.`);\n }\n return [...new Set(value.map((item) => item.trim()).filter(Boolean))];\n}\n\n/**\n * The service ids a mapping may name, checked against the ONE grammar.\n *\n * This used to carry its own copy of the rule — the same alphabet regex, the\n * `.`/`..` tests, the trailing dot and the Windows device names — which is how\n * the two spellings drifted apart to begin with: for a while migrate was the\n * STRICTER of the two, so the primary authoring path accepted ids a migration\n * refused. ids.ts owns the grammar now, so a mapping can only ever name a\n * `services/<id>/` the authoring commands can still address.\n *\n * The refusal keeps migrate's own shape — `OpenSpecCommandError` with\n * `invalid-option`, not an `Issue` — and `serviceIdProblem`'s label carries the\n * mapping key, so the message still points at the line of the YAML to edit.\n */\nfunction serviceList(value: unknown, label: string): string[] {\n const services = stringList(value, label);\n for (const service of services) {\n const problem = serviceIdProblem(service, label);\n if (problem !== null) throw new OpenSpecCommandError(\"invalid-option\", problem);\n }\n return services;\n}\n\nexport async function readMapping(path: string): Promise<OpenSpecMapping> {\n let bytes: Buffer;\n try {\n bytes = await readFile(path);\n } catch (error) {\n throw new OpenSpecCommandError(\"unknown-target\", `Cannot read OpenSpec mapping ${path}: ${message(error)}`);\n }\n // Decoded rather than assumed: a mapping saved as UTF-16 parses as YAML with\n // no capabilities block, and the refusal would name a missing decision\n // instead of the encoding that hid it.\n const raw = decodeSource(bytes, path, \"OpenSpec mapping\");\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (error) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} is invalid YAML: ${message(error)}`);\n }\n const document = asRecord(parsed);\n if (document === null || document.version !== 1) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} must be a YAML mapping with version: 1.`);\n }\n const sourceNode = asRecord(document.source);\n const source = sourceNode !== null\n && typeof sourceNode.root === \"string\"\n && typeof sourceNode.inventoryDigest === \"string\"\n ? { root: sourceNode.root, inventoryDigest: sourceNode.inventoryDigest }\n : null;\n const capabilitiesNode = asRecord(document.capabilities);\n if (capabilitiesNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} must contain a capabilities mapping.`);\n }\n const capabilities = dictionary<OpenSpecMapping[\"capabilities\"][string]>();\n for (const [capability, value] of Object.entries(capabilitiesNode)) {\n const record = asRecord(value);\n const services = serviceList(record?.services ?? value, `capabilities.${capability}.services`);\n const requirementServices = dictionary<string[]>();\n if (record?.requirementServices !== undefined) {\n const allocations = asRecord(record.requirementServices);\n if (allocations === null) {\n throw new OpenSpecCommandError(\n \"invalid-option\",\n `capabilities.${capability}.requirementServices must be a mapping.`,\n );\n }\n for (const [requirement, selected] of Object.entries(allocations)) {\n requirementServices[requirement] = serviceList(\n selected,\n `capabilities.${capability}.requirementServices.${requirement}`,\n );\n }\n }\n capabilities[capability] = { services, requirementServices };\n }\n\n const changesNode = asRecord(document.changes);\n if (changesNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} must contain a changes mapping.`);\n }\n const changes = dictionary<OpenSpecMapping[\"changes\"][string]>();\n for (const [change, value] of Object.entries(changesNode)) {\n const record = asRecord(value);\n if (record === null) {\n throw new OpenSpecCommandError(\n \"invalid-option\",\n `changes.${change} must be a mapping with explicit feature and title fields.`,\n );\n }\n const featureValue = record.feature;\n if (featureValue !== null && typeof featureValue !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `changes.${change}.feature must be a string or null.`);\n }\n if (typeof record.title !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `changes.${change}.title must be a string.`);\n }\n changes[change] = {\n feature: typeof featureValue === \"string\" && featureValue.trim() !== \"\" ? featureValue.trim() : null,\n title: record.title,\n };\n }\n\n const renames = dictionary<string>();\n if (document.renames !== undefined) {\n const renamesNode = asRecord(document.renames);\n if (renamesNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} renames must be a mapping.`);\n }\n for (const [key, value] of Object.entries(renamesNode)) {\n const record = asRecord(value);\n const requirementId = record !== null && \"requirementId\" in record ? record.requirementId : value;\n // A freshly generated skeleton deliberately uses null: it remains a\n // needsIdentity decision rather than becoming a malformed mapping.\n if (requirementId === null || requirementId === \"\") continue;\n if (typeof requirementId !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `renames.${key}.requirementId must be a string or null.`);\n }\n renames[key] = requirementId.trim();\n }\n }\n // Recorded as written. Naming the disposition union here only meant asserting\n // an unreviewed string into it: `mapping.invalid-artifact-disposition` is what\n // actually grades this value, and it has to see what the human typed to be\n // able to name it back to them.\n const artifacts = dictionary<string>();\n if (document.artifacts !== undefined) {\n const artifactsNode = asRecord(document.artifacts);\n if (artifactsNode === null) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec mapping ${path} artifacts must be a mapping.`);\n }\n for (const [artifactPath, value] of Object.entries(artifactsNode)) {\n const record = asRecord(value);\n const disposition = record !== null && \"disposition\" in record ? record.disposition : value;\n if (disposition === null || disposition === \"\") continue;\n if (typeof disposition !== \"string\") {\n throw new OpenSpecCommandError(\"invalid-option\", `artifacts.${artifactPath}.disposition must be a string or null.`);\n }\n artifacts[artifactPath] = disposition;\n }\n }\n return { source, capabilities, changes, renames, artifacts };\n}\n"]}
@@ -2,7 +2,8 @@
2
2
  * The path arithmetic every write decision in this command rests on.
3
3
  *
4
4
  * Three rules, kept together because they answer one question in two
5
- * directions: is a path inside a tree it must stay out of (`contains`), what
5
+ * directions: is a path inside a tree it must stay out of (kernel's
6
+ * `isPathInside`, which this module used to restate as `contains`), what
6
7
  * tree is it really in once symlinks are resolved (`canonicalForCreate`), and
7
8
  * does a path claimed by the OpenSpec source stay inside the directory it was
8
9
  * listed under (`safeArtifactRelative`). `materialize/stage.ts` builds the
@@ -11,12 +12,8 @@
11
12
  */
12
13
  import { existsSync } from "node:fs";
13
14
  import { realpath } from "node:fs/promises";
14
- import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
15
+ import { basename, dirname, resolve } from "node:path";
15
16
  import { OpenSpecCommandError } from "./error.js";
16
- export function contains(parent, candidate) {
17
- const rel = relative(parent, candidate);
18
- return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
19
- }
20
17
  /** Resolve symlinks in the nearest existing ancestor of a not-yet-created path. */
21
18
  export async function canonicalForCreate(path) {
22
19
  let cursor = path;
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/commands/migrate-openspec/openspec/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,SAAiB;IACxD,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAY;IACnD,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,MAAM;YAAE,MAAM;QAC7B,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACjC,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,YAAoB,EAAE,MAAc,EAAE,KAAa;IACtF,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,oBAAoB,CAC5B,gBAAgB,EAChB,GAAG,KAAK,oDAAoD,YAAY,GAAG,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/G,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,kDAAkD,YAAY,GAAG,CAAC,CAAC;IACtH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The path arithmetic every write decision in this command rests on.\n *\n * Three rules, kept together because they answer one question in two\n * directions: is a path inside a tree it must stay out of (`contains`), what\n * tree is it really in once symlinks are resolved (`canonicalForCreate`), and\n * does a path claimed by the OpenSpec source stay inside the directory it was\n * listed under (`safeArtifactRelative`). `materialize/stage.ts` builds the\n * staging refusals out of the first two; `openspec/decisions.ts` uses them to\n * keep a mapping skeleton out of the workspace it describes.\n */\nimport { existsSync } from \"node:fs\";\nimport { realpath } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { OpenSpecCommandError } from \"./error.js\";\n\nexport function contains(parent: string, candidate: string): boolean {\n const rel = relative(parent, candidate);\n return rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\n/** Resolve symlinks in the nearest existing ancestor of a not-yet-created path. */\nexport async function canonicalForCreate(path: string): Promise<string> {\n let cursor = path;\n const suffix: string[] = [];\n while (!existsSync(cursor)) {\n const parent = dirname(cursor);\n if (parent === cursor) break;\n suffix.unshift(basename(cursor));\n cursor = parent;\n }\n return resolve(await realpath(cursor), ...suffix);\n}\n\nexport function safeArtifactRelative(artifactPath: string, prefix: string, owner: string): string[] {\n if (!artifactPath.startsWith(prefix)) {\n throw new OpenSpecCommandError(\n \"invalid-option\",\n `${owner} contains an artifact outside its own directory: ${artifactPath}.`,\n );\n }\n const segments = artifactPath.slice(prefix.length).split(\"/\");\n if (segments.length === 0 || segments.some((segment) => segment === \"\" || segment === \".\" || segment === \"..\")) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec artifact has an unsafe relative path: ${artifactPath}.`);\n }\n return segments;\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/commands/migrate-openspec/openspec/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAY;IACnD,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,MAAM;YAAE,MAAM;QAC7B,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACjC,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,YAAoB,EAAE,MAAc,EAAE,KAAa;IACtF,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,oBAAoB,CAC5B,gBAAgB,EAChB,GAAG,KAAK,oDAAoD,YAAY,GAAG,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/G,MAAM,IAAI,oBAAoB,CAAC,gBAAgB,EAAE,kDAAkD,YAAY,GAAG,CAAC,CAAC;IACtH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The path arithmetic every write decision in this command rests on.\n *\n * Three rules, kept together because they answer one question in two\n * directions: is a path inside a tree it must stay out of (kernel's\n * `isPathInside`, which this module used to restate as `contains`), what\n * tree is it really in once symlinks are resolved (`canonicalForCreate`), and\n * does a path claimed by the OpenSpec source stay inside the directory it was\n * listed under (`safeArtifactRelative`). `materialize/stage.ts` builds the\n * staging refusals out of the first two; `openspec/decisions.ts` uses them to\n * keep a mapping skeleton out of the workspace it describes.\n */\nimport { existsSync } from \"node:fs\";\nimport { realpath } from \"node:fs/promises\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { OpenSpecCommandError } from \"./error.js\";\n\n/** Resolve symlinks in the nearest existing ancestor of a not-yet-created path. */\nexport async function canonicalForCreate(path: string): Promise<string> {\n let cursor = path;\n const suffix: string[] = [];\n while (!existsSync(cursor)) {\n const parent = dirname(cursor);\n if (parent === cursor) break;\n suffix.unshift(basename(cursor));\n cursor = parent;\n }\n return resolve(await realpath(cursor), ...suffix);\n}\n\nexport function safeArtifactRelative(artifactPath: string, prefix: string, owner: string): string[] {\n if (!artifactPath.startsWith(prefix)) {\n throw new OpenSpecCommandError(\n \"invalid-option\",\n `${owner} contains an artifact outside its own directory: ${artifactPath}.`,\n );\n }\n const segments = artifactPath.slice(prefix.length).split(\"/\");\n if (segments.length === 0 || segments.some((segment) => segment === \"\" || segment === \".\" || segment === \"..\")) {\n throw new OpenSpecCommandError(\"invalid-option\", `OpenSpec artifact has an unsafe relative path: ${artifactPath}.`);\n }\n return segments;\n}\n"]}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The capability delta `loam new --capability` scaffolds, and the id shape it
3
+ * offers.
4
+ *
5
+ * Split out of `./templates.ts` along the document-kind seam: the other five
6
+ * templates scaffold a feature's own files and one service's deltas, while this
7
+ * one is a delta against a fleet-level business document with its own altitude
8
+ * rules and its own id grammar (`REQUIREMENT_ID_RE`, which nothing else here
9
+ * reads). The sentinels stay single-sourced in `core/coherence/authoring/
10
+ * sentinels.ts`, so the move changes no string the placeholder gate matches on.
11
+ */
12
+ import { REQUIREMENT_ID_RE } from "../../core/document/spec.js";
13
+ import { CAPABILITY_REQUIREMENT_SENTINEL, CAPABILITY_SHALL_SENTINEL, GIVEN_SENTINEL, SCENARIO_SENTINEL, THEN_SENTINEL, WHEN_SENTINEL, } from "../../core/coherence/authoring/sentinels.js";
14
+ /**
15
+ * The BUSINESS axis, scaffolded by `--capability`: a delta against the living
16
+ * `capabilities/<id>/spec.md`.
17
+ *
18
+ * Same idiom as the two spec templates in `./templates.ts`, for the same reason — the
19
+ * example lives INSIDE an HTML comment, indented past the line-anchored
20
+ * `## ADDED Requirements` / `### Requirement:` patterns core/document/parse.ts
21
+ * matches on — so the scaffold declares nothing until a person copies the block
22
+ * out. That is what lets a freshly scaffolded feature validate clean while
23
+ * still being refused by `loam archive` the moment the block is copied out and
24
+ * left unedited (`scaffold.placeholder`, which reads
25
+ * `features/<FEAT>/capabilities/` as well as `features/<FEAT>/specs/`).
26
+ *
27
+ * WHAT THE COMMENT TEACHES IS THE ALTITUDE, because that is the mistake this
28
+ * document invites. A capability requirement is a promise a customer could
29
+ * check; the four service-scoped lines (`Operations:`, `Covers:`, `Publishes:`,
30
+ * `Consumes:`) and the axis's own two (`Capability:`, `Realizes:`) are all
31
+ * ERRORS here — `capability.requirement-service-scoped` and
32
+ * `capability.requirement-inert-join` — and every one of them parses, so an
33
+ * author who is not told writes one and finds out at archive. `Requirement-ID:`
34
+ * is spelled as mandatory for the same reason: a capability document outlives
35
+ * every service that realizes it, so identity by heading is refused
36
+ * (`capability.requirement-unidentified`).
37
+ *
38
+ * No nested HTML comments, exactly as `specTemplate` (`./templates.ts`) states: an inner `-->`
39
+ * would end the outer comment early and re-expose the example to the parser.
40
+ */
41
+ export function capabilityDeltaTemplate(featureId, capability) {
42
+ return `# ${capability} — capability delta for ${featureId}
43
+
44
+ <!-- The business axis. This is a DELTA against the living
45
+ capabilities/${capability}/spec.md, not the document itself: sections are
46
+ ADDED / MODIFIED / REMOVED, delete the ones you do not need, and a MODIFIED
47
+ requirement carries its full new text rather than a diff. If the fleet has
48
+ no capabilities/${capability}/spec.md yet, this feature's archive creates it.
49
+
50
+ WHAT BELONGS HERE is a promise somebody outside the fleet could check —
51
+ 'a refund reaches the customer's card within five days'. What does NOT is
52
+ the mechanism: \`Operations:\`, \`Covers:\`, \`Publishes:\` and \`Consumes:\`
53
+ all resolve against ONE service's own contract or model, so a requirement
54
+ carrying one is a service requirement filed at the wrong altitude and
55
+ \`loam validate\` says so. \`Capability:\` and \`Realizes:\` are refused here
56
+ too — they are the joins written on the SERVICE requirement that keeps this
57
+ promise, one directory over in specs/<svc>/spec.md, as
58
+ \`Realizes: ${capability}#<Requirement-ID>\`.
59
+
60
+ \`Requirement-ID:\` is mandatory on every requirement below. A capability
61
+ document outlives the services that realize it, so its requirements are
62
+ addressed by a stable id rather than by their heading — rewording a heading
63
+ would otherwise be a removal and an addition, and every \`Realizes:\` line
64
+ pointed at it would break in silence.
65
+
66
+ A MODIFIED or REMOVED requirement also needs a \`Based-On:\` pin quoting the
67
+ living text it rewrites, so two features touching one capability collide
68
+ loudly instead of overwriting each other. Run \`loam rebase ${featureId}\`
69
+ and the pins are written for you.
70
+
71
+ Copy the block below out of this comment, unindent it, and replace the TODO
72
+ and every <angle-bracket> fill-in — \`loam archive\` refuses the scaffold's
73
+ own wording (\`scaffold.placeholder\`):
74
+
75
+ ## ADDED Requirements
76
+
77
+ ### Requirement: ${CAPABILITY_REQUIREMENT_SENTINEL}
78
+ Requirement-ID: ${idHint(capability)}
79
+
80
+ The fleet SHALL ${CAPABILITY_SHALL_SENTINEL}.
81
+
82
+ #### Scenario: ${SCENARIO_SENTINEL}
83
+ - **Given** ${GIVEN_SENTINEL}
84
+ - **When** ${WHEN_SENTINEL}
85
+ - **Then** ${THEN_SENTINEL}
86
+ -->
87
+ `;
88
+ }
89
+ /**
90
+ * A plausible `Requirement-ID:` for a capability, offered as a shape rather than
91
+ * a name: `payments/refunds` -> `PAYMENTS-REFUNDS-1`.
92
+ *
93
+ * Deliberately NOT the feature id. A service requirement's scaffolded id is
94
+ * `<FEAT>.<svc>.requirement` because that requirement is created by, and lives
95
+ * and dies with, one feature; a capability requirement outlives every feature
96
+ * that touches it, so an id carrying the id of the feature that happened to
97
+ * introduce it is exactly the identity-by-accident this axis refuses. The
98
+ * suffix is `-1` and not a count of anything: it is a placeholder digit inside
99
+ * a placeholder id, and the author is being asked to name the promise.
100
+ *
101
+ * CHECKED AGAINST THE REAL GRAMMAR, never assumed to satisfy it. A capability id
102
+ * may begin with a digit (`3ds`, `2fa`, `1099-filing` — `dirNameHazard` allows an
103
+ * alphanumeric head) while `REQUIREMENT_ID_RE` demands a LETTER, so the obvious
104
+ * slug hands the author `3DS-1` and `loam validate` then refuses it with
105
+ * `delta.requirement-id-invalid` — the author refused for using the shape the
106
+ * scaffold offered. Length is the same class of failure at 128 characters. The
107
+ * fallback prefixes rather than truncates, because a truncated id is a plausible
108
+ * name that means something else.
109
+ */
110
+ function idHint(capability) {
111
+ const slug = capability.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
112
+ const candidate = `${slug}-1`;
113
+ if (REQUIREMENT_ID_RE.test(candidate))
114
+ return candidate;
115
+ const prefixed = `CAP-${candidate}`;
116
+ return REQUIREMENT_ID_RE.test(prefixed) ? prefixed : "CAP-1";
117
+ }
118
+ //# sourceMappingURL=capability-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-template.js","sourceRoot":"","sources":["../../../src/commands/new/capability-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,6CAA6C,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAiB,EAAE,UAAkB;IAC3E,OAAO,KAAK,UAAU,2BAA2B,SAAS;;;oBAGxC,UAAU;;;uBAGP,UAAU;;;;;;;;;;mBAUd,UAAU;;;;;;;;;;mEAUsC,SAAS;;;;;;;;;uBASrD,+BAA+B;sBAChC,MAAM,CAAC,UAAU,CAAC;;sBAElB,yBAAyB;;qBAE1B,iBAAiB;kBACpB,cAAc;iBACf,aAAa;iBACb,aAAa;;CAE7B,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,MAAM,CAAC,UAAkB;IAChC,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC1F,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,CAAC;IAC9B,IAAI,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO,SAAS,EAAE,CAAC;IACpC,OAAO,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * The capability delta `loam new --capability` scaffolds, and the id shape it\n * offers.\n *\n * Split out of `./templates.ts` along the document-kind seam: the other five\n * templates scaffold a feature's own files and one service's deltas, while this\n * one is a delta against a fleet-level business document with its own altitude\n * rules and its own id grammar (`REQUIREMENT_ID_RE`, which nothing else here\n * reads). The sentinels stay single-sourced in `core/coherence/authoring/\n * sentinels.ts`, so the move changes no string the placeholder gate matches on.\n */\nimport { REQUIREMENT_ID_RE } from \"../../core/document/spec.js\";\nimport {\n CAPABILITY_REQUIREMENT_SENTINEL,\n CAPABILITY_SHALL_SENTINEL,\n GIVEN_SENTINEL,\n SCENARIO_SENTINEL,\n THEN_SENTINEL,\n WHEN_SENTINEL,\n} from \"../../core/coherence/authoring/sentinels.js\";\n\n/**\n * The BUSINESS axis, scaffolded by `--capability`: a delta against the living\n * `capabilities/<id>/spec.md`.\n *\n * Same idiom as the two spec templates in `./templates.ts`, for the same reason — the\n * example lives INSIDE an HTML comment, indented past the line-anchored\n * `## ADDED Requirements` / `### Requirement:` patterns core/document/parse.ts\n * matches on — so the scaffold declares nothing until a person copies the block\n * out. That is what lets a freshly scaffolded feature validate clean while\n * still being refused by `loam archive` the moment the block is copied out and\n * left unedited (`scaffold.placeholder`, which reads\n * `features/<FEAT>/capabilities/` as well as `features/<FEAT>/specs/`).\n *\n * WHAT THE COMMENT TEACHES IS THE ALTITUDE, because that is the mistake this\n * document invites. A capability requirement is a promise a customer could\n * check; the four service-scoped lines (`Operations:`, `Covers:`, `Publishes:`,\n * `Consumes:`) and the axis's own two (`Capability:`, `Realizes:`) are all\n * ERRORS here — `capability.requirement-service-scoped` and\n * `capability.requirement-inert-join` — and every one of them parses, so an\n * author who is not told writes one and finds out at archive. `Requirement-ID:`\n * is spelled as mandatory for the same reason: a capability document outlives\n * every service that realizes it, so identity by heading is refused\n * (`capability.requirement-unidentified`).\n *\n * No nested HTML comments, exactly as `specTemplate` (`./templates.ts`) states: an inner `-->`\n * would end the outer comment early and re-expose the example to the parser.\n */\nexport function capabilityDeltaTemplate(featureId: string, capability: string): string {\n return `# ${capability} — capability delta for ${featureId}\n\n<!-- The business axis. This is a DELTA against the living\n capabilities/${capability}/spec.md, not the document itself: sections are\n ADDED / MODIFIED / REMOVED, delete the ones you do not need, and a MODIFIED\n requirement carries its full new text rather than a diff. If the fleet has\n no capabilities/${capability}/spec.md yet, this feature's archive creates it.\n\n WHAT BELONGS HERE is a promise somebody outside the fleet could check —\n 'a refund reaches the customer's card within five days'. What does NOT is\n the mechanism: \\`Operations:\\`, \\`Covers:\\`, \\`Publishes:\\` and \\`Consumes:\\`\n all resolve against ONE service's own contract or model, so a requirement\n carrying one is a service requirement filed at the wrong altitude and\n \\`loam validate\\` says so. \\`Capability:\\` and \\`Realizes:\\` are refused here\n too — they are the joins written on the SERVICE requirement that keeps this\n promise, one directory over in specs/<svc>/spec.md, as\n \\`Realizes: ${capability}#<Requirement-ID>\\`.\n\n \\`Requirement-ID:\\` is mandatory on every requirement below. A capability\n document outlives the services that realize it, so its requirements are\n addressed by a stable id rather than by their heading — rewording a heading\n would otherwise be a removal and an addition, and every \\`Realizes:\\` line\n pointed at it would break in silence.\n\n A MODIFIED or REMOVED requirement also needs a \\`Based-On:\\` pin quoting the\n living text it rewrites, so two features touching one capability collide\n loudly instead of overwriting each other. Run \\`loam rebase ${featureId}\\`\n and the pins are written for you.\n\n Copy the block below out of this comment, unindent it, and replace the TODO\n and every <angle-bracket> fill-in — \\`loam archive\\` refuses the scaffold's\n own wording (\\`scaffold.placeholder\\`):\n\n ## ADDED Requirements\n\n ### Requirement: ${CAPABILITY_REQUIREMENT_SENTINEL}\n Requirement-ID: ${idHint(capability)}\n\n The fleet SHALL ${CAPABILITY_SHALL_SENTINEL}.\n\n #### Scenario: ${SCENARIO_SENTINEL}\n - **Given** ${GIVEN_SENTINEL}\n - **When** ${WHEN_SENTINEL}\n - **Then** ${THEN_SENTINEL}\n-->\n`;\n}\n\n/**\n * A plausible `Requirement-ID:` for a capability, offered as a shape rather than\n * a name: `payments/refunds` -> `PAYMENTS-REFUNDS-1`.\n *\n * Deliberately NOT the feature id. A service requirement's scaffolded id is\n * `<FEAT>.<svc>.requirement` because that requirement is created by, and lives\n * and dies with, one feature; a capability requirement outlives every feature\n * that touches it, so an id carrying the id of the feature that happened to\n * introduce it is exactly the identity-by-accident this axis refuses. The\n * suffix is `-1` and not a count of anything: it is a placeholder digit inside\n * a placeholder id, and the author is being asked to name the promise.\n *\n * CHECKED AGAINST THE REAL GRAMMAR, never assumed to satisfy it. A capability id\n * may begin with a digit (`3ds`, `2fa`, `1099-filing` — `dirNameHazard` allows an\n * alphanumeric head) while `REQUIREMENT_ID_RE` demands a LETTER, so the obvious\n * slug hands the author `3DS-1` and `loam validate` then refuses it with\n * `delta.requirement-id-invalid` — the author refused for using the shape the\n * scaffold offered. Length is the same class of failure at 128 characters. The\n * fallback prefixes rather than truncates, because a truncated id is a plausible\n * name that means something else.\n */\nfunction idHint(capability: string): string {\n const slug = capability.toUpperCase().replace(/[^A-Z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n const candidate = `${slug}-1`;\n if (REQUIREMENT_ID_RE.test(candidate)) return candidate;\n const prefixed = `CAP-${candidate}`;\n return REQUIREMENT_ID_RE.test(prefixed) ? prefixed : \"CAP-1\";\n}\n"]}
@@ -11,34 +11,20 @@ import {} from "../../core/kernel/ids/dirs.js";
11
11
  import { resolveFeature } from "../../core/repo/repo.js";
12
12
  import { DocsRepoUnavailableError } from "../../core/repo/state.js";
13
13
  import { stageWrites } from "../../core/staging/commit.js";
14
- import { InterruptedCommitError } from "../../core/staging/interrupted.js";
15
- import { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from "../../core/staging/lock.js";
16
- import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
14
+ import {} from "../../core/staging/interrupted.js";
15
+ import { LOCK_WAIT_MS } from "../../core/staging/lock.js";
16
+ import { openCommitWindow } from "../../core/staging/recovery/recover.js";
17
17
  import { commitStaged } from "../../core/staging/txn/transaction.js";
18
18
  import {} from "../../core/staging/writes.js";
19
19
  export async function commitScaffold(docsDir, featureId, writes) {
20
20
  // The commit window holds the docs lock: two `new`s for one id serialise
21
21
  // into a stable already-exists, and a predecessor's interrupted commit is
22
22
  // rolled forward (or refused) before this one stages anything.
23
- let releaseLock;
23
+ const opened = await openCommitWindow(docsDir, LOCK_WAIT_MS);
24
+ if (!opened.ok)
25
+ return opened;
26
+ const { recovered } = opened;
24
27
  try {
25
- releaseLock = await acquireDocsLockWaiting(docsDir, LOCK_WAIT_MS);
26
- }
27
- catch (err) {
28
- if (err instanceof DocsBusyError)
29
- return { ok: false, code: "docs-busy", message: err.message };
30
- throw err;
31
- }
32
- try {
33
- let recovered;
34
- try {
35
- recovered = await recoverInterruptedCommit(docsDir);
36
- }
37
- catch (err) {
38
- if (!(err instanceof InterruptedCommitError))
39
- throw err;
40
- return { ok: false, code: "commit-interrupted", message: err.message };
41
- }
42
28
  // The one existence check, UNDER the lock and AFTER recovery: two racing
43
29
  // runs serialise on it, and a recovered half-scaffold has been completed
44
30
  // by the recovery above, so this refusal is truthful.
@@ -70,7 +56,7 @@ export async function commitScaffold(docsDir, featureId, writes) {
70
56
  return { ok: true, recovered };
71
57
  }
72
58
  finally {
73
- await releaseLock();
59
+ await opened.release();
74
60
  }
75
61
  }
76
62
  //# sourceMappingURL=commit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/new/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The scaffold's commit window: lock, recover, the one existence check, the\n * exclusive-create transaction. Split from new.ts on the same seam vouch's\n * commitStamp and gherkin's commitEmission sit on — everything in here holds\n * the docs lock, nothing outside it does — and, as there, the split keeps the\n * window's extent visible in the code shape. This module prints nothing; the\n * caller renders every arm.\n */\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { type DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { resolveFeature } from \"../../core/repo/repo.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { 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\";\n\nexport type ScaffoldCommit =\n | { ok: true; recovered: CommitRecovery | null }\n | {\n ok: false;\n code: \"docs-busy\" | \"commit-interrupted\" | \"merge-failed\" | \"rollback-incomplete\";\n message: string;\n }\n /** The feature exists — found by the under-lock check, or by losing the create race. */\n | { ok: false; code: \"already-exists\"; existing: FeatureEntry | null; recovered: CommitRecovery | null }\n /**\n * The docs repo went away between the command's gate and the locked read.\n * Deliberately NOT a `code:` arm: the caller renders it through the shared\n * docs-repo reporter, and the codes-drift collector reads every `code:`\n * literal as a stable emission — which this discriminant is not.\n */\n | { ok: false; repoGone: DocsRepoUnavailableError };\n\nexport async function commitScaffold(\n docsDir: DocsDir,\n featureId: string,\n writes: PlannedWrite[],\n): Promise<ScaffoldCommit> {\n // The commit window holds the docs lock: two `new`s for one id serialise\n // into a stable already-exists, and a predecessor's interrupted commit is\n // rolled forward (or refused) before this one stages anything.\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLockWaiting(docsDir, 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 let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n return { ok: false, code: \"commit-interrupted\", message: err.message };\n }\n // The one existence check, UNDER the lock and AFTER recovery: two racing\n // runs serialise on it, and a recovered half-scaffold has been completed\n // by the recovery above, so this refusal is truthful.\n let existing: FeatureEntry | null;\n try {\n existing = await resolveFeature(docsDir, featureId, \"include\");\n } catch (err) {\n // The command's gate already refused both broken states, so reaching\n // here means the docs repo went away between that check and this read.\n // Same breach, same two codes — the alternative is a stack trace under\n // `internal` that names neither.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return { ok: false, repoGone: err };\n }\n if (existing) return { ok: false, code: \"already-exists\", existing, recovered };\n const staged = await stageWrites(writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"new\", rerun: `loam new ${featureId}`, target: featureId },\n staged,\n \"scaffolded\",\n );\n if (!committed.ok) {\n // A lost exclusive-create race IS the feature existing: the other `new`\n // won, and this refusal is the same stable answer the check above gives\n // when it sees the winner first.\n return committed.raced\n ? { ok: false, code: \"already-exists\", existing: null, recovered }\n : { ok: false, code: committed.code, message: committed.message };\n }\n return { ok: true, recovered };\n } finally {\n await releaseLock();\n }\n}\n"]}
1
+ {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/new/commit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAqB,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAgB,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAuB,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAqB,MAAM,8BAA8B,CAAC;AAmBjE,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAgB,EAChB,SAAiB,EACjB,MAAsB;IAEtB,yEAAyE;IACzE,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAC7B,IAAI,CAAC;QACH,yEAAyE;QACzE,yEAAyE;QACzE,sDAAsD;QACtD,IAAI,QAA6B,CAAC;QAClC,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,qEAAqE;YACrE,uEAAuE;YACvE,uEAAuE;YACvE,iCAAiC;YACjC,IAAI,CAAC,CAAC,GAAG,YAAY,wBAAwB,CAAC;gBAAE,MAAM,GAAG,CAAC;YAC1D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,QAAQ;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QAChF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,MAAM,YAAY,CAClC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,SAAS,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EACpF,MAAM,EACN,YAAY,CACb,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;YAClB,wEAAwE;YACxE,wEAAwE;YACxE,iCAAiC;YACjC,OAAO,SAAS,CAAC,KAAK;gBACpB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE;gBAClE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC;QACtE,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACjC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;AACH,CAAC","sourcesContent":["/**\n * The scaffold's commit window: lock, recover, the one existence check, the\n * exclusive-create transaction. Split from new.ts on the same seam vouch's\n * commitStamp and gherkin's commitEmission sit on — everything in here holds\n * the docs lock, nothing outside it does — and, as there, the split keeps the\n * window's extent visible in the code shape. This module prints nothing; the\n * caller renders every arm.\n */\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { type DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { resolveFeature } from \"../../core/repo/repo.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { 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\";\n\nexport type ScaffoldCommit =\n | { ok: true; recovered: CommitRecovery | null }\n | {\n ok: false;\n code: \"docs-busy\" | \"commit-interrupted\" | \"merge-failed\" | \"rollback-incomplete\";\n message: string;\n }\n /** The feature exists — found by the under-lock check, or by losing the create race. */\n | { ok: false; code: \"already-exists\"; existing: FeatureEntry | null; recovered: CommitRecovery | null }\n /**\n * The docs repo went away between the command's gate and the locked read.\n * Deliberately NOT a `code:` arm: the caller renders it through the shared\n * docs-repo reporter, and the codes-drift collector reads every `code:`\n * literal as a stable emission — which this discriminant is not.\n */\n | { ok: false; repoGone: DocsRepoUnavailableError };\n\nexport async function commitScaffold(\n docsDir: DocsDir,\n featureId: string,\n writes: PlannedWrite[],\n): Promise<ScaffoldCommit> {\n // The commit window holds the docs lock: two `new`s for one id serialise\n // into a stable already-exists, and a predecessor's interrupted commit is\n // rolled forward (or refused) before this one stages anything.\n const opened = await openCommitWindow(docsDir, LOCK_WAIT_MS);\n if (!opened.ok) return opened;\n const { recovered } = opened;\n try {\n // The one existence check, UNDER the lock and AFTER recovery: two racing\n // runs serialise on it, and a recovered half-scaffold has been completed\n // by the recovery above, so this refusal is truthful.\n let existing: FeatureEntry | null;\n try {\n existing = await resolveFeature(docsDir, featureId, \"include\");\n } catch (err) {\n // The command's gate already refused both broken states, so reaching\n // here means the docs repo went away between that check and this read.\n // Same breach, same two codes — the alternative is a stack trace under\n // `internal` that names neither.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return { ok: false, repoGone: err };\n }\n if (existing) return { ok: false, code: \"already-exists\", existing, recovered };\n const staged = await stageWrites(writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"new\", rerun: `loam new ${featureId}`, target: featureId },\n staged,\n \"scaffolded\",\n );\n if (!committed.ok) {\n // A lost exclusive-create race IS the feature existing: the other `new`\n // won, and this refusal is the same stable answer the check above gives\n // when it sees the winner first.\n return committed.raced\n ? { ok: false, code: \"already-exists\", existing: null, recovered }\n : { ok: false, code: committed.code, message: committed.message };\n }\n return { ok: true, recovered };\n } finally {\n await opened.release();\n }\n}\n"]}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * `loam new --modifies <Req-ID>`: the small-change lane's one hand step,
3
+ * done by the scaffold.
4
+ *
5
+ * A change that rewrites a requirement the fleet already has used to cost a
6
+ * hand copy and a second command: the scaffold offered only the ADDED example
7
+ * inside its comment, so the author copied the living requirement into a
8
+ * `## MODIFIED Requirements` section by hand — most of a small delta is that
9
+ * copy — and then ran `loam rebase` to pin it. This module does both at
10
+ * scaffold time: it finds each named requirement in the living specs of the
11
+ * services the feature touches and writes it, UNCOMMENTED, under a MODIFIED
12
+ * heading with its `Based-On:` pin already set.
13
+ *
14
+ * THE PIN IS REBASE'S PIN, not a second rule for one. `requirementDigest` of
15
+ * the living requirement, selected by its `Requirement-ID:` exactly as
16
+ * `selectLiving` (commands/rebase/plan.ts) selects one, and written where
17
+ * `serializeRequirements` puts it — under the id line, where `pinEdit` inserts
18
+ * it too. So a scaffold followed by `loam rebase` reports every pin
19
+ * `unchanged`, and a living requirement that moves between this scaffold and
20
+ * the archive is `delta.baseline-stale`, exactly as if the author had copied
21
+ * and pinned it by hand at this moment. The gate is the same gate; only the
22
+ * typing went away.
23
+ *
24
+ * WHY THE BLOCK SHIPS LIVE, when every other template example ships inside a
25
+ * comment. Those examples are placeholder text nobody wrote, and a live one
26
+ * once archived a literal TODO into the living spec — so they declare nothing
27
+ * until a person copies them out. This block is the opposite: it is the
28
+ * fleet's own current text, and the one honest state for it to be in is the
29
+ * state archive would merge. Left unedited it merges as no change to the
30
+ * living wording, which is why the comment above it says to edit in place.
31
+ *
32
+ * ADDRESSED BY `Requirement-ID:` ONLY. A requirement without that line is
33
+ * addressed by its heading everywhere else, but a heading is free text with
34
+ * spaces in it and no grammar a flag value can be checked against; offering it
35
+ * here would teach identity-by-heading on the one path built to be typed.
36
+ */
37
+ import { existsSync } from "node:fs";
38
+ import { join } from "node:path";
39
+ import { repoPath } from "../../core/envelope/json.js";
40
+ import { NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
41
+ import { parseRequirements, readRequirementsDocument } from "../../core/document/parse.js";
42
+ import { REQUIREMENT_ID_RE, requirementDigest, serializeRequirements, withoutBaseline, } from "../../core/document/spec.js";
43
+ import { compareIds, nearestIds } from "../../core/repo/entries.js";
44
+ import { SPEC_AXES, servicePathsAt } from "../../core/repo/paths.js";
45
+ import { enumeratedServices } from "../../core/repo/service-target.js";
46
+ import { archSpecTemplate, specTemplate } from "./templates.js";
47
+ /** Every identified requirement in one service's living spec files, one entry per (axis, requirement). */
48
+ async function livingRequirements(docsDir, entry) {
49
+ const paths = servicePathsAt(entry.dir);
50
+ const found = [];
51
+ for (const axis of SPEC_AXES) {
52
+ const path = paths[axis.key];
53
+ if (!existsSync(path))
54
+ continue;
55
+ for (const r of parseRequirements(await readRequirementsDocument(path))) {
56
+ if (r.id === undefined)
57
+ continue;
58
+ // The pin is taken over the requirement WITHOUT any baseline it carries —
59
+ // a living document should hold none (archive strips them), and one that
60
+ // does must not end up with two `Based-On:` lines in the scaffold.
61
+ const living = withoutBaseline(r);
62
+ found.push({
63
+ id: r.id,
64
+ service: entry.id,
65
+ axis,
66
+ living: repoPath(docsDir, path),
67
+ requirement: { ...living, basedOn: requirementDigest(living) },
68
+ });
69
+ }
70
+ }
71
+ return found;
72
+ }
73
+ /**
74
+ * Which services OUTSIDE the touched set carry the id — read only on the
75
+ * refusal path, where a fleet-wide walk is paid once by a run that is about to
76
+ * stop anyway. The commonest way to miss is to name the right requirement and
77
+ * the wrong (or no) `--touches`, and the useful refusal names the service.
78
+ * An undecodable document there is skipped: this is a hint, not a grade.
79
+ */
80
+ async function carriersElsewhere(docsDir, id, skip) {
81
+ const carriers = [];
82
+ for (const entry of await enumeratedServices(docsDir)) {
83
+ if (skip.has(entry.id))
84
+ continue;
85
+ try {
86
+ if ((await livingRequirements(docsDir, entry)).some((t) => t.id === id))
87
+ carriers.push(entry.id);
88
+ }
89
+ catch (err) {
90
+ if (!(err instanceof NotUtf8DocumentError))
91
+ throw err;
92
+ }
93
+ }
94
+ return carriers.sort(compareIds);
95
+ }
96
+ function unknownMessage(id, touched, known) {
97
+ const where = touched.length === 0
98
+ ? "this feature touches no service — name the one that owns it with --touches"
99
+ : `it names no requirement in the living specs of ${touched.map((s) => `'${s}'`).join(", ")}`;
100
+ const near = nearestIds(id, [...known]);
101
+ return (`--modifies '${id}': ${where}.` +
102
+ (near.length === 0 ? "" : ` Did you mean ${near.map((n) => `'${n}'`).join(" or ")}?`) +
103
+ " A requirement is addressed by its `Requirement-ID:` line; one without that line cannot be named here.");
104
+ }
105
+ /**
106
+ * Resolve every `--modifies` id against the living specs of the touched
107
+ * services, before anything is written. In flag order, each id once.
108
+ */
109
+ export async function planModifies(docsDir, touched, ids) {
110
+ for (const id of ids) {
111
+ if (!REQUIREMENT_ID_RE.test(id)) {
112
+ return {
113
+ ok: false,
114
+ code: "invalid-option",
115
+ message: `--modifies '${id}' is not a Requirement-ID — it takes the value of a living requirement's \`Requirement-ID:\` line, not its heading.`,
116
+ };
117
+ }
118
+ }
119
+ const scope = new Set(touched);
120
+ let living = [];
121
+ try {
122
+ for (const entry of await enumeratedServices(docsDir)) {
123
+ if (scope.has(entry.id))
124
+ living = living.concat(await livingRequirements(docsDir, entry));
125
+ }
126
+ }
127
+ catch (err) {
128
+ if (!(err instanceof NotUtf8DocumentError))
129
+ throw err;
130
+ // A pin is a claim about text somebody read, and over a document loam could
131
+ // not decode it would be a digest of mojibake — `loam rebase` refuses the
132
+ // same document under the same code, and so does this, before any write.
133
+ return { ok: false, code: "merge-failed", message: `--modifies cannot copy from a living spec loam cannot read — nothing was scaffolded: ${err.message}` };
134
+ }
135
+ const targets = [];
136
+ for (const id of new Set(ids)) {
137
+ const hits = living.filter((t) => t.id === id);
138
+ if (hits.length === 1) {
139
+ targets.push(hits[0]);
140
+ continue;
141
+ }
142
+ if (hits.length > 1) {
143
+ // A Requirement-ID is unique inside ONE document, so two touched
144
+ // documents may both carry it (and a document that repeats one is its
145
+ // own finding, `delta.living-duplicate-requirement`). Picking one would
146
+ // be loam guessing which requirement the author means to rewrite.
147
+ return {
148
+ ok: false,
149
+ code: "invalid-option",
150
+ message: `--modifies '${id}' is a Requirement-ID in more than one living document this feature touches ` +
151
+ `(${[...new Set(hits.map((t) => t.living))].join(", ")}), and an id is unique only inside its own document. ` +
152
+ "Touch only the service whose requirement changes, and write the other service's delta by hand.",
153
+ };
154
+ }
155
+ const elsewhere = await carriersElsewhere(docsDir, id, scope);
156
+ const known = [...new Set(living.map((t) => t.id))];
157
+ return {
158
+ ok: false,
159
+ code: "unknown-target",
160
+ message: elsewhere.length > 0
161
+ ? `--modifies '${id}' is a requirement of ${elsewhere.map((s) => `'${s}'`).join(", ")}, which this feature does not touch — add --touches ${elsewhere[0]}.`
162
+ : unknownMessage(id, touched, known),
163
+ };
164
+ }
165
+ return { ok: true, targets };
166
+ }
167
+ /**
168
+ * The MODIFIED section one delta file carries, appended after the template's
169
+ * own comment. The guidance sits ABOVE the H2 on purpose: below it, before the
170
+ * first `### Requirement:`, it would be section prose of the very section the
171
+ * merge reads, and no comment there is worth that question.
172
+ */
173
+ export function modifiedSection(featureId, targets) {
174
+ return `
175
+ <!-- Each requirement below is the LIVING text, copied by \`loam new\` for an id you
176
+ named and pinned (Based-On:) to the version you are rewriting — the pin
177
+ \`loam rebase ${featureId}\` would write, so there is nothing to rebase unless the
178
+ living text moves first.
179
+ Edit it in place: a MODIFIED carries its full new text, and archive replaces the
180
+ living requirement with exactly what is here — left as copied, it changes nothing.
181
+ Keep the Requirement-ID: and Based-On: lines as they are. -->
182
+
183
+ ## MODIFIED Requirements
184
+
185
+ ${serializeRequirements(targets.map((t) => t.requirement))}`;
186
+ }
187
+ /**
188
+ * The delta files `--modifies` writes or extends, keyed as `new` keys its
189
+ * plan (relative to the feature directory). A business-axis target lands in
190
+ * the `spec.md` the touched service already gets; an architecture-axis target
191
+ * gets the `arch.spec.md` template a touched service is otherwise not handed,
192
+ * because the requirement it rewrites lives there.
193
+ */
194
+ export function modifiedFiles(featureId, targets, planned) {
195
+ const out = {};
196
+ for (const t of targets) {
197
+ const rel = join("specs", t.service, t.axis.file);
198
+ if (rel in out)
199
+ continue;
200
+ const mine = targets.filter((other) => other.service === t.service && other.axis.key === t.axis.key);
201
+ const base = planned[rel] ?? (t.axis.key === "spec" ? specTemplate(featureId, t.service) : archSpecTemplate(featureId, t.service));
202
+ out[rel] = base + modifiedSection(featureId, mine);
203
+ }
204
+ return out;
205
+ }
206
+ //# sourceMappingURL=modifies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modifies.js","sourceRoot":"","sources":["../../../src/commands/new/modifies.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam new --modifies <Req-ID>`: the small-change lane's one hand step,\n * done by the scaffold.\n *\n * A change that rewrites a requirement the fleet already has used to cost a\n * hand copy and a second command: the scaffold offered only the ADDED example\n * inside its comment, so the author copied the living requirement into a\n * `## MODIFIED Requirements` section by hand — most of a small delta is that\n * copy — and then ran `loam rebase` to pin it. This module does both at\n * scaffold time: it finds each named requirement in the living specs of the\n * services the feature touches and writes it, UNCOMMENTED, under a MODIFIED\n * heading with its `Based-On:` pin already set.\n *\n * THE PIN IS REBASE'S PIN, not a second rule for one. `requirementDigest` of\n * the living requirement, selected by its `Requirement-ID:` exactly as\n * `selectLiving` (commands/rebase/plan.ts) selects one, and written where\n * `serializeRequirements` puts it — under the id line, where `pinEdit` inserts\n * it too. So a scaffold followed by `loam rebase` reports every pin\n * `unchanged`, and a living requirement that moves between this scaffold and\n * the archive is `delta.baseline-stale`, exactly as if the author had copied\n * and pinned it by hand at this moment. The gate is the same gate; only the\n * typing went away.\n *\n * WHY THE BLOCK SHIPS LIVE, when every other template example ships inside a\n * comment. Those examples are placeholder text nobody wrote, and a live one\n * once archived a literal TODO into the living spec — so they declare nothing\n * until a person copies them out. This block is the opposite: it is the\n * fleet's own current text, and the one honest state for it to be in is the\n * state archive would merge. Left unedited it merges as no change to the\n * living wording, which is why the comment above it says to edit in place.\n *\n * ADDRESSED BY `Requirement-ID:` ONLY. A requirement without that line is\n * addressed by its heading everywhere else, but a heading is free text with\n * spaces in it and no grammar a flag value can be checked against; offering it\n * here would teach identity-by-heading on the one path built to be typed.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { repoPath } from \"../../core/envelope/json.js\";\nimport { NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../core/document/parse.js\";\nimport {\n REQUIREMENT_ID_RE,\n requirementDigest,\n serializeRequirements,\n withoutBaseline,\n type Requirement,\n} from \"../../core/document/spec.js\";\nimport { compareIds, nearestIds, type ServiceEntry } from \"../../core/repo/entries.js\";\nimport { SPEC_AXES, servicePathsAt, type SpecAxis } from \"../../core/repo/paths.js\";\nimport { enumeratedServices } from \"../../core/repo/service-target.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { archSpecTemplate, specTemplate } from \"./templates.js\";\n\n/** One living requirement `--modifies` named, where it lives, and the pin it is scaffolded with. */\nexport interface ModifiedTarget {\n readonly id: string;\n readonly service: string;\n readonly axis: SpecAxis;\n /** The living document it was copied from, repo-relative — what a message names. */\n readonly living: string;\n /** The living requirement, its pin set — the block the delta carries. */\n readonly requirement: Requirement;\n}\n\nexport type ModifiesPlan =\n | { readonly ok: true; readonly targets: readonly ModifiedTarget[] }\n | { readonly ok: false; readonly code: \"invalid-option\" | \"unknown-target\" | \"merge-failed\"; readonly message: string };\n\n/** Every identified requirement in one service's living spec files, one entry per (axis, requirement). */\nasync function livingRequirements(docsDir: DocsDir, entry: ServiceEntry): Promise<ModifiedTarget[]> {\n const paths = servicePathsAt(entry.dir);\n const found: ModifiedTarget[] = [];\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n for (const r of parseRequirements(await readRequirementsDocument(path))) {\n if (r.id === undefined) continue;\n // The pin is taken over the requirement WITHOUT any baseline it carries —\n // a living document should hold none (archive strips them), and one that\n // does must not end up with two `Based-On:` lines in the scaffold.\n const living = withoutBaseline(r);\n found.push({\n id: r.id,\n service: entry.id,\n axis,\n living: repoPath(docsDir, path),\n requirement: { ...living, basedOn: requirementDigest(living) },\n });\n }\n }\n return found;\n}\n\n/**\n * Which services OUTSIDE the touched set carry the id — read only on the\n * refusal path, where a fleet-wide walk is paid once by a run that is about to\n * stop anyway. The commonest way to miss is to name the right requirement and\n * the wrong (or no) `--touches`, and the useful refusal names the service.\n * An undecodable document there is skipped: this is a hint, not a grade.\n */\nasync function carriersElsewhere(docsDir: DocsDir, id: string, skip: ReadonlySet<string>): Promise<string[]> {\n const carriers: string[] = [];\n for (const entry of await enumeratedServices(docsDir)) {\n if (skip.has(entry.id)) continue;\n try {\n if ((await livingRequirements(docsDir, entry)).some((t) => t.id === id)) carriers.push(entry.id);\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n }\n }\n return carriers.sort(compareIds);\n}\n\nfunction unknownMessage(id: string, touched: readonly string[], known: readonly string[]): string {\n const where = touched.length === 0\n ? \"this feature touches no service — name the one that owns it with --touches\"\n : `it names no requirement in the living specs of ${touched.map((s) => `'${s}'`).join(\", \")}`;\n const near = nearestIds(id, [...known]);\n return (\n `--modifies '${id}': ${where}.` +\n (near.length === 0 ? \"\" : ` Did you mean ${near.map((n) => `'${n}'`).join(\" or \")}?`) +\n \" A requirement is addressed by its `Requirement-ID:` line; one without that line cannot be named here.\"\n );\n}\n\n/**\n * Resolve every `--modifies` id against the living specs of the touched\n * services, before anything is written. In flag order, each id once.\n */\nexport async function planModifies(\n docsDir: DocsDir,\n touched: readonly string[],\n ids: readonly string[],\n): Promise<ModifiesPlan> {\n for (const id of ids) {\n if (!REQUIREMENT_ID_RE.test(id)) {\n return {\n ok: false,\n code: \"invalid-option\",\n message: `--modifies '${id}' is not a Requirement-ID — it takes the value of a living requirement's \\`Requirement-ID:\\` line, not its heading.`,\n };\n }\n }\n const scope = new Set(touched);\n let living: ModifiedTarget[] = [];\n try {\n for (const entry of await enumeratedServices(docsDir)) {\n if (scope.has(entry.id)) living = living.concat(await livingRequirements(docsDir, entry));\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n // A pin is a claim about text somebody read, and over a document loam could\n // not decode it would be a digest of mojibake — `loam rebase` refuses the\n // same document under the same code, and so does this, before any write.\n return { ok: false, code: \"merge-failed\", message: `--modifies cannot copy from a living spec loam cannot read — nothing was scaffolded: ${err.message}` };\n }\n const targets: ModifiedTarget[] = [];\n for (const id of new Set(ids)) {\n const hits = living.filter((t) => t.id === id);\n if (hits.length === 1) {\n targets.push(hits[0]!);\n continue;\n }\n if (hits.length > 1) {\n // A Requirement-ID is unique inside ONE document, so two touched\n // documents may both carry it (and a document that repeats one is its\n // own finding, `delta.living-duplicate-requirement`). Picking one would\n // be loam guessing which requirement the author means to rewrite.\n return {\n ok: false,\n code: \"invalid-option\",\n message:\n `--modifies '${id}' is a Requirement-ID in more than one living document this feature touches ` +\n `(${[...new Set(hits.map((t) => t.living))].join(\", \")}), and an id is unique only inside its own document. ` +\n \"Touch only the service whose requirement changes, and write the other service's delta by hand.\",\n };\n }\n const elsewhere = await carriersElsewhere(docsDir, id, scope);\n const known = [...new Set(living.map((t) => t.id))];\n return {\n ok: false,\n code: \"unknown-target\",\n message: elsewhere.length > 0\n ? `--modifies '${id}' is a requirement of ${elsewhere.map((s) => `'${s}'`).join(\", \")}, which this feature does not touch — add --touches ${elsewhere[0]}.`\n : unknownMessage(id, touched, known),\n };\n }\n return { ok: true, targets };\n}\n\n/**\n * The MODIFIED section one delta file carries, appended after the template's\n * own comment. The guidance sits ABOVE the H2 on purpose: below it, before the\n * first `### Requirement:`, it would be section prose of the very section the\n * merge reads, and no comment there is worth that question.\n */\nexport function modifiedSection(featureId: string, targets: readonly ModifiedTarget[]): string {\n return `\n<!-- Each requirement below is the LIVING text, copied by \\`loam new\\` for an id you\n named and pinned (Based-On:) to the version you are rewriting — the pin\n \\`loam rebase ${featureId}\\` would write, so there is nothing to rebase unless the\n living text moves first.\n Edit it in place: a MODIFIED carries its full new text, and archive replaces the\n living requirement with exactly what is here — left as copied, it changes nothing.\n Keep the Requirement-ID: and Based-On: lines as they are. -->\n\n## MODIFIED Requirements\n\n${serializeRequirements(targets.map((t) => t.requirement))}`;\n}\n\n/**\n * The delta files `--modifies` writes or extends, keyed as `new` keys its\n * plan (relative to the feature directory). A business-axis target lands in\n * the `spec.md` the touched service already gets; an architecture-axis target\n * gets the `arch.spec.md` template a touched service is otherwise not handed,\n * because the requirement it rewrites lives there.\n */\nexport function modifiedFiles(\n featureId: string,\n targets: readonly ModifiedTarget[],\n planned: Readonly<Record<string, string>>,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const t of targets) {\n const rel = join(\"specs\", t.service, t.axis.file);\n if (rel in out) continue;\n const mine = targets.filter((other) => other.service === t.service && other.axis.key === t.axis.key);\n const base = planned[rel] ?? (t.axis.key === \"spec\" ? specTemplate(featureId, t.service) : archSpecTemplate(featureId, t.service));\n out[rel] = base + modifiedSection(featureId, mine);\n }\n return out;\n}\n"]}