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

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 (1036) hide show
  1. package/CHANGELOG.md +2635 -86
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +599 -0
  8. package/SCHEMA.md +2355 -256
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +323 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +180 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +252 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +72 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +204 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +270 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +251 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +251 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +337 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +48 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +174 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +90 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/deployment.js +158 -0
  398. package/dist/core/c4/parsed/deployment.js.map +1 -0
  399. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  400. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  401. package/dist/core/c4/parsed/specification.js +78 -0
  402. package/dist/core/c4/parsed/specification.js.map +1 -0
  403. package/dist/core/c4/parsed/values.js +57 -0
  404. package/dist/core/c4/parsed/values.js.map +1 -0
  405. package/dist/core/c4/parsed/view-ids.js +75 -0
  406. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  407. package/dist/core/c4/project/architecture.js +45 -0
  408. package/dist/core/c4/project/architecture.js.map +1 -0
  409. package/dist/core/c4/project/documents.js +58 -0
  410. package/dist/core/c4/project/documents.js.map +1 -0
  411. package/dist/core/c4/project/load.js +187 -0
  412. package/dist/core/c4/project/load.js.map +1 -0
  413. package/dist/core/c4/project/staged.js +220 -0
  414. package/dist/core/c4/project/staged.js.map +1 -0
  415. package/dist/core/c4/resolve/service.js +159 -0
  416. package/dist/core/c4/resolve/service.js.map +1 -0
  417. package/dist/core/c4/resolve/steps.js +144 -0
  418. package/dist/core/c4/resolve/steps.js.map +1 -0
  419. package/dist/core/c4/seed/fleet-file.js +201 -0
  420. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  421. package/dist/core/c4/seed/items.js +91 -0
  422. package/dist/core/c4/seed/items.js.map +1 -0
  423. package/dist/core/c4/seed/stamp.js +48 -0
  424. package/dist/core/c4/seed/stamp.js.map +1 -0
  425. package/dist/core/c4/seed/template.js +109 -0
  426. package/dist/core/c4/seed/template.js.map +1 -0
  427. package/dist/core/c4/source-mask.js +82 -0
  428. package/dist/core/c4/source-mask.js.map +1 -0
  429. package/dist/core/c4/source-scan.js +227 -0
  430. package/dist/core/c4/source-scan.js.map +1 -0
  431. package/dist/core/c4/splice/authored-source.js +101 -0
  432. package/dist/core/c4/splice/authored-source.js.map +1 -0
  433. package/dist/core/c4/splice/contract.js +10 -0
  434. package/dist/core/c4/splice/contract.js.map +1 -0
  435. package/dist/core/c4/splice/delta-blocks.js +139 -0
  436. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  437. package/dist/core/c4/splice/identity/edges.js +36 -0
  438. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  439. package/dist/core/c4/splice/landscape-merge.js +269 -0
  440. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  441. package/dist/core/c4/splice/placement.js +194 -0
  442. package/dist/core/c4/splice/placement.js.map +1 -0
  443. package/dist/core/c4/workspace.js +174 -0
  444. package/dist/core/c4/workspace.js.map +1 -0
  445. package/dist/core/capabilities/authoring/notes.js +141 -0
  446. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  447. package/dist/core/capabilities/capabilities.js +124 -0
  448. package/dist/core/capabilities/capabilities.js.map +1 -0
  449. package/dist/core/capabilities/delta/doc.js +50 -0
  450. package/dist/core/capabilities/delta/doc.js.map +1 -0
  451. package/dist/core/capabilities/delta/overlay.js +129 -0
  452. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  453. package/dist/core/capabilities/delta/summary.js +57 -0
  454. package/dist/core/capabilities/delta/summary.js.map +1 -0
  455. package/dist/core/capabilities/delta/tree.js +43 -0
  456. package/dist/core/capabilities/delta/tree.js.map +1 -0
  457. package/dist/core/capabilities/delta/uncovered.js +222 -0
  458. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  459. package/dist/core/capabilities/findings.js +342 -0
  460. package/dist/core/capabilities/findings.js.map +1 -0
  461. package/dist/core/capabilities/realizes/findings.js +181 -0
  462. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  463. package/dist/core/capabilities/realizes/join.js +143 -0
  464. package/dist/core/capabilities/realizes/join.js.map +1 -0
  465. package/dist/core/capabilities/rollup.js +199 -0
  466. package/dist/core/capabilities/rollup.js.map +1 -0
  467. package/dist/core/capabilities/tree.js +126 -0
  468. package/dist/core/capabilities/tree.js.map +1 -0
  469. package/dist/core/capabilities/usecase-join.js +277 -0
  470. package/dist/core/capabilities/usecase-join.js.map +1 -0
  471. package/dist/core/coherence/authoring/scaffold.js +177 -0
  472. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  473. package/dist/core/coherence/authoring/sentinels.js +74 -0
  474. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  475. package/dist/core/coherence/coherence.js +287 -0
  476. package/dist/core/coherence/coherence.js.map +1 -0
  477. package/dist/core/coherence/declared.js +269 -0
  478. package/dist/core/coherence/declared.js.map +1 -0
  479. package/dist/core/coherence/events/declared.js +206 -0
  480. package/dist/core/coherence/events/declared.js.map +1 -0
  481. package/dist/core/coherence/events/events.js +204 -0
  482. package/dist/core/coherence/events/events.js.map +1 -0
  483. package/dist/core/coherence/events/grades.js +166 -0
  484. package/dist/core/coherence/events/grades.js.map +1 -0
  485. package/dist/core/coherence/events/lookups.js +122 -0
  486. package/dist/core/coherence/events/lookups.js.map +1 -0
  487. package/dist/core/coherence/events/merged.js +40 -0
  488. package/dist/core/coherence/events/merged.js.map +1 -0
  489. package/dist/core/coherence/living.js +198 -0
  490. package/dist/core/coherence/living.js.map +1 -0
  491. package/dist/core/coherence/lookups.js +155 -0
  492. package/dist/core/coherence/lookups.js.map +1 -0
  493. package/dist/core/coherence/pending.js +39 -0
  494. package/dist/core/coherence/pending.js.map +1 -0
  495. package/dist/core/conflict-markers.js +60 -0
  496. package/dist/core/conflict-markers.js.map +1 -0
  497. package/dist/core/delta/claims.js +73 -0
  498. package/dist/core/delta/claims.js.map +1 -0
  499. package/dist/core/delta/delta.js +296 -0
  500. package/dist/core/delta/delta.js.map +1 -0
  501. package/dist/core/delta/document.js +177 -0
  502. package/dist/core/delta/document.js.map +1 -0
  503. package/dist/core/delta/scope.js +48 -0
  504. package/dist/core/delta/scope.js.map +1 -0
  505. package/dist/core/delta/select.js +237 -0
  506. package/dist/core/delta/select.js.map +1 -0
  507. package/dist/core/dependencies/dependencies.js +141 -0
  508. package/dist/core/dependencies/dependencies.js.map +1 -0
  509. package/dist/core/dependencies/facts.js +165 -0
  510. package/dist/core/dependencies/facts.js.map +1 -0
  511. package/dist/core/dependencies/fanin.js +88 -0
  512. package/dist/core/dependencies/fanin.js.map +1 -0
  513. package/dist/core/dependencies/graph.js +106 -0
  514. package/dist/core/dependencies/graph.js.map +1 -0
  515. package/dist/core/deployment/delta.js +188 -0
  516. package/dist/core/deployment/delta.js.map +1 -0
  517. package/dist/core/deployment/objects.js +145 -0
  518. package/dist/core/deployment/objects.js.map +1 -0
  519. package/dist/core/diff/base-git.js +148 -0
  520. package/dist/core/diff/base-git.js.map +1 -0
  521. package/dist/core/diff/base-state.js +209 -0
  522. package/dist/core/diff/base-state.js.map +1 -0
  523. package/dist/core/diff/current-state.js +96 -0
  524. package/dist/core/diff/current-state.js.map +1 -0
  525. package/dist/core/diff/scope/changed-paths.js +188 -0
  526. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  527. package/dist/core/diff/semantic.js +243 -0
  528. package/dist/core/diff/semantic.js.map +1 -0
  529. package/dist/core/diff/victims.js +247 -0
  530. package/dist/core/diff/victims.js.map +1 -0
  531. package/dist/core/docs.js +7 -64
  532. package/dist/core/docs.js.map +1 -1
  533. package/dist/core/doctor/agents.js +222 -0
  534. package/dist/core/doctor/agents.js.map +1 -0
  535. package/dist/core/doctor/config.js +135 -0
  536. package/dist/core/doctor/config.js.map +1 -0
  537. package/dist/core/doctor/doctor.js +275 -0
  538. package/dist/core/doctor/doctor.js.map +1 -0
  539. package/dist/core/doctor/report.js +13 -0
  540. package/dist/core/doctor/report.js.map +1 -0
  541. package/dist/core/doctor/residue.js +110 -0
  542. package/dist/core/doctor/residue.js.map +1 -0
  543. package/dist/core/document/apply.js +77 -0
  544. package/dist/core/document/apply.js.map +1 -0
  545. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  546. package/dist/core/document/frontmatter.js.map +1 -0
  547. package/dist/core/document/grammar/directives.js +143 -0
  548. package/dist/core/document/grammar/directives.js.map +1 -0
  549. package/dist/core/document/parse.js +263 -0
  550. package/dist/core/document/parse.js.map +1 -0
  551. package/dist/core/document/scenarios.js +221 -0
  552. package/dist/core/document/scenarios.js.map +1 -0
  553. package/dist/core/document/spec.js +241 -0
  554. package/dist/core/document/spec.js.map +1 -0
  555. package/dist/core/{config.js → envelope/config.js} +79 -18
  556. package/dist/core/envelope/config.js.map +1 -0
  557. package/dist/core/envelope/json.js +175 -0
  558. package/dist/core/envelope/json.js.map +1 -0
  559. package/dist/core/{version.js → envelope/version.js} +3 -3
  560. package/dist/core/envelope/version.js.map +1 -0
  561. package/dist/core/explain/families.js +175 -0
  562. package/dist/core/explain/families.js.map +1 -0
  563. package/dist/core/explain/fix-tables.js +178 -0
  564. package/dist/core/explain/fix-tables.js.map +1 -0
  565. package/dist/core/explain/lookup.js +181 -0
  566. package/dist/core/explain/lookup.js.map +1 -0
  567. package/dist/core/explain/refusals.js +49 -0
  568. package/dist/core/explain/refusals.js.map +1 -0
  569. package/dist/core/explain/terms.js +144 -0
  570. package/dist/core/explain/terms.js.map +1 -0
  571. package/dist/core/explore/describe.js +105 -0
  572. package/dist/core/explore/describe.js.map +1 -0
  573. package/dist/core/{explore.js → explore/explore.js} +50 -101
  574. package/dist/core/explore/explore.js.map +1 -0
  575. package/dist/core/fleet-context.js +120 -78
  576. package/dist/core/fleet-context.js.map +1 -1
  577. package/dist/core/gate/checks.js +201 -0
  578. package/dist/core/gate/checks.js.map +1 -0
  579. package/dist/core/gate/partners.js +205 -0
  580. package/dist/core/gate/partners.js.map +1 -0
  581. package/dist/core/gate/report.js +47 -0
  582. package/dist/core/gate/report.js.map +1 -0
  583. package/dist/core/gate/verification.js +113 -0
  584. package/dist/core/gate/verification.js.map +1 -0
  585. package/dist/core/gherkin/digest.js +60 -0
  586. package/dist/core/gherkin/digest.js.map +1 -0
  587. package/dist/core/gherkin/emit.js +177 -0
  588. package/dist/core/gherkin/emit.js.map +1 -0
  589. package/dist/core/gherkin/read.js +76 -0
  590. package/dist/core/gherkin/read.js.map +1 -0
  591. package/dist/core/gherkin/stale.js +234 -0
  592. package/dist/core/gherkin/stale.js.map +1 -0
  593. package/dist/core/gherkin/stamp.js +77 -0
  594. package/dist/core/gherkin/stamp.js.map +1 -0
  595. package/dist/core/gherkin/steps/catalogue.js +154 -0
  596. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  597. package/dist/core/gherkin/steps/inventory.js +98 -0
  598. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  599. package/dist/core/gherkin/steps/phrase.js +72 -0
  600. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  601. package/dist/core/glossary/delta.js +106 -0
  602. package/dist/core/glossary/delta.js.map +1 -0
  603. package/dist/core/glossary/tree.js +86 -0
  604. package/dist/core/glossary/tree.js.map +1 -0
  605. package/dist/core/kernel/concurrency.js.map +1 -0
  606. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  607. package/dist/core/kernel/document-bytes.js.map +1 -0
  608. package/dist/core/kernel/fences.js +42 -0
  609. package/dist/core/kernel/fences.js.map +1 -0
  610. package/dist/core/kernel/ids/capability.js +72 -0
  611. package/dist/core/kernel/ids/capability.js.map +1 -0
  612. package/dist/core/kernel/ids/dirs.js +33 -0
  613. package/dist/core/kernel/ids/dirs.js.map +1 -0
  614. package/dist/core/kernel/ids/feature.js +75 -0
  615. package/dist/core/kernel/ids/feature.js.map +1 -0
  616. package/dist/core/kernel/ids/service.js +156 -0
  617. package/dist/core/kernel/ids/service.js.map +1 -0
  618. package/dist/core/kernel/ids/subsystem.js +63 -0
  619. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  620. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  621. package/dist/core/kernel/path-safety.js.map +1 -0
  622. package/dist/core/kernel/records.js.map +1 -0
  623. package/dist/core/links/backlinks.js +68 -0
  624. package/dist/core/links/backlinks.js.map +1 -0
  625. package/dist/core/links/case.js +74 -0
  626. package/dist/core/links/case.js.map +1 -0
  627. package/dist/core/links/corpus.js +104 -0
  628. package/dist/core/links/corpus.js.map +1 -0
  629. package/dist/core/links/findings.js +110 -0
  630. package/dist/core/links/findings.js.map +1 -0
  631. package/dist/core/links/parse.js +236 -0
  632. package/dist/core/links/parse.js.map +1 -0
  633. package/dist/core/mcp/argv.js +111 -0
  634. package/dist/core/mcp/argv.js.map +1 -0
  635. package/dist/core/mcp/author-tools.js +72 -0
  636. package/dist/core/mcp/author-tools.js.map +1 -0
  637. package/dist/core/mcp/framing.js +46 -0
  638. package/dist/core/mcp/framing.js.map +1 -0
  639. package/dist/core/mcp/protocol.js +280 -0
  640. package/dist/core/mcp/protocol.js.map +1 -0
  641. package/dist/core/mcp/tools.js +314 -0
  642. package/dist/core/mcp/tools.js.map +1 -0
  643. package/dist/core/obligations/obligations.js +106 -0
  644. package/dist/core/obligations/obligations.js.map +1 -0
  645. package/dist/core/openapi/baseline/gate.js +166 -0
  646. package/dist/core/openapi/baseline/gate.js.map +1 -0
  647. package/dist/core/openapi/baseline/plan.js +112 -0
  648. package/dist/core/openapi/baseline/plan.js.map +1 -0
  649. package/dist/core/openapi/baseline/record.js +175 -0
  650. package/dist/core/openapi/baseline/record.js.map +1 -0
  651. package/dist/core/openapi/depth.js +122 -0
  652. package/dist/core/openapi/depth.js.map +1 -0
  653. package/dist/core/openapi/digest.js +189 -0
  654. package/dist/core/openapi/digest.js.map +1 -0
  655. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  656. package/dist/core/openapi/doc.js.map +1 -0
  657. package/dist/core/openapi/merge/components.js +234 -0
  658. package/dist/core/openapi/merge/components.js.map +1 -0
  659. package/dist/core/openapi/merge/error.js +26 -0
  660. package/dist/core/openapi/merge/error.js.map +1 -0
  661. package/dist/core/openapi/merge/markers.js +208 -0
  662. package/dist/core/openapi/merge/markers.js.map +1 -0
  663. package/dist/core/openapi/merge/merge.js +256 -0
  664. package/dist/core/openapi/merge/merge.js.map +1 -0
  665. package/dist/core/openapi/merge/pin.js +128 -0
  666. package/dist/core/openapi/merge/pin.js.map +1 -0
  667. package/dist/core/openspec/decide/capabilities.js +244 -0
  668. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  669. package/dist/core/openspec/decide/dispositions.js +63 -0
  670. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  671. package/dist/core/openspec/decide/renames.js +179 -0
  672. package/dist/core/openspec/decide/renames.js.map +1 -0
  673. package/dist/core/openspec/decide/verdict.js +84 -0
  674. package/dist/core/openspec/decide/verdict.js.map +1 -0
  675. package/dist/core/openspec/inventory.js +221 -0
  676. package/dist/core/openspec/inventory.js.map +1 -0
  677. package/dist/core/openspec/model/mapping.js +76 -0
  678. package/dist/core/openspec/model/mapping.js.map +1 -0
  679. package/dist/core/openspec/model/model.js +65 -0
  680. package/dist/core/openspec/model/model.js.map +1 -0
  681. package/dist/core/openspec/scan/changes.js +235 -0
  682. package/dist/core/openspec/scan/changes.js.map +1 -0
  683. package/dist/core/openspec/scan/config.js +200 -0
  684. package/dist/core/openspec/scan/config.js.map +1 -0
  685. package/dist/core/openspec/scan/shape.js +260 -0
  686. package/dist/core/openspec/scan/shape.js.map +1 -0
  687. package/dist/core/openspec/scan/walk.js +133 -0
  688. package/dist/core/openspec/scan/walk.js.map +1 -0
  689. package/dist/core/openspec/scan/workspace.js +71 -0
  690. package/dist/core/openspec/scan/workspace.js.map +1 -0
  691. package/dist/core/owners/codeowners.js +141 -0
  692. package/dist/core/owners/codeowners.js.map +1 -0
  693. package/dist/core/pack/features.js +120 -0
  694. package/dist/core/pack/features.js.map +1 -0
  695. package/dist/core/pack/joins.js +112 -0
  696. package/dist/core/pack/joins.js.map +1 -0
  697. package/dist/core/pack/living.js +137 -0
  698. package/dist/core/pack/living.js.map +1 -0
  699. package/dist/core/pack/pack.js +76 -0
  700. package/dist/core/pack/pack.js.map +1 -0
  701. package/dist/core/permissions/permissions.js +87 -0
  702. package/dist/core/permissions/permissions.js.map +1 -0
  703. package/dist/core/projection/api.js +84 -0
  704. package/dist/core/projection/api.js.map +1 -0
  705. package/dist/core/projection/arch-slice.js +63 -0
  706. package/dist/core/projection/arch-slice.js.map +1 -0
  707. package/dist/core/projection/events.js +34 -0
  708. package/dist/core/projection/events.js.map +1 -0
  709. package/dist/core/provenance/findings.js +208 -0
  710. package/dist/core/provenance/findings.js.map +1 -0
  711. package/dist/core/provenance/git.js +225 -0
  712. package/dist/core/provenance/git.js.map +1 -0
  713. package/dist/core/provenance/gitq/moves.js +182 -0
  714. package/dist/core/provenance/gitq/moves.js.map +1 -0
  715. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  716. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  717. package/dist/core/provenance/sample/scope.js +238 -0
  718. package/dist/core/provenance/sample/scope.js.map +1 -0
  719. package/dist/core/provenance/sample/sections.js +61 -0
  720. package/dist/core/provenance/sample/sections.js.map +1 -0
  721. package/dist/core/provenance/sources.js +263 -0
  722. package/dist/core/provenance/sources.js.map +1 -0
  723. package/dist/core/provenance/stamp.js +232 -0
  724. package/dist/core/provenance/stamp.js.map +1 -0
  725. package/dist/core/provenance/walk.js +157 -0
  726. package/dist/core/provenance/walk.js.map +1 -0
  727. package/dist/core/repo/authored/paths.js +210 -0
  728. package/dist/core/repo/authored/paths.js.map +1 -0
  729. package/dist/core/repo/entries.js +126 -0
  730. package/dist/core/repo/entries.js.map +1 -0
  731. package/dist/core/repo/paths.js +277 -0
  732. package/dist/core/repo/paths.js.map +1 -0
  733. package/dist/core/repo/repo.js +248 -0
  734. package/dist/core/repo/repo.js.map +1 -0
  735. package/dist/core/repo/service-target.js +153 -0
  736. package/dist/core/repo/service-target.js.map +1 -0
  737. package/dist/core/repo/state.js +58 -0
  738. package/dist/core/repo/state.js.map +1 -0
  739. package/dist/core/repo/tree/find.js +52 -0
  740. package/dist/core/repo/tree/find.js.map +1 -0
  741. package/dist/core/repo/tree/fs.js +64 -0
  742. package/dist/core/repo/tree/fs.js.map +1 -0
  743. package/dist/core/repo/tree/marker.js +61 -0
  744. package/dist/core/repo/tree/marker.js.map +1 -0
  745. package/dist/core/repo/tree/views.js +172 -0
  746. package/dist/core/repo/tree/views.js.map +1 -0
  747. package/dist/core/repo/tree/walk.js +234 -0
  748. package/dist/core/repo/tree/walk.js.map +1 -0
  749. package/dist/core/results.js +99 -2
  750. package/dist/core/results.js.map +1 -1
  751. package/dist/core/scaffold/landscape.js +131 -0
  752. package/dist/core/scaffold/landscape.js.map +1 -0
  753. package/dist/core/scaffold/readme.js +84 -0
  754. package/dist/core/scaffold/readme.js.map +1 -0
  755. package/dist/core/staging/commit.js +241 -0
  756. package/dist/core/staging/commit.js.map +1 -0
  757. package/dist/core/staging/interrupted.js +96 -0
  758. package/dist/core/staging/interrupted.js.map +1 -0
  759. package/dist/core/staging/lock.js +206 -0
  760. package/dist/core/staging/lock.js.map +1 -0
  761. package/dist/core/staging/recovery/finding.js +35 -0
  762. package/dist/core/staging/recovery/finding.js.map +1 -0
  763. package/dist/core/staging/recovery/intent.js +98 -0
  764. package/dist/core/staging/recovery/intent.js.map +1 -0
  765. package/dist/core/staging/recovery/recover.js +126 -0
  766. package/dist/core/staging/recovery/recover.js.map +1 -0
  767. package/dist/core/staging/recovery/residue.js +62 -0
  768. package/dist/core/staging/recovery/residue.js.map +1 -0
  769. package/dist/core/staging/snapshot.js +202 -0
  770. package/dist/core/staging/snapshot.js.map +1 -0
  771. package/dist/core/staging/txn/forward.js +171 -0
  772. package/dist/core/staging/txn/forward.js.map +1 -0
  773. package/dist/core/staging/txn/journal.js +139 -0
  774. package/dist/core/staging/txn/journal.js.map +1 -0
  775. package/dist/core/staging/txn/transaction.js +75 -0
  776. package/dist/core/staging/txn/transaction.js.map +1 -0
  777. package/dist/core/staging/writes.js +113 -0
  778. package/dist/core/staging/writes.js.map +1 -0
  779. package/dist/core/status/actions/execution.js +49 -0
  780. package/dist/core/status/actions/execution.js.map +1 -0
  781. package/dist/core/status/contracts.js +102 -0
  782. package/dist/core/status/contracts.js.map +1 -0
  783. package/dist/core/status/feature/artifacts.js +196 -0
  784. package/dist/core/status/feature/artifacts.js.map +1 -0
  785. package/dist/core/status/feature/feature.js +207 -0
  786. package/dist/core/status/feature/feature.js.map +1 -0
  787. package/dist/core/status/feature/next.js +150 -0
  788. package/dist/core/status/feature/next.js.map +1 -0
  789. package/dist/core/status/feature/state.js +21 -0
  790. package/dist/core/status/feature/state.js.map +1 -0
  791. package/dist/core/status/fleet/fleet.js +197 -0
  792. package/dist/core/status/fleet/fleet.js.map +1 -0
  793. package/dist/core/status/fleet/next.js +207 -0
  794. package/dist/core/status/fleet/next.js.map +1 -0
  795. package/dist/core/status/interrupted.js +97 -0
  796. package/dist/core/status/interrupted.js.map +1 -0
  797. package/dist/core/status/report.js +31 -0
  798. package/dist/core/status/report.js.map +1 -0
  799. package/dist/core/status/scan.js +41 -0
  800. package/dist/core/status/scan.js.map +1 -0
  801. package/dist/core/status/verification.js +201 -0
  802. package/dist/core/status/verification.js.map +1 -0
  803. package/dist/core/usecases/capability.js +169 -0
  804. package/dist/core/usecases/capability.js.map +1 -0
  805. package/dist/core/usecases/delta/claims.js +116 -0
  806. package/dist/core/usecases/delta/claims.js.map +1 -0
  807. package/dist/core/usecases/delta/flows.js +103 -0
  808. package/dist/core/usecases/delta/flows.js.map +1 -0
  809. package/dist/core/usecases/delta/overlay.js +72 -0
  810. package/dist/core/usecases/delta/overlay.js.map +1 -0
  811. package/dist/core/usecases/fleet.js +137 -0
  812. package/dist/core/usecases/fleet.js.map +1 -0
  813. package/dist/core/usecases/operations.js +111 -0
  814. package/dist/core/usecases/operations.js.map +1 -0
  815. package/dist/core/usecases/place.js +44 -0
  816. package/dist/core/usecases/place.js.map +1 -0
  817. package/dist/core/usecases/touch.js +101 -0
  818. package/dist/core/usecases/touch.js.map +1 -0
  819. package/dist/core/verify/answers.js +173 -0
  820. package/dist/core/verify/answers.js.map +1 -0
  821. package/dist/core/verify/build.js +150 -0
  822. package/dist/core/verify/build.js.map +1 -0
  823. package/dist/core/verify/checklist.js +201 -0
  824. package/dist/core/verify/checklist.js.map +1 -0
  825. package/dist/core/verify/claims/identity.js +61 -0
  826. package/dist/core/verify/claims/identity.js.map +1 -0
  827. package/dist/core/verify/cross/diff.js +97 -0
  828. package/dist/core/verify/cross/diff.js.map +1 -0
  829. package/dist/core/verify/evidence/contract.js +169 -0
  830. package/dist/core/verify/evidence/contract.js.map +1 -0
  831. package/dist/core/verify/file.js +257 -0
  832. package/dist/core/verify/file.js.map +1 -0
  833. package/dist/core/verify/pins/lint.js +157 -0
  834. package/dist/core/verify/pins/lint.js.map +1 -0
  835. package/dist/core/verify/pins/pin.js +95 -0
  836. package/dist/core/verify/pins/pin.js.map +1 -0
  837. package/dist/core/verify/record.js +142 -0
  838. package/dist/core/verify/record.js.map +1 -0
  839. package/dist/core/verify/store/commit.js +89 -0
  840. package/dist/core/verify/store/commit.js.map +1 -0
  841. package/dist/core/verify/store/render.js +57 -0
  842. package/dist/core/verify/store/render.js.map +1 -0
  843. package/dist/core/vocabulary/health.js +91 -0
  844. package/dist/core/vocabulary/health.js.map +1 -0
  845. package/dist/core/vocabulary/issue.js +58 -0
  846. package/dist/core/vocabulary/issue.js.map +1 -0
  847. package/dist/core/vocabulary/maturity.js +145 -0
  848. package/dist/core/vocabulary/maturity.js.map +1 -0
  849. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  850. package/dist/core/vocabulary/report.js.map +1 -0
  851. package/dist/core/vocabulary/steps.js +266 -0
  852. package/dist/core/vocabulary/steps.js.map +1 -0
  853. package/dist/core/workspace/discover.js +239 -0
  854. package/dist/core/workspace/discover.js.map +1 -0
  855. package/dist/core/workspace/render.js +52 -0
  856. package/dist/core/workspace/render.js.map +1 -0
  857. package/examples/README.md +189 -0
  858. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  859. package/examples/docs/architecture/capabilities.yaml +31 -0
  860. package/examples/docs/architecture/landscape.likec4 +280 -0
  861. package/examples/docs/architecture/obligations.yaml +23 -0
  862. package/examples/docs/architecture/permissions.yaml +50 -0
  863. package/examples/docs/architecture/subsystems.likec4 +11 -0
  864. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  865. package/examples/docs/capabilities/checkout/spec.md +55 -0
  866. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  867. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  868. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  869. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  870. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  871. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  872. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  873. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  874. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  875. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  876. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  877. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  878. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  879. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  882. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  883. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  884. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  885. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  886. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  887. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  888. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  889. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  890. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  891. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  896. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  897. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  898. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  899. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  900. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  901. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  902. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  903. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  904. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  905. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  906. package/examples/docs/glossary/order.md +15 -0
  907. package/examples/docs/glossary/payments/authorization.md +14 -0
  908. package/examples/docs/likec4.config.json +9 -0
  909. package/examples/docs/loam.json +3 -0
  910. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  911. package/examples/docs/services/checkout-web/spec.md +91 -0
  912. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  913. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  914. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  915. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  916. package/examples/docs/services/order-service/arch.spec.md +127 -0
  917. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  918. package/examples/docs/services/order-service/health.yaml +57 -0
  919. package/examples/docs/services/order-service/model.likec4 +138 -0
  920. package/examples/docs/services/order-service/openapi.yaml +257 -0
  921. package/examples/docs/services/order-service/runbook.md +62 -0
  922. package/examples/docs/services/order-service/spec.md +134 -0
  923. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  924. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  925. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  926. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  927. package/examples/docs/services/payment-service/health.yaml +39 -0
  928. package/examples/docs/services/payment-service/model.likec4 +67 -0
  929. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  930. package/examples/docs/services/payment-service/runbook.md +37 -0
  931. package/examples/docs/services/payment-service/spec.md +88 -0
  932. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  933. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  934. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  935. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  936. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  937. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  938. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  939. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  940. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  941. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  942. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  943. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  944. package/examples/docs/services/platform/subsystem.yaml +2 -0
  945. package/examples/fleet.yaml +57 -0
  946. package/package.json +19 -6
  947. package/dist/commands/adopt.js +0 -240
  948. package/dist/commands/adopt.js.map +0 -1
  949. package/dist/commands/archive.js +0 -1313
  950. package/dist/commands/archive.js.map +0 -1
  951. package/dist/commands/delta.js +0 -388
  952. package/dist/commands/delta.js.map +0 -1
  953. package/dist/commands/docs-repo-gate.js.map +0 -1
  954. package/dist/commands/format.js +0 -17
  955. package/dist/commands/format.js.map +0 -1
  956. package/dist/commands/gherkin.js +0 -309
  957. package/dist/commands/gherkin.js.map +0 -1
  958. package/dist/commands/init.js.map +0 -1
  959. package/dist/commands/list.js +0 -295
  960. package/dist/commands/list.js.map +0 -1
  961. package/dist/commands/migrate-openspec.js +0 -950
  962. package/dist/commands/migrate-openspec.js.map +0 -1
  963. package/dist/commands/new.js +0 -413
  964. package/dist/commands/new.js.map +0 -1
  965. package/dist/commands/rebase.js +0 -356
  966. package/dist/commands/rebase.js.map +0 -1
  967. package/dist/commands/show.js.map +0 -1
  968. package/dist/commands/status.js +0 -262
  969. package/dist/commands/status.js.map +0 -1
  970. package/dist/commands/unarchive.js.map +0 -1
  971. package/dist/commands/validate.js +0 -1773
  972. package/dist/commands/validate.js.map +0 -1
  973. package/dist/commands/verify.js +0 -775
  974. package/dist/commands/verify.js.map +0 -1
  975. package/dist/commands/vouch.js +0 -421
  976. package/dist/commands/vouch.js.map +0 -1
  977. package/dist/core/agent.js +0 -2173
  978. package/dist/core/agent.js.map +0 -1
  979. package/dist/core/agents-stamp.js.map +0 -1
  980. package/dist/core/arch.js +0 -130
  981. package/dist/core/arch.js.map +0 -1
  982. package/dist/core/asyncapi.js.map +0 -1
  983. package/dist/core/brief.js +0 -755
  984. package/dist/core/brief.js.map +0 -1
  985. package/dist/core/coherence.js +0 -577
  986. package/dist/core/coherence.js.map +0 -1
  987. package/dist/core/concurrency.js.map +0 -1
  988. package/dist/core/config.js.map +0 -1
  989. package/dist/core/delta.js +0 -476
  990. package/dist/core/delta.js.map +0 -1
  991. package/dist/core/dependencies.js +0 -356
  992. package/dist/core/dependencies.js.map +0 -1
  993. package/dist/core/doctor.js +0 -596
  994. package/dist/core/doctor.js.map +0 -1
  995. package/dist/core/document-bytes.js.map +0 -1
  996. package/dist/core/explore.js.map +0 -1
  997. package/dist/core/frontmatter.js.map +0 -1
  998. package/dist/core/gherkin.js +0 -463
  999. package/dist/core/gherkin.js.map +0 -1
  1000. package/dist/core/health.js +0 -67
  1001. package/dist/core/health.js.map +0 -1
  1002. package/dist/core/ids.js +0 -122
  1003. package/dist/core/ids.js.map +0 -1
  1004. package/dist/core/issue.js +0 -9
  1005. package/dist/core/issue.js.map +0 -1
  1006. package/dist/core/json.js +0 -74
  1007. package/dist/core/json.js.map +0 -1
  1008. package/dist/core/likec4.js +0 -489
  1009. package/dist/core/likec4.js.map +0 -1
  1010. package/dist/core/maturity.js +0 -66
  1011. package/dist/core/maturity.js.map +0 -1
  1012. package/dist/core/openapi-merge.js +0 -552
  1013. package/dist/core/openapi-merge.js.map +0 -1
  1014. package/dist/core/openapi.js.map +0 -1
  1015. package/dist/core/openspec-inventory.js +0 -1440
  1016. package/dist/core/openspec-inventory.js.map +0 -1
  1017. package/dist/core/path-safety.js.map +0 -1
  1018. package/dist/core/provenance.js +0 -787
  1019. package/dist/core/provenance.js.map +0 -1
  1020. package/dist/core/records.js.map +0 -1
  1021. package/dist/core/repo.js +0 -438
  1022. package/dist/core/repo.js.map +0 -1
  1023. package/dist/core/report.js.map +0 -1
  1024. package/dist/core/spec.js +0 -505
  1025. package/dist/core/spec.js.map +0 -1
  1026. package/dist/core/staging.js +0 -890
  1027. package/dist/core/staging.js.map +0 -1
  1028. package/dist/core/status.js +0 -1132
  1029. package/dist/core/status.js.map +0 -1
  1030. package/dist/core/steps.js +0 -72
  1031. package/dist/core/steps.js.map +0 -1
  1032. package/dist/core/verify.js +0 -807
  1033. package/dist/core/verify.js.map +0 -1
  1034. package/dist/core/version.js.map +0 -1
  1035. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1036. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,355 @@
