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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -12,8 +12,9 @@
12
12
  */
13
13
  import { readFile } from "node:fs/promises";
14
14
  import { ownValue } from "../../kernel/records.js";
15
+ import { fenceTracker } from "../../kernel/fences.js";
15
16
  import { isRequirementsHeading, parseRequirements, sectionHeadings } from "../../document/parse.js";
16
- import { requirementIdProblems } from "../../document/spec.js";
17
+ import { KIND_RE, requirementIdProblems } from "../../document/spec.js";
17
18
  import {} from "./workspace.js";
18
19
  import { portable } from "./walk.js";
19
20
  import {} from "../model/model.js";
@@ -28,37 +29,76 @@ export function addCounts(target, source) {
28
29
  target.requirements += source.requirements;
29
30
  target.scenarios += source.scenarios;
30
31
  }
32
+ /**
33
+ * An ATX heading's optional closing run — `### Requirement: Foo ###` renders as
34
+ * `Foo`, so the run is not part of the name (OpenSpec 1.13.1 strips it the same
35
+ * way). Only a run after a space or tab closes a heading, so `C#` keeps its `#`.
36
+ */
37
+ const CLOSING_HASHES = /[ \t]+#+[ \t]*$/;
38
+ /** A REMOVED entry written as a bullet carrying the heading — any CommonMark marker. */
39
+ const REMOVED_BULLET = /^\s*[-*+]\s*`?###\s*Requirement:\s*(.+?)`?\s*$/;
31
40
  export function cleanRenameName(value) {
32
41
  return value
33
42
  .trim()
34
43
  .replace(/^`|`$/g, "")
35
44
  .replace(/^###\s+Requirement:\s*/i, "")
45
+ .replace(CLOSING_HASHES, "")
36
46
  .trim();
37
47
  }
48
+ /**
49
+ * OpenSpec's requirement grammar, as `parseRequirements` reads it. The two read
50
+ * the same Markdown except where OpenSpec accepts more, and every OpenSpec
51
+ * read in the migration goes through here so audit and `--apply` cannot
52
+ * disagree about a name:
53
+ *
54
+ * - a closing `#` run on a `### Requirement:` / `#### Scenario:` heading is
55
+ * dropped. Kept, a living `### Requirement: Invoice totals ###` audited
56
+ * "ready", staged under the name `Invoice totals ###`, and the change's
57
+ * MODIFIED `Invoice totals` then failed `loam validate` as
58
+ * `delta.modified-unknown` with a "Did you mean ADDED?" that sent the author
59
+ * the wrong way.
60
+ * - a `## REMOVED Requirements` list of bullets (`-`, `*` or `+`) each naming
61
+ * a requirement heading reads as that heading. v1.9.0 already accepted the
62
+ * `-` list and 1.13.0 added the other two markers; loam read neither, so a
63
+ * removal-only change was `openspec.change-empty`.
64
+ *
65
+ * A line-for-line rewrite, never a reflow, so every line number a finding
66
+ * cites still points at the author's file — and loam's own parser is not
67
+ * touched: a closing run in a loam document is the author's to keep.
68
+ */
69
+ export function openspecRequirements(raw) {
70
+ const lines = raw.replace(/^\uFEFF/, "").split(/\r?\n/);
71
+ const fenced = fencedLines(lines);
72
+ let removed = false;
73
+ const rewritten = lines.map((line, index) => {
74
+ if (fenced[index] === true)
75
+ return line;
76
+ if (/^##\s+/.test(line) && !line.startsWith("###"))
77
+ removed = KIND_RE.exec(line)?.[1]?.toUpperCase() === "REMOVED";
78
+ const bullet = removed ? REMOVED_BULLET.exec(line) : null;
79
+ const heading = bullet === null ? line : `### Requirement: ${bullet[1]}`;
80
+ return /^(###\s+Requirement:|####\s+Scenario:)/.test(heading) ? heading.replace(CLOSING_HASHES, "") : heading;
81
+ });
82
+ return parseRequirements(rewritten.join("\n"));
83
+ }
38
84
  export function changeSpecCoordinates(path, scope) {
39
85
  const prefix = scope === "active" ? /^changes\/([^/]+)\/specs\/(.+)\/spec\.md$/ : /^changes\/archive\/([^/]+)\/specs\/(.+)\/spec\.md$/;
40
86
  const match = prefix.exec(path);
41
87
  return { changeId: match?.[1] ?? "", capability: match?.[2] ?? "" };
42
88
  }
43
89
  /**
44
- * Which lines are fenced content, marker included \u2014 the same rule
45
- * `parseRequirements` and `sectionHeadings` apply, which spec.ts keeps private.
90
+ * Which lines are fenced content, marker included — the same rule
91
+ * `parseRequirements` and `sectionHeadings` apply, read from the one place it
92
+ * is spelled (`core/kernel/fences.ts`). This used to be a private copy of it,
93
+ * written while the rule still lived inside the parser module; a second copy is
94
+ * how two walks of one file come to disagree about where a fence ends.
46
95
  * Without it a FROM/TO line inside a fenced example of the rename syntax became
47
96
  * a phantom pair, and the only cure was editing the OpenSpec source migration
48
97
  * promises never to touch.
49
98
  */
50
99
  export function fencedLines(lines) {
51
- let fence = null;
52
- return lines.map((line) => {
53
- const marker = /^\s*(```|~~~)/.exec(line);
54
- if (marker === null)
55
- return fence !== null;
56
- if (fence === null)
57
- fence = marker[1];
58
- else if (fence === marker[1])
59
- fence = null;
60
- return true;
61
- });
100
+ const fenced = fenceTracker();
101
+ return lines.map((line) => fenced(line));
62
102
  }
63
103
  export function renameUsages(path, raw, scope, mapped) {
64
104
  const lines = raw.replace(/^\uFEFF/, "").split(/\r?\n/);
@@ -76,14 +116,18 @@ export function renameUsages(path, raw, scope, mapped) {
76
116
  if (fenced[line] === true)
77
117
  continue;
78
118
  const text = lines[line] ?? "";
79
- const from = /^\s*-\s*FROM:\s*(.*?)\s*$/i.exec(text);
119
+ // The bullet is optional and any CommonMark marker counts, as in
120
+ // OpenSpec since 1.13.0 (v1.9.0 already took a bare `FROM:`): a `*`
121
+ // pair used to read as no pair at all, `openspec.renamed-malformed`
122
+ // saying "must contain a FROM/TO pair" about a section that had one.
123
+ const from = /^\s*[-*+]?\s*FROM:\s*(.*?)\s*$/i.exec(text);
80
124
  if (from !== null) {
81
125
  if (pendingFrom !== null)
82
126
  pairs.push({ from: pendingFrom, to: null });
83
127
  pendingFrom = cleanRenameName(from[1] ?? "");
84
128
  continue;
85
129
  }
86
- const to = /^\s*-\s*TO:\s*(.*?)\s*$/i.exec(text);
130
+ const to = /^\s*[-*+]?\s*TO:\s*(.*?)\s*$/i.exec(text);
87
131
  if (to !== null) {
88
132
  pairs.push({ from: pendingFrom, to: cleanRenameName(to[1] ?? "") });
89
133
  pendingFrom = null;
@@ -125,7 +169,7 @@ export function issue(target, scope, finding) {
125
169
  target.push({ ...finding, scope });
126
170
  }
127
171
  export function inspectLivingShape(path, raw, unsupported) {
128
- const requirements = parseRequirements(raw);
172
+ const requirements = openspecRequirements(raw);
129
173
  if (requirements.length === 0) {
130
174
  issue(unsupported, "living", {
131
175
  code: "openspec.living-empty",
@@ -152,7 +196,7 @@ export function inspectLivingShape(path, raw, unsupported) {
152
196
  }
153
197
  export function inspectChangeShape(unsupported, path, raw, found) {
154
198
  const { scope, renames } = found;
155
- const requirements = parseRequirements(raw);
199
+ const requirements = openspecRequirements(raw);
156
200
  const fileRenames = renames.filter((rename) => rename.path === path);
157
201
  for (const rename of fileRenames) {
158
202
  if (rename.from === null || rename.from === "" || rename.to === null || rename.to === "") {
@@ -220,7 +264,7 @@ export async function inspectSpecFile(ingest, absolute, kind, scope) {
220
264
  const { root, mapping, renamed, unsupported } = ingest;
221
265
  const path = portable(root, absolute);
222
266
  const raw = await readFile(absolute, "utf8");
223
- const requirements = parseRequirements(raw);
267
+ const requirements = openspecRequirements(raw);
224
268
  const counts = { specFiles: 1, ...countsFor(requirements) };
225
269
  const issueScope = kind === "living" ? "living" : scope ?? "active";
226
270
  for (const problem of requirementIdProblems(requirements)) {
@@ -1 +1 @@
1
- {"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/shape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Is this document a shape loam can migrate?\n *\n * The living side and the change side are graded separately because they fail\n * differently: a living spec that will not parse is a corpus loam cannot read,\n * while a change spec carries RENAMED pairs, which are the one OpenSpec\n * construct with no loam equivalent and the reason this package has an identity\n * problem to solve at all.\n *\n * Nothing here decides anything — it appends findings to the `Ingest` it was\n * given. The decisions are in `../decide/`.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { ownValue } from \"../../kernel/records.js\";\nimport { isRequirementsHeading, parseRequirements, sectionHeadings } from \"../../document/parse.js\";\nimport { requirementIdProblems } from \"../../document/spec.js\";\nimport { type Ingest } from \"./workspace.js\";\nimport { portable } from \"./walk.js\";\nimport {\n type OpenSpecCounts,\n type OpenSpecRenamedUsage,\n type OpenSpecUnsupportedShape,\n type OpenSpecIssueScope,\n} from \"../model/model.js\";\n\nexport function countsFor(requirements: ReturnType<typeof parseRequirements>): Omit<OpenSpecCounts, \"specFiles\"> {\n return {\n requirements: requirements.length,\n scenarios: requirements.reduce((sum, requirement) => sum + requirement.scenarios.length, 0),\n };\n}\n\nexport function addCounts(target: OpenSpecCounts, source: OpenSpecCounts): void {\n target.specFiles += source.specFiles;\n target.requirements += source.requirements;\n target.scenarios += source.scenarios;\n}\n\nexport function cleanRenameName(value: string): string {\n return value\n .trim()\n .replace(/^`|`$/g, \"\")\n .replace(/^###\\s+Requirement:\\s*/i, \"\")\n .trim();\n}\n\nexport function changeSpecCoordinates(\n path: string,\n scope: \"active\" | \"archive\",\n): { changeId: string; capability: string } {\n const prefix = scope === \"active\" ? /^changes\\/([^/]+)\\/specs\\/(.+)\\/spec\\.md$/ : /^changes\\/archive\\/([^/]+)\\/specs\\/(.+)\\/spec\\.md$/;\n const match = prefix.exec(path);\n return { changeId: match?.[1] ?? \"\", capability: match?.[2] ?? \"\" };\n}\n\n/**\n * Which lines are fenced content, marker included \\u2014 the same rule\n * `parseRequirements` and `sectionHeadings` apply, which spec.ts keeps private.\n * Without it a FROM/TO line inside a fenced example of the rename syntax became\n * a phantom pair, and the only cure was editing the OpenSpec source migration\n * promises never to touch.\n */\nexport function fencedLines(lines: string[]): boolean[] {\n let fence: string | null = null;\n return lines.map((line) => {\n const marker = /^\\s*(```|~~~)/.exec(line);\n if (marker === null) return fence !== null;\n if (fence === null) fence = marker[1]!;\n else if (fence === marker[1]!) fence = null;\n return true;\n });\n}\n\nexport function renameUsages(\n path: string,\n raw: string,\n scope: \"active\" | \"archive\",\n mapped: Record<string, string>,\n): OpenSpecRenamedUsage[] {\n const lines = raw.replace(/^\\uFEFF/, \"\").split(/\\r?\\n/);\n const fenced = fencedLines(lines);\n const headings = sectionHeadings(raw);\n const coordinates = changeSpecCoordinates(path, scope);\n const output: OpenSpecRenamedUsage[] = [];\n for (const [headingIndex, heading] of headings.entries()) {\n if (!/^##\\s+RENAMED\\s+Requirements?\\s*:?\\s*$/i.test(heading.text)) continue;\n const end = headings[headingIndex + 1]?.line ?? lines.length + 1;\n const pairs: Array<{ from: string | null; to: string | null }> = [];\n let pendingFrom: string | null = null;\n for (let line = heading.line; line < end - 1; line += 1) {\n if (fenced[line] === true) continue;\n const text = lines[line] ?? \"\";\n const from = /^\\s*-\\s*FROM:\\s*(.*?)\\s*$/i.exec(text);\n if (from !== null) {\n if (pendingFrom !== null) pairs.push({ from: pendingFrom, to: null });\n pendingFrom = cleanRenameName(from[1] ?? \"\");\n continue;\n }\n const to = /^\\s*-\\s*TO:\\s*(.*?)\\s*$/i.exec(text);\n if (to !== null) {\n pairs.push({ from: pendingFrom, to: cleanRenameName(to[1] ?? \"\") });\n pendingFrom = null;\n }\n }\n if (pendingFrom !== null) pairs.push({ from: pendingFrom, to: null });\n if (pairs.length === 0) pairs.push({ from: null, to: null });\n pairs.forEach((pair, pairIndex) => {\n const key = `${path}:${heading.line}:${pairIndex + 1}`;\n const requirementId = ownValue(mapped, key) ?? null;\n output.push({\n key,\n path,\n line: heading.line,\n scope,\n changeId: coordinates.changeId,\n capability: coordinates.capability,\n from: pair.from,\n to: pair.to,\n existingRequirementId: null,\n requirementId,\n status: requirementId === null ? \"needsIdentity\" : \"mapped\",\n });\n });\n }\n return output;\n}\n\n/**\n * Record one shape loam cannot migrate.\n *\n * The finding is a record rather than three positional strings because code,\n * path and message are all strings and nothing but their order distinguished\n * them — two of the three were transposed at a call site once, and the report\n * named the message as the path.\n */\nexport function issue(\n target: OpenSpecUnsupportedShape[],\n scope: OpenSpecIssueScope,\n finding: { code: string; path: string; message: string },\n): void {\n target.push({ ...finding, scope });\n}\n\nexport function inspectLivingShape(\n path: string,\n raw: string,\n unsupported: OpenSpecUnsupportedShape[],\n): void {\n const requirements = parseRequirements(raw);\n if (requirements.length === 0) {\n issue(unsupported, \"living\", {\n code: \"openspec.living-empty\",\n path: path,\n message: \"Living spec has no parseable Requirement headings.\",\n });\n return;\n }\n const outside = requirements.filter(\n (requirement) => requirement.section === undefined || !isRequirementsHeading(requirement.section),\n );\n if (outside.length > 0) {\n issue(unsupported, \"living\", {\n code: \"openspec.living-requirements-outside-section\",\n path: path,\n message: `${outside.length} requirement(s) are outside the canonical ## Requirements section.`,\n });\n }\n if (requirements.some((requirement) => requirement.kind !== \"BASE\")) {\n issue(unsupported, \"living\", {\n code: \"openspec.living-delta-section\",\n path: path,\n message: \"Living spec contains delta-kind requirements.\",\n });\n }\n}\n\nexport function inspectChangeShape(\n unsupported: OpenSpecUnsupportedShape[],\n path: string,\n raw: string,\n found: { scope: \"active\" | \"archive\"; renames: OpenSpecRenamedUsage[] },\n): void {\n const { scope, renames } = found;\n const requirements = parseRequirements(raw);\n const fileRenames = renames.filter((rename) => rename.path === path);\n for (const rename of fileRenames) {\n if (rename.from === null || rename.from === \"\" || rename.to === null || rename.to === \"\") {\n issue(unsupported, scope, {\n code: \"openspec.renamed-malformed\",\n path: path,\n message: `RENAMED Requirements at line ${rename.line} must contain a FROM/TO pair.`,\n });\n }\n }\n if (requirements.length === 0) {\n // A well-formed rename-only delta is meaningful even though the requirement\n // parser correctly returns no requirement blocks for it.\n if (fileRenames.length === 0) {\n issue(unsupported, scope, {\n code: \"openspec.change-empty\",\n path: path,\n message: \"Change spec has no parseable Requirement headings.\",\n });\n }\n return;\n }\n // `## Requirements` is the heading OpenSpec's own living-spec template\n // mandates, so this is the shape a team produces by copying a living spec into\n // a change directory. It parses, it is counted, and nothing routes it: only\n // delta-kind requirements reach the staged feature. Blocking is what keeps the\n // per-change counters and what actually lands the same number.\n const quoted = requirements.filter(\n (requirement) => requirement.kind === \"BASE\"\n && requirement.section !== undefined\n && isRequirementsHeading(requirement.section),\n );\n if (quoted.length > 0) {\n unsupported.push({\n code: \"openspec.change-quoted-requirements\",\n path,\n message: `${quoted.length} requirement(s) sit under ## Requirements in a change delta, which stages nothing; re-home them under ADDED, MODIFIED, or REMOVED.`,\n scope,\n });\n }\n const stranded = requirements.filter(\n (requirement) => requirement.kind === \"BASE\"\n && (requirement.section === undefined || !isRequirementsHeading(requirement.section)),\n );\n if (stranded.length === requirements.length) {\n issue(unsupported, scope, {\n code: \"openspec.change-without-delta-sections\",\n path: path,\n message: \"Change requirements are not under ADDED, MODIFIED, REMOVED, or the explicitly non-merging ## Requirements section.\",\n });\n } else if (stranded.length > 0) {\n issue(unsupported, scope, {\n code: \"openspec.change-requirements-outside-delta-sections\",\n path: path,\n message: `${stranded.length} requirement(s) would be stranded outside delta sections.`,\n });\n }\n}\n\n/**\n * The parsed requirements come back with the counts because both callers wanted\n * them and each opened the file a second time to get its own copy — two reads\n * of the same bytes per spec.md, and two readings of it: the counts came from\n * this parse while the requirement names, and the living corpus every rename is\n * resolved against, came from the other. A file edited mid-audit could be\n * counted from one of them and routed from the other.\n */\nexport async function inspectSpecFile(\n ingest: Ingest,\n absolute: string,\n kind: \"living\" | \"change\",\n scope: \"active\" | \"archive\" | null,\n): Promise<{ path: string; counts: OpenSpecCounts; requirements: ReturnType<typeof parseRequirements> }> {\n const { root, mapping, renamed, unsupported } = ingest;\n const path = portable(root, absolute);\n const raw = await readFile(absolute, \"utf8\");\n const requirements = parseRequirements(raw);\n const counts = { specFiles: 1, ...countsFor(requirements) };\n const issueScope = kind === \"living\" ? \"living\" : scope ?? \"active\";\n for (const problem of requirementIdProblems(requirements)) {\n if (problem.kind === \"invalid\") {\n issue(unsupported, issueScope, {\n code: \"openspec.requirement-id-invalid\",\n path: path,\n message: `Requirement '${problem.requirement}' has invalid Requirement-ID '${problem.value}'; use [A-Za-z][A-Za-z0-9._-]{0,127}.`,\n });\n } else if (problem.kind === \"repeated\") {\n issue(unsupported, issueScope, {\n code: \"openspec.requirement-id-repeated\",\n path: path,\n message: `Requirement '${problem.requirement}' declares Requirement-ID ${problem.values.length} times; keep exactly one identity line.`,\n });\n } else {\n issue(unsupported, issueScope, {\n code: \"openspec.requirement-id-duplicate\",\n path: path,\n message: `Requirement-ID '${problem.id}' is shared by ${problem.requirements.map((name) => `'${name}'`).join(\", \")}.`,\n });\n }\n }\n if (kind === \"living\") {\n inspectLivingShape(path, raw, unsupported);\n } else {\n const changeScope = scope ?? \"active\";\n const usages = renameUsages(path, raw, changeScope, mapping.renames);\n renamed.push(...usages);\n inspectChangeShape(unsupported, path, raw, { scope: changeScope, renames: usages });\n }\n return { path, counts, requirements };\n}\n"]}
1
+ {"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/shape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Is this document a shape loam can migrate?\n *\n * The living side and the change side are graded separately because they fail\n * differently: a living spec that will not parse is a corpus loam cannot read,\n * while a change spec carries RENAMED pairs, which are the one OpenSpec\n * construct with no loam equivalent and the reason this package has an identity\n * problem to solve at all.\n *\n * Nothing here decides anything — it appends findings to the `Ingest` it was\n * given. The decisions are in `../decide/`.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { ownValue } from \"../../kernel/records.js\";\nimport { fenceTracker } from \"../../kernel/fences.js\";\nimport { isRequirementsHeading, parseRequirements, sectionHeadings } from \"../../document/parse.js\";\nimport { KIND_RE, requirementIdProblems } from \"../../document/spec.js\";\nimport { type Ingest } from \"./workspace.js\";\nimport { portable } from \"./walk.js\";\nimport {\n type OpenSpecCounts,\n type OpenSpecRenamedUsage,\n type OpenSpecUnsupportedShape,\n type OpenSpecIssueScope,\n} from \"../model/model.js\";\n\nexport function countsFor(requirements: ReturnType<typeof parseRequirements>): Omit<OpenSpecCounts, \"specFiles\"> {\n return {\n requirements: requirements.length,\n scenarios: requirements.reduce((sum, requirement) => sum + requirement.scenarios.length, 0),\n };\n}\n\nexport function addCounts(target: OpenSpecCounts, source: OpenSpecCounts): void {\n target.specFiles += source.specFiles;\n target.requirements += source.requirements;\n target.scenarios += source.scenarios;\n}\n\n/**\n * An ATX heading's optional closing run — `### Requirement: Foo ###` renders as\n * `Foo`, so the run is not part of the name (OpenSpec 1.13.1 strips it the same\n * way). Only a run after a space or tab closes a heading, so `C#` keeps its `#`.\n */\nconst CLOSING_HASHES = /[ \\t]+#+[ \\t]*$/;\n\n/** A REMOVED entry written as a bullet carrying the heading — any CommonMark marker. */\nconst REMOVED_BULLET = /^\\s*[-*+]\\s*`?###\\s*Requirement:\\s*(.+?)`?\\s*$/;\n\nexport function cleanRenameName(value: string): string {\n return value\n .trim()\n .replace(/^`|`$/g, \"\")\n .replace(/^###\\s+Requirement:\\s*/i, \"\")\n .replace(CLOSING_HASHES, \"\")\n .trim();\n}\n\n/**\n * OpenSpec's requirement grammar, as `parseRequirements` reads it. The two read\n * the same Markdown except where OpenSpec accepts more, and every OpenSpec\n * read in the migration goes through here so audit and `--apply` cannot\n * disagree about a name:\n *\n * - a closing `#` run on a `### Requirement:` / `#### Scenario:` heading is\n * dropped. Kept, a living `### Requirement: Invoice totals ###` audited\n * \"ready\", staged under the name `Invoice totals ###`, and the change's\n * MODIFIED `Invoice totals` then failed `loam validate` as\n * `delta.modified-unknown` with a \"Did you mean ADDED?\" that sent the author\n * the wrong way.\n * - a `## REMOVED Requirements` list of bullets (`-`, `*` or `+`) each naming\n * a requirement heading reads as that heading. v1.9.0 already accepted the\n * `-` list and 1.13.0 added the other two markers; loam read neither, so a\n * removal-only change was `openspec.change-empty`.\n *\n * A line-for-line rewrite, never a reflow, so every line number a finding\n * cites still points at the author's file — and loam's own parser is not\n * touched: a closing run in a loam document is the author's to keep.\n */\nexport function openspecRequirements(raw: string): ReturnType<typeof parseRequirements> {\n const lines = raw.replace(/^\\uFEFF/, \"\").split(/\\r?\\n/);\n const fenced = fencedLines(lines);\n let removed = false;\n const rewritten = lines.map((line, index) => {\n if (fenced[index] === true) return line;\n if (/^##\\s+/.test(line) && !line.startsWith(\"###\")) removed = KIND_RE.exec(line)?.[1]?.toUpperCase() === \"REMOVED\";\n const bullet = removed ? REMOVED_BULLET.exec(line) : null;\n const heading = bullet === null ? line : `### Requirement: ${bullet[1]!}`;\n return /^(###\\s+Requirement:|####\\s+Scenario:)/.test(heading) ? heading.replace(CLOSING_HASHES, \"\") : heading;\n });\n return parseRequirements(rewritten.join(\"\\n\"));\n}\n\nexport function changeSpecCoordinates(\n path: string,\n scope: \"active\" | \"archive\",\n): { changeId: string; capability: string } {\n const prefix = scope === \"active\" ? /^changes\\/([^/]+)\\/specs\\/(.+)\\/spec\\.md$/ : /^changes\\/archive\\/([^/]+)\\/specs\\/(.+)\\/spec\\.md$/;\n const match = prefix.exec(path);\n return { changeId: match?.[1] ?? \"\", capability: match?.[2] ?? \"\" };\n}\n\n/**\n * Which lines are fenced content, marker included — the same rule\n * `parseRequirements` and `sectionHeadings` apply, read from the one place it\n * is spelled (`core/kernel/fences.ts`). This used to be a private copy of it,\n * written while the rule still lived inside the parser module; a second copy is\n * how two walks of one file come to disagree about where a fence ends.\n * Without it a FROM/TO line inside a fenced example of the rename syntax became\n * a phantom pair, and the only cure was editing the OpenSpec source migration\n * promises never to touch.\n */\nexport function fencedLines(lines: string[]): boolean[] {\n const fenced = fenceTracker();\n return lines.map((line) => fenced(line));\n}\n\nexport function renameUsages(\n path: string,\n raw: string,\n scope: \"active\" | \"archive\",\n mapped: Record<string, string>,\n): OpenSpecRenamedUsage[] {\n const lines = raw.replace(/^\\uFEFF/, \"\").split(/\\r?\\n/);\n const fenced = fencedLines(lines);\n const headings = sectionHeadings(raw);\n const coordinates = changeSpecCoordinates(path, scope);\n const output: OpenSpecRenamedUsage[] = [];\n for (const [headingIndex, heading] of headings.entries()) {\n if (!/^##\\s+RENAMED\\s+Requirements?\\s*:?\\s*$/i.test(heading.text)) continue;\n const end = headings[headingIndex + 1]?.line ?? lines.length + 1;\n const pairs: Array<{ from: string | null; to: string | null }> = [];\n let pendingFrom: string | null = null;\n for (let line = heading.line; line < end - 1; line += 1) {\n if (fenced[line] === true) continue;\n const text = lines[line] ?? \"\";\n // The bullet is optional and any CommonMark marker counts, as in\n // OpenSpec since 1.13.0 (v1.9.0 already took a bare `FROM:`): a `*`\n // pair used to read as no pair at all, `openspec.renamed-malformed`\n // saying \"must contain a FROM/TO pair\" about a section that had one.\n const from = /^\\s*[-*+]?\\s*FROM:\\s*(.*?)\\s*$/i.exec(text);\n if (from !== null) {\n if (pendingFrom !== null) pairs.push({ from: pendingFrom, to: null });\n pendingFrom = cleanRenameName(from[1] ?? \"\");\n continue;\n }\n const to = /^\\s*[-*+]?\\s*TO:\\s*(.*?)\\s*$/i.exec(text);\n if (to !== null) {\n pairs.push({ from: pendingFrom, to: cleanRenameName(to[1] ?? \"\") });\n pendingFrom = null;\n }\n }\n if (pendingFrom !== null) pairs.push({ from: pendingFrom, to: null });\n if (pairs.length === 0) pairs.push({ from: null, to: null });\n pairs.forEach((pair, pairIndex) => {\n const key = `${path}:${heading.line}:${pairIndex + 1}`;\n const requirementId = ownValue(mapped, key) ?? null;\n output.push({\n key,\n path,\n line: heading.line,\n scope,\n changeId: coordinates.changeId,\n capability: coordinates.capability,\n from: pair.from,\n to: pair.to,\n existingRequirementId: null,\n requirementId,\n status: requirementId === null ? \"needsIdentity\" : \"mapped\",\n });\n });\n }\n return output;\n}\n\n/**\n * Record one shape loam cannot migrate.\n *\n * The finding is a record rather than three positional strings because code,\n * path and message are all strings and nothing but their order distinguished\n * them — two of the three were transposed at a call site once, and the report\n * named the message as the path.\n */\nexport function issue(\n target: OpenSpecUnsupportedShape[],\n scope: OpenSpecIssueScope,\n finding: { code: string; path: string; message: string },\n): void {\n target.push({ ...finding, scope });\n}\n\nexport function inspectLivingShape(\n path: string,\n raw: string,\n unsupported: OpenSpecUnsupportedShape[],\n): void {\n const requirements = openspecRequirements(raw);\n if (requirements.length === 0) {\n issue(unsupported, \"living\", {\n code: \"openspec.living-empty\",\n path: path,\n message: \"Living spec has no parseable Requirement headings.\",\n });\n return;\n }\n const outside = requirements.filter(\n (requirement) => requirement.section === undefined || !isRequirementsHeading(requirement.section),\n );\n if (outside.length > 0) {\n issue(unsupported, \"living\", {\n code: \"openspec.living-requirements-outside-section\",\n path: path,\n message: `${outside.length} requirement(s) are outside the canonical ## Requirements section.`,\n });\n }\n if (requirements.some((requirement) => requirement.kind !== \"BASE\")) {\n issue(unsupported, \"living\", {\n code: \"openspec.living-delta-section\",\n path: path,\n message: \"Living spec contains delta-kind requirements.\",\n });\n }\n}\n\nexport function inspectChangeShape(\n unsupported: OpenSpecUnsupportedShape[],\n path: string,\n raw: string,\n found: { scope: \"active\" | \"archive\"; renames: OpenSpecRenamedUsage[] },\n): void {\n const { scope, renames } = found;\n const requirements = openspecRequirements(raw);\n const fileRenames = renames.filter((rename) => rename.path === path);\n for (const rename of fileRenames) {\n if (rename.from === null || rename.from === \"\" || rename.to === null || rename.to === \"\") {\n issue(unsupported, scope, {\n code: \"openspec.renamed-malformed\",\n path: path,\n message: `RENAMED Requirements at line ${rename.line} must contain a FROM/TO pair.`,\n });\n }\n }\n if (requirements.length === 0) {\n // A well-formed rename-only delta is meaningful even though the requirement\n // parser correctly returns no requirement blocks for it.\n if (fileRenames.length === 0) {\n issue(unsupported, scope, {\n code: \"openspec.change-empty\",\n path: path,\n message: \"Change spec has no parseable Requirement headings.\",\n });\n }\n return;\n }\n // `## Requirements` is the heading OpenSpec's own living-spec template\n // mandates, so this is the shape a team produces by copying a living spec into\n // a change directory. It parses, it is counted, and nothing routes it: only\n // delta-kind requirements reach the staged feature. Blocking is what keeps the\n // per-change counters and what actually lands the same number.\n const quoted = requirements.filter(\n (requirement) => requirement.kind === \"BASE\"\n && requirement.section !== undefined\n && isRequirementsHeading(requirement.section),\n );\n if (quoted.length > 0) {\n unsupported.push({\n code: \"openspec.change-quoted-requirements\",\n path,\n message: `${quoted.length} requirement(s) sit under ## Requirements in a change delta, which stages nothing; re-home them under ADDED, MODIFIED, or REMOVED.`,\n scope,\n });\n }\n const stranded = requirements.filter(\n (requirement) => requirement.kind === \"BASE\"\n && (requirement.section === undefined || !isRequirementsHeading(requirement.section)),\n );\n if (stranded.length === requirements.length) {\n issue(unsupported, scope, {\n code: \"openspec.change-without-delta-sections\",\n path: path,\n message: \"Change requirements are not under ADDED, MODIFIED, REMOVED, or the explicitly non-merging ## Requirements section.\",\n });\n } else if (stranded.length > 0) {\n issue(unsupported, scope, {\n code: \"openspec.change-requirements-outside-delta-sections\",\n path: path,\n message: `${stranded.length} requirement(s) would be stranded outside delta sections.`,\n });\n }\n}\n\n/**\n * The parsed requirements come back with the counts because both callers wanted\n * them and each opened the file a second time to get its own copy — two reads\n * of the same bytes per spec.md, and two readings of it: the counts came from\n * this parse while the requirement names, and the living corpus every rename is\n * resolved against, came from the other. A file edited mid-audit could be\n * counted from one of them and routed from the other.\n */\nexport async function inspectSpecFile(\n ingest: Ingest,\n absolute: string,\n kind: \"living\" | \"change\",\n scope: \"active\" | \"archive\" | null,\n): Promise<{ path: string; counts: OpenSpecCounts; requirements: ReturnType<typeof parseRequirements> }> {\n const { root, mapping, renamed, unsupported } = ingest;\n const path = portable(root, absolute);\n const raw = await readFile(absolute, \"utf8\");\n const requirements = openspecRequirements(raw);\n const counts = { specFiles: 1, ...countsFor(requirements) };\n const issueScope = kind === \"living\" ? \"living\" : scope ?? \"active\";\n for (const problem of requirementIdProblems(requirements)) {\n if (problem.kind === \"invalid\") {\n issue(unsupported, issueScope, {\n code: \"openspec.requirement-id-invalid\",\n path: path,\n message: `Requirement '${problem.requirement}' has invalid Requirement-ID '${problem.value}'; use [A-Za-z][A-Za-z0-9._-]{0,127}.`,\n });\n } else if (problem.kind === \"repeated\") {\n issue(unsupported, issueScope, {\n code: \"openspec.requirement-id-repeated\",\n path: path,\n message: `Requirement '${problem.requirement}' declares Requirement-ID ${problem.values.length} times; keep exactly one identity line.`,\n });\n } else {\n issue(unsupported, issueScope, {\n code: \"openspec.requirement-id-duplicate\",\n path: path,\n message: `Requirement-ID '${problem.id}' is shared by ${problem.requirements.map((name) => `'${name}'`).join(\", \")}.`,\n });\n }\n }\n if (kind === \"living\") {\n inspectLivingShape(path, raw, unsupported);\n } else {\n const changeScope = scope ?? \"active\";\n const usages = renameUsages(path, raw, changeScope, mapping.renames);\n renamed.push(...usages);\n inspectChangeShape(unsupported, path, raw, { scope: changeScope, renames: usages });\n }\n return { path, counts, requirements };\n}\n"]}
@@ -9,8 +9,9 @@
9
9
  */
10
10
  import { createHash } from "node:crypto";
11
11
  import { lstat, readFile, readdir, stat } from "node:fs/promises";
12
- import { dirname, isAbsolute, join, relative, sep } from "node:path";
12
+ import { dirname, join, relative, sep } from "node:path";
13
13
  import { parse as parseYaml } from "yaml";
14
+ import { isPathInside } from "../../kernel/path-safety.js";
14
15
  import { asRecord } from "../../kernel/records.js";
15
16
  import { compareIds } from "../../repo/entries.js";
16
17
  export async function isDirectory(path) {
@@ -26,10 +27,8 @@ export function portable(root, path) {
26
27
  }
27
28
  /** Stable inventory namespace: planning-root relative, with one explicit external prefix. */
28
29
  export function sourceInventoryPath(root, absolute) {
29
- const rel = relative(root, absolute);
30
- if (rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel))) {
31
- return rel.split(sep).join("/");
32
- }
30
+ if (isPathInside(root, absolute))
31
+ return portable(root, absolute);
33
32
  return `@workspace/${portable(dirname(root), absolute)}`;
34
33
  }
