@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
@@ -1,79 +1,27 @@
1
- import { existsSync, lstatSync } from "node:fs";
2
- import { readdir, readFile, rename, rmdir } from "node:fs/promises";
3
- import { dirname, join, relative } from "node:path";
4
- import { loadConfig } from "../core/config.js";
5
- import { emitJson, fail, repoPath, reportNoConfig } from "../core/json.js";
6
- import { resolveInside, resolvePortableFileInside } from "../core/path-safety.js";
7
- import { isRecord } from "../core/records.js";
8
- import { archiveDir as archiveRoot, featuresDir as featuresRoot, resolveFeature } from "../core/repo.js";
9
- import { sayRecovery } from "./archive.js";
10
- import { acquireDocsLock, clearCommitIntent, DocsBusyError, InterruptedCommitError, message, planWrite, quietRm, recoverInterruptedCommit, rollbackError, rollbackStaged, sha256, stageWrites, swapStaged, writeCommitIntent, SNAPSHOT_DIR, SNAPSHOT_MANIFEST, SNAPSHOT_VERSION, } from "../core/staging.js";
11
1
  /**
12
- * A commit-phase failure with its stable `--json` code attached — the mirror of
13
- * archive's discipline. `restore-failed` answers "can I trust the repo?" with
14
- * yes: nothing was restored, or everything was rolled back, and re-running can
15
- * work. `rollback-incomplete` is the code that demands a human: the restore
16
- * failed AND some files could not be put back — the message lists them.
17
- * Anything ELSE that escapes runUnarchive never touched the commit phase, so
18
- * the action handler reports it as `restore-failed`.
2
+ * Putting the docs back: read the snapshot, verify every pre-image, then write.
3
+ *
4
+ * The whole restore runs under the docs lock, and the verification is the point
5
+ * of the split — `unarchive` says "I put your docs back", and that sentence must
6
+ * never be sayable over a pre-image that was edited in the archived feature
7
+ * directory since. Each entry carries both the digest of what archive WROTE and
8
+ * the digest of what it will RESTORE, and a mismatch on either refuses.
19
9
  */