1
+ /**
2
+ * The six files `loam new` scaffolds, and what each one says.
3
+ *
4
+ * They are here rather than inline because every one of them is a document a
5
+ * person edits next, and the wording IS the instruction: the delta's comments
6
+ * teach the grammar `core/document/parse.ts` matches on, and the intent's
7
+ * frontmatter is serialized rather than interpolated because a title is free
8
+ * text somebody types — `Checkout: split payments` reopens the mapping and the
9
+ * file stops parsing.
10
+ */
11
+ import { stringify as stringifyYaml } from "yaml";
12
+ import { REQUIREMENT_ID_RE } from "../../core/document/spec.js";
13
+ import { ARCH_REQUIREMENT_SENTINEL, ARCH_SHALL_SENTINEL, CAPABILITY_REQUIREMENT_SENTINEL, CAPABILITY_SHALL_SENTINEL, REQUIREMENT_SENTINEL, SCENARIO_SENTINEL, SERVICE_DESCRIPTION_SENTINEL, GIVEN_SENTINEL, SHALL_SENTINEL, THEN_SENTINEL, WHEN_SENTINEL, } from "../../core/coherence/authoring/sentinels.js";
14
+ function identifier(name, taken) {
15
+ const parts = name.split(/[^A-Za-z0-9]+/).filter((p) => p.length > 0);
16
+ const head = (parts[0] ?? "svc").replace(/^\d+/, "") || "svc";
17
+ const base = head[0].toLowerCase() +
18
+ head.slice(1) +
19
+ parts.slice(1).map((p) => p[0].toUpperCase() + p.slice(1)).join("");
20
+ let id = base;
21
+ for (let n = 2; taken.has(id); n += 1)
22
+ id = `${base}${n}`;
23
+ taken.add(id);
24
+ return id;
25
+ }
26
+ /** `FEAT-101` -> `feat_101`: LikeC4 view names take no dashes. */
27
+ function viewName(featureId) {
28
+ return featureId.toLowerCase().replace(/[^a-z0-9]+/g, "_");
29
+ }
30
+ /* ------------------------------------------------------------------ */
31
+ /* Templates */
32
+ /* ------------------------------------------------------------------ */
33
+ export function intentTemplate(featureId, title) {
34
+ const heading = title ?? featureId;
35
+ // The frontmatter is SERIALIZED, never interpolated. A title is free text a
36
+ // person types — `Checkout: split payments` used to be pasted straight after
37
+ // `title: `, where the colon reopens the mapping and the file stops parsing;
38
+ // `Orders #42 rework` lost everything from the `#` as a YAML comment, so the
39
+ // feature quietly answered to a title nobody wrote. The yaml serializer is the
40
+ // only thing that knows every case, and it is already how migrate-openspec
41
+ // writes the same three keys.
42
+ //
43
+ // `owner` is deliberately NOT a key here. Scaffolding `owner:` writes an
44
+ // explicit null, which is a claim ("this feature has no owner") rather than
45
+ // the truth ("nobody has said yet") — and every reader then has to tell an
46
+ // absent key from a null one. The prompt survives as a comment.
47
+ const frontmatter = stringifyYaml({ feature: featureId, ...(title === undefined ? {} : { title }), status: "proposed" }, { lineWidth: 0 }).trimEnd();
48
+ return `---
49
+ ${frontmatter}
50
+ # owner: <the team or person who answers for this>
51
+ ---
52
+
53
+ # ${heading}
54
+
55
+ ## Why
56
+
57
+ <!-- The problem in business terms. This is what a reviewer reads first, and what
58
+ the requirement deltas below have to be justified by. -->
59
+
60
+ ## Scope
61
+
62
+ <!-- Which services this touches — and, more usefully, which it deliberately does not. -->
63
+ `;
64
+ }
65
+ export function deltaTemplate(featureId, touched, created) {
66
+ const taken = new Set();
67
+ const touchedIds = touched.map((s) => [identifier(s, taken), s]);
68
+ const createdIds = created.map((s) => [identifier(s, taken), s]);
69
+ const lines = [];
70
+ if (touchedIds.length > 0) {
71
+ // Context elements ship COMMENTED OUT, exactly like the example edge below.
72
+ // Declared-and-untagged is the one shape `delta.nothing-tagged` exists to
73
+ // refuse (an author who forgot the tags), so writing these live made every
74
+ // `loam new --touches X` fail its own validator on the very first run —
75
+ // which teaches people that the validator is noise. Uncomment a line when an
76
+ // edge below actually needs the identifier.
77
+ lines.push(" // Services this feature touches, as context for the diagram. Uncomment the");
78
+ lines.push(" // ones an edge below names, and reuse the identifiers from");
79
+ lines.push(" // architecture/landscape.likec4 so the merge lines up. They stay UNTAGGED:");
80
+ lines.push(` // only #${featureId} is the change, and everything else here is context.`);
81
+ for (const [id, name] of touchedIds)
82
+ lines.push(` // ${id} = softwareSystem '${name}'`);
83
+ lines.push("");
84
+ }
85
+ if (createdIds.length > 0) {
86
+ lines.push(" // Services this feature introduces. The tag is what `loam archive` folds");
87
+ lines.push(" // into the living landscape.");
88
+ for (const [id, name] of createdIds) {
89
+ lines.push(` ${id} = softwareSystem '${name}' {`);
90
+ lines.push(` #${featureId}`);
91
+ // Interpolated from sentinels.ts, never spelled here: the placeholder
92
+ // gate refuses this exact string at archive, and a second spelling is
93
+ // how a template rewording silently retires the check that guards it.
94
+ lines.push(` description '${SERVICE_DESCRIPTION_SENTINEL}'`);
95
+ lines.push(" }");
96
+ }
97
+ lines.push("");
98
+ }
99
+ // The example edge uses identifiers already declared above when there are two
100
+ // to join; otherwise placeholders, so it never invents a service.
101
+ const from = touchedIds[0]?.[0] ?? createdIds[0]?.[0] ?? "consumer";
102
+ const to = createdIds[0]?.[0] ?? touchedIds[1]?.[0] ?? "provider";
103
+ lines.push(" // New calls. `metadata { op }` is the spine: it names the OpenAPI operationId");
104
+ lines.push(" // the call uses, and `loam validate` checks it against the target's contract.");
105
+ lines.push(" // Uncomment and adjust (both endpoints have to be declared above):");
106
+ lines.push(" //");
107
+ lines.push(` // ${from} -> ${to} 'Calls createSplit' {`);
108
+ lines.push(` // #${featureId}`);
109
+ lines.push(" // metadata { op 'createSplit' }");
110
+ lines.push(" // }");
111
+ return `// ${featureId} — architecture delta.
112
+ //
113
+ // Everything tagged #${featureId} is exactly what \`loam archive\` folds into
114
+ // architecture/landscape.likec4. Everything else here is context for the diagram.
115
+ //
116
+ // If ${featureId} changes no architecture, DELETE this file: a requirements-only
117
+ // feature is complete without it, and an empty \`model {}\` is just as legal.
118
+
119
+ specification {
120
+ element softwareSystem
121
+ tag ${featureId}
122
+ }
123
+
124
+ model {
125
+ ${lines.join("\n")}
126
+ }
127
+
128
+ views {
129
+ view ${viewName(featureId)} {
130
+ include *
131
+ }
132
+ }
133
+ `;
134
+ }
135
+ /**
136
+ * The business axis, scaffolded with its example INSIDE the comment — the same
137
+ * mechanism as `archSpecTemplate` below, for the same reason: this template
138
+ * used to ship a live `### Requirement: TODO — name the behaviour` that
139
+ * parsed as a real requirement, validated clean, and archived a literal TODO
140
+ * into the living spec at exit 0. The indented headings sit past the
141
+ * line-anchored patterns core/document/parse.ts matches on, so the scaffold
142
+ * declares nothing until a person copies the block out and writes over the
143
+ * fill-ins — and `scaffold.placeholder` gates the archive if the fill-ins
144
+ * survive the copy.
145
+ *
146
+ * No nested HTML comments: the `Operations:` guidance that used to be its own
147
+ * comment lives in this one's prose, because an inner `-->` would end the
148
+ * outer comment early and re-expose the example to the parser.
149
+ */
150
+ export function specTemplate(featureId, service) {
151
+ return `# ${service} — requirement delta for ${featureId}
152
+
153
+ <!-- Sections: ADDED / MODIFIED / REMOVED. Delete the ones you do not need.
154
+ A MODIFIED requirement carries its full new text, not a diff.
155
+ Every requirement needs at least one scenario — \`loam validate\` gates on it.
156
+
157
+ An \`Operations:\` body line names the operationIds the requirement
158
+ governs. \`loam validate\` checks each one against the service's OpenAPI,
159
+ and \`loam archive\` refuses to merge a requirement that governs an
160
+ operation nobody defines — add the line once the contract exists.
161
+
162
+ A \`Publishes:\` / \`Consumes:\` body line does the same for event messages:
163
+ it names what the requirement puts on or takes off the bus, checked
164
+ against the service's AsyncAPI contract. If ${featureId} changes that
165
+ contract, hand-create specs/${service}/asyncapi.yaml — a complete
166
+ AsyncAPI 3.0 document, messages declared under \`components.messages\`
167
+ (loam scaffolds none: an event contract is genuinely optional) — and run
168
+ \`loam rebase ${featureId}\` so every restated slot pins as a quote.
169
+
170
+ Copy the block below out of this comment, unindent it, and replace the
171
+ TODOs and every <angle-bracket> fill-in — \`loam archive\` refuses the
172
+ scaffold's own wording (\`scaffold.placeholder\`):
173
+
174
+ ## ADDED Requirements
175
+
176
+ ### Requirement: ${REQUIREMENT_SENTINEL}
177
+ Requirement-ID: ${featureId}.${service}.requirement
178
+
179
+ The service SHALL ${SHALL_SENTINEL}.
180
+
181
+ #### Scenario: ${SCENARIO_SENTINEL}
182
+ - **Given** ${GIVEN_SENTINEL}
183
+ - **When** ${WHEN_SENTINEL}
184
+ - **Then** ${THEN_SENTINEL}
185
+ -->
186
+ `;
187
+ }
188
+ /**
189
+ * The architecture axis, scaffolded EMPTY on purpose.
190
+ *
191
+ * Same grammar as spec.md, so the template body has to be unparseable as
192
+ * requirements or the scaffold would ship a requirement nobody wrote — the
193
+ * headings are therefore indented inside an HTML comment, which puts them past
194
+ * the line-anchored `## ADDED Requirements` / `### Requirement:` patterns
195
+ * core/document/parse.ts matches on. Copy the block out of the comment and unindent it.
196
+ */
197
+ export function archSpecTemplate(featureId, service) {
198
+ return `# ${service} — architecture requirement delta for ${featureId}
199
+
200
+ <!-- The architecture axis: the obligations no business scenario was ever going
201
+ to mention — the outbox, the retries, the timeouts, the alerts.
202
+
203
+ \`Covers:\` is to an arch requirement what \`Operations:\` is to a business
204
+ one — it names the MODEL OBJECTS the scenarios below exercise, so coverage
205
+ is derived instead of trusted. Three forms, comma-separated:
206
+
207
+ Covers: ${service} a C4 element — its id, or the service a
208
+ bound/titled element stands for
209
+ Covers: consumer -> ${service} an edge, each side resolved the same way
210
+ Covers: alert:<id>, sli:<id> a signal declared in health.yaml
211
+
212
+ Every tagged element and edge in delta.likec4 wants one (\`c4.uncovered\`).
213
+ Delete this file if ${featureId} adds no architectural obligation.
214
+
215
+ Copy the block below out of this comment and unindent it:
216
+
217
+ ## ADDED Requirements
218
+
219
+ ### Requirement: ${ARCH_REQUIREMENT_SENTINEL}
220
+ Requirement-ID: ${featureId}.${service}.arch
221
+
222
+ The service SHALL ${ARCH_SHALL_SENTINEL}.
223
+
224
+ Covers: ${service}
225
+
226
+ #### Scenario: ${SCENARIO_SENTINEL}
227
+ - **Given** ${GIVEN_SENTINEL}
228
+ - **When** ${WHEN_SENTINEL}
229
+ - **Then** ${THEN_SENTINEL}
230
+ -->
231
+ `;
232
+ }
233
+ /**
234
+ * The BUSINESS axis, scaffolded by `--capability`: a delta against the living
235
+ * `capabilities/<id>/spec.md`.
236
+ *
237
+ * Same idiom as the two spec templates above and for the same reason — the
238
+ * example lives INSIDE an HTML comment, indented past the line-anchored
239
+ * `## ADDED Requirements` / `### Requirement:` patterns core/document/parse.ts
240
+ * matches on — so the scaffold declares nothing until a person copies the block
241
+ * out. That is what lets a freshly scaffolded feature validate clean while
242
+ * still being refused by `loam archive` the moment the block is copied out and
243
+ * left unedited (`scaffold.placeholder`, which reads
244
+ * `features/<FEAT>/capabilities/` as well as `features/<FEAT>/specs/`).
245
+ *
246
+ * WHAT THE COMMENT TEACHES IS THE ALTITUDE, because that is the mistake this
247
+ * document invites. A capability requirement is a promise a customer could
248
+ * check; the four service-scoped lines (`Operations:`, `Covers:`, `Publishes:`,
249
+ * `Consumes:`) and the axis's own two (`Capability:`, `Realizes:`) are all
250
+ * ERRORS here — `capability.requirement-service-scoped` and
251
+ * `capability.requirement-inert-join` — and every one of them parses, so an
252
+ * author who is not told writes one and finds out at archive. `Requirement-ID:`
253
+ * is spelled as mandatory for the same reason: a capability document outlives
254
+ * every service that realizes it, so identity by heading is refused
255
+ * (`capability.requirement-unidentified`).
256
+ *
257
+ * No nested HTML comments, exactly as `specTemplate` states: an inner `-->`
258
+ * would end the outer comment early and re-expose the example to the parser.
259
+ */
260
+ export function capabilityDeltaTemplate(featureId, capability) {
261
+ return `# ${capability} — capability delta for ${featureId}
262
+
263
+ <!-- The business axis. This is a DELTA against the living
264
+ capabilities/${capability}/spec.md, not the document itself: sections are
265
+ ADDED / MODIFIED / REMOVED, delete the ones you do not need, and a MODIFIED
266
+ requirement carries its full new text rather than a diff. If the fleet has
267
+ no capabilities/${capability}/spec.md yet, this feature's archive creates it.
268
+
269
+ WHAT BELONGS HERE is a promise somebody outside the fleet could check —
270
+ 'a refund reaches the customer's card within five days'. What does NOT is
271
+ the mechanism: \`Operations:\`, \`Covers:\`, \`Publishes:\` and \`Consumes:\`
272
+ all resolve against ONE service's own contract or model, so a requirement
273
+ carrying one is a service requirement filed at the wrong altitude and
274
+ \`loam validate\` says so. \`Capability:\` and \`Realizes:\` are refused here
275
+ too — they are the joins written on the SERVICE requirement that keeps this
276
+ promise, one directory over in specs/<svc>/spec.md, as
277
+ \`Realizes: ${capability}#<Requirement-ID>\`.
278
+
279
+ \`Requirement-ID:\` is mandatory on every requirement below. A capability
280
+ document outlives the services that realize it, so its requirements are
281
+ addressed by a stable id rather than by their heading — rewording a heading
282
+ would otherwise be a removal and an addition, and every \`Realizes:\` line
283
+ pointed at it would break in silence.
284
+
285
+ A MODIFIED or REMOVED requirement also needs a \`Based-On:\` pin quoting the
286
+ living text it rewrites, so two features touching one capability collide
287
+ loudly instead of overwriting each other. Run \`loam rebase ${featureId}\`
288
+ and the pins are written for you.
289
+
290
+ Copy the block below out of this comment, unindent it, and replace the TODO
291
+ and every <angle-bracket> fill-in — \`loam archive\` refuses the scaffold's
292
+ own wording (\`scaffold.placeholder\`):
293
+
294
+ ## ADDED Requirements
295
+
296
+ ### Requirement: ${CAPABILITY_REQUIREMENT_SENTINEL}
297
+ Requirement-ID: ${idHint(capability)}
298
+
299
+ The fleet SHALL ${CAPABILITY_SHALL_SENTINEL}.
300
+
301
+ #### Scenario: ${SCENARIO_SENTINEL}
302
+ - **Given** ${GIVEN_SENTINEL}
303
+ - **When** ${WHEN_SENTINEL}
304
+ - **Then** ${THEN_SENTINEL}
305
+ -->
306
+ `;
307
+ }
308
+ /**
309
+ * A plausible `Requirement-ID:` for a capability, offered as a shape rather than
310
+ * a name: `payments/refunds` -> `PAYMENTS-REFUNDS-1`.
311
+ *
312
+ * Deliberately NOT the feature id. A service requirement's scaffolded id is
313
+ * `<FEAT>.<svc>.requirement` because that requirement is created by, and lives
314
+ * and dies with, one feature; a capability requirement outlives every feature
315
+ * that touches it, so an id carrying the id of the feature that happened to
316
+ * introduce it is exactly the identity-by-accident this axis refuses. The
317
+ * suffix is `-1` and not a count of anything: it is a placeholder digit inside
318
+ * a placeholder id, and the author is being asked to name the promise.
319
+ *
320
+ * CHECKED AGAINST THE REAL GRAMMAR, never assumed to satisfy it. A capability id
321
+ * may begin with a digit (`3ds`, `2fa`, `1099-filing` — `dirNameHazard` allows an
322
+ * alphanumeric head) while `REQUIREMENT_ID_RE` demands a LETTER, so the obvious
323
+ * slug hands the author `3DS-1` and `loam validate` then refuses it with
324
+ * `delta.requirement-id-invalid` — the author refused for using the shape the
325
+ * scaffold offered. Length is the same class of failure at 128 characters. The
326
+ * fallback prefixes rather than truncates, because a truncated id is a plausible
327
+ * name that means something else.
328
+ */
329
+ function idHint(capability) {
330
+ const slug = capability.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
331
+ const candidate = `${slug}-1`;
332
+ if (REQUIREMENT_ID_RE.test(candidate))
333
+ return candidate;
334
+ const prefixed = `CAP-${candidate}`;
335
+ return REQUIREMENT_ID_RE.test(prefixed) ? prefixed : "CAP-1";
336
+ }
337
+ export function openapiTemplate(service) {
338
+ return `openapi: 3.1.0
339
+ info:
340
+ title: ${service}
341
+ version: "0.1"
342
+
343
+ # The operations this feature adds. \`operationId\` is the token the C4 edge
344
+ # (metadata { op }) and the requirement (Operations:) both point at — keep the
345
+ # three spellings identical or \`loam validate\` will say so.
346
+ paths: {}
347
+ # /splits:
348
+ # post:
349
+ # operationId: createSplit
350
+ # responses:
351
+ # "201":
352
+ # description: Created
353
+ `;
354
+ }
355
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/commands/new/templates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The six files `loam new` scaffolds, and what each one says.\n *\n * They are here rather than inline because every one of them is a document a\n * person edits next, and the wording IS the instruction: the delta's comments\n * teach the grammar `core/document/parse.ts` matches on, and the intent's\n * frontmatter is serialized rather than interpolated because a title is free\n * text somebody types — `Checkout: split payments` reopens the mapping and the\n * file stops parsing.\n */\nimport { stringify as stringifyYaml } from \"yaml\";\nimport { REQUIREMENT_ID_RE } from \"../../core/document/spec.js\";\nimport {\n ARCH_REQUIREMENT_SENTINEL,\n ARCH_SHALL_SENTINEL,\n CAPABILITY_REQUIREMENT_SENTINEL,\n CAPABILITY_SHALL_SENTINEL,\n REQUIREMENT_SENTINEL,\n SCENARIO_SENTINEL,\n SERVICE_DESCRIPTION_SENTINEL,\n GIVEN_SENTINEL,\n SHALL_SENTINEL,\n THEN_SENTINEL,\n WHEN_SENTINEL,\n} from \"../../core/coherence/authoring/sentinels.js\";\n\nfunction identifier(name: string, taken: Set<string>): string {\n const parts = name.split(/[^A-Za-z0-9]+/).filter((p) => p.length > 0);\n const head = (parts[0] ?? \"svc\").replace(/^\\d+/, \"\") || \"svc\";\n const base =\n head[0]!.toLowerCase() +\n head.slice(1) +\n parts.slice(1).map((p) => p[0]!.toUpperCase() + p.slice(1)).join(\"\");\n let id = base;\n for (let n = 2; taken.has(id); n += 1) id = `${base}${n}`;\n taken.add(id);\n return id;\n}\n\n/** `FEAT-101` -> `feat_101`: LikeC4 view names take no dashes. */\nfunction viewName(featureId: string): string {\n return featureId.toLowerCase().replace(/[^a-z0-9]+/g, \"_\");\n}\n\n/* ------------------------------------------------------------------ */\n/* Templates */\n/* ------------------------------------------------------------------ */\n\nexport function intentTemplate(featureId: string, title: string | undefined): string {\n const heading = title ?? featureId;\n // The frontmatter is SERIALIZED, never interpolated. A title is free text a\n // person types — `Checkout: split payments` used to be pasted straight after\n // `title: `, where the colon reopens the mapping and the file stops parsing;\n // `Orders #42 rework` lost everything from the `#` as a YAML comment, so the\n // feature quietly answered to a title nobody wrote. The yaml serializer is the\n // only thing that knows every case, and it is already how migrate-openspec\n // writes the same three keys.\n //\n // `owner` is deliberately NOT a key here. Scaffolding `owner:` writes an\n // explicit null, which is a claim (\"this feature has no owner\") rather than\n // the truth (\"nobody has said yet\") — and every reader then has to tell an\n // absent key from a null one. The prompt survives as a comment.\n const frontmatter = stringifyYaml(\n { feature: featureId, ...(title === undefined ? {} : { title }), status: \"proposed\" },\n { lineWidth: 0 },\n ).trimEnd();\n return `---\n${frontmatter}\n# owner: <the team or person who answers for this>\n---\n\n# ${heading}\n\n## Why\n\n<!-- The problem in business terms. This is what a reviewer reads first, and what\n the requirement deltas below have to be justified by. -->\n\n## Scope\n\n<!-- Which services this touches — and, more usefully, which it deliberately does not. -->\n`;\n}\n\nexport function deltaTemplate(featureId: string, touched: string[], created: string[]): string {\n const taken = new Set<string>();\n const touchedIds = touched.map((s) => [identifier(s, taken), s] as const);\n const createdIds = created.map((s) => [identifier(s, taken), s] as const);\n\n const lines: string[] = [];\n if (touchedIds.length > 0) {\n // Context elements ship COMMENTED OUT, exactly like the example edge below.\n // Declared-and-untagged is the one shape `delta.nothing-tagged` exists to\n // refuse (an author who forgot the tags), so writing these live made every\n // `loam new --touches X` fail its own validator on the very first run —\n // which teaches people that the validator is noise. Uncomment a line when an\n // edge below actually needs the identifier.\n lines.push(\" // Services this feature touches, as context for the diagram. Uncomment the\");\n lines.push(\" // ones an edge below names, and reuse the identifiers from\");\n lines.push(\" // architecture/landscape.likec4 so the merge lines up. They stay UNTAGGED:\");\n lines.push(` // only #${featureId} is the change, and everything else here is context.`);\n for (const [id, name] of touchedIds) lines.push(` // ${id} = softwareSystem '${name}'`);\n lines.push(\"\");\n }\n if (createdIds.length > 0) {\n lines.push(\" // Services this feature introduces. The tag is what `loam archive` folds\");\n lines.push(\" // into the living landscape.\");\n for (const [id, name] of createdIds) {\n lines.push(` ${id} = softwareSystem '${name}' {`);\n lines.push(` #${featureId}`);\n // Interpolated from sentinels.ts, never spelled here: the placeholder\n // gate refuses this exact string at archive, and a second spelling is\n // how a template rewording silently retires the check that guards it.\n lines.push(` description '${SERVICE_DESCRIPTION_SENTINEL}'`);\n lines.push(\" }\");\n }\n lines.push(\"\");\n }\n\n // The example edge uses identifiers already declared above when there are two\n // to join; otherwise placeholders, so it never invents a service.\n const from = touchedIds[0]?.[0] ?? createdIds[0]?.[0] ?? \"consumer\";\n const to = createdIds[0]?.[0] ?? touchedIds[1]?.[0] ?? \"provider\";\n lines.push(\" // New calls. `metadata { op }` is the spine: it names the OpenAPI operationId\");\n lines.push(\" // the call uses, and `loam validate` checks it against the target's contract.\");\n lines.push(\" // Uncomment and adjust (both endpoints have to be declared above):\");\n lines.push(\" //\");\n lines.push(` // ${from} -> ${to} 'Calls createSplit' {`);\n lines.push(` // #${featureId}`);\n lines.push(\" // metadata { op 'createSplit' }\");\n lines.push(\" // }\");\n\n return `// ${featureId} — architecture delta.\n//\n// Everything tagged #${featureId} is exactly what \\`loam archive\\` folds into\n// architecture/landscape.likec4. Everything else here is context for the diagram.\n//\n// If ${featureId} changes no architecture, DELETE this file: a requirements-only\n// feature is complete without it, and an empty \\`model {}\\` is just as legal.\n\nspecification {\n element softwareSystem\n tag ${featureId}\n}\n\nmodel {\n${lines.join(\"\\n\")}\n}\n\nviews {\n view ${viewName(featureId)} {\n include *\n }\n}\n`;\n}\n\n/**\n * The business axis, scaffolded with its example INSIDE the comment — the same\n * mechanism as `archSpecTemplate` below, for the same reason: this template\n * used to ship a live `### Requirement: TODO — name the behaviour` that\n * parsed as a real requirement, validated clean, and archived a literal TODO\n * into the living spec at exit 0. The indented headings sit past the\n * line-anchored patterns core/document/parse.ts matches on, so the scaffold\n * declares nothing until a person copies the block out and writes over the\n * fill-ins — and `scaffold.placeholder` gates the archive if the fill-ins\n * survive the copy.\n *\n * No nested HTML comments: the `Operations:` guidance that used to be its own\n * comment lives in this one's prose, because an inner `-->` would end the\n * outer comment early and re-expose the example to the parser.\n */\nexport function specTemplate(featureId: string, service: string): string {\n return `# ${service} — requirement delta for ${featureId}\n\n<!-- Sections: ADDED / MODIFIED / REMOVED. Delete the ones you do not need.\n A MODIFIED requirement carries its full new text, not a diff.\n Every requirement needs at least one scenario — \\`loam validate\\` gates on it.\n\n An \\`Operations:\\` body line names the operationIds the requirement\n governs. \\`loam validate\\` checks each one against the service's OpenAPI,\n and \\`loam archive\\` refuses to merge a requirement that governs an\n operation nobody defines — add the line once the contract exists.\n\n A \\`Publishes:\\` / \\`Consumes:\\` body line does the same for event messages:\n it names what the requirement puts on or takes off the bus, checked\n against the service's AsyncAPI contract. If ${featureId} changes that\n contract, hand-create specs/${service}/asyncapi.yaml — a complete\n AsyncAPI 3.0 document, messages declared under \\`components.messages\\`\n (loam scaffolds none: an event contract is genuinely optional) — and run\n \\`loam rebase ${featureId}\\` so every restated slot pins as a quote.\n\n Copy the block below out of this comment, unindent it, and replace the\n TODOs and every <angle-bracket> fill-in — \\`loam archive\\` refuses the\n scaffold's own wording (\\`scaffold.placeholder\\`):\n\n ## ADDED Requirements\n\n ### Requirement: ${REQUIREMENT_SENTINEL}\n Requirement-ID: ${featureId}.${service}.requirement\n\n The service SHALL ${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 * The architecture axis, scaffolded EMPTY on purpose.\n *\n * Same grammar as spec.md, so the template body has to be unparseable as\n * requirements or the scaffold would ship a requirement nobody wrote — the\n * headings are therefore indented inside an HTML comment, which puts them past\n * the line-anchored `## ADDED Requirements` / `### Requirement:` patterns\n * core/document/parse.ts matches on. Copy the block out of the comment and unindent it.\n */\nexport function archSpecTemplate(featureId: string, service: string): string {\n return `# ${service} — architecture requirement delta for ${featureId}\n\n<!-- The architecture axis: the obligations no business scenario was ever going\n to mention — the outbox, the retries, the timeouts, the alerts.\n\n \\`Covers:\\` is to an arch requirement what \\`Operations:\\` is to a business\n one — it names the MODEL OBJECTS the scenarios below exercise, so coverage\n is derived instead of trusted. Three forms, comma-separated:\n\n Covers: ${service} a C4 element — its id, or the service a\n bound/titled element stands for\n Covers: consumer -> ${service} an edge, each side resolved the same way\n Covers: alert:<id>, sli:<id> a signal declared in health.yaml\n\n Every tagged element and edge in delta.likec4 wants one (\\`c4.uncovered\\`).\n Delete this file if ${featureId} adds no architectural obligation.\n\n Copy the block below out of this comment and unindent it:\n\n ## ADDED Requirements\n\n ### Requirement: ${ARCH_REQUIREMENT_SENTINEL}\n Requirement-ID: ${featureId}.${service}.arch\n\n The service SHALL ${ARCH_SHALL_SENTINEL}.\n\n Covers: ${service}\n\n #### Scenario: ${SCENARIO_SENTINEL}\n - **Given** ${GIVEN_SENTINEL}\n - **When** ${WHEN_SENTINEL}\n - **Then** ${THEN_SENTINEL}\n-->\n`;\n}\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 above and 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` 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\nexport function openapiTemplate(service: string): string {\n return `openapi: 3.1.0\ninfo:\n title: ${service}\n version: \"0.1\"\n\n# The operations this feature adds. \\`operationId\\` is the token the C4 edge\n# (metadata { op }) and the requirement (Operations:) both point at — keep the\n# three spellings identical or \\`loam validate\\` will say so.\npaths: {}\n# /splits:\n# post:\n# operationId: createSplit\n# responses:\n# \"201\":\n# description: Created\n`;\n}\n"]}
@@ -0,0 +1,203 @@
1
+ import { statSync } from "node:fs";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { dirname, resolve } from "node:path";
4
+ import { loadConfig } from "../core/envelope/config.js";
5
+ import { emitJson, fail, reportNoConfig } from "../core/envelope/json.js";
6
+ import { DocsRepoUnavailableError } from "../core/repo/state.js";
7
+ import { discoverMembers } from "../core/workspace/discover.js";
8
+ import { renderWorkspace } from "../core/workspace/render.js";
9
+ import { docsRepoReady, reportDocsRepoError } from "./policy/gate.js";
10
+ /**
11
+ * A fixed default name, not `<basename(docsDir)>.code-workspace`: predictable
12
+ * beats collision-proof for a file that is regenerable on demand, and `--out`
13
+ * is the lever for anyone holding two fleets' workspaces in one directory.
14
+ */
15
+ const DEFAULT_OUT = "loam.code-workspace";
16
+ /**
17
+ * Is there a directory at `path`? A probe that cannot answer says "no": for a
18
+ * value the user typed, "cannot be read" earns the same `invalid-option`
19
+ * refusal as "is not there" — never an `internal` escape. throwIfNoEntry folds
20
+ * the absent case into the same answer with no window between probe and use;
21
+ * the catch covers the errnos statSync can still raise (EACCES, ENOTDIR — a
22
+ * path routed through a file).
23
+ */
24
+ function directoryAt(path) {
25
+ try {
26
+ return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /** The write-path errnos that mean the OUT PATH is wrong, not loam. */
33
+ const OUT_PATH_ERRNOS = new Set(["EISDIR", "EACCES", "EPERM", "ENOTDIR", "EINVAL"]);
34
+ export function registerOpen(program) {
35
+ program
36
+ .command("open")
37
+ .description("Write a .code-workspace joining the docs repo and every bound service checkout")
38
+ .option("--root <dir>", "scan this directory for bound checkouts instead of the defaults; repeatable", (value, prev = []) => [...prev, value])
39
+ .option("--out <file>", `workspace file to write (default ${DEFAULT_OUT} in the current directory)`)
40
+ .option("--force", "overwrite the workspace file if it already exists")
41
+ .option("--json", "emit the machine contract instead of the human view")
42
+ .action(async (opts) => {
43
+ const json = opts.json === true;
44
+ const loaded = await loadConfig();
45
+ if (loaded.kind !== "loaded") {
46
+ reportNoConfig(json, loaded);
47
+ return;
48
+ }
49
+ const config = loaded.config;
50
+ const { docsDir } = config;
51
+ // "docs", not "services": this command enumerates checkouts on the LOCAL
52
+ // filesystem, not services/ in the docs repo, and a docs repo nobody has
53
+ // run `loam adopt` against is still worth opening an editor on.
54
+ if (!docsRepoReady(json, docsDir, "docs"))
55
+ return;
56
+ // Both typed paths are validated before any discovery runs: a --root or
57
+ // --out typo is a fact about the INVOCATION, not the fleet, and it must
58
+ // refuse before the scan — not after verdicts were computed over
59
+ // directories nobody meant, and not from the write at the very end.
60
+ const roots = (opts.root ?? []).map((dir) => resolve(dir));
61
+ for (const dir of roots) {
62
+ if (!directoryAt(dir)) {
63
+ fail(json, "invalid-option", `--root does not name a readable directory: ${dir}`);
64
+ return;
65
+ }
66
+ }
67
+ const out = resolve(opts.out ?? DEFAULT_OUT);
68
+ const parent = dirname(out);
69
+ if (!directoryAt(parent)) {
70
+ fail(json, "invalid-option", `--out points into a directory that does not exist: ${parent}`);
71
+ return;
72
+ }
73
+ let discovery;
74
+ try {
75
+ discovery = await discoverMembers({
76
+ docsDir,
77
+ // parseConfig always derives `root`; the fallback only documents
78
+ // which absence the optional type would otherwise leave a guess.
79
+ configRoot: config.root ?? process.cwd(),
80
+ ...(config.service === undefined ? {} : { service: config.service }),
81
+ ...(roots.length === 0 ? {} : { roots }),
82
+ });
83
+ }
84
+ catch (err) {
85
+ // The gate above passed, so this is the repo vanishing mid-run —
86
+ // the same breach the enumeration reports, spelled the same way.
87
+ if (!(err instanceof DocsRepoUnavailableError))
88
+ throw err;
89
+ reportDocsRepoError(json, err);
90
+ return;
91
+ }
92
+ // An explicit --root that cannot be LISTED refuses: the caller named the
93
+ // directory, so "I could not look" must never come back as an empty
94
+ // scan. The default roots keep degrading to empty — a CI sandbox that
95
+ // denies the docs repo's parent is survivable, and both views still
96
+ // report the degrade below.
97
+ if (roots.length > 0 && discovery.unreadableRoots.length > 0) {
98
+ const listing = discovery.unreadableRoots
99
+ .map((entry) => `${entry.root} (${entry.problem})`)
100
+ .join("; ");
101
+ fail(json, "invalid-option", `--root cannot be scanned: ${listing}`);
102
+ return;
103
+ }
104
+ if (discovery.duplicates.length > 0) {
105
+ const claims = discovery.duplicates.map((d) => `'${d.service}' is declared by ${d.paths.join(" and ")}`);
106
+ fail(json, "binding-duplicate", `Two checkouts declare the same service for this docs repo: ${claims.join("; ")}. ` +
107
+ "loam will not guess which checkout speaks for the service — narrow the scan " +
108
+ "with --root, or fix the stray loam.json, then re-run.");
109
+ return;
110
+ }
111
+ // Only reachable from the docs repo itself — a service-repo invocation
112
+ // always contributes its own checkout as a member. Skipped candidates
113
+ // ride the message: a checkout whose binding would not parse is the
114
+ // likeliest reason the scan came up empty, and the real fix is that
115
+ // file, not --root.
116
+ if (!discovery.members.some((member) => member.via !== "docs-repo")) {
117
+ const unread = discovery.skipped.length === 0
118
+ ? ""
119
+ : ` ${discovery.skipped.length} candidate binding(s) could not be read and did not` +
120
+ ` count: ${discovery.skipped.map((s) => `${s.path} (${s.problem})`).join("; ")}.`;
121
+ fail(json, "no-members", `No service checkout bound to ${docsDir} was found under ` +
122
+ `${discovery.scannedRoots.join(", ")} — the workspace would hold only the docs ` +
123
+ "repo. Clone a bound service repo beside it, run `loam open` from a service " +
124
+ `repo, or pass --root naming the directory the checkouts live in.${unread}`);
125
+ return;
126
+ }
127
+ const rendered = renderWorkspace(discovery.members, out);
128
+ try {
129
+ // "wx" makes probe-and-write one operation, so a concurrent writer
130
+ // cannot land between an existsSync and the write. --force is honest
131
+ // here where init's generated files needed skip-forever: a workspace
132
+ // file is wholly derived, so overwriting it loses nothing authored.
133
+ await writeFile(out, rendered.text, {
134
+ encoding: "utf8",
135
+ flag: opts.force === true ? "w" : "wx",
136
+ });
137
+ }
138
+ catch (err) {
139
+ const e = err;
140
+ if (e.code === "EEXIST") {
141
+ fail(json, "already-exists", `${out} already exists. Pass --out naming another file, or --force to overwrite it.`);
142
+ return;
143
+ }
144
+ // The path-shaped failures: --out names a directory (EISDIR — the
145
+ // exact target the EEXIST remedy above just told the caller to
146
+ // --force), or a place this process may not write. Each is a fact
147
+ // about the out path, so it earns invalid-option, not internal.
148
+ if (typeof e.code === "string" && OUT_PATH_ERRNOS.has(e.code)) {
149
+ fail(json, "invalid-option", `The workspace file cannot be written at ${out}: ${e.message}. ` +
150
+ "Pass --out naming a writable file path.");
151
+ return;
152
+ }
153
+ throw err;
154
+ }
155
+ if (json) {
156
+ emitJson({
157
+ command: "open",
158
+ docsDir,
159
+ out,
160
+ written: true,
161
+ members: rendered.folders.map((folder) => ({
162
+ path: folder.member.path,
163
+ // The exact spelling written into the workspace file, so a
164
+ // consumer never re-derives (and mis-derives) the relative form.
165
+ folder: folder.path,
166
+ name: folder.name,
167
+ service: folder.member.service,
168
+ via: folder.member.via,
169
+ ...(folder.member.via === "scan" ? { root: folder.member.root } : {}),
170
+ })),
171
+ scannedRoots: discovery.scannedRoots,
172
+ unreadableRoots: discovery.unreadableRoots,
173
+ skipped: discovery.skipped,
174
+ });
175
+ return;
176
+ }
177
+ printWorkspace(out, discovery, rendered.folders);
178
+ });
179
+ }
180
+ function viaText(folder) {
181
+ const member = folder.member;
182
+ if (member.via === "docs-repo")
183
+ return "docs repo";
184
+ if (member.via === "current-repo")
185
+ return "this repo";
186
+ return `found under ${member.root}`;
187
+ }
188
+ function printWorkspace(out, discovery, folders) {
189
+ console.log(`workspace: ${out} (${folders.length} folders)`);
190
+ for (const folder of folders) {
191
+ console.log(` ${folder.name} ${folder.path} (${viaText(folder)})`);
192
+ }
193
+ // Reported beside the result, not folded into it: an unreadable binding or
194
+ // an unlistable default root proves nothing, but silence about either would
195
+ // read as "not bound" / "nothing there".
196
+ for (const entry of discovery.unreadableRoots) {
197
+ console.log(` ! could not scan ${entry.root}: ${entry.problem}`);
198
+ }
199
+ for (const entry of discovery.skipped) {
200
+ console.log(` ! skipped ${entry.path}: ${entry.problem}`);
201
+ }
202
+ }
203
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { statSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { loadConfig } from \"../core/envelope/config.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { discoverMembers, type Discovery } from \"../core/workspace/discover.js\";\nimport { renderWorkspace, type RenderedFolder } from \"../core/workspace/render.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"./policy/gate.js\";\n\ninterface OpenOptions {\n json?: boolean;\n /** --root, collected: each value REPLACES the default scan roots. */\n root?: string[];\n out?: string;\n force?: boolean;\n}\n\n/**\n * A fixed default name, not `<basename(docsDir)>.code-workspace`: predictable\n * beats collision-proof for a file that is regenerable on demand, and `--out`\n * is the lever for anyone holding two fleets' workspaces in one directory.\n */\nconst DEFAULT_OUT = \"loam.code-workspace\";\n\n/**\n * Is there a directory at `path`? A probe that cannot answer says \"no\": for a\n * value the user typed, \"cannot be read\" earns the same `invalid-option`\n * refusal as \"is not there\" — never an `internal` escape. throwIfNoEntry folds\n * the absent case into the same answer with no window between probe and use;\n * the catch covers the errnos statSync can still raise (EACCES, ENOTDIR — a\n * path routed through a file).\n */\nfunction directoryAt(path: string): boolean {\n try {\n return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;\n } catch {\n return false;\n }\n}\n\n/** The write-path errnos that mean the OUT PATH is wrong, not loam. */\nconst OUT_PATH_ERRNOS = new Set([\"EISDIR\", \"EACCES\", \"EPERM\", \"ENOTDIR\", \"EINVAL\"]);\n\nexport function registerOpen(program: Command): void {\n program\n .command(\"open\")\n .description(\"Write a .code-workspace joining the docs repo and every bound service checkout\")\n .option(\n \"--root <dir>\",\n \"scan this directory for bound checkouts instead of the defaults; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--out <file>\", `workspace file to write (default ${DEFAULT_OUT} in the current directory)`)\n .option(\"--force\", \"overwrite the workspace file if it already exists\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: OpenOptions) => {\n const json = opts.json === true;\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // \"docs\", not \"services\": this command enumerates checkouts on the LOCAL\n // filesystem, not services/ in the docs repo, and a docs repo nobody has\n // run `loam adopt` against is still worth opening an editor on.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n\n // Both typed paths are validated before any discovery runs: a --root or\n // --out typo is a fact about the INVOCATION, not the fleet, and it must\n // refuse before the scan — not after verdicts were computed over\n // directories nobody meant, and not from the write at the very end.\n const roots = (opts.root ?? []).map((dir) => resolve(dir));\n for (const dir of roots) {\n if (!directoryAt(dir)) {\n fail(json, \"invalid-option\", `--root does not name a readable directory: ${dir}`);\n return;\n }\n }\n const out = resolve(opts.out ?? DEFAULT_OUT);\n const parent = dirname(out);\n if (!directoryAt(parent)) {\n fail(json, \"invalid-option\", `--out points into a directory that does not exist: ${parent}`);\n return;\n }\n\n let discovery: Discovery;\n try {\n discovery = await discoverMembers({\n docsDir,\n // parseConfig always derives `root`; the fallback only documents\n // which absence the optional type would otherwise leave a guess.\n configRoot: config.root ?? process.cwd(),\n ...(config.service === undefined ? {} : { service: config.service }),\n ...(roots.length === 0 ? {} : { roots }),\n });\n } catch (err) {\n // The gate above passed, so this is the repo vanishing mid-run —\n // the same breach the enumeration reports, spelled the same way.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n reportDocsRepoError(json, err);\n return;\n }\n\n // An explicit --root that cannot be LISTED refuses: the caller named the\n // directory, so \"I could not look\" must never come back as an empty\n // scan. The default roots keep degrading to empty — a CI sandbox that\n // denies the docs repo's parent is survivable, and both views still\n // report the degrade below.\n if (roots.length > 0 && discovery.unreadableRoots.length > 0) {\n const listing = discovery.unreadableRoots\n .map((entry) => `${entry.root} (${entry.problem})`)\n .join(\"; \");\n fail(json, \"invalid-option\", `--root cannot be scanned: ${listing}`);\n return;\n }\n\n if (discovery.duplicates.length > 0) {\n const claims = discovery.duplicates.map(\n (d) => `'${d.service}' is declared by ${d.paths.join(\" and \")}`,\n );\n fail(\n json,\n \"binding-duplicate\",\n `Two checkouts declare the same service for this docs repo: ${claims.join(\"; \")}. ` +\n \"loam will not guess which checkout speaks for the service — narrow the scan \" +\n \"with --root, or fix the stray loam.json, then re-run.\",\n );\n return;\n }\n // Only reachable from the docs repo itself — a service-repo invocation\n // always contributes its own checkout as a member. Skipped candidates\n // ride the message: a checkout whose binding would not parse is the\n // likeliest reason the scan came up empty, and the real fix is that\n // file, not --root.\n if (!discovery.members.some((member) => member.via !== \"docs-repo\")) {\n const unread = discovery.skipped.length === 0\n ? \"\"\n : ` ${discovery.skipped.length} candidate binding(s) could not be read and did not` +\n ` count: ${discovery.skipped.map((s) => `${s.path} (${s.problem})`).join(\"; \")}.`;\n fail(\n json,\n \"no-members\",\n `No service checkout bound to ${docsDir} was found under ` +\n `${discovery.scannedRoots.join(\", \")} — the workspace would hold only the docs ` +\n \"repo. Clone a bound service repo beside it, run `loam open` from a service \" +\n `repo, or pass --root naming the directory the checkouts live in.${unread}`,\n );\n return;\n }\n\n const rendered = renderWorkspace(discovery.members, out);\n try {\n // \"wx\" makes probe-and-write one operation, so a concurrent writer\n // cannot land between an existsSync and the write. --force is honest\n // here where init's generated files needed skip-forever: a workspace\n // file is wholly derived, so overwriting it loses nothing authored.\n await writeFile(out, rendered.text, {\n encoding: \"utf8\",\n flag: opts.force === true ? \"w\" : \"wx\",\n });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"EEXIST\") {\n fail(\n json,\n \"already-exists\",\n `${out} already exists. Pass --out naming another file, or --force to overwrite it.`,\n );\n return;\n }\n // The path-shaped failures: --out names a directory (EISDIR — the\n // exact target the EEXIST remedy above just told the caller to\n // --force), or a place this process may not write. Each is a fact\n // about the out path, so it earns invalid-option, not internal.\n if (typeof e.code === \"string\" && OUT_PATH_ERRNOS.has(e.code)) {\n fail(\n json,\n \"invalid-option\",\n `The workspace file cannot be written at ${out}: ${e.message}. ` +\n \"Pass --out naming a writable file path.\",\n );\n return;\n }\n throw err;\n }\n\n if (json) {\n emitJson({\n command: \"open\",\n docsDir,\n out,\n written: true,\n members: rendered.folders.map((folder) => ({\n path: folder.member.path,\n // The exact spelling written into the workspace file, so a\n // consumer never re-derives (and mis-derives) the relative form.\n folder: folder.path,\n name: folder.name,\n service: folder.member.service,\n via: folder.member.via,\n ...(folder.member.via === \"scan\" ? { root: folder.member.root } : {}),\n })),\n scannedRoots: discovery.scannedRoots,\n unreadableRoots: discovery.unreadableRoots,\n skipped: discovery.skipped,\n });\n return;\n }\n printWorkspace(out, discovery, rendered.folders);\n });\n}\n\nfunction viaText(folder: RenderedFolder): string {\n const member = folder.member;\n if (member.via === \"docs-repo\") return \"docs repo\";\n if (member.via === \"current-repo\") return \"this repo\";\n return `found under ${member.root}`;\n}\n\nfunction printWorkspace(out: string, discovery: Discovery, folders: RenderedFolder[]): void {\n console.log(`workspace: ${out} (${folders.length} folders)`);\n for (const folder of folders) {\n console.log(` ${folder.name} ${folder.path} (${viaText(folder)})`);\n }\n // Reported beside the result, not folded into it: an unreadable binding or\n // an unlistable default root proves nothing, but silence about either would\n // read as \"not bound\" / \"nothing there\".\n for (const entry of discovery.unreadableRoots) {\n console.log(` ! could not scan ${entry.root}: ${entry.problem}`);\n }\n for (const entry of discovery.skipped) {\n console.log(` ! skipped ${entry.path}: ${entry.problem}`);\n }\n}\n"]}