35
34
  export async function subdirs(path) {
@@ -41,7 +40,7 @@ export async function subdirs(path) {
41
40
  .sort(compareIds);
42
41
  }
43
42
  /**
44
- * Exactly what `subdirs` drops. walkFiles does not drop it, so a change behind a
43
+ * Exactly what `subdirs` drops. walkTree does not drop it, so a change behind a
45
44
  * dot-directory was never enumerated as a change while its files were still
46
45
  * classified, counted and given a disposition slot — a decision recorded as
47
46
  * selected for an artifact nothing migrates.
@@ -54,29 +53,18 @@ export async function hiddenSubdirs(path) {
54
53
  .map((entry) => entry.name)
55
54
  .sort(compareIds);
56
55
  }
57
- export async function walkFiles(path) {
58
- if (!await isDirectory(path))
59
- return [];
56
+ /**
57
+ * Every regular file and every symbolic link under `path`, sorted and
58
+ * depth-first, skipping `.git`. Links are inventoried, never followed into
59
+ * unbound source trees. One walk serves both answers because a link's Dirent
60
+ * is neither a directory nor a file, so recording links separately leaves the
61
+ * file set and the set of directories descended exactly what two walks gave.
62
+ * A `Dirent` reports a link as a link, never as what it points at, so a link
63
+ * to a directory is recorded and never descended: the walk cannot leave the
64
+ * tree through one.
65
+ */
66
+ export async function walkTree(path) {
60
67
  const files = [];
61
- const walk = async (dir) => {
62
- const entries = (await readdir(dir, { withFileTypes: true }))
63
- .filter((entry) => entry.name !== ".git")
64
- .sort((a, b) => compareIds(a.name, b.name));
65
- for (const entry of entries) {
66
- const absolute = join(dir, entry.name);
67
- if (entry.isDirectory())
68
- await walk(absolute);
69
- else if (entry.isFile())
70
- files.push(absolute);
71
- }
72
- };
73
- await walk(path);
74
- return files;
75
- }
76
- /** Inventory symbolic links without following them into unbound source trees. */
77
- export async function walkSymlinks(path) {
78
- if (!await isDirectory(path))
79
- return [];
80
68
  const links = [];
81
69
  const walk = async (dir) => {
82
70
  const entries = (await readdir(dir, { withFileTypes: true }))
@@ -88,10 +76,13 @@ export async function walkSymlinks(path) {
88
76
  links.push(absolute);
89
77
  else if (entry.isDirectory())
90
78
  await walk(absolute);
79
+ else if (entry.isFile())
80
+ files.push(absolute);
91
81
  }
92
82
  };
93
- await walk(path);
94
- return links;
83
+ if (await isDirectory(path))
84
+ await walk(path);
85
+ return { files, links };
95
86
  }
96
87
  export async function isSymbolicLink(path) {
97
88
  try {
@@ -101,14 +92,6 @@ export async function isSymbolicLink(path) {
101
92
  return false;
102
93
  }
103
94
  }
104
- /**
105
- * `Array.isArray` narrows an `unknown` to `any[]`, which is how a checked
106
- * element type went on being asserted back in a line later. Deciding both
107
- * halves in one predicate makes the narrowing carry the check.
108
- */
109
- export function isStringArray(value) {
110
- return Array.isArray(value) && value.every((item) => typeof item === "string");
111
- }
112
95
  export async function yamlRecord(path) {
113
96
  const parsed = parseYaml(await readFile(path, "utf8"));
114
97
  const record = asRecord(parsed);
@@ -1 +1 @@
1
- {"version":3,"file":"walk.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/walk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reading the tree: which directories exist, which files are under them, which\n * of them are symlinks, and the digest that binds a mapping to what was audited.\n *\n * Symlinks are enumerated rather than followed. The whole package reads\n * somebody else's repository, so \"what is under this directory\" has to be a\n * question about THIS workspace — a link out of it is a finding, never a file\n * to walk into.\n */\nimport { createHash } from \"node:crypto\";\nimport { lstat, readFile, readdir, stat } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, sep } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { asRecord } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\n\nexport async function isDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function portable(root: string, path: string): string {\n return relative(root, path).split(sep).join(\"/\");\n}\n\n/** Stable inventory namespace: planning-root relative, with one explicit external prefix. */\nexport function sourceInventoryPath(root: string, absolute: string): string {\n const rel = relative(root, absolute);\n if (rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel))) {\n return rel.split(sep).join(\"/\");\n }\n return `@workspace/${portable(dirname(root), absolute)}`;\n}\n\nexport async function subdirs(path: string): Promise<string[]> {\n if (!await isDirectory(path)) return [];\n return (await readdir(path, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"))\n .map((entry) => entry.name)\n .sort(compareIds);\n}\n\n/**\n * Exactly what `subdirs` drops. walkFiles does not drop it, so a change behind a\n * dot-directory was never enumerated as a change while its files were still\n * classified, counted and given a disposition slot — a decision recorded as\n * selected for an artifact nothing migrates.\n */\nexport async function hiddenSubdirs(path: string): Promise<string[]> {\n if (!await isDirectory(path)) return [];\n return (await readdir(path, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && entry.name.startsWith(\".\"))\n .map((entry) => entry.name)\n .sort(compareIds);\n}\n\nexport async function walkFiles(path: string): Promise<string[]> {\n if (!await isDirectory(path)) return [];\n const files: string[] = [];\n const walk = async (dir: string): Promise<void> => {\n const entries = (await readdir(dir, { withFileTypes: true }))\n .filter((entry) => entry.name !== \".git\")\n .sort((a, b) => compareIds(a.name, b.name));\n for (const entry of entries) {\n const absolute = join(dir, entry.name);\n if (entry.isDirectory()) await walk(absolute);\n else if (entry.isFile()) files.push(absolute);\n }\n };\n await walk(path);\n return files;\n}\n\n/** Inventory symbolic links without following them into unbound source trees. */\nexport async function walkSymlinks(path: string): Promise<string[]> {\n if (!await isDirectory(path)) return [];\n const links: string[] = [];\n const walk = async (dir: string): Promise<void> => {\n const entries = (await readdir(dir, { withFileTypes: true }))\n .filter((entry) => entry.name !== \".git\")\n .sort((a, b) => compareIds(a.name, b.name));\n for (const entry of entries) {\n const absolute = join(dir, entry.name);\n if (entry.isSymbolicLink()) links.push(absolute);\n else if (entry.isDirectory()) await walk(absolute);\n }\n };\n await walk(path);\n return links;\n}\n\nexport async function isSymbolicLink(path: string): Promise<boolean> {\n try {\n return (await lstat(path)).isSymbolicLink();\n } catch {\n return false;\n }\n}\n\n/**\n * `Array.isArray` narrows an `unknown` to `any[]`, which is how a checked\n * element type went on being asserted back in a line later. Deciding both\n * halves in one predicate makes the narrowing carry the check.\n */\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nexport async function yamlRecord(path: string): Promise<Record<string, unknown>> {\n const parsed: unknown = parseYaml(await readFile(path, \"utf8\"));\n const record = asRecord(parsed);\n if (record === null) throw new Error(\"expected a YAML mapping\");\n return record;\n}\n\nexport async function digestInventory(root: string, files: string[]): Promise<string> {\n const hash = createHash(\"sha256\");\n // v3 narrowed the covered set to living + active. The version string is what\n // stops a v2 mapping from being accepted against a v3 reading of the same tree.\n hash.update(\"loam-openspec-inventory-v3\\0\");\n for (const absolute of [...new Set(files)].sort((a, b) => compareIds(sourceInventoryPath(root, a), sourceInventoryPath(root, b)))) {\n const path = sourceInventoryPath(root, absolute);\n const bytes = await readFile(absolute);\n hash.update(`${path.length}:${path}:${bytes.length}:`);\n hash.update(bytes);\n hash.update(\"\\0\");\n }\n return `sha256:${hash.digest(\"hex\")}`;\n}\n"]}
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/walk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reading the tree: which directories exist, which files are under them, which\n * of them are symlinks, and the digest that binds a mapping to what was audited.\n *\n * Symlinks are enumerated rather than followed. The whole package reads\n * somebody else's repository, so \"what is under this directory\" has to be a\n * question about THIS workspace — a link out of it is a finding, never a file\n * to walk into.\n */\nimport { createHash } from \"node:crypto\";\nimport { lstat, readFile, readdir, stat } from \"node:fs/promises\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { isPathInside } from \"../../kernel/path-safety.js\";\nimport { asRecord } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\n\nexport async function isDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function portable(root: string, path: string): string {\n return relative(root, path).split(sep).join(\"/\");\n}\n\n/** Stable inventory namespace: planning-root relative, with one explicit external prefix. */\nexport function sourceInventoryPath(root: string, absolute: string): string {\n if (isPathInside(root, absolute)) return portable(root, absolute);\n return `@workspace/${portable(dirname(root), absolute)}`;\n}\n\nexport async function subdirs(path: string): Promise<string[]> {\n if (!await isDirectory(path)) return [];\n return (await readdir(path, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"))\n .map((entry) => entry.name)\n .sort(compareIds);\n}\n\n/**\n * Exactly what `subdirs` drops. walkTree does not drop it, so a change behind a\n * dot-directory was never enumerated as a change while its files were still\n * classified, counted and given a disposition slot — a decision recorded as\n * selected for an artifact nothing migrates.\n */\nexport async function hiddenSubdirs(path: string): Promise<string[]> {\n if (!await isDirectory(path)) return [];\n return (await readdir(path, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && entry.name.startsWith(\".\"))\n .map((entry) => entry.name)\n .sort(compareIds);\n}\n\n/**\n * Every regular file and every symbolic link under `path`, sorted and\n * depth-first, skipping `.git`. Links are inventoried, never followed into\n * unbound source trees. One walk serves both answers because a link's Dirent\n * is neither a directory nor a file, so recording links separately leaves the\n * file set and the set of directories descended exactly what two walks gave.\n * A `Dirent` reports a link as a link, never as what it points at, so a link\n * to a directory is recorded and never descended: the walk cannot leave the\n * tree through one.\n */\nexport async function walkTree(path: string): Promise<{ files: string[]; links: string[] }> {\n const files: string[] = [];\n const links: string[] = [];\n const walk = async (dir: string): Promise<void> => {\n const entries = (await readdir(dir, { withFileTypes: true }))\n .filter((entry) => entry.name !== \".git\")\n .sort((a, b) => compareIds(a.name, b.name));\n for (const entry of entries) {\n const absolute = join(dir, entry.name);\n if (entry.isSymbolicLink()) links.push(absolute);\n else if (entry.isDirectory()) await walk(absolute);\n else if (entry.isFile()) files.push(absolute);\n }\n };\n if (await isDirectory(path)) await walk(path);\n return { files, links };\n}\n\nexport async function isSymbolicLink(path: string): Promise<boolean> {\n try {\n return (await lstat(path)).isSymbolicLink();\n } catch {\n return false;\n }\n}\n\nexport async function yamlRecord(path: string): Promise<Record<string, unknown>> {\n const parsed: unknown = parseYaml(await readFile(path, \"utf8\"));\n const record = asRecord(parsed);\n if (record === null) throw new Error(\"expected a YAML mapping\");\n return record;\n}\n\nexport async function digestInventory(root: string, files: string[]): Promise<string> {\n const hash = createHash(\"sha256\");\n // v3 narrowed the covered set to living + active. The version string is what\n // stops a v2 mapping from being accepted against a v3 reading of the same tree.\n hash.update(\"loam-openspec-inventory-v3\\0\");\n for (const absolute of [...new Set(files)].sort((a, b) => compareIds(sourceInventoryPath(root, a), sourceInventoryPath(root, b)))) {\n const path = sourceInventoryPath(root, absolute);\n const bytes = await readFile(absolute);\n hash.update(`${path.length}:${path}:${bytes.length}:`);\n hash.update(bytes);\n hash.update(\"\\0\");\n }\n return `sha256:${hash.digest(\"hex\")}`;\n}\n"]}
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
12
  import { FleetContext } from "../fleet-context.js";
13
+ import { brokenDocumentPaths } from "../c4/likec4.js";
13
14
  import { serviceResolver } from "../c4/resolve/service.js";
14
15
  import { repoPath } from "../envelope/json.js";
15
16
  import { listField, parseFrontmatter, stringField } from "../document/frontmatter.js";
@@ -18,6 +19,8 @@ import {} from "../document/spec.js";
18
19
  import { landscapeEvidence, maturityGaps, serviceMaturity, } from "../vocabulary/maturity.js";
19
20
  import {} from "../repo/entries.js";
20
21
  import { landscapePath, servicePathsAt } from "../repo/paths.js";
22
+ import { NO_DEPLOYMENT } from "../c4/parsed/deployment.js";
23
+ import { serviceTopology } from "../deployment/objects.js";
21
24
  export function packRequirement(r) {
22
25
  return {
23
26
  kind: r.kind,
@@ -47,7 +50,21 @@ export async function buildLiving(req) {
47
50
  // Both headers, exactly as `show` reads both (commands/show/service.ts):
48
51
  // a memo hit on the same read the requirements above already paid for.
49
52
  existsSync(paths.archSpec) ? context.readText(paths.archSpec) : null,
50
- existsSync(landscapePath(docsDir)) ? context.loadLikeC4(landscapePath(docsDir)) : null,
53
+ // The `architecture/` PROJECT, never `architecture/landscape.likec4` alone.
54
+ // The map is a project — the landscape merged with every use case, palette
55
+ // and second `model { }` block beside it — and reading one file of it made
56
+ // this pack answer differently from `loam adopt` about the same tree
57
+ // (review finding F26, reproduced: with a broken `architecture/palette.likec4`
58
+ // beside a landscape that parses, `adopt --service order-service` reported
59
+ // `parses: false` and refused to say anything about the map, while `context
60
+ // order-service` reported `parses: true` and listed its edges). The brief's
61
+ // own banner (core/brief/landscape.ts) already named this module as the
62
+ // reader that disagreed. Whichever answer is right, an agent handed both
63
+ // has no way to tell — and the renderer's answer is the project's.
64
+ //
65
+ // `present` stays a fact about the FILE (`existsSync` below), so a repo
66
+ // with no landscape is still "absent" rather than "empty project".
67
+ existsSync(landscapePath(docsDir)) ? context.architecture(docsDir) : null,
51
68
  ]);
52
69
  const present = land !== null;
53
70
  const parses = land !== null && land.errors.length === 0;
@@ -59,8 +76,12 @@ export async function buildLiving(req) {
59
76
  const svcOf = serviceResolver(elements, known);
60
77
  // The partition, the modelled probe and the apiExpected rule are ONE shared
61
78
  // derivation (core/vocabulary/maturity.ts `landscapeEvidence`) — the same
62
- // one explore's describe uses, so the pack and explore cannot grade the
63
- // same service differently.
79
+ // one explore's describe uses, so the pack and explore cannot spell the same
80
+ // rule two ways. They are handed the same DOCUMENTS as well: `explore` reads
81
+ // the `architecture/` project too (core/explore/explore.ts), which it did not
82
+ // when this slice moved off the single file — for a while the two differed by
83
+ // input on a fleet whose sibling document is broken, which is a disagreement
84
+ // no reader of both could resolve.
64
85
  const { inbound, outbound, modelled, apiExpected } = landscapeEvidence({
65
86
  id: entry.id,
66
87
  parses,
@@ -68,6 +89,23 @@ export async function buildLiving(req) {
68
89
  elementIds: elements.map((e) => e.id),
69
90
  svcOf,
70
91
  });
92
+ // Where this service RUNS, from the landscape's own deployment model. The
93
+ // pack's reader is an agent about to write the implementation, and until this
94
+ // slice existed it described a service's calls and contracts while saying
95
+ // nothing whatever about its topology — so a failover got written against a
96
+ // map nobody had read. Empty for every fleet that draws none, which is the
97
+ // axis's opt-in and not a finding.
98
+ //
99
+ // Read off the PROJECT, for the reason `validate/service/specs.ts` pays the
100
+ // same load: `architecture/` is one LikeC4 project and a fleet's
101
+ // `deployment { }` block is ordinarily a file beside the landscape, not
102
+ // inside it. A single-file read would report "this service runs nowhere" over
103
+ // a repo that draws it, which is the one answer a briefing must never give.
104
+ // This used to be a SECOND load, made only when the landscape file itself
105
+ // declared no topology; `land` is that project now, so the fallback would be
106
+ // the same document parsed twice.
107
+ const topology = parses ? (land.deployment ?? NO_DEPLOYMENT) : NO_DEPLOYMENT;
108
+ const deployment = serviceTopology(topology, elements, known, entry.id);
71
109
  const input = { entry, archSpec: existsSync(paths.archSpec), apiExpected };
72
110
  const header = (text) => text === null ? { present: false, malformed: false, data: {}, body: "" } : parseFrontmatter(text);
73
111
  const fm = header(specText);
@@ -88,7 +126,8 @@ export async function buildLiving(req) {
88
126
  sources: listField(fm, "sources"),
89
127
  },
90
128
  archSpec: { vouch_scope: scopeText(header(archText)) },
91
- landscape: { present, parses, modelled, inbound, outbound },
129
+ landscape: { present, parses, broken: land === null ? [] : brokenDocumentPaths(docsDir, land.errors), modelled, inbound, outbound },
130
+ deployment,
92
131
  requirements: reqs.map(packRequirement),
93
132
  archRequirements: archReqs.map(packRequirement),
94
133
  openapi: { unreadable: api.unreadable, ...(api.error === undefined ? {} : { error: api.error }) },
@@ -1 +1 @@
1
- {"version":3,"file":"living.js","sourceRoot":"","sources":["../../../src/core/pack/living.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAoB,MAAM,4BAA4B,CAAC;AACxG,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAoB,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,GAGhB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAqB,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBjE,MAAM,UAAU,eAAe,CAAC,CAAc;IAC5C,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;QAC3C,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;QAC9B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;KACtE,CAAC;AACJ,CAAC;AAoED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAkB;IAClD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC;IACxC,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAExC,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAChF,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;QAC1D,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;QAC1E,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC;QAClC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC;QACpC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;QACpD,yEAAyE;QACzE,uEAAuE;QACvE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;QACpE,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;KACvF,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,qEAAqE;IACrE,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,4EAA4E;IAC5E,0EAA0E;IAC1E,wEAAwE;IACxE,4BAA4B;IAC5B,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC;QACrE,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,MAAM;QACN,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE;QAC/C,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,KAAK;KACN,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IAE3E,MAAM,MAAM,GAAG,CAAC,IAAmB,EAAe,EAAE,CAClD,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpG,MAAM,EAAE,GAAgB,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEzC,MAAM,OAAO,GAAG,CAAC,EAAU,EAAY,EAAE,CACvC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE1C,OAAO;QACL,QAAQ,EAAE,eAAe,CAAC,KAAK,CAAC;QAChC,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC;QAC5B,WAAW,EAAE;YACX,MAAM,EAAE,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,IAAI;YACzC,KAAK,EAAE,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI;YACvC,aAAa,EAAE,WAAW,CAAC,EAAE,EAAE,eAAe,CAAC,IAAI,IAAI;YACvD,qEAAqE;YACrE,oEAAoE;YACpE,mEAAmE;YACnE,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC;SAClC;QACD,QAAQ,EAAE,EAAE,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE;QACtD,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC3D,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC;QACvC,gBAAgB,EAAE,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC;QAC/C,OAAO,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE;QACjG,2EAA2E;QAC3E,yEAAyE;QACzE,yDAAyD;QACzD,UAAU,EAAE,GAAG,CAAC,GAAG;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;aACxB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACtG,QAAQ,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE;QAC3G,QAAQ,EAAE,MAAM,CAAC,QAAQ;aACtB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC;aAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;SAC/E,CAAC,CAAC;QACL,QAAQ,EAAE;YACR,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;YAC/E,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;YAC5E,wEAAwE;YACxE,yBAAyB;YACzB,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;SACpE;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The living slice of one service's context pack: its requirements verbatim,\n * its two contracts, its provenance, its maturity, and the fleet edges around\n * it — everything the docs repo says about the service TODAY, before any\n * feature in flight is consulted.\n *\n * Split from `./pack.ts` because it answers about the LIVING documents while\n * `./features.ts` answers about the deltas over them — two phases of one\n * briefing, and the line limit is what asked the question.\n */\nimport { existsSync } from \"node:fs\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { listField, parseFrontmatter, stringField, type Frontmatter } from \"../document/frontmatter.js\";\nimport { scopeText } from \"../provenance/sample/scope.js\";\nimport { type Requirement } from \"../document/spec.js\";\nimport {\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n type LandscapeEdge,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/**\n * A requirement as the pack carries it: the delta brief's `reqJson` shape plus\n * the four join lines (`Requires:`, `Capability:`, `Publishes:`, `Consumes:`)\n * the brief never needed. Verbatim on purpose — the pack is the briefing, and\n * a heading without its body is a table of contents for a file the reader then\n * has to open.\n */\nexport interface PackRequirement {\n kind: Requirement[\"kind\"];\n id?: string;\n name: string;\n text: string;\n operations: string[];\n covers: string[];\n requires: string[];\n capabilities: string[];\n publishes: string[];\n consumes: string[];\n /** Verbatim: the acceptance criteria, and the source for the tests whoever picks this up writes. */\n scenarios: Array<{ name: string; lines: string[] }>;\n}\n\nexport function packRequirement(r: Requirement): PackRequirement {\n return {\n kind: r.kind,\n ...(r.id === undefined ? {} : { id: r.id }),\n name: r.name,\n text: r.text.join(\"\\n\").trim(),\n operations: r.operations,\n covers: r.covers,\n requires: r.requires,\n capabilities: r.capabilities,\n publishes: r.publishes,\n consumes: r.consumes,\n scenarios: r.scenarios.map((s) => ({ name: s.name, lines: s.lines })),\n };\n}\n\nexport interface LivingSlice {\n maturity: Maturity;\n /** What stands between the service and `vouched` — `maturityGaps`, so it matches `list --needs-work`. */\n missing: string[];\n /**\n * `vouch_scope` rides beside `status` because this pack is read by an agent\n * about to work on the service, and a bare `status: verified` over a vouch\n * that read a recorded sample of the document would be read as the whole of\n * it having been checked. Verbatim, null when there is none.\n *\n * spec.md's header ALONE — arch.spec.md carries its own, below.\n */\n frontmatter: {\n status: string | null;\n owner: string | null;\n last_verified: string | null;\n vouch_scope: string | null;\n sources: string[];\n };\n /**\n * arch.spec.md's own `vouch_scope`, under `show --json`'s spelling\n * (`archSpec.vouch_scope`) so the two surfaces name one fact one way.\n *\n * The sample is per FILE: one `loam vouch --sample` run reads a short\n * spec.md in full and stamps a long arch.spec.md sampled, and the pack read\n * only the first header. Every other trust surface — list, show,\n * `vouch --pack`, `validate --all`, status — reported that service as\n * sampled; context printed `verified`, `vouch_scope: null`, and then all of\n * arch.spec.md's requirements verbatim, of which nobody had read half. It is\n * the one surface whose reader is an agent that will act on the licence, and\n * `loam_context` exposes it as an MCP tool besides.\n */\n archSpec: { vouch_scope: string | null };\n /**\n * `present`/`parses` ride beside the edges (explore's shape) so silence is\n * evidence, not absence: an empty edge list under `parses: false` says\n * \"nobody could look\", not \"nobody calls this\".\n */\n landscape: {\n present: boolean;\n parses: boolean;\n modelled: boolean;\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n };\n requirements: PackRequirement[];\n archRequirements: PackRequirement[];\n /** Whether the living contract could be read at all — the vacuously-green guard every sibling carries. */\n openapi: { unreadable: boolean; error?: string };\n operations: Array<{ id: string; method: string; path: string; governedBy: string[] }>;\n asyncapi: { unreadable: boolean; error?: string };\n messages: Array<{ name: string; slot: string; direction: \"send\" | \"receive\" | null }>;\n /** Free-form prose stays a pointer: unbounded documents do not belong in a bounded pack. */\n pointers: {\n runbook: { present: boolean; path: string };\n health: { present: boolean; path: string };\n adrs: { count: number; path: string };\n };\n}\n\nexport interface LivingRequest {\n docsDir: DocsDir;\n entry: ServiceEntry;\n context: FleetContext;\n}\n\nexport async function buildLiving(req: LivingRequest): Promise<LivingSlice> {\n const { docsDir, entry, context } = req;\n const paths = servicePathsAt(entry.dir);\n\n // Independent reads, fanned out together; each is memoized on the shared\n // context, so anything `./joins.ts` or `./features.ts` re-asks is free.\n const [reqs, archReqs, api, events, specText, archText, land] = await Promise.all([\n entry.has.spec ? context.readRequirements(paths.spec) : [],\n existsSync(paths.archSpec) ? context.readRequirements(paths.archSpec) : [],\n context.readOpenapi(paths.openapi),\n context.readAsyncapi(paths.asyncapi),\n entry.has.spec ? context.readText(paths.spec) : null,\n // Both headers, exactly as `show` reads both (commands/show/service.ts):\n // a memo hit on the same read the requirements above already paid for.\n existsSync(paths.archSpec) ? context.readText(paths.archSpec) : null,\n existsSync(landscapePath(docsDir)) ? context.loadLikeC4(landscapePath(docsDir)) : null,\n ]);\n\n const present = land !== null;\n const parses = land !== null && land.errors.length === 0;\n const elements = parses ? land.elements : [];\n // Edges are filed under the service an element is BOUND to, with the\n // enumerated fleet riding in — the same resolution `show`, `explore` and\n // `validate` use, so no two commands can disagree about who calls whom.\n const known = new Set((await context.listServices(docsDir)).map((s) => s.id));\n const svcOf = serviceResolver(elements, known);\n // The partition, the modelled probe and the apiExpected rule are ONE shared\n // derivation (core/vocabulary/maturity.ts `landscapeEvidence`) — the same\n // one explore's describe uses, so the pack and explore cannot grade the\n // same service differently.\n const { inbound, outbound, modelled, apiExpected } = landscapeEvidence({\n id: entry.id,\n parses,\n relationships: parses ? land.relationships : [],\n elementIds: elements.map((e) => e.id),\n svcOf,\n });\n const input = { entry, archSpec: existsSync(paths.archSpec), apiExpected };\n\n const header = (text: string | null): Frontmatter =>\n text === null ? { present: false, malformed: false, data: {}, body: \"\" } : parseFrontmatter(text);\n const fm: Frontmatter = header(specText);\n\n const governs = (op: string): string[] =>\n reqs.filter((r) => r.operations.includes(op)).map((r) => r.name);\n const sent = new Set(events.sent);\n const received = new Set(events.received);\n\n return {\n maturity: serviceMaturity(input),\n missing: maturityGaps(input),\n frontmatter: {\n status: stringField(fm, \"status\") ?? null,\n owner: stringField(fm, \"owner\") ?? null,\n last_verified: stringField(fm, \"last_verified\") ?? null,\n // Through `scopeText`, not `stringField`: a scope whose value is not\n // text is still a stamped partial read, and this pack is read by an\n // agent that will otherwise take `status: verified` at face value.\n vouch_scope: scopeText(fm),\n sources: listField(fm, \"sources\"),\n },\n archSpec: { vouch_scope: scopeText(header(archText)) },\n landscape: { present, parses, modelled, inbound, outbound },\n requirements: reqs.map(packRequirement),\n archRequirements: archReqs.map(packRequirement),\n openapi: { unreadable: api.unreadable, ...(api.error === undefined ? {} : { error: api.error }) },\n // Document order — a fact of the file, hence of the state. `x-loam-remove`\n // markers are filtered exactly as `show` filters them: they are deletion\n // markers from a delta, not operations anybody can call.\n operations: api.ops\n .filter((o) => !o.remove)\n .map((o) => ({ id: o.id, method: o.method.toUpperCase(), path: o.path, governedBy: governs(o.id) })),\n asyncapi: { unreadable: events.unreadable, ...(events.error === undefined ? {} : { error: events.error }) },\n messages: events.messages\n .filter((m) => m.remove !== true)\n .map((m) => ({\n name: m.name,\n slot: m.slot,\n direction: sent.has(m.name) ? \"send\" : received.has(m.name) ? \"receive\" : null,\n })),\n pointers: {\n runbook: { present: entry.has.runbook, path: repoPath(docsDir, paths.runbook) },\n health: { present: entry.has.health, path: repoPath(docsDir, paths.health) },\n // The enumeration already counted the ADRs; a second readdir here could\n // only disagree with it.\n adrs: { count: entry.adrs, path: repoPath(docsDir, paths.adrsDir) },\n },\n };\n}\n"]}
1
+ {"version":3,"file":"living.js","sourceRoot":"","sources":["../../../src/core/pack/living.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The living slice of one service's context pack: its requirements verbatim,\n * its two contracts, its provenance, its maturity, and the fleet edges around\n * it — everything the docs repo says about the service TODAY, before any\n * feature in flight is consulted.\n *\n * Split from `./pack.ts` because it answers about the LIVING documents while\n * `./features.ts` answers about the deltas over them — two phases of one\n * briefing, and the line limit is what asked the question.\n */\nimport { existsSync } from \"node:fs\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { brokenDocumentPaths } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { listField, parseFrontmatter, stringField, type Frontmatter } from \"../document/frontmatter.js\";\nimport { scopeText } from \"../provenance/sample/scope.js\";\nimport { type Requirement } from \"../document/spec.js\";\nimport {\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n type LandscapeEdge,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { NO_DEPLOYMENT } from \"../c4/parsed/deployment.js\";\nimport { serviceTopology, type ServiceTopology } from \"../deployment/objects.js\";\n\n/**\n * A requirement as the pack carries it: the delta brief's `reqJson` shape plus\n * the four join lines (`Requires:`, `Capability:`, `Publishes:`, `Consumes:`)\n * the brief never needed. Verbatim on purpose — the pack is the briefing, and\n * a heading without its body is a table of contents for a file the reader then\n * has to open.\n */\nexport interface PackRequirement {\n kind: Requirement[\"kind\"];\n id?: string;\n name: string;\n text: string;\n operations: string[];\n covers: string[];\n requires: string[];\n capabilities: string[];\n publishes: string[];\n consumes: string[];\n /** Verbatim: the acceptance criteria, and the source for the tests whoever picks this up writes. */\n scenarios: Array<{ name: string; lines: string[] }>;\n}\n\nexport function packRequirement(r: Requirement): PackRequirement {\n return {\n kind: r.kind,\n ...(r.id === undefined ? {} : { id: r.id }),\n name: r.name,\n text: r.text.join(\"\\n\").trim(),\n operations: r.operations,\n covers: r.covers,\n requires: r.requires,\n capabilities: r.capabilities,\n publishes: r.publishes,\n consumes: r.consumes,\n scenarios: r.scenarios.map((s) => ({ name: s.name, lines: s.lines })),\n };\n}\n\nexport interface LivingSlice {\n maturity: Maturity;\n /** What stands between the service and `vouched` — `maturityGaps`, so it matches `list --needs-work`. */\n missing: string[];\n /**\n * `vouch_scope` rides beside `status` because this pack is read by an agent\n * about to work on the service, and a bare `status: verified` over a vouch\n * that read a recorded sample of the document would be read as the whole of\n * it having been checked. Verbatim, null when there is none.\n *\n * spec.md's header ALONE — arch.spec.md carries its own, below.\n */\n frontmatter: {\n status: string | null;\n owner: string | null;\n last_verified: string | null;\n vouch_scope: string | null;\n sources: string[];\n };\n /**\n * arch.spec.md's own `vouch_scope`, under `show --json`'s spelling\n * (`archSpec.vouch_scope`) so the two surfaces name one fact one way.\n *\n * The sample is per FILE: one `loam vouch --sample` run reads a short\n * spec.md in full and stamps a long arch.spec.md sampled, and the pack read\n * only the first header. Every other trust surface — list, show,\n * `vouch --pack`, `validate --all`, status — reported that service as\n * sampled; context printed `verified`, `vouch_scope: null`, and then all of\n * arch.spec.md's requirements verbatim, of which nobody had read half. It is\n * the one surface whose reader is an agent that will act on the licence, and\n * `loam_context` exposes it as an MCP tool besides.\n */\n archSpec: { vouch_scope: string | null };\n /**\n * `present`/`parses` ride beside the edges (explore's shape) so silence is\n * evidence, not absence: an empty edge list under `parses: false` says\n * \"nobody could look\", not \"nobody calls this\".\n *\n * `present` is about the FILE `architecture/landscape.likec4`; `parses` is\n * about the `architecture/` PROJECT, which is what the renderer loads and\n * what `adopt`, `validate --all` and `explore` all grade. A sibling document\n * with a syntax error therefore reads here as \"nobody could look\", exactly as\n * it does everywhere else.\n */\n landscape: {\n present: boolean;\n parses: boolean;\n /**\n * The `architecture/` documents that failed, docs-relative POSIX, deduped\n * and sorted — `[]` while `parses` is true. Spelled exactly as\n * `landscape.invalid` and the adopt brief spell them (all three through\n * `c4/likec4.ts`'s `brokenDocumentPaths`), because the three surfaces are\n * read one after another on the same tree and a reader has to be able to\n * tell they name the same file. It exists because `parses` moved\n * to the PROJECT while every sentence about it still said\n * `architecture/landscape.likec4`: with a broken sibling that file has no\n * errors at all, so the name sent a reader to bytes loam had read fine\n * (verification 2026-09-04).\n */\n broken: string[];\n modelled: boolean;\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n };\n /**\n * Where this service runs, off the landscape's `deployment { }` model, and\n * the deployment edges touching it — replication, failover, a network hop.\n *\n * Both ends, not only the outbound one: a replication edge is as often read\n * from the standby's side as from the primary's. Empty for a fleet that draws\n * no topology, and empty is not a finding here any more than it is anywhere\n * else on this axis.\n *\n * WHAT IT IS NOT. A green deployment axis means the documents agree with each\n * other — never that the second cluster exists, is reachable, or holds the\n * data a requirement claims. `core/brief/unchecked.ts` says so beside the\n * sixteen statements that say the same about the rest of the map, and an\n * agent reading this slice is exactly the reader that sentence is for.\n */\n deployment: ServiceTopology;\n requirements: PackRequirement[];\n archRequirements: PackRequirement[];\n /** Whether the living contract could be read at all — the vacuously-green guard every sibling carries. */\n openapi: { unreadable: boolean; error?: string };\n operations: Array<{ id: string; method: string; path: string; governedBy: string[] }>;\n asyncapi: { unreadable: boolean; error?: string };\n messages: Array<{ name: string; slot: string; direction: \"send\" | \"receive\" | null }>;\n /** Free-form prose stays a pointer: unbounded documents do not belong in a bounded pack. */\n pointers: {\n runbook: { present: boolean; path: string };\n health: { present: boolean; path: string };\n adrs: { count: number; path: string };\n };\n}\n\nexport interface LivingRequest {\n docsDir: DocsDir;\n entry: ServiceEntry;\n context: FleetContext;\n}\n\nexport async function buildLiving(req: LivingRequest): Promise<LivingSlice> {\n const { docsDir, entry, context } = req;\n const paths = servicePathsAt(entry.dir);\n\n // Independent reads, fanned out together; each is memoized on the shared\n // context, so anything `./joins.ts` or `./features.ts` re-asks is free.\n const [reqs, archReqs, api, events, specText, archText, land] = await Promise.all([\n entry.has.spec ? context.readRequirements(paths.spec) : [],\n existsSync(paths.archSpec) ? context.readRequirements(paths.archSpec) : [],\n context.readOpenapi(paths.openapi),\n context.readAsyncapi(paths.asyncapi),\n entry.has.spec ? context.readText(paths.spec) : null,\n // Both headers, exactly as `show` reads both (commands/show/service.ts):\n // a memo hit on the same read the requirements above already paid for.\n existsSync(paths.archSpec) ? context.readText(paths.archSpec) : null,\n // The `architecture/` PROJECT, never `architecture/landscape.likec4` alone.\n // The map is a project — the landscape merged with every use case, palette\n // and second `model { }` block beside it — and reading one file of it made\n // this pack answer differently from `loam adopt` about the same tree\n // (review finding F26, reproduced: with a broken `architecture/palette.likec4`\n // beside a landscape that parses, `adopt --service order-service` reported\n // `parses: false` and refused to say anything about the map, while `context\n // order-service` reported `parses: true` and listed its edges). The brief's\n // own banner (core/brief/landscape.ts) already named this module as the\n // reader that disagreed. Whichever answer is right, an agent handed both\n // has no way to tell — and the renderer's answer is the project's.\n //\n // `present` stays a fact about the FILE (`existsSync` below), so a repo\n // with no landscape is still \"absent\" rather than \"empty project\".\n existsSync(landscapePath(docsDir)) ? context.architecture(docsDir) : null,\n ]);\n\n const present = land !== null;\n const parses = land !== null && land.errors.length === 0;\n const elements = parses ? land.elements : [];\n // Edges are filed under the service an element is BOUND to, with the\n // enumerated fleet riding in — the same resolution `show`, `explore` and\n // `validate` use, so no two commands can disagree about who calls whom.\n const known = new Set((await context.listServices(docsDir)).map((s) => s.id));\n const svcOf = serviceResolver(elements, known);\n // The partition, the modelled probe and the apiExpected rule are ONE shared\n // derivation (core/vocabulary/maturity.ts `landscapeEvidence`) — the same\n // one explore's describe uses, so the pack and explore cannot spell the same\n // rule two ways. They are handed the same DOCUMENTS as well: `explore` reads\n // the `architecture/` project too (core/explore/explore.ts), which it did not\n // when this slice moved off the single file — for a while the two differed by\n // input on a fleet whose sibling document is broken, which is a disagreement\n // no reader of both could resolve.\n const { inbound, outbound, modelled, apiExpected } = landscapeEvidence({\n id: entry.id,\n parses,\n relationships: parses ? land.relationships : [],\n elementIds: elements.map((e) => e.id),\n svcOf,\n });\n // Where this service RUNS, from the landscape's own deployment model. The\n // pack's reader is an agent about to write the implementation, and until this\n // slice existed it described a service's calls and contracts while saying\n // nothing whatever about its topology — so a failover got written against a\n // map nobody had read. Empty for every fleet that draws none, which is the\n // axis's opt-in and not a finding.\n //\n // Read off the PROJECT, for the reason `validate/service/specs.ts` pays the\n // same load: `architecture/` is one LikeC4 project and a fleet's\n // `deployment { }` block is ordinarily a file beside the landscape, not\n // inside it. A single-file read would report \"this service runs nowhere\" over\n // a repo that draws it, which is the one answer a briefing must never give.\n // This used to be a SECOND load, made only when the landscape file itself\n // declared no topology; `land` is that project now, so the fallback would be\n // the same document parsed twice.\n const topology = parses ? (land.deployment ?? NO_DEPLOYMENT) : NO_DEPLOYMENT;\n const deployment = serviceTopology(topology, elements, known, entry.id);\n\n const input = { entry, archSpec: existsSync(paths.archSpec), apiExpected };\n\n const header = (text: string | null): Frontmatter =>\n text === null ? { present: false, malformed: false, data: {}, body: \"\" } : parseFrontmatter(text);\n const fm: Frontmatter = header(specText);\n\n const governs = (op: string): string[] =>\n reqs.filter((r) => r.operations.includes(op)).map((r) => r.name);\n const sent = new Set(events.sent);\n const received = new Set(events.received);\n\n return {\n maturity: serviceMaturity(input),\n missing: maturityGaps(input),\n frontmatter: {\n status: stringField(fm, \"status\") ?? null,\n owner: stringField(fm, \"owner\") ?? null,\n last_verified: stringField(fm, \"last_verified\") ?? null,\n // Through `scopeText`, not `stringField`: a scope whose value is not\n // text is still a stamped partial read, and this pack is read by an\n // agent that will otherwise take `status: verified` at face value.\n vouch_scope: scopeText(fm),\n sources: listField(fm, \"sources\"),\n },\n archSpec: { vouch_scope: scopeText(header(archText)) },\n landscape: { present, parses, broken: land === null ? [] : brokenDocumentPaths(docsDir, land.errors), modelled, inbound, outbound },\n deployment,\n requirements: reqs.map(packRequirement),\n archRequirements: archReqs.map(packRequirement),\n openapi: { unreadable: api.unreadable, ...(api.error === undefined ? {} : { error: api.error }) },\n // Document order — a fact of the file, hence of the state. `x-loam-remove`\n // markers are filtered exactly as `show` filters them: they are deletion\n // markers from a delta, not operations anybody can call.\n operations: api.ops\n .filter((o) => !o.remove)\n .map((o) => ({ id: o.id, method: o.method.toUpperCase(), path: o.path, governedBy: governs(o.id) })),\n asyncapi: { unreadable: events.unreadable, ...(events.error === undefined ? {} : { error: events.error }) },\n messages: events.messages\n .filter((m) => m.remove !== true)\n .map((m) => ({\n name: m.name,\n slot: m.slot,\n direction: sent.has(m.name) ? \"send\" : received.has(m.name) ? \"receive\" : null,\n })),\n pointers: {\n runbook: { present: entry.has.runbook, path: repoPath(docsDir, paths.runbook) },\n health: { present: entry.has.health, path: repoPath(docsDir, paths.health) },\n // The enumeration already counted the ADRs; a second readdir here could\n // only disagree with it.\n adrs: { count: entry.adrs, path: repoPath(docsDir, paths.adrsDir) },\n },\n };\n}\n"]}