20
- class RestoreFailure extends Error {
21
- code;
22
- constructor(code, msg) {
23
- super(msg);
24
- this.code = code;
25
- }
26
- }
27
- export function registerUnarchive(program) {
28
- program
29
- .command("unarchive")
30
- .argument("<featureId>", "feature id, e.g. FEAT-101")
31
- .description("Take back a `loam archive`: restore the living docs and re-open the feature")
32
- .option("--json", "emit the machine contract instead of the human view")
33
- .option("--force", "restore even if the living docs changed after the archive (discards those changes)")
34
- .action(async (featureId, opts) => {
35
- const json = opts.json === true;
36
- try {
37
- await runUnarchive(featureId, json, opts.force === true);
38
- }
39
- catch (err) {
40
- const code = err instanceof RestoreFailure
41
- ? err.code
42
- : err instanceof InterruptedCommitError
43
- ? "commit-interrupted"
44
- : "restore-failed";
45
- fail(json, code, `unarchive ${featureId} failed: ${message(err)}`);
46
- }
47
- });
48
- }
49
- async function runUnarchive(featureId, json, force) {
50
- const config = await loadConfig();
51
- if (!config) {
52
- reportNoConfig(json);
53
- return;
54
- }
55
- // The same lock archive takes, for the same reason: unarchive rewrites the
56
- // living spec, contract and landscape from a snapshot, and an archive landing
57
- // inside that window would have its merge silently reverted by our restore —
58
- // or ours by its merge. One writer per docs repo, whichever direction it
59
- // writes in.
60
- let release;
61
- try {
62
- release = await acquireDocsLock(config.docsDir);
63
- }
64
- catch (err) {
65
- if (err instanceof DocsBusyError)
66
- throw new RestoreFailure("docs-busy", err.message);
67
- throw err;
68
- }
69
- try {
70
- await unarchiveLocked(config.docsDir, featureId, json, force);
71
- }
72
- finally {
73
- await release();
74
- }
75
- }
76
- async function unarchiveLocked(docsDir, featureId, json, force) {
10
+ import { existsSync } from "node:fs";
11
+ import { readdir, readFile, rename, rmdir } from "node:fs/promises";
12
+ import { dirname, join, relative } from "node:path";
13
+ import { emitJson, fail, repoPath } from "../../core/envelope/json.js";
14
+ import { archiveDir as archiveRoot, featuresDir as featuresRoot } from "../../core/repo/paths.js";
15
+ import { listServices, resolveFeature } from "../../core/repo/repo.js";
16
+ import { docsRepoState } from "../../core/repo/state.js";
17
+ import { sayRecovery } from "../archive/plan/refusal.js";
18
+ import { quietRm, rollbackError, rollbackStaged, stageWrites, swapStaged, } from "../../core/staging/commit.js";
19
+ import { clearCommitIntent, writeCommitIntent } from "../../core/staging/recovery/intent.js";
20
+ import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
21
+ import { SNAPSHOT_DIR } from "../../core/staging/snapshot.js";
22
+ import { planWrite, sha256 } from "../../core/staging/writes.js";
23
+ import { readManifest, RestoreFailure } from "./manifest.js";
24
+ export async function unarchiveLocked(docsDir, featureId, json, force) {
77
25
  // Same first move as archive, under the same lock: a commit killed between two
78
26
  // renames is exactly the state an undo must not be computed on top of.
79
27
  const recovered = await recoverInterruptedCommit(docsDir);
@@ -97,7 +45,24 @@ async function unarchiveLocked(docsDir, featureId, json, force) {
97
45
  fail(json, "feature-active", `unarchive ${feature.id} — BLOCKED: features/${name} already exists. Remove or rename it, then re-run.`);
98
46
  return;
99
47
  }
100
- const manifest = await readManifest(feature.dir, docsDir, feature.id, feature.dirName);
48
+ // The restore-time half of the snapshot's (service, artifact) re-keying: a
49
+ // version-3 entry restores into wherever its service lives TODAY, and the
50
+ // enumeration is the only thing that may say where that is. Built once here,
51
+ // as a map, so the manifest validation stays a pure question of one file
52
+ // against one repo state. "no-services" is tolerated the way `listFeatures`
53
+ // tolerates it: a docs repo whose `services/` is gone still restores by the
54
+ // literal paths (every id resolves to null), and it is `doctor`'s job to say
55
+ // the directory is missing — refusing the undo here would add a second,
56
+ // contradictory diagnosis.
57
+ const enumerated = docsRepoState(docsDir).kind === "ok" ? await listServices(docsDir) : [];
58
+ const serviceDirs = new Map(enumerated.map((s) => [s.id, repoPath(docsDir, s.dir)]));
59
+ const manifest = await readManifest({
60
+ featureDir: feature.dir,
61
+ docsDir,
62
+ featureId: feature.id,
63
+ dirName: feature.dirName,
64
+ serviceDirOf: (service) => serviceDirs.get(service) ?? null,
65
+ });
101
66
  if (manifest === null) {
102
67
  fail(json, "snapshot-missing", `unarchive ${feature.id} — BLOCKED: features/archive/${feature.dirName}/${SNAPSHOT_DIR}/ is missing or was written by a different loam. ` +
103
68
  `It was archived before loam recorded what it overwrote, and the previous text of a MODIFIED requirement exists nowhere else — ` +
@@ -196,6 +161,7 @@ async function unarchiveLocked(docsDir, featureId, json, force) {
196
161
  const removed = writes.filter((w) => w.content === null).map((w) => repoPath(docsDir, w.path));
197
162
  if (json) {
198
163
  emitJson({
164
+ command: "unarchive",
199
165
  feature: feature.id,
200
166
  dirName: feature.dirName,
201
167
  path: repoPath(docsDir, dest),
@@ -213,90 +179,11 @@ async function unarchiveLocked(docsDir, featureId, json, force) {
213
179
  console.log(`\n reopened: features/archive/${feature.dirName} → features/${feature.dirName}`);
214
180
  console.log(" the living docs are back to what they said before the archive.");
215
181
  }
216
- async function readManifest(featureDir, docsDir, featureId, dirName) {
217
- try {
218
- const path = resolveInside(featureDir, join(SNAPSHOT_DIR, SNAPSHOT_MANIFEST), "snapshot manifest path");
219
- if (!existsSync(path))
220
- return null;
221
- const parsed = JSON.parse(await readFile(path, "utf8"));
222
- if (!isRecord(parsed))
223
- return null;
224
- if (parsed.version !== SNAPSHOT_VERSION)
225
- return null;
226
- if (parsed.feature !== featureId || parsed.dirName !== dirName)
227
- return null;
228
- if (typeof parsed.archivedAt !== "string" || !isCanonicalIsoDate(parsed.archivedAt))
229
- return null;
230
- if (!Array.isArray(parsed.files))
231
- return null;
232
- const seen = new Set();
233
- const files = [];
234
- for (const raw of parsed.files) {
235
- if (!isRecord(raw))
236
- return null;
237
- if (typeof raw.path !== "string" || typeof raw.existed !== "boolean")
238
- return null;
239
- if (typeof raw.after !== "string" || !/^[0-9a-f]{64}$/.test(raw.after))
240
- return null;
241
- // A manifest that claims a pre-image must say what it should hash to, and
242
- // one that claims none must not. Either way it is a shape question, so it
243
- // is answered here with the rest of them; whether the bytes MATCH is a
244
- // different answer with its own code (`snapshot-corrupt`).
245
- if (raw.existed ? typeof raw.before !== "string" || !/^[0-9a-f]{64}$/.test(raw.before) : raw.before !== null) {
246
- return null;
247
- }
248
- const before = raw.existed ? raw.before : null;
249
- if (seen.has(raw.path))
250
- return null;
251
- seen.add(raw.path);
252
- // The realpath test stays on this side, and it is not negotiable: this
253
- // path is WRITTEN through. A symlink that leaves the repo is
254
- // indistinguishable, lexically, from a service directory the operator
255
- // mounted on purpose — `escape/owned.txt` and `services/<svc>/spec.md`
256
- // are both "inside docsDir" until something resolves them — so the only
257
- // check that can refuse the first is the one that also refuses the
258
- // second. Restoring is the write; `staging.ts` only reads and compares a
259
- // digest, which is why its resolution of this same field can be lexical
260
- // and this one cannot.
261
- const target = resolvePortableFileInside(docsDir, raw.path, `snapshot path '${raw.path}'`);
262
- let snapshot = null;
263
- const snapshotRel = `${SNAPSHOT_DIR}/files/${raw.path}`;
264
- if (raw.existed) {
265
- // Resolve from the feature directory, which always exists. The `files/`
266
- // directory legitimately does not exist when every archive write was a
267
- // creation; anchoring here permits that case while still inspecting an
268
- // existing `files` component for symlink escape.
269
- snapshot = resolvePortableFileInside(featureDir, snapshotRel, `snapshot pre-image '${raw.path}'`);
270
- // Archive writes plain files. A missing pre-image or a symlink (even an
271
- // internally-contained one) is not the byte snapshot this manifest
272
- // claims, so refuse before any destination is staged.
273
- if (!existsSync(snapshot) || !lstatSync(snapshot).isFile())
274
- return null;
275
- }
276
- else {
277
- const unexpected = resolvePortableFileInside(featureDir, snapshotRel, `snapshot pre-image '${raw.path}'`);
278
- if (existsSync(unexpected))
279
- return null;
280
- }
281
- files.push({ path: raw.path, existed: raw.existed, after: raw.after, before, target, snapshot });
282
- }
283
- return {
284
- version: SNAPSHOT_VERSION,
285
- feature: featureId,
286
- dirName,
287
- archivedAt: parsed.archivedAt,
288
- files,
289
- };
290
- }
291
- catch {
292
- return null;
293
- }
294
- }
295
- function isCanonicalIsoDate(value) {
296
- const millis = Date.parse(value);
297
- return Number.isFinite(millis) && new Date(millis).toISOString() === value;
298
- }
299
- /** Remove `dir` and each empty ancestor, stopping short of `stopAt`. Best effort. */
182
+ /**
183
+ * The snapshot manifest, or null when there is nothing this command can honour —
184
+ * absent, unreadable, or a layout version it would have to guess at. All three are
185
+ * the same answer to the caller: this feature cannot be unarchived automatically.
186
+ */
300
187
  async function pruneEmptyDirs(dir, stopAt) {
301
188
  let cur = dir;
302
189
  for (;;) {
@@ -314,4 +201,4 @@ async function pruneEmptyDirs(dir, stopAt) {
314
201
  cur = dirname(cur);
315
202
  }
316
203
  }
317
- //# sourceMappingURL=unarchive.js.map
204
+ //# sourceMappingURL=restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../src/commands/unarchive/restore.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Putting the docs back: read the snapshot, verify every pre-image, then write.\n *\n * The whole restore runs under the docs lock, and the verification is the point\n * of the split — `unarchive` says \"I put your docs back\", and that sentence must\n * never be sayable over a pre-image that was edited in the archived feature\n * directory since. Each entry carries both the digest of what archive WROTE and\n * the digest of what it will RESTORE, and a mismatch on either refuses.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile, rename, rmdir } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { emitJson, fail, repoPath } from \"../../core/envelope/json.js\";\nimport { archiveDir as archiveRoot, featuresDir as featuresRoot } from \"../../core/repo/paths.js\";\nimport { listServices, resolveFeature } from \"../../core/repo/repo.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { sayRecovery } from \"../archive/plan/refusal.js\";\nimport {\n quietRm,\n rollbackError,\n rollbackStaged,\n stageWrites,\n swapStaged,\n} from \"../../core/staging/commit.js\";\nimport { clearCommitIntent, writeCommitIntent } from \"../../core/staging/recovery/intent.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { SNAPSHOT_DIR } from \"../../core/staging/snapshot.js\";\nimport { planWrite, sha256, type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { readManifest, RestoreFailure } from \"./manifest.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport async function unarchiveLocked(\n docsDir: DocsDir,\n featureId: string,\n json: boolean,\n force: boolean,\n): Promise<void> {\n // Same first move as archive, under the same lock: a commit killed between two\n // renames is exactly the state an undo must not be computed on top of.\n const recovered = await recoverInterruptedCommit(docsDir);\n if (recovered !== null && !json) sayRecovery(recovered);\n\n const featuresDir = featuresRoot(docsDir);\n const archiveDir = archiveRoot(docsDir);\n\n const feature = await resolveFeature(docsDir, featureId, \"only\");\n if (!feature) {\n fail(json, \"unknown-target\", `No archived feature '${featureId}' under ${archiveDir}.`);\n return;\n }\n const dest = join(featuresDir, feature.dirName);\n\n // Refuse to clobber. A feature that is active again carries its own delta, and\n // dropping the archived copy on top of it would bury work in flight. Resolved\n // by the feature's OWN id, not the raw argument — the collision is with\n // whatever answers to this feature's name, however the caller spelled it.\n const active = await resolveFeature(docsDir, feature.id, \"exclude\");\n if (active !== null || existsSync(dest)) {\n const name = active?.dirName ?? feature.dirName;\n fail(\n json,\n \"feature-active\",\n `unarchive ${feature.id} — BLOCKED: features/${name} already exists. Remove or rename it, then re-run.`,\n );\n return;\n }\n\n // The restore-time half of the snapshot's (service, artifact) re-keying: a\n // version-3 entry restores into wherever its service lives TODAY, and the\n // enumeration is the only thing that may say where that is. Built once here,\n // as a map, so the manifest validation stays a pure question of one file\n // against one repo state. \"no-services\" is tolerated the way `listFeatures`\n // tolerates it: a docs repo whose `services/` is gone still restores by the\n // literal paths (every id resolves to null), and it is `doctor`'s job to say\n // the directory is missing — refusing the undo here would add a second,\n // contradictory diagnosis.\n const enumerated = docsRepoState(docsDir).kind === \"ok\" ? await listServices(docsDir) : [];\n const serviceDirs = new Map<string, string>(enumerated.map((s) => [s.id, repoPath(docsDir, s.dir)]));\n const manifest = await readManifest({\n featureDir: feature.dir,\n docsDir,\n featureId: feature.id,\n dirName: feature.dirName,\n serviceDirOf: (service: string): string | null => serviceDirs.get(service) ?? null,\n });\n if (manifest === null) {\n fail(\n json,\n \"snapshot-missing\",\n `unarchive ${feature.id} — BLOCKED: features/archive/${feature.dirName}/${SNAPSHOT_DIR}/ is missing or was written by a different loam. ` +\n `It was archived before loam recorded what it overwrote, and the previous text of a MODIFIED requirement exists nowhere else — ` +\n `restore the living docs from version control instead.`,\n );\n return;\n }\n\n // What archive wrote is recorded, so a file edited since can be told from one\n // this archive is still the top of. Restoring over a later change is silent data\n // loss, so it takes --force to say it was meant.\n const writes: PlannedWrite[] = [];\n const drifted: string[] = [];\n // Pre-images whose bytes are not the bytes archive wrote down. Collected\n // whole, then refused together: \"which files\" is what makes the message\n // actionable, and no destination is staged until every one is accounted for.\n const corrupt: string[] = [];\n for (const entry of manifest.files) {\n const current = existsSync(entry.target) ? await readFile(entry.target) : null;\n const now = current === null ? null : sha256(current);\n // Drift is \"restoring this would destroy something\", so a file already\n // holding its OWN pre-image is not drifted — putting it back writes the\n // same bytes. It is also the state an interrupted restore leaves behind\n // (the repair finishes the swaps it can, then this command re-runs): the\n // narrower test used to call every repaired file a later change and demand\n // `--force` to discard this command's own half-finished undo.\n const restored = entry.existed ? now === entry.before : now === null;\n if (now !== entry.after && !restored) drifted.push(entry.path);\n let before: Buffer | null = null;\n if (entry.snapshot !== null) {\n before = await readFile(entry.snapshot);\n // The one check that was missing entirely. `after` describes what archive\n // WROTE and is checked above; nothing described the bytes it would\n // RESTORE, so a pre-image edited inside the archived feature — a bad\n // merge, a rebase, an editor — was written back verbatim and this command\n // said \"the living docs are back to what they said before the archive\"\n // over text nobody ever wrote.\n if (sha256(before) !== entry.before) corrupt.push(entry.path);\n }\n // A restore whose destination is GONE is a create, and takes the same\n // no-clobber swap an archive's creates take: between the manifest read and\n // the swap, another writer may have put that path back, and a rename would\n // bury it. The same helper both commands use, so neither can decide\n // differently about the same file.\n writes.push(planWrite(entry.target, before));\n }\n if (corrupt.length > 0) {\n fail(\n json,\n \"snapshot-corrupt\",\n `unarchive ${feature.id} — BLOCKED: ${corrupt.length} snapshot pre-image(s) no longer match the digest archive ` +\n `recorded for them, so restoring them would write text nobody authored: ${corrupt.join(\", \")}. ` +\n `--force does not override this — it discards LATER changes to the living docs, and the damage here is to the ` +\n `undo itself. Restore the living docs from version control instead.`,\n );\n return;\n }\n if (drifted.length > 0 && !force) {\n fail(\n json,\n \"snapshot-stale\",\n `unarchive ${feature.id} — BLOCKED: ${drifted.length} file(s) changed since the archive, so this is no longer an undo: ` +\n `${drifted.join(\", \")}. Re-run with --force to restore anyway, discarding those changes.`,\n );\n return;\n }\n\n if (!json) {\n console.log(`unarchive ${feature.id}\\n`);\n if (drifted.length > 0) {\n console.log(`⚠ discarding later changes to ${drifted.length} file(s) (--force):`);\n for (const p of drifted) console.log(` ⚠ ${p}`);\n console.log(\"\");\n }\n }\n\n const staged = await stageWrites(writes);\n try {\n // The same journal archive writes, in the other direction: a restore killed\n // between two renames leaves the docs half-back, and the merged text it was\n // replacing is recorded nowhere — so a repair FINISHES this restore rather\n // than undoing it, from the same digest-checked pre-images.\n await writeCommitIntent(\n docsDir,\n { command: \"unarchive\", restore: \"after\", feature: feature.id, moveFrom: feature.dir, moveTo: dest },\n staged,\n );\n await swapStaged(staged);\n await rename(feature.dir, dest);\n await clearCommitIntent(docsDir);\n } catch (err) {\n // The code is a caller's answer to \"can I trust the repo?\": restore-failed\n // means yes (rolled back), rollback-incomplete means look at it by hand —\n // rollbackError's message lists the files that need one.\n const failures = await rollbackStaged(staged);\n await clearCommitIntent(docsDir);\n const wrapped = rollbackError(err, failures);\n throw new RestoreFailure(failures.length > 0 ? \"rollback-incomplete\" : \"restore-failed\", wrapped.message);\n }\n\n // The snapshot describes an archive that no longer exists; the empty directories\n // are what the merge left behind. Both are tidying — neither can fail the undo.\n await quietRm(join(dest, SNAPSHOT_DIR));\n for (const w of writes) if (w.content === null) await pruneEmptyDirs(dirname(w.path), docsDir);\n await pruneEmptyDirs(archiveDir, docsDir);\n\n const restored = writes.filter((w) => w.content !== null).map((w) => repoPath(docsDir, w.path));\n const removed = writes.filter((w) => w.content === null).map((w) => repoPath(docsDir, w.path));\n\n if (json) {\n emitJson({\n command: \"unarchive\",\n feature: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, dest),\n restored,\n removed,\n discarded: drifted,\n ...(recovered === null ? {} : { recovered }),\n });\n return;\n }\n for (const p of restored) console.log(` restored: ${p}`);\n for (const p of removed) console.log(` removed: ${p}`);\n console.log(`\\n reopened: features/archive/${feature.dirName} → features/${feature.dirName}`);\n console.log(\" the living docs are back to what they said before the archive.\");\n}\n\n/**\n * The snapshot manifest, or null when there is nothing this command can honour —\n * absent, unreadable, or a layout version it would have to guess at. All three are\n * the same answer to the caller: this feature cannot be unarchived automatically.\n */\n\nasync function pruneEmptyDirs(dir: string, stopAt: string): Promise<void> {\n let cur = dir;\n for (;;) {\n const rel = relative(stopAt, cur);\n if (rel === \"\" || rel.startsWith(\"..\")) return;\n try {\n if ((await readdir(cur)).length > 0) return;\n await rmdir(cur);\n } catch {\n return;\n }\n cur = dirname(cur);\n }\n}\n"]}
@@ -0,0 +1,67 @@
1
+ import { loadConfig } from "../../core/envelope/config.js";
2
+ import { fail, reportNoConfig } from "../../core/envelope/json.js";
3
+ import { message } from "../../core/staging/commit.js";
4
+ import { acquireDocsLock, DocsBusyError } from "../../core/staging/lock.js";
5
+ import { InterruptedCommitError } from "../../core/staging/interrupted.js";
6
+ import { unarchiveLocked } from "./restore.js";
7
+ import { RestoreFailure } from "./manifest.js";
8
+ /**
9
+ * A commit-phase failure with its stable `--json` code attached — the mirror of
10
+ * archive's discipline. `restore-failed` answers "can I trust the repo?" with
11
+ * yes: nothing was restored, or everything was rolled back, and re-running can
12
+ * work. `rollback-incomplete` is the code that demands a human: the restore
13
+ * failed AND some files could not be put back — the message lists them.
14
+ * Anything ELSE that escapes runUnarchive never touched the commit phase, so
15
+ * the action handler reports it as `restore-failed`.
16
+ */
17
+ export function registerUnarchive(program) {
18
+ program
19
+ .command("unarchive")
20
+ .argument("<featureId>", "feature id, e.g. FEAT-101")
21
+ .description("Take back a `loam archive`: restore the living docs and re-open the feature")
22
+ .option("--json", "emit the machine contract instead of the human view")
23
+ .option("--force", "restore even if the living docs changed after the archive (discards those changes)")
24
+ .action(async (featureId, opts) => {
25
+ const json = opts.json === true;
26
+ try {
27
+ await runUnarchive(featureId, json, opts.force === true);
28
+ }
29
+ catch (err) {
30
+ const code = err instanceof RestoreFailure
31
+ ? err.code
32
+ : err instanceof InterruptedCommitError
33
+ ? "commit-interrupted"
34
+ : "restore-failed";
35
+ fail(json, code, `unarchive ${featureId} failed: ${message(err)}`);
36
+ }
37
+ });
38
+ }
39
+ async function runUnarchive(featureId, json, force) {
40
+ const loaded = await loadConfig();
41
+ if (loaded.kind !== "loaded") {
42
+ reportNoConfig(json, loaded);
43
+ return;
44
+ }
45
+ const config = loaded.config;
46
+ // The same lock archive takes, for the same reason: unarchive rewrites the
47
+ // living spec, contract and landscape from a snapshot, and an archive landing
48
+ // inside that window would have its merge silently reverted by our restore —
49
+ // or ours by its merge. One writer per docs repo, whichever direction it
50
+ // writes in.
51
+ let release;
52
+ try {
53
+ release = await acquireDocsLock(config.docsDir);
54
+ }
55
+ catch (err) {
56
+ if (err instanceof DocsBusyError)
57
+ throw new RestoreFailure("docs-busy", err.message);
58
+ throw err;
59
+ }
60
+ try {
61
+ await unarchiveLocked(config.docsDir, featureId, json, force);
62
+ }
63
+ finally {
64
+ await release();
65
+ }
66
+ }
67
+ //# sourceMappingURL=unarchive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unarchive.js","sourceRoot":"","sources":["../../../src/commands/unarchive/unarchive.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAuB/C;;;;;;;;GAQG;AAEH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO;SACJ,OAAO,CAAC,WAAW,CAAC;SACpB,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;SACpD,WAAW,CAAC,6EAA6E,CAAC;SAC1F,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,SAAS,EAAE,oFAAoF,CAAC;SACvG,MAAM,CAAC,KAAK,EAAE,SAAiB,EAAE,IAAsB,EAAE,EAAE;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,GAAG,YAAY,cAAc;gBACxC,CAAC,CAAC,GAAG,CAAC,IAAI;gBACV,CAAC,CAAC,GAAG,YAAY,sBAAsB;oBACrC,CAAC,CAAC,oBAAoB;oBACtB,CAAC,CAAC,gBAAgB,CAAC;YACvB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,SAAS,YAAY,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,IAAa,EAAE,KAAc;IAC1E,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,2EAA2E;IAC3E,8EAA8E;IAC9E,6EAA6E;IAC7E,yEAAyE;IACzE,aAAa;IACb,IAAI,OAA4B,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,aAAa;YAAE,MAAM,IAAI,cAAc,CAAC,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACrF,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAChE,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { fail, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { message } from \"../../core/staging/commit.js\";\nimport { acquireDocsLock, DocsBusyError } from \"../../core/staging/lock.js\";\nimport { InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { unarchiveLocked } from \"./restore.js\";\nimport { RestoreFailure } from \"./manifest.js\";\n\n/**\n * `loam unarchive <FEAT>` — take back a `loam archive`.\n *\n * Not an inverse merge, and it cannot be one. Undoing the requirements axis would\n * mean restoring a MODIFIED requirement's PREVIOUS text, which appears nowhere:\n * the delta records what the requirement became, never what it was. The landscape\n * axis is no better — the merge drops the feature tags, so the added lines are\n * indistinguishable from lines that were always there. Anything reconstructed\n * from the archived feature would be a plausible guess at the old docs, which is\n * exactly the kind of quiet fiction the rest of loam exists to prevent.\n *\n * So archive writes the bytes down instead (`<feature>/.loam-before/`), and this\n * command puts them back. The snapshot is optional on restore: a feature archived\n * before it existed fails with a clear message rather than a crash.\n */\n\ninterface UnarchiveOptions {\n json?: boolean;\n force?: boolean;\n}\n\n/**\n * A commit-phase failure with its stable `--json` code attached — the mirror of\n * archive's discipline. `restore-failed` answers \"can I trust the repo?\" with\n * yes: nothing was restored, or everything was rolled back, and re-running can\n * work. `rollback-incomplete` is the code that demands a human: the restore\n * failed AND some files could not be put back — the message lists them.\n * Anything ELSE that escapes runUnarchive never touched the commit phase, so\n * the action handler reports it as `restore-failed`.\n */\n\nexport function registerUnarchive(program: Command): void {\n program\n .command(\"unarchive\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Take back a `loam archive`: restore the living docs and re-open the feature\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--force\", \"restore even if the living docs changed after the archive (discards those changes)\")\n .action(async (featureId: string, opts: UnarchiveOptions) => {\n const json = opts.json === true;\n try {\n await runUnarchive(featureId, json, opts.force === true);\n } catch (err) {\n const code = err instanceof RestoreFailure\n ? err.code\n : err instanceof InterruptedCommitError\n ? \"commit-interrupted\"\n : \"restore-failed\";\n fail(json, code, `unarchive ${featureId} failed: ${message(err)}`);\n }\n });\n}\n\nasync function runUnarchive(featureId: string, json: boolean, force: boolean): Promise<void> {\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n // The same lock archive takes, for the same reason: unarchive rewrites the\n // living spec, contract and landscape from a snapshot, and an archive landing\n // inside that window would have its merge silently reverted by our restore —\n // or ours by its merge. One writer per docs repo, whichever direction it\n // writes in.\n let release: () => Promise<void>;\n try {\n release = await acquireDocsLock(config.docsDir);\n } catch (err) {\n if (err instanceof DocsBusyError) throw new RestoreFailure(\"docs-busy\", err.message);\n throw err;\n }\n try {\n await unarchiveLocked(config.docsDir, featureId, json, force);\n } finally {\n await release();\n }\n}\n"]}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Whether the architecture a delta INTRODUCES is covered by a requirement that
3
+ * will actually merge.
4
+ *
5
+ * Three findings, one question asked three ways: `archedge.uncovered` (a
6
+ * scenario names the new edge — heuristic, warn-only), `c4.uncovered` (an
7
+ * arch.spec.md requirement covers the new element or edge — mechanical), and
8
+ * `covers.unknown` (the `Covers:` line resolves to something real). This is
9
+ * where agent-built code cuts its corners — the outbox, the retries, the alerts
10
+ * — because no business scenario was ever going to mention them.
11
+ *
12
+ * Split out of `validateFeature` because it is the only part of the feature
13
+ * target that needs the LIVING landscape: everything else there reads the
14
+ * feature directory alone. Keeping the lazy load and its two exemption sets in
15
+ * one module is what stops "new" being decided differently in three places.
16
+ */
17
+ import { existsSync } from "node:fs";
18
+ import { loadFile } from "../../core/c4/likec4.js";
19
+ import { elementService, serviceResolver } from "../../core/c4/resolve/service.js";
20
+ import {} from "../../core/kernel/ids/service.js";
21
+ import { landscapePath as landscapeFile } from "../../core/repo/paths.js";
22
+ import { locateServicePaths } from "../../core/repo/service-target.js";
23
+ import { enumeratedServiceIds } from "../../core/repo/service-target.js";
24
+ import {} from "../../core/vocabulary/report.js";
25
+ import {} from "../../core/document/spec.js";
26
+ import { coversEdge, coversElement } from "../../core/c4/arch.js";
27
+ import { readHealth } from "../../core/vocabulary/health.js";
28
+ import { FleetContext } from "../../core/fleet-context.js";
29
+ import { ACTOR_KINDS, EXTERNAL_TAG } from "../../core/vocabulary/maturity.js";
30
+ import { coversEntries, coversUnknownFindings } from "./checks/requirements.js";
31
+ export async function deltaArchCoverage(delta) {
32
+ const { docsDir, elements, relationships, taggedEls, taggedRels, archDeltas, featureServices, scenarioText, preloadedLand, fleet, } = delta;
33
+ const findings = [];
34
+ // The delta's own element→service resolver, built once for the whole feature.
35
+ // `serviceOf` is a one-shot wrapper that rebuilds its index on every call, and
36
+ // the two loops below ask it up to five times per tagged edge — so a delta
37
+ // over a large model paid for one Map of every element per question asked.
38
+ // The enumerated fleet — `services/` plus the feature's own `specs/` names,
39
+ // where a service it introduces lives — rides into it, and into `baseSvcOf`
40
+ // below, which MUST resolve the same way or the already-living exemption
41
+ // keys never match: so an edge drawn into a modelled container
42
+ // (`payment.api`) grades against the service that owns it instead of a
43
+ // service called "api" that has never existed. An unenumerable services/
44
+ // degrades to the feature's own names, exactly what every caller got before
45
+ // the set existed.
46
+ const known = new Set([
47
+ ...(await enumeratedServiceIds(docsDir, fleet)),
48
+ ...featureServices,
49
+ ]);
50
+ const svcOf = serviceResolver(elements, known);
51
+ // Arch-edge coverage (heuristic, warn-only): each new tagged edge should be named by a scenario.
52
+ for (const r of taggedRels) {
53
+ const target = svcOf(r.target);
54
+ const covered = edgeCovered(target, r.title, scenarioText);
55
+ findings.push({
56
+ severity: covered ? "ok" : "warn",
57
+ code: covered ? "archedge.covered" : "archedge.uncovered",
58
+ subject: target,
59
+ message: `${svcOf(r.source)} → ${target} "${r.title ?? ""}"${covered ? "" : " — no scenario names it"}`,
60
+ text: { indent: 4, header: "arch-edge coverage (heuristic):" },
61
+ });
62
+ }
63
+ // The architecture spec axis, feature scope — the mechanical counterpart of
64
+ // the heuristic above. Every NEW tagged element and edge in the delta wants a
65
+ // `Covers:` line in one of the feature's arch.spec.md deltas (c4.uncovered):
66
+ // this is where agent-built code cuts its corners — the outbox, the retries,
67
+ // the alerts — because no business scenario was ever going to mention them.
68
+ // Grouping-only elements take the same exemptions the fleet map applies
69
+ // (person kinds, #external) — one set, in checks/vocabulary.ts, so "not a
70
+ // service" cannot mean two things. Warnings, never archive gates; `--strict`
71
+ // escalates.
72
+ //
73
+ // Only requirements the archive will MERGE grant coverage here. In a delta,
74
+ // BASE means "the living state, quoted": it merges nothing, emits no
75
+ // .feature, and yields no scenario.tested claim — so a Covers: line under a
76
+ // plain `## Requirements` quote is an obligation that ships nowhere, and
77
+ // counting it silenced c4.uncovered for free. (The service-scope pass keeps
78
+ // the unfiltered call: a LIVING spec is legitimately all BASE.)
79
+ const activeCovers = archDeltas.flatMap(({ reqs }) => coversEntries(reqs.filter((r) => r.kind === "ADDED" || r.kind === "MODIFIED")));
80
+ // What the living landscape ALREADY holds. A delta has to re-declare the
81
+ // elements its new edges attach to, and authors tag those re-declarations
82
+ // along with everything else — so a requirements-only feature that touches an
83
+ // existing service was told to write `Covers:` lines for architecture it is
84
+ // not adding. c4.uncovered is an obligation on NEW architecture; an element
85
+ // the living landscape already resolves is not new, whatever the tag says.
86
+ // Loaded lazily: a delta with nothing tagged never pays for the parse.
87
+ let living = preloadedLand;
88
+ const livingLandscape = async () => {
89
+ if (living === undefined) {
90
+ const lp = landscapeFile(docsDir);
91
+ living = existsSync(lp)
92
+ ? fleet === undefined
93
+ ? await loadFile(lp)
94
+ : await fleet.loadLikeC4(lp)
95
+ : null;
96
+ }
97
+ return living;
98
+ };
99
+ const alreadyLiving = async () => {
100
+ if (taggedEls.length === 0 && taggedRels.length === 0)
101
+ return null;
102
+ const doc = await livingLandscape();
103
+ return doc !== null && doc.errors.length === 0 ? doc : null;
104
+ };
105
+ const base = await alreadyLiving();
106
+ const baseSvcOf = base === null ? null : serviceResolver(base.elements, known);
107
+ const baseIds = new Set(base?.elements.map((e) => e.id) ?? []);
108
+ const baseServices = new Set((base?.elements ?? []).map(elementService));
109
+ // How a service→service pair is keyed, spelled ONCE. The two sides used to
110
+ // join with different separators — a NUL where the set was built, a space
111
+ // where it was queried — so the exemption never matched anything, and every
112
+ // edge a delta re-declares verbatim (which it must, to attach anything to
113
+ // it) was reported as new architecture nobody covers. The join is structural
114
+ // rather than a separator character: the last one was a NUL, and a raw NUL in
115
+ // a template literal makes the source read as binary to `file` and invisible
116
+ // to `grep` — which is how a one-character mismatch survived review.
117
+ const edgeKey = (source, target) => JSON.stringify([source, target]);
118
+ const baseEdges = new Set((base?.relationships ?? []).map((r) => edgeKey(baseSvcOf(r.source), baseSvcOf(r.target))));
119
+ for (const e of taggedEls) {
120
+ if (ACTOR_KINDS.has(e.kind.toLowerCase()) || e.tags.includes(EXTERNAL_TAG))
121
+ continue;
122
+ if (baseIds.has(e.id) || baseServices.has(elementService(e)))
123
+ continue;
124
+ if (activeCovers.some((c) => coversElement(c, e)))
125
+ continue;
126
+ findings.push({
127
+ severity: "warn",
128
+ code: "c4.uncovered",
129
+ subject: elementService(e),
130
+ message: `delta adds '${e.title}' (${e.id}) but no arch requirement covers it — add 'Covers: ${e.id}' to a specs/<svc>/arch.spec.md delta, or its architectural obligations ship unchecked`,
131
+ });
132
+ }
133
+ for (const r of taggedRels) {
134
+ if (baseEdges.has(edgeKey(svcOf(r.source), svcOf(r.target))))
135
+ continue;
136
+ if (activeCovers.some((c) => coversEdge(c, r, elements, known)))
137
+ continue;
138
+ findings.push({
139
+ severity: "warn",
140
+ code: "c4.uncovered",
141
+ subject: svcOf(r.target),
142
+ message: `delta adds edge ${svcOf(r.source)} → ${svcOf(r.target)} ("${r.title ?? ""}") but no arch requirement covers it — add 'Covers: ${r.source} -> ${r.target}' to a specs/<svc>/arch.spec.md delta`,
143
+ });
144
+ }
145
+ // covers.unknown, feature scope. Resolution looks at the delta itself, the
146
+ // living landscape, the service's own model and its health.yaml — a delta's
147
+ // arch requirement may cover an element it adds, one that already exists, or
148
+ // an alert the service declares. The landscape and each model are loaded
149
+ // lazily, and only when an entry fails against what is already in hand: the
150
+ // clean path never pays for a workspace spin.
151
+ if (archDeltas.some(({ reqs }) => coversEntries(reqs).length > 0)) {
152
+ const land = await livingLandscape();
153
+ const landParses = land !== null && land.errors.length === 0 ? land : null;
154
+ const baseElements = [...elements, ...(landParses?.elements ?? [])];
155
+ const baseRels = [...relationships, ...(landParses?.relationships ?? [])];
156
+ // A feature never brings topology through `delta.likec4` — a `deployment { }`
157
+ // block in one is refused by the merge — so the living map is the whole
158
+ // scope for a `node:` entry in a delta, exactly as it is in service scope.
159
+ const baseDeployment = landParses?.deployment;
160
+ for (const { service: svc, reqs } of archDeltas) {
161
+ // An unreadable living health.yaml mutes the alert:/sli: entries here
162
+ // exactly as in service scope — the health.invalid finding itself
163
+ // belongs to the service target, which owns the file's diagnosis.
164
+ const health = await readHealth((await locateServicePaths(docsDir, svc, fleet)).health);
165
+ let scope = {
166
+ elements: baseElements,
167
+ relationships: baseRels,
168
+ ...(baseDeployment === undefined ? {} : { deployment: baseDeployment }),
169
+ health: health.ids,
170
+ known,
171
+ };
172
+ const unresolved = coversUnknownFindings(reqs, { where: `${svc}: arch.spec.md`, subject: svc }, scope, health.unreadable);
173
+ if (unresolved.length > 0) {
174
+ const modelPath = (await locateServicePaths(docsDir, svc, fleet)).model;
175
+ const model = existsSync(modelPath)
176
+ ? fleet === undefined
177
+ ? await loadFile(modelPath)
178
+ : await fleet.loadLikeC4(modelPath)
179
+ : null;
180
+ if (model !== null && model.errors.length === 0) {
181
+ scope = {
182
+ elements: [...baseElements, ...model.elements],
183
+ relationships: [...baseRels, ...model.relationships],
184
+ health: health.ids,
185
+ known,
186
+ };
187
+ }
188
+ findings.push(...coversUnknownFindings(reqs, { where: `${svc}: arch.spec.md`, subject: svc }, scope, health.unreadable));
189
+ }
190
+ }
191
+ }
192
+ return findings;
193
+ }
194
+ /** Heuristic: an edge is "covered" if a scenario names the target or a keyword from the edge title. */
195
+ function edgeCovered(target, title, scenarioText) {
196
+ if (scenarioText.includes(target.toLowerCase()))
197
+ return true;
198
+ for (const token of (title ?? "").split(/[^A-Za-z0-9]+/)) {
199
+ if (token.length >= 5 && scenarioText.includes(token.toLowerCase()))
200
+ return true;
201
+ }
202
+ return false;
203
+ }
204
+ //# sourceMappingURL=arch-coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arch-coverage.js","sourceRoot":"","sources":["../../../src/commands/validate/arch-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Whether the architecture a delta INTRODUCES is covered by a requirement that\n * will actually merge.\n *\n * Three findings, one question asked three ways: `archedge.uncovered` (a\n * scenario names the new edge — heuristic, warn-only), `c4.uncovered` (an\n * arch.spec.md requirement covers the new element or edge — mechanical), and\n * `covers.unknown` (the `Covers:` line resolves to something real). This is\n * where agent-built code cuts its corners — the outbox, the retries, the alerts\n * — because no business scenario was ever going to mention them.\n *\n * Split out of `validateFeature` because it is the only part of the feature\n * target that needs the LIVING landscape: everything else there reads the\n * feature directory alone. Keeping the lazy load and its two exemption sets in\n * one module is what stops \"new\" being decided differently in three places.\n */\nimport { existsSync } from \"node:fs\";\nimport { loadFile, type Elem, type LoadedDoc, type Rel } from \"../../core/c4/likec4.js\";\nimport { elementService, serviceResolver } from \"../../core/c4/resolve/service.js\";\nimport { type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile } from \"../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { enumeratedServiceIds } from \"../../core/repo/service-target.js\";\nimport { type Finding } from \"../../core/vocabulary/report.js\";\nimport { type Requirement } from \"../../core/document/spec.js\";\nimport { coversEdge, coversElement, type CoverageScope } from \"../../core/c4/arch.js\";\nimport { readHealth } from \"../../core/vocabulary/health.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { ACTOR_KINDS, EXTERNAL_TAG } from \"../../core/vocabulary/maturity.js\";\nimport { coversEntries, coversUnknownFindings } from \"./checks/requirements.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\n/** One feature delta's architecture, and everything needed to decide what in it is new. */\nexport interface DeltaArch {\n docsDir: DocsDir;\n /** Every element the delta declares — the resolver's index, and `coversEdge`'s scope. */\n elements: Elem[];\n /** Every relationship the delta declares. */\n relationships: Rel[];\n /** The delta's own additions: elements and edges carrying the feature id as a tag. */\n taggedEls: Elem[];\n taggedRels: Rel[];\n /** Each addressed service's arch.spec.md delta, in enumeration order. */\n archDeltas: Array<{ service: PathableService; reqs: Requirement[] }>;\n /**\n * Every service the feature's `specs/` addresses — not just the ones with an\n * arch delta. The resolver's fleet set unions these in because a service the\n * feature INTRODUCES has no `services/` directory yet, and without its name\n * an edge into that service's own modelled container resolved to the\n * container's title — a phantom the fleet half alone cannot rule out.\n */\n featureServices: PathableService[];\n /** Every scenario the feature's deltas carry, lowercased — the heuristic's haystack. */\n scenarioText: string;\n /** The living landscape under --all; undefined means \"load it if you need it\", null \"there is none\". */\n preloadedLand?: LoadedDoc | null;\n fleet?: FleetContext;\n}\n\nexport async function deltaArchCoverage(delta: DeltaArch): Promise<Finding[]> {\n const {\n docsDir,\n elements,\n relationships,\n taggedEls,\n taggedRels,\n archDeltas,\n featureServices,\n scenarioText,\n preloadedLand,\n fleet,\n } = delta;\n const findings: Finding[] = [];\n\n // The delta's own element→service resolver, built once for the whole feature.\n // `serviceOf` is a one-shot wrapper that rebuilds its index on every call, and\n // the two loops below ask it up to five times per tagged edge — so a delta\n // over a large model paid for one Map of every element per question asked.\n // The enumerated fleet — `services/` plus the feature's own `specs/` names,\n // where a service it introduces lives — rides into it, and into `baseSvcOf`\n // below, which MUST resolve the same way or the already-living exemption\n // keys never match: so an edge drawn into a modelled container\n // (`payment.api`) grades against the service that owns it instead of a\n // service called \"api\" that has never existed. An unenumerable services/\n // degrades to the feature's own names, exactly what every caller got before\n // the set existed.\n const known: ReadonlySet<string> = new Set<string>([\n ...(await enumeratedServiceIds(docsDir, fleet)),\n ...featureServices,\n ]);\n const svcOf = serviceResolver(elements, known);\n\n // Arch-edge coverage (heuristic, warn-only): each new tagged edge should be named by a scenario.\n for (const r of taggedRels) {\n const target = svcOf(r.target);\n const covered = edgeCovered(target, r.title, scenarioText);\n findings.push({\n severity: covered ? \"ok\" : \"warn\",\n code: covered ? \"archedge.covered\" : \"archedge.uncovered\",\n subject: target,\n message: `${svcOf(r.source)} → ${target} \"${r.title ?? \"\"}\"${covered ? \"\" : \" — no scenario names it\"}`,\n text: { indent: 4, header: \"arch-edge coverage (heuristic):\" },\n });\n }\n\n // The architecture spec axis, feature scope — the mechanical counterpart of\n // the heuristic above. Every NEW tagged element and edge in the delta wants a\n // `Covers:` line in one of the feature's arch.spec.md deltas (c4.uncovered):\n // this is where agent-built code cuts its corners — the outbox, the retries,\n // the alerts — because no business scenario was ever going to mention them.\n // Grouping-only elements take the same exemptions the fleet map applies\n // (person kinds, #external) — one set, in checks/vocabulary.ts, so \"not a\n // service\" cannot mean two things. Warnings, never archive gates; `--strict`\n // escalates.\n //\n // Only requirements the archive will MERGE grant coverage here. In a delta,\n // BASE means \"the living state, quoted\": it merges nothing, emits no\n // .feature, and yields no scenario.tested claim — so a Covers: line under a\n // plain `## Requirements` quote is an obligation that ships nowhere, and\n // counting it silenced c4.uncovered for free. (The service-scope pass keeps\n // the unfiltered call: a LIVING spec is legitimately all BASE.)\n const activeCovers = archDeltas.flatMap(({ reqs }) =>\n coversEntries(reqs.filter((r) => r.kind === \"ADDED\" || r.kind === \"MODIFIED\")),\n );\n\n // What the living landscape ALREADY holds. A delta has to re-declare the\n // elements its new edges attach to, and authors tag those re-declarations\n // along with everything else — so a requirements-only feature that touches an\n // existing service was told to write `Covers:` lines for architecture it is\n // not adding. c4.uncovered is an obligation on NEW architecture; an element\n // the living landscape already resolves is not new, whatever the tag says.\n // Loaded lazily: a delta with nothing tagged never pays for the parse.\n let living: LoadedDoc | null | undefined = preloadedLand;\n const livingLandscape = async (): Promise<LoadedDoc | null> => {\n if (living === undefined) {\n const lp = landscapeFile(docsDir);\n living = existsSync(lp)\n ? fleet === undefined\n ? await loadFile(lp)\n : await fleet.loadLikeC4(lp)\n : null;\n }\n return living;\n };\n const alreadyLiving = async (): Promise<LoadedDoc | null> => {\n if (taggedEls.length === 0 && taggedRels.length === 0) return null;\n const doc = await livingLandscape();\n return doc !== null && doc.errors.length === 0 ? doc : null;\n };\n const base = await alreadyLiving();\n const baseSvcOf = base === null ? null : serviceResolver(base.elements, known);\n const baseIds = new Set(base?.elements.map((e) => e.id) ?? []);\n const baseServices = new Set((base?.elements ?? []).map(elementService));\n // How a service→service pair is keyed, spelled ONCE. The two sides used to\n // join with different separators — a NUL where the set was built, a space\n // where it was queried — so the exemption never matched anything, and every\n // edge a delta re-declares verbatim (which it must, to attach anything to\n // it) was reported as new architecture nobody covers. The join is structural\n // rather than a separator character: the last one was a NUL, and a raw NUL in\n // a template literal makes the source read as binary to `file` and invisible\n // to `grep` — which is how a one-character mismatch survived review.\n const edgeKey = (source: string, target: string): string => JSON.stringify([source, target]);\n const baseEdges = new Set(\n (base?.relationships ?? []).map((r) => edgeKey(baseSvcOf!(r.source), baseSvcOf!(r.target))),\n );\n\n for (const e of taggedEls) {\n if (ACTOR_KINDS.has(e.kind.toLowerCase()) || e.tags.includes(EXTERNAL_TAG)) continue;\n if (baseIds.has(e.id) || baseServices.has(elementService(e))) continue;\n if (activeCovers.some((c) => coversElement(c, e))) continue;\n findings.push({\n severity: \"warn\",\n code: \"c4.uncovered\",\n subject: elementService(e),\n message: `delta adds '${e.title}' (${e.id}) but no arch requirement covers it — add 'Covers: ${e.id}' to a specs/<svc>/arch.spec.md delta, or its architectural obligations ship unchecked`,\n });\n }\n for (const r of taggedRels) {\n if (baseEdges.has(edgeKey(svcOf(r.source), svcOf(r.target)))) continue;\n if (activeCovers.some((c) => coversEdge(c, r, elements, known))) continue;\n findings.push({\n severity: \"warn\",\n code: \"c4.uncovered\",\n subject: svcOf(r.target),\n message: `delta adds edge ${svcOf(r.source)} → ${svcOf(r.target)} (\"${r.title ?? \"\"}\") but no arch requirement covers it — add 'Covers: ${r.source} -> ${r.target}' to a specs/<svc>/arch.spec.md delta`,\n });\n }\n\n // covers.unknown, feature scope. Resolution looks at the delta itself, the\n // living landscape, the service's own model and its health.yaml — a delta's\n // arch requirement may cover an element it adds, one that already exists, or\n // an alert the service declares. The landscape and each model are loaded\n // lazily, and only when an entry fails against what is already in hand: the\n // clean path never pays for a workspace spin.\n if (archDeltas.some(({ reqs }) => coversEntries(reqs).length > 0)) {\n const land = await livingLandscape();\n const landParses = land !== null && land.errors.length === 0 ? land : null;\n const baseElements = [...elements, ...(landParses?.elements ?? [])];\n const baseRels = [...relationships, ...(landParses?.relationships ?? [])];\n // A feature never brings topology through `delta.likec4` — a `deployment { }`\n // block in one is refused by the merge — so the living map is the whole\n // scope for a `node:` entry in a delta, exactly as it is in service scope.\n const baseDeployment = landParses?.deployment;\n for (const { service: svc, reqs } of archDeltas) {\n // An unreadable living health.yaml mutes the alert:/sli: entries here\n // exactly as in service scope — the health.invalid finding itself\n // belongs to the service target, which owns the file's diagnosis.\n const health = await readHealth((await locateServicePaths(docsDir, svc, fleet)).health);\n let scope: CoverageScope = {\n elements: baseElements,\n relationships: baseRels,\n ...(baseDeployment === undefined ? {} : { deployment: baseDeployment }),\n health: health.ids,\n known,\n };\n const unresolved = coversUnknownFindings(reqs, { where: `${svc}: arch.spec.md`, subject: svc }, scope, health.unreadable);\n if (unresolved.length > 0) {\n const modelPath = (await locateServicePaths(docsDir, svc, fleet)).model;\n const model = existsSync(modelPath)\n ? fleet === undefined\n ? await loadFile(modelPath)\n : await fleet.loadLikeC4(modelPath)\n : null;\n if (model !== null && model.errors.length === 0) {\n scope = {\n elements: [...baseElements, ...model.elements],\n relationships: [...baseRels, ...model.relationships],\n health: health.ids,\n known,\n };\n }\n findings.push(...coversUnknownFindings(reqs, { where: `${svc}: arch.spec.md`, subject: svc }, scope, health.unreadable));\n }\n }\n }\n\n return findings;\n}\n\n/** Heuristic: an edge is \"covered\" if a scenario names the target or a keyword from the edge title. */\nfunction edgeCovered(target: string, title: string | undefined, scenarioText: string): boolean {\n if (scenarioText.includes(target.toLowerCase())) return true;\n for (const token of (title ?? \"\").split(/[^A-Za-z0-9]+/)) {\n if (token.length >= 5 && scenarioText.includes(token.toLowerCase())) return true;\n }\n return false;\n}\n"]}