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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../src/core/provenance/stamp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The stamp vocabulary: what `loam vouch` computes for a frontmatter stamp and\n * what a later check reads back out of one — `sources_digest` and the per-file\n * `sources_files` index beside it, the document's own `content_digest`, and\n * the local calendar day `last_verified` carries.\n */\nimport { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { rawBody } from \"../document/frontmatter.js\";\nimport { collectSources, type SkippedSource } from \"./walk.js\";\n\n/**\n * The local calendar day, for the two commands that date a stamp. A vouch or a\n * verification record is a person saying \"today I read this\", so it is their\n * date, not UTC's — `toISOString` files an evening in the Americas under\n * tomorrow. It sits here because this module already owns the stamp vocabulary\n * the date is written into.\n */\nexport function today(now: Date): string {\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* The content digest */\n/* ------------------------------------------------------------------ */\n\n/** How much of the sha256 is written into the document. */\nconst DIGEST_LENGTH = 16;\n\n/** How many source files the digest reads at once — see `sourcesDigest`. */\nconst DIGEST_READ_BATCH = 32;\n\nexport interface SourcesDigest extends SourcesExpansion {\n /** The stamp that goes in `sources_digest`. */\n digest: string;\n /**\n * Per file, its own short sha — the record `sources_files` carries so a later\n * `sources.stale` can name what moved instead of guessing. Same order as `files`.\n */\n index: SourceIndexEntry[];\n}\n\n/** One file's share of the stamp: what it is called, and what it said. */\nexport interface SourceIndexEntry {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** sha256 of its bytes, truncated the way `sources_digest` is. */\n sha: string;\n}\n\n/** What a `sources` list covers, and what it does not. */\nexport interface SourcesExpansion {\n /** Repo-relative paths, sorted — exactly what a digest would hash. */\n files: string[];\n /** Everything the walk refused to follow. Empty in the ordinary case. */\n skipped: SkippedSource[];\n /**\n * Set only when `files` is empty: the one sentence that says the list covers\n * nothing. `loam vouch` refuses with it, `loam validate` grades it — see\n * emptySourcesMessage for why they must be the same words.\n */\n empty?: string;\n}\n\n/**\n * Digest the CONTENT of the files `sources` names. The recipe is spelled out\n * because the value is written into documents, and anything reading them has to\n * be able to reproduce it:\n *\n * 1. expand each entry to repo-relative file paths (`/` separators) — a file\n * is itself, a directory everything beneath it, minus what the repository\n * does not consider its own (see collectSources) — sorted and de-duplicated;\n * 2. per file, `sha256(bytes)`;\n * 3. feed `<path>\\0<hex>\\n` for each file, in that order, into an outer\n * sha256;\n * 4. keep the first 16 hex characters.\n *\n * Content, not mtime: git does not preserve modification times, so after a\n * fresh clone every file looks changed and the check would be false positives\n * end to end. Bytes survive the clone. Hashing the path alongside the content\n * means a rename registers, which is what a reader of the doc would want.\n *\n * 64 bits is a change detector, not a seal — it answers \"did this move?\", and\n * an adversary who wants a collision can have one.\n */\nexport async function sourcesDigest(repoDir: string, sources: string[]): Promise<SourcesDigest> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const outer = createHash(\"sha256\");\n const index: SourceIndexEntry[] = [];\n // Read in fixed batches, hash in array order. `found` is already sorted and\n // every file's own sha depends on nothing but its own bytes, so the sequence\n // fed to `outer` — and therefore the digest — is unchanged by construction;\n // only the waiting overlaps. A `sources: [src/]` over a real service repo is\n // thousands of files, and one-await-per-file spent nearly all of its time\n // idle. The batch is bounded rather than a single `Promise.all` over the\n // whole list because an unbounded fan-out over a source tree runs the process\n // out of file descriptors, which would turn a stamp into an EMFILE.\n for (let start = 0; start < found.length; start += DIGEST_READ_BATCH) {\n const hashed = await Promise.all(\n found.slice(start, start + DIGEST_READ_BATCH).map(async (file) => ({\n rel: file.rel,\n content: createHash(\"sha256\").update(await readFile(file.abs)).digest(\"hex\"),\n })),\n );\n for (const { rel, content } of hashed) {\n outer.update(`${rel}\\0${content}\\n`);\n index.push({ path: rel, sha: content.slice(0, DIGEST_LENGTH) });\n }\n }\n // `empty` is deliberately not set here: it is a sentence about a named\n // document, and a digest has no name to put in it. Callers that need it say\n // emptySourcesMessage(label, sources) — the same words, from one definition.\n return { digest: outer.digest(\"hex\").slice(0, DIGEST_LENGTH), files: index.map((e) => e.path), index, skipped };\n}\n\n/**\n * What a `sources` list actually covers — the digest's file set without the\n * hashing.\n *\n * Exported because two commands must give the SAME answer to \"does this list\n * cover anything?\". `loam vouch` refuses to stamp an expansion that covers no\n * files (a digest over nothing never changes, so the stamp would read as\n * current forever), and until `loam validate` could say the same thing in the\n * same words, an author got a green validate followed by a vouch that refused —\n * two commands contradicting each other about one document, with nothing in the\n * green run hinting at it.\n */\nexport async function expandSourceFiles(\n repoDir: string,\n sources: string[],\n label: string,\n): Promise<SourcesExpansion> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const files = found.map((f) => f.rel);\n return { files, skipped, ...(files.length === 0 ? { empty: emptySourcesMessage(label, sources) } : {}) };\n}\n\n/**\n * The one sentence for \"these paths exist and cover no file\". One definition,\n * because `vouch`'s refusal and `validate`'s finding are the same diagnosis\n * about the same document and must not drift into two descriptions of it — the\n * author fixes it once, in the sources list.\n */\nexport function emptySourcesMessage(label: string, sources: string[]): string {\n return `${label}: the sources listed match no files — ${sources.join(\", \")}. A digest over nothing would read as current forever.`;\n}\n\n/**\n * The stamp `loam vouch` writes into `content_digest`: sha256 of the\n * document's own BODY — every byte after the frontmatter block (below the\n * closing `---` line and its newline; `rawBody` is the one definition of that\n * cut) — first 16 hex characters, the sources recipe's length.\n *\n * Byte-exact, no normalization. Body-only is load-bearing: vouch itself\n * rewrites the frontmatter as it stamps, and a later frontmatter-only edit\n * (another vouch, a corrected owner) must not read as the document moving.\n */\nexport function contentDigest(source: string): string {\n return createHash(\"sha256\").update(rawBody(source), \"utf8\").digest(\"hex\").slice(0, DIGEST_LENGTH);\n}\n\n/* ------------------------------------------------------------------ */\n/* The per-file index: what `sources.stale` names */\n/* ------------------------------------------------------------------ */\n\n/**\n * How many files `sources_files` will list one by one before it gives up and\n * records only how many there were.\n *\n * A readability budget, not a correctness one: the index lives in the header of\n * a document a person reads, and a `sources: [src/]` over a legacy service can\n * expand to thousands of files. Past the limit `sources.stale` falls back to\n * repeating the `sources` entries — worse advice, but a spec.md whose\n * frontmatter is longer than its requirements is worse still.\n */\nconst SOURCE_INDEX_LIMIT = 100;\n\n/**\n * The `sources_files` value `loam vouch` stamps beside `sources_digest`: one\n * `<sha> <path>` line per file (sha256sum's layout, and its column order, so\n * the path may contain spaces), or just the file count once the list would run\n * past SOURCE_INDEX_LIMIT.\n *\n * Its whole purpose is the next `sources.stale`. `sources_digest` can say THAT\n * the code moved and never which part of it; with the index a reader gets the\n * added, removed and changed paths, which is the difference between re-reading\n * one file and re-reading a directory. It is also a readable git diff in the\n * docs repo: the vouch commit shows exactly which source files the stamp now\n * stands over.\n */\nexport function encodeSourceIndex(index: SourceIndexEntry[]): string {\n if (index.length > SOURCE_INDEX_LIMIT) return String(index.length);\n return index.map((e) => `${e.sha} ${e.path}`).join(\"\\n\");\n}\n\n/** What a stamped `sources_files` says: the per-file shas, the count, or nothing. */\ninterface StampedIndex {\n /** path -> sha, or null when the stamp did not record them. */\n entries: Map<string, string> | null;\n /** How many files the stamp covered, when that is knowable. */\n count?: number;\n}\n\n/**\n * Read back what `encodeSourceIndex` wrote. Anything unrecognised reads as \"the\n * stamp said nothing\" rather than as an empty index: a header nobody can parse\n * must not turn into a `sources.stale` claiming every file was deleted.\n */\nexport function decodeSourceIndex(stamped: string | undefined): StampedIndex {\n if (stamped === undefined) return { entries: null };\n const text = stamped.trim();\n if (text.length === 0) return { entries: null };\n if (/^\\d+$/.test(text)) return { entries: null, count: Number(text) };\n\n const entries = new Map<string, string>();\n for (const line of text.split(\"\\n\")) {\n const match = /^([0-9a-f]{16})\\s+(.+)$/.exec(line.trim());\n if (match === null) return { entries: null };\n entries.set(match[2]!, match[1]!);\n }\n return { entries, count: entries.size };\n}\n\n/** The paths that moved between a stamp and now, with a phrase for the message. */\ninterface MovedSources {\n /** Annotated paths, or undefined when the stamp did not record enough to say. */\n paths?: string[];\n /** Appended to the `sources.stale` message; empty when there is nothing to add. */\n summary: string;\n}\n\n/**\n * The structured half of the answer: which paths were added, changed and\n * removed between a stamp and the current index — or null when the stamp\n * cannot say. Null covers two distinct silences on purpose, both of which mean\n * \"do not print a path list\": a stamp that recorded no per-file entries (the\n * count-only >100-file fallback, or a header nobody could parse), and an index\n * that AGREES with the current tree while the caller's digest comparison said\n * stale — a contradiction (a truncation collision, or a hand-edited header),\n * where an empty delta under a staleness warning would read as proof that\n * nothing moved. Callers that need the counts anyway read `stamp.count`.\n *\n * Extracted from `movedSources` (which now consumes it) when the re-vouch\n * reading pack became a second reader: the pack wants the three lists as\n * data, the stale finding wants them annotated in one column.\n */\nexport function sourceDelta(\n stamp: StampedIndex,\n now: SourceIndexEntry[],\n): { added: string[]; changed: string[]; removed: string[] } | null {\n if (stamp.entries === null) return null;\n const added: string[] = [];\n const changed: string[] = [];\n const current = new Map(now.map((e) => [e.path, e.sha]));\n for (const [path, sha] of current) {\n const before = stamp.entries.get(path);\n if (before === undefined) added.push(path);\n else if (before !== sha) changed.push(path);\n }\n const removed = [...stamp.entries.keys()].filter((path) => !current.has(path));\n if (added.length + changed.length + removed.length === 0) return null;\n added.sort();\n changed.sort();\n removed.sort();\n return { added, changed, removed };\n}\n\n/**\n * added / removed / changed, from the stamped index against the current one.\n * The annotation travels IN the detail line rather than in three separate\n * lists: a reader scanning a stale report wants the paths in one column, and\n * \"which of the three\" is one word wide.\n */\nexport function movedSources(stamp: StampedIndex, now: SourceIndexEntry[]): MovedSources {\n const delta = sourceDelta(stamp, now);\n if (delta === null) {\n // A recorded index that came back null is the contradiction case — say\n // nothing rather than print an empty list under a staleness warning. With\n // no index at all, the count alone still beats silence: \"12 files then,\n // 14 now\" tells a reader to go looking for two new files.\n return {\n summary:\n stamp.entries !== null || stamp.count === undefined\n ? \"\"\n : ` (${stamp.count} file(s) then, ${now.length} now)`,\n };\n }\n const paths = [\n ...delta.added.map((path) => `added ${path}`),\n ...delta.changed.map((path) => `changed ${path}`),\n ...delta.removed.map((path) => `removed ${path}`),\n ].sort();\n return { paths, summary: ` (${paths.length} path(s) moved)` };\n}\n"]}
1
+ {"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../src/core/provenance/stamp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The stamp vocabulary: what `loam vouch` computes for a frontmatter stamp and\n * what a later check reads back out of one — `sources_digest` and the per-file\n * `sources_files` index beside it, and the local calendar day `last_verified`\n * carries. The document's own `content_digest` lived here too; it moved to\n * core/document/frontmatter.ts, beside the body cut it hashes, when the fleet\n * enumeration needed it (`contentDigest`'s docblock says why).\n */\nimport { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { collectSources, type SkippedSource } from \"./walk.js\";\n\n/**\n * The local calendar day, for the two commands that date a stamp. A vouch or a\n * verification record is a person saying \"today I read this\", so it is their\n * date, not UTC's — `toISOString` files an evening in the Americas under\n * tomorrow. It sits here because this module already owns the stamp vocabulary\n * the date is written into.\n */\nexport function today(now: Date): string {\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* The sources digest */\n/* ------------------------------------------------------------------ */\n\n/** How much of the sha256 is written into the document. */\nconst DIGEST_LENGTH = 16;\n\n/** How many source files the digest reads at once — see `sourcesDigest`. */\nconst DIGEST_READ_BATCH = 32;\n\nexport interface SourcesDigest extends SourcesExpansion {\n /** The stamp that goes in `sources_digest`. */\n digest: string;\n /**\n * Per file, its own short sha — the record `sources_files` carries so a later\n * `sources.stale` can name what moved instead of guessing. Same order as `files`.\n */\n index: SourceIndexEntry[];\n}\n\n/** One file's share of the stamp: what it is called, and what it said. */\nexport interface SourceIndexEntry {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** sha256 of its bytes, truncated the way `sources_digest` is. */\n sha: string;\n}\n\n/** What a `sources` list covers, and what it does not. */\nexport interface SourcesExpansion {\n /** Repo-relative paths, sorted — exactly what a digest would hash. */\n files: string[];\n /** Everything the walk refused to follow. Empty in the ordinary case. */\n skipped: SkippedSource[];\n /**\n * Set only when `files` is empty: the one sentence that says the list covers\n * nothing. `loam vouch` refuses with it, `loam validate` grades it — see\n * emptySourcesMessage for why they must be the same words.\n */\n empty?: string;\n}\n\n/**\n * Digest the CONTENT of the files `sources` names. The recipe is spelled out\n * because the value is written into documents, and anything reading them has to\n * be able to reproduce it:\n *\n * 1. expand each entry to repo-relative file paths (`/` separators) — a file\n * is itself, a directory everything beneath it, minus what the repository\n * does not consider its own (see collectSources) — sorted and de-duplicated;\n * 2. per file, `sha256(bytes)`;\n * 3. feed `<path>\\0<hex>\\n` for each file, in that order, into an outer\n * sha256;\n * 4. keep the first 16 hex characters.\n *\n * Content, not mtime: git does not preserve modification times, so after a\n * fresh clone every file looks changed and the check would be false positives\n * end to end. Bytes survive the clone. Hashing the path alongside the content\n * means a rename registers, which is what a reader of the doc would want.\n *\n * 64 bits is a change detector, not a seal — it answers \"did this move?\", and\n * an adversary who wants a collision can have one.\n */\nexport async function sourcesDigest(repoDir: string, sources: string[]): Promise<SourcesDigest> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const outer = createHash(\"sha256\");\n const index: SourceIndexEntry[] = [];\n // Read in fixed batches, hash in array order. `found` is already sorted and\n // every file's own sha depends on nothing but its own bytes, so the sequence\n // fed to `outer` — and therefore the digest — is unchanged by construction;\n // only the waiting overlaps. A `sources: [src/]` over a real service repo is\n // thousands of files, and one-await-per-file spent nearly all of its time\n // idle. The batch is bounded rather than a single `Promise.all` over the\n // whole list because an unbounded fan-out over a source tree runs the process\n // out of file descriptors, which would turn a stamp into an EMFILE.\n for (let start = 0; start < found.length; start += DIGEST_READ_BATCH) {\n const hashed = await Promise.all(\n found.slice(start, start + DIGEST_READ_BATCH).map(async (file) => ({\n rel: file.rel,\n content: createHash(\"sha256\").update(await readFile(file.abs)).digest(\"hex\"),\n })),\n );\n for (const { rel, content } of hashed) {\n outer.update(`${rel}\\0${content}\\n`);\n index.push({ path: rel, sha: content.slice(0, DIGEST_LENGTH) });\n }\n }\n // `empty` is deliberately not set here: it is a sentence about a named\n // document, and a digest has no name to put in it. Callers that need it say\n // emptySourcesMessage(label, sources) — the same words, from one definition.\n return { digest: outer.digest(\"hex\").slice(0, DIGEST_LENGTH), files: index.map((e) => e.path), index, skipped };\n}\n\n/**\n * What a `sources` list actually covers — the digest's file set without the\n * hashing.\n *\n * Exported because two commands must give the SAME answer to \"does this list\n * cover anything?\". `loam vouch` refuses to stamp an expansion that covers no\n * files (a digest over nothing never changes, so the stamp would read as\n * current forever), and until `loam validate` could say the same thing in the\n * same words, an author got a green validate followed by a vouch that refused —\n * two commands contradicting each other about one document, with nothing in the\n * green run hinting at it.\n */\nexport async function expandSourceFiles(\n repoDir: string,\n sources: string[],\n label: string,\n): Promise<SourcesExpansion> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const files = found.map((f) => f.rel);\n return { files, skipped, ...(files.length === 0 ? { empty: emptySourcesMessage(label, sources) } : {}) };\n}\n\n/**\n * The one sentence for \"these paths exist and cover no file\". One definition,\n * because `vouch`'s refusal and `validate`'s finding are the same diagnosis\n * about the same document and must not drift into two descriptions of it — the\n * author fixes it once, in the sources list.\n */\nexport function emptySourcesMessage(label: string, sources: string[]): string {\n return `${label}: the sources listed match no files — ${sources.join(\", \")}. A digest over nothing would read as current forever.`;\n}\n\n/* ------------------------------------------------------------------ */\n/* The per-file index: what `sources.stale` names */\n/* ------------------------------------------------------------------ */\n\n/**\n * How many files `sources_files` will list one by one before it gives up and\n * records only how many there were.\n *\n * A readability budget, not a correctness one: the index lives in the header of\n * a document a person reads, and a `sources: [src/]` over a legacy service can\n * expand to thousands of files. Past the limit `sources.stale` falls back to\n * repeating the `sources` entries — worse advice, but a spec.md whose\n * frontmatter is longer than its requirements is worse still.\n */\nconst SOURCE_INDEX_LIMIT = 100;\n\n/**\n * The `sources_files` value `loam vouch` stamps beside `sources_digest`: one\n * `<sha> <path>` line per file (sha256sum's layout, and its column order, so\n * the path may contain spaces), or just the file count once the list would run\n * past SOURCE_INDEX_LIMIT.\n *\n * Its whole purpose is the next `sources.stale`. `sources_digest` can say THAT\n * the code moved and never which part of it; with the index a reader gets the\n * added, removed and changed paths, which is the difference between re-reading\n * one file and re-reading a directory. It is also a readable git diff in the\n * docs repo: the vouch commit shows exactly which source files the stamp now\n * stands over.\n */\nexport function encodeSourceIndex(index: SourceIndexEntry[]): string {\n if (index.length > SOURCE_INDEX_LIMIT) return String(index.length);\n return index.map((e) => `${e.sha} ${e.path}`).join(\"\\n\");\n}\n\n/** What a stamped `sources_files` says: the per-file shas, the count, or nothing. */\ninterface StampedIndex {\n /** path -> sha, or null when the stamp did not record them. */\n entries: Map<string, string> | null;\n /** How many files the stamp covered, when that is knowable. */\n count?: number;\n}\n\n/**\n * Read back what `encodeSourceIndex` wrote. Anything unrecognised reads as \"the\n * stamp said nothing\" rather than as an empty index: a header nobody can parse\n * must not turn into a `sources.stale` claiming every file was deleted.\n */\nexport function decodeSourceIndex(stamped: string | undefined): StampedIndex {\n if (stamped === undefined) return { entries: null };\n const text = stamped.trim();\n if (text.length === 0) return { entries: null };\n if (/^\\d+$/.test(text)) return { entries: null, count: Number(text) };\n\n const entries = new Map<string, string>();\n for (const line of text.split(\"\\n\")) {\n const match = /^([0-9a-f]{16})\\s+(.+)$/.exec(line.trim());\n if (match === null) return { entries: null };\n entries.set(match[2]!, match[1]!);\n }\n return { entries, count: entries.size };\n}\n\n/** The paths that moved between a stamp and now, with a phrase for the message. */\ninterface MovedSources {\n /** Annotated paths, or undefined when the stamp did not record enough to say. */\n paths?: string[];\n /** Appended to the `sources.stale` message; empty when there is nothing to add. */\n summary: string;\n}\n\n/**\n * The structured half of the answer: which paths were added, changed and\n * removed between a stamp and the current index — or null when the stamp\n * cannot say. Null covers two distinct silences on purpose, both of which mean\n * \"do not print a path list\": a stamp that recorded no per-file entries (the\n * count-only >100-file fallback, or a header nobody could parse), and an index\n * that AGREES with the current tree while the caller's digest comparison said\n * stale — a contradiction (a truncation collision, or a hand-edited header),\n * where an empty delta under a staleness warning would read as proof that\n * nothing moved. Callers that need the counts anyway read `stamp.count`.\n *\n * Extracted from `movedSources` (which now consumes it) when the re-vouch\n * reading pack became a second reader: the pack wants the three lists as\n * data, the stale finding wants them annotated in one column.\n */\nexport function sourceDelta(\n stamp: StampedIndex,\n now: SourceIndexEntry[],\n): { added: string[]; changed: string[]; removed: string[] } | null {\n if (stamp.entries === null) return null;\n const added: string[] = [];\n const changed: string[] = [];\n const current = new Map(now.map((e) => [e.path, e.sha]));\n for (const [path, sha] of current) {\n const before = stamp.entries.get(path);\n if (before === undefined) added.push(path);\n else if (before !== sha) changed.push(path);\n }\n const removed = [...stamp.entries.keys()].filter((path) => !current.has(path));\n if (added.length + changed.length + removed.length === 0) return null;\n added.sort();\n changed.sort();\n removed.sort();\n return { added, changed, removed };\n}\n\n/**\n * added / removed / changed, from the stamped index against the current one.\n * The annotation travels IN the detail line rather than in three separate\n * lists: a reader scanning a stale report wants the paths in one column, and\n * \"which of the three\" is one word wide.\n */\nexport function movedSources(stamp: StampedIndex, now: SourceIndexEntry[]): MovedSources {\n const delta = sourceDelta(stamp, now);\n if (delta === null) {\n // A recorded index that came back null is the contradiction case — say\n // nothing rather than print an empty list under a staleness warning. With\n // no index at all, the count alone still beats silence: \"12 files then,\n // 14 now\" tells a reader to go looking for two new files.\n return {\n summary:\n stamp.entries !== null || stamp.count === undefined\n ? \"\"\n : ` (${stamp.count} file(s) then, ${now.length} now)`,\n };\n }\n const paths = [\n ...delta.added.map((path) => `added ${path}`),\n ...delta.changed.map((path) => `changed ${path}`),\n ...delta.removed.map((path) => `removed ${path}`),\n ].sort();\n return { paths, summary: ` (${paths.length} path(s) moved)` };\n}\n"]}
@@ -189,4 +189,22 @@ export function featureGlossaryDir(featureDir) {
189
189
  export function featureUseCasesDir(featureDir) {
190
190
  return join(featureDir, "usecases");
191
191
  }
192
+ /**
193
+ * `features/<FEAT>/deployment/` — the TOPOLOGY a feature brings.
194
+ *
195
+ * The third document a feature carries whole rather than as a delta, and for
196
+ * the same reason as the two above: a `deployment { }` block is a topology, the
197
+ * merge is a whole-file copy, and there is nothing to merge partially. What
198
+ * makes the slot work at all is that LikeC4 `extend` resolves ACROSS documents
199
+ * of one project — measured before the axis was written — so a feature adds a
200
+ * standby datacenter inside a living region without touching the living file.
201
+ *
202
+ * `delta.likec4` still refuses a `deployment { }` block, and the refusal now
203
+ * names this directory: the C4 delta re-declares the landscape's identifiers
204
+ * and carries its own `specification`, so it cannot be staged beside the map in
205
+ * one project, and a document that declares neither can.
206
+ */
207
+ export function featureDeploymentDir(featureDir) {
208
+ return join(featureDir, "deployment");
209
+ }
192
210
  //# sourceMappingURL=paths.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/core/repo/authored/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AACvC,CAAC;AAaD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB,EAAE,EAAU;IAChE,OAAO,oBAAoB,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,0BAA0B,CAAC,UAAsB;IAC/D,OAAO,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC","sourcesContent":["/**\n * WHERE THE TWO AUTHORED VOCABULARY TREES LIVE — `capabilities/<id>/` and\n * `glossary/<term>.md`.\n *\n * Split out of `../paths.ts` when that file passed 400 lines, and along the seam\n * that was already there rather than at the line the count happened to land on:\n * everything left in the parent spells the layout loam MAINTAINS — a service's\n * artifacts, a feature's deltas, the generated views — while these two spell the\n * trees a human AUTHORS and loam only reads. They share a rule the parent's\n * paths do not have: the directory IS the list, its existence is the axis's\n * opt-in, and `loam init` scaffolds neither.\n *\n * `ARTIFACT_FILES` is imported back from the parent rather than restated. The\n * capability document is `spec.md` because it is the same grammar read by the\n * same parser as a service's, and a second spelling of that name is exactly the\n * drift the parent's one-table rule exists to prevent.\n */\nimport { join } from \"node:path\";\nimport { ARTIFACT_FILES } from \"../paths.js\";\nimport type { DocsDir, FeatureDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * The AUTHORED business tree — `capabilities/<id>/`, one directory per\n * capability, each holding the document that describes it.\n *\n * Top-level and not under `architecture/`, because the two altitudes have\n * different authors and different reasons to change: `architecture/` is the\n * architect's — the map, the vocabularies, the use cases — while a capability\n * document is the analyst's, and it must survive every redesign of the fleet\n * that realizes it. A business tree filed inside the architecture tree would\n * say the opposite of what the axis is for.\n *\n * NESTING IS SPELLED BY THE TREE, not inside a name. A capability id keeps its\n * slashes wherever it is written (`core/capabilities/capabilities.ts` preserves\n * the YAML key exactly as a `Capability:` line spells it), so `payments/refunds`\n * lives at `capabilities/payments/refunds/spec.md` and `payments` may be a\n * capability in its own right at `capabilities/payments/spec.md`. A directory is\n * a capability if and only if it holds the document — the same\n * presence-classifies rule `isServiceArtifactName` applies one tree over, and\n * the reason it is safe here too: a group directory that acquires a document\n * becomes a capability visibly, in a diff, rather than by a marker file nobody\n * updated.\n *\n * NOT SCAFFOLDED BY `loam init`, exactly as `architecture/adrs/` is not. git\n * does not carry an empty directory, so a scaffolded one vanishes on the first\n * clone and returns as a diff on the next `init`; and an empty `capabilities/`\n * in a fresh repo reads as an obligation nobody has met, when a fleet that has\n * not adopted the axis owes nothing. The directory's EXISTENCE is the opt-in.\n */\nexport function capabilityDocsDir(docsDir: DocsDir): string {\n return join(docsDir, \"capabilities\");\n}\n\nexport interface CapabilityDocPaths {\n dir: string;\n /**\n * The capability's own document: narrative and requirements in one file.\n * `spec.md`, the same name a service's requirements live under, because it is\n * the same grammar read by the same parser — a second spelling would be a\n * second thing to keep in step for no reader's benefit.\n */\n spec: string;\n}\n\n/**\n * The document paths of a capability whose directory is already KNOWN — the\n * enumeration's `dir`, at whatever depth the walk found it. Same discipline as\n * `servicePathsAt`: joining `capabilities/<id>/` at the root is only true for a\n * flat id, and a nested capability would silently grade as absent through it.\n */\nexport function capabilityDocPathsAt(dir: string): CapabilityDocPaths {\n return { dir, spec: join(dir, ARTIFACT_FILES.spec) };\n}\n\n/**\n * The LIVING document a capability id addresses — `capabilities/` with the id's\n * own nesting spelled back out as directories, so `payments/refunds` resolves to\n * `capabilities/payments/refunds/spec.md`.\n *\n * Spelled here because FIVE readers need it — the delta grade, the archive\n * merge, the conflict-marker scan, the archive gate's strayed-requirement scan\n * and `loam rebase` — and a reader that resolved it differently would disagree\n * about which document a feature is changing. The delta grade is where that costs most: a living path that finds\n * nothing reads as an empty document, so every ADDED looks new and no\n * `Based-On:` is ever compared, and the merge then lands over text nobody\n * re-read.\n *\n * `...id.split(\"/\")` rather than passing the id whole is explicitness, not a\n * fix: `node:path.join` already normalizes an embedded separator, so both\n * spellings resolve identically on every platform. The split is what makes the\n * nesting visible at the one place the rule is written down. The mistake this\n * function actually prevents is the OTHER one — resolving a nested id by its\n * leaf, which silently addresses a different capability.\n *\n * `id` IS caller-controlled path input and carries no brand, for the reason\n * `capabilityDocPathsAt` above takes a bare `dir`: a capability id has no\n * grammar — it is a YAML key and a directory name, constrained nowhere — so\n * there is nothing for a smart constructor to validate. What holds the join is\n * PROVENANCE, and every caller owes it: the id must have come from a\n * `readCapabilityTree` walk, where each component is a `readdir` entry name.\n * An id read out of `architecture/capabilities.yaml` has not, and must not\n * reach here.\n */\nexport function livingCapabilityPaths(docsDir: DocsDir, id: string): CapabilityDocPaths {\n return capabilityDocPathsAt(join(capabilityDocsDir(docsDir), ...id.split(\"/\")));\n}\n\n/**\n * A feature's own capability deltas — `features/<FEAT>/capabilities/<id>/`,\n * the delta tree that merges into `capabilityDocsDir` above.\n *\n * The layout MIRRORS the living one exactly, nesting included: a delta for\n * `payments/refunds` sits at `features/<FEAT>/capabilities/payments/refunds/`,\n * so `readCapabilityTree` walks both sides with one implementation and the\n * merge target is the same id split the same way. The alternative — a flat\n * escaped spelling like `payments%2Frefunds/` — would be a second grammar with\n * its own escaping defects, and a diff nobody could read by eye against\n * `specs/<svc>/spec.md` ↔ `services/<svc>/spec.md` one directory over.\n *\n * The DIRECTORY'S EXISTENCE is this axis's per-feature opt-in, exactly as\n * `capabilityDocsDir`'s is the fleet's: a feature without one pays a single\n * `existsSync` and produces no finding, so a fleet that has not adopted the\n * business axis sees nothing.\n */\nexport function featureCapabilityDeltasDir(featureDir: FeatureDir): string {\n return join(featureDir, \"capabilities\");\n}\n\n/**\n * The domain's own vocabulary — `glossary/<term>.md`, one FILE per term.\n *\n * A FILE, where a capability gets a directory, and the difference is what sits\n * beside the document. A capability's directory is the seam its feature-local\n * deltas and anything later join through; a term has nothing beside it but its\n * definition, and a directory holding one file is ceremony. The general rule\n * both follow is the one `core/capabilities/tree.ts` states: an entry with prose\n * gets a file, an entry without prose stays a line in YAML — and a domain term\n * is prose or it is nothing.\n *\n * NO `glossary.yaml`, for the reason the capability tree gives at length: a\n * single vocabulary file at fleet scale is unworkable, and a second list is the\n * drift `loam init`'s removed `loam.docs.json` was removed for. The DIRECTORY is\n * the list. Unlike the capability axis there is no YAML half to union with —\n * that file predates the tree and carries `description`/`owner` fields prose\n * cannot overwrite, and nothing here has ever had a second side.\n *\n * NESTING IS ALLOWED and spelled by the tree: `glossary/payments/order.md` is\n * the term `payments/order`. A fleet with three hundred terms in one flat\n * directory is the state this prevents, and it costs nothing — the walk is\n * recursive and the id is the path.\n *\n * TOP-LEVEL, not under `architecture/`, and for `capabilityDocsDir`'s reason:\n * the vocabulary is the domain's, and it must survive every redesign of the\n * fleet that speaks it.\n *\n * NOT SCAFFOLDED BY `loam init`, exactly as `architecture/adrs/` and\n * `capabilities/` are not. The directory's existence is the opt-in.\n */\nexport function glossaryDir(docsDir: DocsDir): string {\n return join(docsDir, \"glossary\");\n}\n\n/**\n * A feature's own glossary deltas — `features/<FEAT>/glossary/<term>.md`, the\n * vocabulary axis's answer to `specs/<svc>/spec.md`.\n *\n * A term a feature INTRODUCES ships and unships with it: the archive copies the\n * definition into `glossary/`, and unarchive takes it back out with everything\n * else the feature landed. That is the whole reason this route exists, and it is\n * why it is CREATE-ONLY (`core/glossary/delta.ts` states the rule and the\n * refusal): a definition is prose with no delta algebra to protect, so rewriting\n * a living one belongs in a pull request where git itself produces the conflict.\n *\n * The DIRECTORY'S EXISTENCE is this axis's per-feature opt-in, exactly as\n * `featureCapabilityDeltasDir`'s is the business axis's: a feature without one\n * pays a single `existsSync` and produces nothing.\n */\nexport function featureGlossaryDir(featureDir: FeatureDir): string {\n return join(featureDir, \"glossary\");\n}\n\n/**\n * A feature's own flows — `features/<FEAT>/usecases/<name>.likec4`, the use-case\n * axis's answer to `specs/<svc>/spec.md`.\n *\n * A `dynamic view` is the only realizer a promise that CROSSES services has, and\n * it was the one realizer no feature could carry: every other axis had a delta\n * slot and this one had none, so the business axis's headline case — an analyst\n * adds a cross-service requirement, the architect answers it with a flow — could\n * not ship as one change. The promise had to land first, kept by nothing, and\n * the flow followed in a second pull request.\n *\n * The name mirrors the LIVING layout rather than inventing a second one:\n * `architecture/usecases/` is where a flow already lives, `usecases/` is where a\n * feature writes the one it brings, and the merge is the same path one directory\n * over. Nesting is spelled by the tree, as it is for every other authored tree\n * here.\n *\n * CREATE-ONLY (`core/usecases/delta/flows.ts` states the rule and the refusal),\n * for `featureGlossaryDir`'s reason: a flow is one ordered hop sequence with no\n * delta algebra to protect, so rewriting a living one belongs in a pull request\n * where git itself produces the conflict.\n *\n * The DIRECTORY'S EXISTENCE is this axis's per-feature opt-in, exactly as the\n * two above: a feature without one pays a single walk over a directory that is\n * not there and produces nothing.\n */\nexport function featureUseCasesDir(featureDir: FeatureDir): string {\n return join(featureDir, \"usecases\");\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/core/repo/authored/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AACvC,CAAC;AAaD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB,EAAE,EAAU;IAChE,OAAO,oBAAoB,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,0BAA0B,CAAC,UAAsB;IAC/D,OAAO,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,OAAO,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * WHERE THE TWO AUTHORED VOCABULARY TREES LIVE — `capabilities/<id>/` and\n * `glossary/<term>.md`.\n *\n * Split out of `../paths.ts` when that file passed 400 lines, and along the seam\n * that was already there rather than at the line the count happened to land on:\n * everything left in the parent spells the layout loam MAINTAINS — a service's\n * artifacts, a feature's deltas, the generated views — while these two spell the\n * trees a human AUTHORS and loam only reads. They share a rule the parent's\n * paths do not have: the directory IS the list, its existence is the axis's\n * opt-in, and `loam init` scaffolds neither.\n *\n * `ARTIFACT_FILES` is imported back from the parent rather than restated. The\n * capability document is `spec.md` because it is the same grammar read by the\n * same parser as a service's, and a second spelling of that name is exactly the\n * drift the parent's one-table rule exists to prevent.\n */\nimport { join } from \"node:path\";\nimport { ARTIFACT_FILES } from \"../paths.js\";\nimport type { DocsDir, FeatureDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * The AUTHORED business tree — `capabilities/<id>/`, one directory per\n * capability, each holding the document that describes it.\n *\n * Top-level and not under `architecture/`, because the two altitudes have\n * different authors and different reasons to change: `architecture/` is the\n * architect's — the map, the vocabularies, the use cases — while a capability\n * document is the analyst's, and it must survive every redesign of the fleet\n * that realizes it. A business tree filed inside the architecture tree would\n * say the opposite of what the axis is for.\n *\n * NESTING IS SPELLED BY THE TREE, not inside a name. A capability id keeps its\n * slashes wherever it is written (`core/capabilities/capabilities.ts` preserves\n * the YAML key exactly as a `Capability:` line spells it), so `payments/refunds`\n * lives at `capabilities/payments/refunds/spec.md` and `payments` may be a\n * capability in its own right at `capabilities/payments/spec.md`. A directory is\n * a capability if and only if it holds the document — the same\n * presence-classifies rule `isServiceArtifactName` applies one tree over, and\n * the reason it is safe here too: a group directory that acquires a document\n * becomes a capability visibly, in a diff, rather than by a marker file nobody\n * updated.\n *\n * NOT SCAFFOLDED BY `loam init`, exactly as `architecture/adrs/` is not. git\n * does not carry an empty directory, so a scaffolded one vanishes on the first\n * clone and returns as a diff on the next `init`; and an empty `capabilities/`\n * in a fresh repo reads as an obligation nobody has met, when a fleet that has\n * not adopted the axis owes nothing. The directory's EXISTENCE is the opt-in.\n */\nexport function capabilityDocsDir(docsDir: DocsDir): string {\n return join(docsDir, \"capabilities\");\n}\n\nexport interface CapabilityDocPaths {\n dir: string;\n /**\n * The capability's own document: narrative and requirements in one file.\n * `spec.md`, the same name a service's requirements live under, because it is\n * the same grammar read by the same parser — a second spelling would be a\n * second thing to keep in step for no reader's benefit.\n */\n spec: string;\n}\n\n/**\n * The document paths of a capability whose directory is already KNOWN — the\n * enumeration's `dir`, at whatever depth the walk found it. Same discipline as\n * `servicePathsAt`: joining `capabilities/<id>/` at the root is only true for a\n * flat id, and a nested capability would silently grade as absent through it.\n */\nexport function capabilityDocPathsAt(dir: string): CapabilityDocPaths {\n return { dir, spec: join(dir, ARTIFACT_FILES.spec) };\n}\n\n/**\n * The LIVING document a capability id addresses — `capabilities/` with the id's\n * own nesting spelled back out as directories, so `payments/refunds` resolves to\n * `capabilities/payments/refunds/spec.md`.\n *\n * Spelled here because FIVE readers need it — the delta grade, the archive\n * merge, the conflict-marker scan, the archive gate's strayed-requirement scan\n * and `loam rebase` — and a reader that resolved it differently would disagree\n * about which document a feature is changing. The delta grade is where that costs most: a living path that finds\n * nothing reads as an empty document, so every ADDED looks new and no\n * `Based-On:` is ever compared, and the merge then lands over text nobody\n * re-read.\n *\n * `...id.split(\"/\")` rather than passing the id whole is explicitness, not a\n * fix: `node:path.join` already normalizes an embedded separator, so both\n * spellings resolve identically on every platform. The split is what makes the\n * nesting visible at the one place the rule is written down. The mistake this\n * function actually prevents is the OTHER one — resolving a nested id by its\n * leaf, which silently addresses a different capability.\n *\n * `id` IS caller-controlled path input and carries no brand, for the reason\n * `capabilityDocPathsAt` above takes a bare `dir`: a capability id has no\n * grammar — it is a YAML key and a directory name, constrained nowhere — so\n * there is nothing for a smart constructor to validate. What holds the join is\n * PROVENANCE, and every caller owes it: the id must have come from a\n * `readCapabilityTree` walk, where each component is a `readdir` entry name.\n * An id read out of `architecture/capabilities.yaml` has not, and must not\n * reach here.\n */\nexport function livingCapabilityPaths(docsDir: DocsDir, id: string): CapabilityDocPaths {\n return capabilityDocPathsAt(join(capabilityDocsDir(docsDir), ...id.split(\"/\")));\n}\n\n/**\n * A feature's own capability deltas — `features/<FEAT>/capabilities/<id>/`,\n * the delta tree that merges into `capabilityDocsDir` above.\n *\n * The layout MIRRORS the living one exactly, nesting included: a delta for\n * `payments/refunds` sits at `features/<FEAT>/capabilities/payments/refunds/`,\n * so `readCapabilityTree` walks both sides with one implementation and the\n * merge target is the same id split the same way. The alternative — a flat\n * escaped spelling like `payments%2Frefunds/` — would be a second grammar with\n * its own escaping defects, and a diff nobody could read by eye against\n * `specs/<svc>/spec.md` ↔ `services/<svc>/spec.md` one directory over.\n *\n * The DIRECTORY'S EXISTENCE is this axis's per-feature opt-in, exactly as\n * `capabilityDocsDir`'s is the fleet's: a feature without one pays a single\n * `existsSync` and produces no finding, so a fleet that has not adopted the\n * business axis sees nothing.\n */\nexport function featureCapabilityDeltasDir(featureDir: FeatureDir): string {\n return join(featureDir, \"capabilities\");\n}\n\n/**\n * The domain's own vocabulary — `glossary/<term>.md`, one FILE per term.\n *\n * A FILE, where a capability gets a directory, and the difference is what sits\n * beside the document. A capability's directory is the seam its feature-local\n * deltas and anything later join through; a term has nothing beside it but its\n * definition, and a directory holding one file is ceremony. The general rule\n * both follow is the one `core/capabilities/tree.ts` states: an entry with prose\n * gets a file, an entry without prose stays a line in YAML — and a domain term\n * is prose or it is nothing.\n *\n * NO `glossary.yaml`, for the reason the capability tree gives at length: a\n * single vocabulary file at fleet scale is unworkable, and a second list is the\n * drift `loam init`'s removed `loam.docs.json` was removed for. The DIRECTORY is\n * the list. Unlike the capability axis there is no YAML half to union with —\n * that file predates the tree and carries `description`/`owner` fields prose\n * cannot overwrite, and nothing here has ever had a second side.\n *\n * NESTING IS ALLOWED and spelled by the tree: `glossary/payments/order.md` is\n * the term `payments/order`. A fleet with three hundred terms in one flat\n * directory is the state this prevents, and it costs nothing — the walk is\n * recursive and the id is the path.\n *\n * TOP-LEVEL, not under `architecture/`, and for `capabilityDocsDir`'s reason:\n * the vocabulary is the domain's, and it must survive every redesign of the\n * fleet that speaks it.\n *\n * NOT SCAFFOLDED BY `loam init`, exactly as `architecture/adrs/` and\n * `capabilities/` are not. The directory's existence is the opt-in.\n */\nexport function glossaryDir(docsDir: DocsDir): string {\n return join(docsDir, \"glossary\");\n}\n\n/**\n * A feature's own glossary deltas — `features/<FEAT>/glossary/<term>.md`, the\n * vocabulary axis's answer to `specs/<svc>/spec.md`.\n *\n * A term a feature INTRODUCES ships and unships with it: the archive copies the\n * definition into `glossary/`, and unarchive takes it back out with everything\n * else the feature landed. That is the whole reason this route exists, and it is\n * why it is CREATE-ONLY (`core/glossary/delta.ts` states the rule and the\n * refusal): a definition is prose with no delta algebra to protect, so rewriting\n * a living one belongs in a pull request where git itself produces the conflict.\n *\n * The DIRECTORY'S EXISTENCE is this axis's per-feature opt-in, exactly as\n * `featureCapabilityDeltasDir`'s is the business axis's: a feature without one\n * pays a single `existsSync` and produces nothing.\n */\nexport function featureGlossaryDir(featureDir: FeatureDir): string {\n return join(featureDir, \"glossary\");\n}\n\n/**\n * A feature's own flows — `features/<FEAT>/usecases/<name>.likec4`, the use-case\n * axis's answer to `specs/<svc>/spec.md`.\n *\n * A `dynamic view` is the only realizer a promise that CROSSES services has, and\n * it was the one realizer no feature could carry: every other axis had a delta\n * slot and this one had none, so the business axis's headline case — an analyst\n * adds a cross-service requirement, the architect answers it with a flow — could\n * not ship as one change. The promise had to land first, kept by nothing, and\n * the flow followed in a second pull request.\n *\n * The name mirrors the LIVING layout rather than inventing a second one:\n * `architecture/usecases/` is where a flow already lives, `usecases/` is where a\n * feature writes the one it brings, and the merge is the same path one directory\n * over. Nesting is spelled by the tree, as it is for every other authored tree\n * here.\n *\n * CREATE-ONLY (`core/usecases/delta/flows.ts` states the rule and the refusal),\n * for `featureGlossaryDir`'s reason: a flow is one ordered hop sequence with no\n * delta algebra to protect, so rewriting a living one belongs in a pull request\n * where git itself produces the conflict.\n *\n * The DIRECTORY'S EXISTENCE is this axis's per-feature opt-in, exactly as the\n * two above: a feature without one pays a single walk over a directory that is\n * not there and produces nothing.\n */\nexport function featureUseCasesDir(featureDir: FeatureDir): string {\n return join(featureDir, \"usecases\");\n}\n\n/**\n * `features/<FEAT>/deployment/` — the TOPOLOGY a feature brings.\n *\n * The third document a feature carries whole rather than as a delta, and for\n * the same reason as the two above: a `deployment { }` block is a topology, the\n * merge is a whole-file copy, and there is nothing to merge partially. What\n * makes the slot work at all is that LikeC4 `extend` resolves ACROSS documents\n * of one project — measured before the axis was written — so a feature adds a\n * standby datacenter inside a living region without touching the living file.\n *\n * `delta.likec4` still refuses a `deployment { }` block, and the refusal now\n * names this directory: the C4 delta re-declares the landscape's identifiers\n * and carries its own `specification`, so it cannot be staged beside the map in\n * one project, and a document that declares neither can.\n */\nexport function featureDeploymentDir(featureDir: FeatureDir): string {\n return join(featureDir, \"deployment\");\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"entries.js","sourceRoot":"","sources":["../../../src/core/repo/entries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the docs repo's two enumerations hand back, and how their ids compare.\n *\n * Split from the read model because these are the shapes every OTHER module\n * names — `ServiceEntry` is the type a dozen call sites annotate against — and\n * a shape should not drag the `readdir` that produces it into every importer.\n * `compareIds` lives here for the same reason: sorting ids is what a renderer\n * does, and no renderer wants the enumeration.\n */\nimport { serviceTreePath, type FeatureDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport { rawFeatureId, type RawFeatureId } from \"../kernel/ids/feature.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\n\n\nexport interface ServiceArtifacts {\n model: boolean;\n spec: boolean;\n openapi: boolean;\n /**\n * The async contract (AsyncAPI 3). Presence-tracked only, like `runbook` and\n * `health` — deliberately NOT part of the maturity ladder (core/vocabulary/maturity.ts),\n * whose `documented` rung is the model/spec/openapi triple. Adding a fourth\n * required artifact would demote every already-`documented` service in the\n * fleet on upgrade, without one byte of their files changing.\n */\n asyncapi: boolean;\n runbook: boolean;\n health: boolean;\n}\n\nexport interface ServiceEntry {\n /** Canonical service id — the LEAF directory name, at whatever depth the tree walk found it. */\n id: RawServiceId;\n /** Absolute path to the service's directory, as enumerated — the value every artifact path is resolved from. */\n dir: ServiceDir;\n /**\n * Names of the subsystem directories between `services/` and the service,\n * outermost first. Empty = unfiled — directly under `services/`, which is\n * permanent, normal and never a finding. Placement only: no identity, no\n * check and no join may branch on it.\n */\n subsystem: string[];\n has: ServiceArtifacts;\n /** Number of ADR files under adrs/. */\n adrs: number;\n /** `status` from the living spec's frontmatter; null when nobody has said. */\n status: string | null;\n /**\n * Provenance signals from the living spec's frontmatter: whether it declares\n * any `sources`, and whether a `sources_digest` stamp exists. Presence only —\n * whether the digest still matches the code is `validate`'s question, and it\n * can only be answered from inside the service's own repo.\n */\n sources: { declared: boolean; stamped: boolean };\n /**\n * \"sampled\" when EITHER spec axis carries a `vouch_scope` — a vouch a person\n * gave after reading a recorded sample of the document rather than all of\n * it — and null when neither does. Either axis, because the sample is per\n * file: a service whose arch.spec.md alone was sampled is a service part of\n * whose documentation nobody read.\n *\n * PRESENCE decides it, not decodability: a scope nobody can parse still\n * means somebody stamped a partial read, so it grades as sampled. Fail\n * closed, because the alternative is that mangling one field turns a partial\n * vouch into a full one on every dashboard reading this table.\n *\n * The RUNG never changes: `vouched` is a published contract and this is a\n * qualification of it, so the maturity ladder, the rollup and `--needs-work`\n * are all untouched. Only the renderings say \"vouched (sampled)\".\n */\n vouchScope: \"sampled\" | null;\n /**\n * Why no loam command can author this directory, or absent when the name is a\n * legal service id — `serviceIdProblem`'s own sentence, so the read model and\n * the refusal say the same thing.\n *\n * The enumeration still LISTS it: the directory is there, it holds somebody's\n * documents, and hiding it would repeat the symlink mistake one field up. But\n * every authoring command (`adopt`, `delta`, `rebase`, `new --touches`,\n * `init`) refuses this id with `invalid-option`, so grading it as an ordinary\n * service left the fleet gate demanding fixes no loam command can make. The\n * NAME is the finding — see `serviceIdFindings`.\n */\n idProblem?: string;\n}\n\nexport interface FeatureEntry {\n /** Feature id, derived from the directory name (FEAT-1-split -> FEAT-1). */\n id: RawFeatureId;\n /** The directory name as it is on disk, slug and all. */\n dirName: string;\n /** Absolute path to the feature directory. */\n dir: FeatureDir;\n archived: boolean;\n /** Services this feature carries a delta for, from specs/<svc>/. */\n services: RawServiceId[];\n has: { intent: boolean; delta: boolean };\n}\n\n/**\n * The directories in the `services/` tree that no loam command can address.\n *\n * A finding rather than a silent skip, and an ERROR rather than a warning,\n * because the state is not survivable in either direction: `loam adopt`,\n * `loam delta`, `loam rebase` and `loam new --touches` all refuse the id with\n * `invalid-option`, so the documents in there can never be updated by loam\n * again, while `validate --all` used to grade the directory as an ordinary\n * service and demand `model.likec4`, a spec and a landscape binding for it. The\n * fix is a rename, and it is a rename loam cannot do for you — the id is\n * spelled in the landscape binding, in the frontmatter and in every feature\n * that touched the service — so the message names all three.\n *\n * Emitted from the fleet target (`validate --all`), where the SET of service\n * directories is the thing being checked; a per-service run is already refused\n * by its own `--service` guard before it gets here. Lives beside the entry\n * shape rather than the enumeration because it is a projection of `idProblem`,\n * not a read — the entry docblock already points here.\n */\nexport function serviceIdFindings(services: ServiceEntry[]): Finding[] {\n return services\n .filter((s) => s.idProblem !== undefined)\n .map((s) => ({\n severity: \"error\" as const,\n code: \"service.id-invalid\",\n subject: s.id,\n message:\n // The path is spelled from the entry's own placement chain, not a\n // `services/<id>/` join: a FILED service with an illegal name lives at\n // services/<subsystem>/…/<id>/, and a finding naming a root directory\n // that does not exist sends the fix to the wrong place.\n `${serviceTreePath(s)}/ — ${s.idProblem} ` +\n `Every authoring command refuses this id (\\`loam adopt\\`, \\`loam delta\\`, \\`loam new --touches\\`), so nothing in that directory can be changed through loam. ` +\n `Rename the directory to a legal id, then update its \\`service:\\` frontmatter, its \\`metadata { service '${s.id}' }\\` binding in architecture/landscape.likec4, and any features/<FEAT>/specs/${s.id}/ that names it.`,\n }));\n}\n\n/**\n * Compare ids so digit runs sort numerically: FEAT-2 before FEAT-10.\n * Deterministic and locale-independent — ordering is part of the output\n * contract for `list`, and `--json` consumers diff it.\n */\nexport function compareIds(a: string, b: string): number {\n const ta = tokenize(a);\n const tb = tokenize(b);\n for (let i = 0; i < Math.max(ta.length, tb.length); i += 1) {\n const x = ta[i];\n const y = tb[i];\n if (x === undefined) return -1;\n if (y === undefined) return 1;\n if (typeof x === \"number\" && typeof y === \"number\") {\n if (x !== y) return x - y;\n } else if (String(x) !== String(y)) {\n return String(x) < String(y) ? -1 : 1;\n }\n }\n return 0;\n}\n\nfunction tokenize(s: string): (string | number)[] {\n return (s.match(/\\d+|\\D+/g) ?? []).map((t) => (/^\\d/.test(t) ? Number(t) : t));\n}\n\n/**\n * Known ids within a small edit distance of `id`, closest first, at most three.\n *\n * The did-you-mean hint for a name somebody typed: `loam new --touches` and\n * `loam delta <svc>` answer the same mistake, and each carried its own copy of\n * these twenty-four lines. It belongs here because the ids being scored are\n * this module's ids — service directory names, feature ids — and because\n * `compareIds` right above is the tiebreak that keeps the list deterministic\n * when two candidates score equally. Not in `core/kernel/ids/`: `compareIds` lives\n * here and repo.ts imports ids.js, so putting it there would close a cycle.\n *\n * `arch.ts`'s `closeIds` looks similar and is not: substring and prefix, capped\n * at five, answering \"which element could this be\" rather than \"did you\n * misspell a directory\". Two rules, two homes.\n */\nexport function nearestIds(id: string, known: string[]): string[] {\n const budget = Math.max(1, Math.floor(id.length / 4));\n return known\n .map((candidate) => ({ candidate, distance: editDistance(id.toLowerCase(), candidate.toLowerCase()) }))\n .filter((scored) => scored.distance <= budget)\n .sort((a, b) => a.distance - b.distance || compareIds(a.candidate, b.candidate))\n .slice(0, 3)\n .map((scored) => scored.candidate);\n}\n\n/** Plain Levenshtein — ids are short, and the row-at-a-time form keeps it obvious. */\nfunction editDistance(a: string, b: string): number {\n let previous = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i += 1) {\n const current = [i];\n for (let j = 1; j <= b.length; j += 1) {\n const substitution = previous[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1);\n current.push(Math.min(previous[j]! + 1, current[j - 1]! + 1, substitution));\n }\n previous = current;\n }\n return previous[b.length]!;\n}\n\n/**\n * Feature id from a directory name: everything up to and including the first\n * number run (`FEAT-101-payment-splitting` -> `FEAT-101`). A name with no\n * `<word>-<number>` head is its own id. Quirk: a dated slug keeps only its first\n * segment (`release-2024-01-x` -> `release-2024`) — ids are not meant to be dates.\n */\nexport function featureIdFromDirName(dirName: string): RawFeatureId {\n const m = /^(.*?-\\d+)(?:-|$)/.exec(dirName);\n // Constructed through the kernel helper, so this module holds no cast: the\n // derived id's provenance is the repository — a directory the enumeration\n // listed — which is exactly what `RawFeatureId` records.\n return rawFeatureId(m ? m[1]! : dirName);\n}\n"]}
1
+ {"version":3,"file":"entries.js","sourceRoot":"","sources":["../../../src/core/repo/entries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the docs repo's two enumerations hand back, and how their ids compare.\n *\n * Split from the read model because these are the shapes every OTHER module\n * names — `ServiceEntry` is the type a dozen call sites annotate against — and\n * a shape should not drag the `readdir` that produces it into every importer.\n * `compareIds` lives here for the same reason: sorting ids is what a renderer\n * does, and no renderer wants the enumeration.\n */\nimport { serviceTreePath, type FeatureDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport { rawFeatureId, type RawFeatureId } from \"../kernel/ids/feature.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\n\n\nexport interface ServiceArtifacts {\n model: boolean;\n spec: boolean;\n openapi: boolean;\n /**\n * The async contract (AsyncAPI 3). Presence-tracked only, like `runbook` and\n * `health` — deliberately NOT part of the maturity ladder (core/vocabulary/maturity.ts),\n * whose `documented` rung is the model/spec/openapi triple. Adding a fourth\n * required artifact would demote every already-`documented` service in the\n * fleet on upgrade, without one byte of their files changing.\n */\n asyncapi: boolean;\n runbook: boolean;\n health: boolean;\n}\n\nexport interface ServiceEntry {\n /** Canonical service id — the LEAF directory name, at whatever depth the tree walk found it. */\n id: RawServiceId;\n /** Absolute path to the service's directory, as enumerated — the value every artifact path is resolved from. */\n dir: ServiceDir;\n /**\n * Names of the subsystem directories between `services/` and the service,\n * outermost first. Empty = unfiled — directly under `services/`, which is\n * permanent, normal and never a finding. Placement only: no identity, no\n * check and no join may branch on it.\n */\n subsystem: string[];\n has: ServiceArtifacts;\n /** Number of ADR files under adrs/. */\n adrs: number;\n /** `status` from the living spec's frontmatter; null when nobody has said. */\n status: string | null;\n /**\n * Provenance signals from the living spec's frontmatter: whether it declares\n * any `sources`, and whether a `sources_digest` stamp exists. Presence only —\n * whether the digest still matches the code is `validate`'s question, and it\n * can only be answered from inside the service's own repo.\n */\n sources: { declared: boolean; stamped: boolean };\n /**\n * \"sampled\" when EITHER spec axis carries a `vouch_scope` — a vouch a person\n * gave after reading a recorded sample of the document rather than all of\n * it — and null when neither does. Either axis, because the sample is per\n * file: a service whose arch.spec.md alone was sampled is a service part of\n * whose documentation nobody read.\n *\n * PRESENCE decides it, not decodability: a scope nobody can parse still\n * means somebody stamped a partial read, so it grades as sampled. Fail\n * closed, because the alternative is that mangling one field turns a partial\n * vouch into a full one on every dashboard reading this table.\n *\n * The RUNG never changes: `vouched` is a published contract and this is a\n * qualification of it, so the maturity ladder, the rollup and `--needs-work`\n * are all untouched. Only the renderings say \"vouched (sampled)\".\n */\n vouchScope: \"sampled\" | null;\n /**\n * The spec files (`SPEC_AXES`' own names, `spec.md` / `arch.spec.md`) whose\n * BODY moved after the vouch that stamped it — `content_digest` no longer\n * matches — and empty when none did. `content.stale`'s question, carried on\n * the entry because it needs nothing but the document: the docs repo can\n * answer it for every service, where `sources.stale` needs each service's\n * own repository.\n *\n * A qualification of the rung, exactly like `vouchScope` and for its reason:\n * a person DID stamp the document, so `vouched` stands, while the renderings\n * say \"vouched (stale)\" and the worklist owes the service a re-vouch. Without\n * it every fleet readout — `list`, `status`, the scorecard — called a service\n * vouched over words nobody had read since, while `validate` beside them\n * warned that nobody had.\n */\n contentStale: string[];\n /**\n * \"partial\" when the living spec.md's frontmatter says `adoption: partial`,\n * null otherwise. `loam archive` writes it on a service's FIRST spec.md when\n * the service existed before the feature and was never adopted: what landed\n * is that feature's slice, and nothing about what the service already did is\n * written down. Presence of the file used to be the whole answer, so the one\n * archive turned a `partial` service `documented` and `loam status` stopped\n * asking for its adoption. The maturity ladder and `next.adopt` read this\n * instead; a person deletes the line once the baseline is written.\n */\n adoption: \"partial\" | null;\n /**\n * Why no loam command can author this directory, or absent when the name is a\n * legal service id — `serviceIdProblem`'s own sentence, so the read model and\n * the refusal say the same thing.\n *\n * The enumeration still LISTS it: the directory is there, it holds somebody's\n * documents, and hiding it would repeat the symlink mistake one field up. But\n * every authoring command (`adopt`, `delta`, `rebase`, `new --touches`,\n * `init`) refuses this id with `invalid-option`, so grading it as an ordinary\n * service left the fleet gate demanding fixes no loam command can make. The\n * NAME is the finding — see `serviceIdFindings`.\n */\n idProblem?: string;\n}\n\nexport interface FeatureEntry {\n /** Feature id, derived from the directory name (FEAT-1-split -> FEAT-1). */\n id: RawFeatureId;\n /** The directory name as it is on disk, slug and all. */\n dirName: string;\n /** Absolute path to the feature directory. */\n dir: FeatureDir;\n archived: boolean;\n /** Services this feature carries a delta for, from specs/<svc>/. */\n services: RawServiceId[];\n has: { intent: boolean; delta: boolean };\n}\n\n/**\n * The directories in the `services/` tree that no loam command can address.\n *\n * A finding rather than a silent skip, and an ERROR rather than a warning,\n * because the state is not survivable in either direction: `loam adopt`,\n * `loam delta`, `loam rebase` and `loam new --touches` all refuse the id with\n * `invalid-option`, so the documents in there can never be updated by loam\n * again, while `validate --all` used to grade the directory as an ordinary\n * service and demand `model.likec4`, a spec and a landscape binding for it. The\n * fix is a rename, and it is a rename loam cannot do for you — the id is\n * spelled in the landscape binding, in the frontmatter and in every feature\n * that touched the service — so the message names all three.\n *\n * Emitted from the fleet target (`validate --all`), where the SET of service\n * directories is the thing being checked; a per-service run is already refused\n * by its own `--service` guard before it gets here. Lives beside the entry\n * shape rather than the enumeration because it is a projection of `idProblem`,\n * not a read — the entry docblock already points here.\n */\nexport function serviceIdFindings(services: ServiceEntry[]): Finding[] {\n return services\n .filter((s) => s.idProblem !== undefined)\n .map((s) => ({\n severity: \"error\" as const,\n code: \"service.id-invalid\",\n subject: s.id,\n message:\n // The path is spelled from the entry's own placement chain, not a\n // `services/<id>/` join: a FILED service with an illegal name lives at\n // services/<subsystem>/…/<id>/, and a finding naming a root directory\n // that does not exist sends the fix to the wrong place.\n `${serviceTreePath(s)}/ — ${s.idProblem} ` +\n `Every authoring command refuses this id (\\`loam adopt\\`, \\`loam delta\\`, \\`loam new --touches\\`), so nothing in that directory can be changed through loam. ` +\n `Rename the directory to a legal id, then update its \\`service:\\` frontmatter, its \\`metadata { service '${s.id}' }\\` binding in architecture/landscape.likec4, and any features/<FEAT>/specs/${s.id}/ that names it.`,\n }));\n}\n\n/**\n * Compare ids so digit runs sort numerically: FEAT-2 before FEAT-10.\n * Deterministic and locale-independent — ordering is part of the output\n * contract for `list`, and `--json` consumers diff it.\n */\nexport function compareIds(a: string, b: string): number {\n const ta = tokenize(a);\n const tb = tokenize(b);\n for (let i = 0; i < Math.max(ta.length, tb.length); i += 1) {\n const x = ta[i];\n const y = tb[i];\n if (x === undefined) return -1;\n if (y === undefined) return 1;\n if (typeof x === \"number\" && typeof y === \"number\") {\n if (x !== y) return x - y;\n } else if (String(x) !== String(y)) {\n return String(x) < String(y) ? -1 : 1;\n }\n }\n return 0;\n}\n\nfunction tokenize(s: string): (string | number)[] {\n return (s.match(/\\d+|\\D+/g) ?? []).map((t) => (/^\\d/.test(t) ? Number(t) : t));\n}\n\n/**\n * Known ids within a small edit distance of `id`, closest first, at most three.\n *\n * The did-you-mean hint for a name somebody typed: `loam new --touches` and\n * `loam delta <svc>` answer the same mistake, and each carried its own copy of\n * these twenty-four lines. It belongs here because the ids being scored are\n * this module's ids — service directory names, feature ids — and because\n * `compareIds` right above is the tiebreak that keeps the list deterministic\n * when two candidates score equally. Not in `core/kernel/ids/`: `compareIds` lives\n * here and repo.ts imports ids.js, so putting it there would close a cycle.\n *\n * `arch.ts`'s `closeIds` looks similar and is not: substring and prefix, capped\n * at five, answering \"which element could this be\" rather than \"did you\n * misspell a directory\". Two rules, two homes.\n */\nexport function nearestIds(id: string, known: string[]): string[] {\n const budget = Math.max(1, Math.floor(id.length / 4));\n return known\n .map((candidate) => ({ candidate, distance: editDistance(id.toLowerCase(), candidate.toLowerCase()) }))\n .filter((scored) => scored.distance <= budget)\n .sort((a, b) => a.distance - b.distance || compareIds(a.candidate, b.candidate))\n .slice(0, 3)\n .map((scored) => scored.candidate);\n}\n\n/** Plain Levenshtein — ids are short, and the row-at-a-time form keeps it obvious. */\nfunction editDistance(a: string, b: string): number {\n let previous = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i += 1) {\n const current = [i];\n for (let j = 1; j <= b.length; j += 1) {\n const substitution = previous[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1);\n current.push(Math.min(previous[j]! + 1, current[j - 1]! + 1, substitution));\n }\n previous = current;\n }\n return previous[b.length]!;\n}\n\n/**\n * Feature id from a directory name: everything up to and including the first\n * number run (`FEAT-101-payment-splitting` -> `FEAT-101`). A name with no\n * `<word>-<number>` head is its own id. Quirk: a dated slug keeps only its first\n * segment (`release-2024-01-x` -> `release-2024`) — ids are not meant to be dates.\n */\nexport function featureIdFromDirName(dirName: string): RawFeatureId {\n const m = /^(.*?-\\d+)(?:-|$)/.exec(dirName);\n // Constructed through the kernel helper, so this module holds no cast: the\n // derived id's provenance is the repository — a directory the enumeration\n // listed — which is exactly what `RawFeatureId` records.\n return rawFeatureId(m ? m[1]! : dirName);\n}\n"]}
@@ -140,7 +140,7 @@ export function featureSpecPaths(featureDir, service) {
140
140
  };
141
141
  }
142
142
  export function landscapePath(docsDir) {
143
- return join(docsDir, "architecture", "landscape.likec4");
143
+ return join(docsDir, "architecture", LANDSCAPE_FILE);
144
144
  }
145
145
  /** The fleet's authorization vocabulary — beside the fleet map, for the same reason. */
146
146
  export function permissionsPath(docsDir) {
@@ -182,16 +182,62 @@ export function obligationsPath(docsDir) {
182
182
  export function fleetAdrsDir(docsDir) {
183
183
  return join(docsDir, "architecture", ADRS_DIR);
184
184
  }
185
+ const SUBSYSTEM_VIEWS_FILE = "subsystems.likec4";
186
+ const SERVICE_VIEWS_FILE = "services.likec4";
185
187
  /**
186
- * The GENERATED subsystem views — beside the fleet map because the LikeC4
188
+ * The documents inside `architecture/` that LOAM GENERATES — ONE list, because
189
+ * four questions read it and a name in one reader and missing from another fails
190
+ * silently both ways: the path functions below, the reservation below, the project
191
+ * loads that must EXCLUDE these files (`core/c4/project/architecture.ts` and
192
+ * `commands/validate/fleet/views/projects.ts` — a views-only document does not
193
+ * parse standalone, so loading one blanks the project it lands in), and `loam
194
+ * archive`'s withhold-and-name — each of which hard-coded "the generated one"
195
+ * while there was only one. The VALUE is what the reservation below prints.
196
+ */
197
+ export const GENERATED_ARCHITECTURE_FILES = {
198
+ [SUBSYSTEM_VIEWS_FILE]: "`subsystems.likec4` is generated by `loam subsystem sync`, which overwrites it wholesale — and nothing in loam ever parses it, so anything landing there is invisible from the moment it archives",
199
+ [SERVICE_VIEWS_FILE]: "`services.likec4` is generated by `loam subsystem sync`, one overview view per service, and is overwritten wholesale — and nothing in loam ever parses it, so anything landing there is invisible from the moment it archives",
200
+ };
201
+ /**
202
+ * The two GENERATED views files — beside the fleet map because the LikeC4
187
203
  * renderer merges the whole `architecture/` project, and a view can only
188
- * `include` elements the landscape beside it defines. `loam subsystem sync`
189
- * owns the bytes (`core/repo/tree/views.ts` renders them); nothing in loam
190
- * ever parses the file, and staleness against the tree is a byte compare.
204
+ * `include` elements the landscape beside it defines. `loam subsystem sync` owns
205
+ * both files' bytes (`render/views.ts`, `render/service/views.ts`); loam parses
206
+ * neither, and staleness against the tree is a content compare.
191
207
  */
192
208
  export function subsystemViewsPath(docsDir) {
193
- return join(docsDir, "architecture", "subsystems.likec4");
209
+ return join(docsDir, "architecture", SUBSYSTEM_VIEWS_FILE);
210
+ }
211
+ export function serviceViewsPath(docsDir) {
212
+ return join(docsDir, "architecture", SERVICE_VIEWS_FILE);
194
213
  }
214
+ /**
215
+ * Names inside `architecture/` that LOAM owns, and what goes wrong if a
216
+ * feature's own document claims one. `features/<FEAT>/deployment/<name>.likec4`
217
+ * merges as a whole-file COPY straight into `architecture/` — unlike a flow,
218
+ * which lands under `usecases/` — so its `<name>` is free to be any of them. A
219
+ * generated name fails silently both ways (the table above says how); a copy over
220
+ * `landscape.likec4` would discard the living map the archive SPLICES; and
221
+ * `usecases/` is the flow tree the scan walks, where a deployment document parses
222
+ * and is then looked for by a reader asking another question. Returns the reason,
223
+ * or null when the name is the author's — over the rel a walk produced, never argv.
224
+ */
225
+ export function reservedArchitectureName(rel) {
226
+ const parts = rel.split("/");
227
+ if (parts[0] === USECASE_DIR) {
228
+ return `\`${USECASE_DIR}/\` is the use-case tree — a flow belongs in \`features/<FEAT>/usecases/\`, which merges there`;
229
+ }
230
+ // Own key only: a bare lookup answers `constructor` with something inherited.
231
+ if (Object.hasOwn(GENERATED_ARCHITECTURE_FILES, rel))
232
+ return GENERATED_ARCHITECTURE_FILES[rel];
233
+ if (rel === LANDSCAPE_FILE) {
234
+ return "`landscape.likec4` is the living fleet map, which the archive SPLICES into rather than replaces";
235
+ }
236
+ return null;
237
+ }
238
+ /** The living map's file name, and the flow tree's directory — spelled once, read by the reservation. */
239
+ const LANDSCAPE_FILE = "landscape.likec4";
240
+ const USECASE_DIR = "usecases";
195
241
  export function featuresDir(docsDir) {
196
242
  return join(docsDir, "features");
197
243
  }
@@ -233,4 +279,44 @@ export function agentsPath(docsDir) {
233
279
  export function archiveDir(docsDir) {
234
280
  return join(featuresDir(docsDir), ARCHIVE_DIR);
235
281
  }
282
+ /**
283
+ * The LikeC4 project file — the one thing in a docs repo loam writes for a
284
+ * tool other than itself — and it lives at two altitudes.
285
+ *
286
+ * At the ROOT, `loam init --create` writes the project `fleet` (`core/docs.ts`
287
+ * composes those bytes), which holds the fleet map and every service model that
288
+ * EXTENDS it. Beside a model that STANDS ALONE — one declaring its own
289
+ * `specification` block, which can only be parsed on its own — `loam subsystem
290
+ * sync` writes a project of the service's own, and adds that directory to the
291
+ * root's `exclude`; without both, such a model is either a duplicate of the map
292
+ * or a box on it with nothing renderable inside.
293
+ * `core/repo/tree/render/projects.ts` composes the per-service bytes and records
294
+ * what was measured; `core/c4/root-project/exclude.ts` owns reading the root's
295
+ * `exclude` list and deciding what an entry covers; `core/c4/service-model/renderer.ts`
296
+ * owns the list `loam subsystem sync` writes back.
297
+ *
298
+ * Spelled here and NOT in `ARTIFACT_FILES`, and the distinction is
299
+ * load-bearing: a filename in that table classifies a directory as a service,
300
+ * and this file is the renderer's — a stray one must mint nothing. The root's
301
+ * NAME sits beside the filename because the per-service rule needs it too (a
302
+ * service id equal to the root project's name would be silently renamed by
303
+ * the renderer), and two spellings of the word are how the scaffold and the
304
+ * rule drift apart.
305
+ */
306
+ export const LIKEC4_PROJECT_FILENAME = "likec4.config.json";
307
+ /** The root project's name, exactly as `loam init --create` scaffolds it. */
308
+ export const LIKEC4_ROOT_PROJECT = "fleet";
309
+ export function rootProjectPath(docsDir) {
310
+ return join(docsDir, LIKEC4_PROJECT_FILENAME);
311
+ }
312
+ /**
313
+ * The two files the per-service project question is asked of — the model that
314
+ * needs rendering and the project file that renders it — in one spelling for
315
+ * the writer (`subsystem sync`) and the grader (`validate --all`) alike. Handed
316
+ * INTO `render/projects.ts` rather than imported there, because that package
317
+ * cannot import this module: `render/stale.ts` records the cycle.
318
+ */
319
+ export function serviceRenderPaths(dir) {
320
+ return { model: join(dir, ARTIFACT_FILES.model), project: join(dir, LIKEC4_PROJECT_FILENAME) };
321
+ }
236
322
  //# sourceMappingURL=paths.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/core/repo/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAkD,MAAM,uBAAuB,CAAC;AAIrG,kFAAkF;AAClF,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,cAAc;IACrB,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,cAAc;IACxB,OAAO,EAAE,cAAc;IACvB,QAAQ,EAAE,eAAe;IACzB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;CACX,CAAC;AACX,MAAM,QAAQ,GAAG,MAAM,CAAC;AAExB,yGAAyG;AACzG,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,IAAoB;IACtE,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,KAAK,QAAQ,CAAC;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACrE,CAAC;AA4BD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,GAAe;IAC5C,OAAO;QACL,GAAG;QACH,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC;QACtC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC;QACpC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC;QAC5C,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC;QAC1C,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC;QAC5C,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC;QAC1C,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC;QACxC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC;QACtC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC;KAC7B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAgB,EAAE,OAAwB;IAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAoB,EAAE,OAAwB;IAC9E,OAAO,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,IAAmB;IACvE,OAAO,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,EAAE;IACvD,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,mBAAmB,EAAE;CAC7D,CAAC;AAWX,MAAM,UAAU,YAAY,CAAC,UAAsB;IACjD,OAAO;QACL,GAAG,EAAE,UAAU;QACf,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC;QACrC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC;QACvC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC;QACnC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC;KAClC,CAAC;AACJ,CAAC;AAWD,MAAM,UAAU,gBAAgB,CAAC,UAAsB,EAAE,OAAwB;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,OAAO;QACL,GAAG;QACH,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC;QAC1B,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC;QACnC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC;QAClC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,kBAAkB,CAAC,CAAC;AAC3D,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,kBAAkB,CAAC,CAAC;AAC3D,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,mBAAmB,CAAC,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,kBAAkB,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,mBAAmB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAE3C,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,OAAO,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * Where every artifact lives. Artifact filenames are spelled here and nowhere\n * else, so a rename is one edit rather than a grep.\n *\n * This is also the module that carries loam's one path guarantee.\n * `unfiledServicePaths(docsDir, service)` spells `<docsDir>/services/<service>/`,\n * so both parts are caller-controlled path input, and `node:path` cannot help:\n * `join(...paths: string[])` accepts every string there is. The guarantee IS\n * the parameter brands and nothing else — `PathableService` for the id (a name\n * whose provenance is the repository, unconstructible from document text),\n * `DocsDir`/`FeatureDir` for the roots (a resolved config or validated\n * `--docs`; a directory an enumeration read), and `ServiceDir` for\n * `servicePathsAt` (the enumeration's own resolved directory, narrower still)\n * — the `kernel/ids/` package holds the only casts, so an unchecked value at\n * these call sites does not compile.\n * Code that spells `services/<id>/` or a feature layout with a bare join is\n * outside the guarantee — `commands/new.ts` and the openspec migrator both do,\n * held instead by `resolveInside` at the write.\n */\nimport { join } from \"node:path\";\nimport { serviceDirOf, type DocsDir, type FeatureDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport type { SubsystemName } from \"../kernel/ids/subsystem.js\";\n\n/** Directory under features/ holding shipped features. Never a feature itself. */\nexport const ARCHIVE_DIR = \"archive\";\n\n/* ------------------------------------------------------------------ */\n/* Paths — artifact filenames are spelled here and nowhere else */\n/* ------------------------------------------------------------------ */\n\n/**\n * The living artifact filenames, as one table, because three questions read\n * them: `servicePathsAt` below spells the paths, the tree walk classifies\n * a directory as a service by whether any of these names sit in it, and\n * `core/diff/base-state.ts` classifies the SAME way over a base git ref's\n * tree listing (exported for that reader — a filename respelled there would\n * be the drift this table exists to prevent). The walk cannot import this\n * module — `repo → repo/tree` is the package direction, and an edge back\n * would be the cycle `scripts/package-graph.mjs` refuses — so `repo.ts`\n * injects `isServiceArtifactName` into the walk request instead.\n */\nexport const ARTIFACT_FILES = {\n model: \"model.likec4\",\n spec: \"spec.md\",\n archSpec: \"arch.spec.md\",\n openapi: \"openapi.yaml\",\n asyncapi: \"asyncapi.yaml\",\n runbook: \"runbook.md\",\n health: \"health.yaml\",\n steps: \"steps.yaml\",\n} as const;\nconst ADRS_DIR = \"adrs\";\n\n/** Does a directory entry name a living service artifact? The classification half of the table above. */\nexport function isServiceArtifactName(name: string, kind: \"file\" | \"dir\"): boolean {\n if (kind === \"dir\") return name === ADRS_DIR;\n return Object.values(ARTIFACT_FILES).some((file) => file === name);\n}\n\nexport interface ServicePaths {\n dir: ServiceDir;\n model: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract — the event axis's sibling of `openapi`. */\n asyncapi: string;\n runbook: string;\n health: string;\n /**\n * The step catalogue — which phrases this service's suite has DECIDED to\n * define. Authored and opt-in; its CONTENTS are read by `loam steps` alone and\n * nothing in `validate` consults them\n * (`core/gherkin/steps/catalogue.ts` records why).\n *\n * Its EXISTENCE is read more widely, and that is not the same statement: the\n * filename is in `ARTIFACT_FILES`, so `isServiceArtifactName` classifies it\n * and the tree walk treats a directory holding one as a service. That is\n * correct — it is a service artifact — and it is worth writing down, because\n * it means a `steps.yaml` written at the wrong path mints a service.\n */\n steps: string;\n adrsDir: string;\n}\n\n/**\n * The artifact paths of a service whose directory is already KNOWN — the\n * enumeration's `dir`, at whatever depth the tree walk found it. This is the\n * spelling every reader of an existing service must use: joining\n * `services/<id>/` at the root is only true for a fleet nobody has filed, and\n * a moved service would silently grade as absent through it.\n */\nexport function servicePathsAt(dir: ServiceDir): ServicePaths {\n return {\n dir,\n model: join(dir, ARTIFACT_FILES.model),\n spec: join(dir, ARTIFACT_FILES.spec),\n archSpec: join(dir, ARTIFACT_FILES.archSpec),\n openapi: join(dir, ARTIFACT_FILES.openapi),\n asyncapi: join(dir, ARTIFACT_FILES.asyncapi),\n runbook: join(dir, ARTIFACT_FILES.runbook),\n health: join(dir, ARTIFACT_FILES.health),\n steps: join(dir, ARTIFACT_FILES.steps),\n adrsDir: join(dir, ADRS_DIR),\n };\n}\n\n/**\n * The UNFILED spelling — `<docsDir>/services/<id>/`, the tree's root level.\n * This is where creation lands (`adopt`'s brief, a new service materialised by\n * `archive`, the OpenSpec migration) and the honest fallback for a service the\n * enumeration does not answer; for a service that EXISTS, resolve through the\n * enumeration instead (`servicePathsAt(entry.dir)`, or\n * `locateServicePaths` in `service-target.ts`, which chooses between the two).\n * Renamed from `servicePaths` when the tree landed, precisely so the compiler\n * would put every remaining caller in front of a reviewer: a root join that\n * survives unreviewed is a moved service silently grading as absent.\n */\nexport function unfiledServicePaths(docsDir: DocsDir, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(docsDir, \"services\", service)));\n}\n\n/**\n * The creation spelling INSIDE a subsystem — `<subsystemDir>/<id>/`, the\n * target `adopt --subsystem` briefs so an adoption need not land unfiled and\n * cost a second command. The same two provenances the unfiled spelling\n * combines, one level down: the subsystem directory came off the tree walk's\n * readdir, and the id passed the service grammar before it reached here.\n */\nexport function servicePathsUnder(subsystemDir: string, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(subsystemDir, service)));\n}\n\n/**\n * The directory a SUBSYSTEM occupies (or will occupy) under a parent — the\n * spelling `subsystem new` creates and `subsystem rename` renames into. The\n * same guarantee-by-brand as the service spellings above: the parent came off\n * the tree walk's readdir (or is `servicesDir` itself), and demanding\n * `SubsystemName` here is what makes the validator unskippable — a raw argv\n * string at this call site does not compile, so no future verb can hoist the\n * join above the check that earns it.\n */\nexport function subsystemPathUnder(parentDir: string, name: SubsystemName): string {\n return join(parentDir, name);\n}\n\n/**\n * The pair of requirement-carrying spec files, living and delta alike: the\n * business spec and the architecture spec. Same grammar, same delta algebra,\n * same merge — everything that walks \"the spec files\" of a service walks this\n * list, so the two axes cannot drift apart in what handles them. `key` indexes\n * ServicePaths/FeatureSpecPaths; `label` is how the axis names itself in prose.\n */\nexport const SPEC_AXES = [\n { key: \"spec\", file: \"spec.md\", label: \"requirements\" },\n { key: \"archSpec\", file: \"arch.spec.md\", label: \"arch requirements\" },\n] as const;\nexport type SpecAxis = (typeof SPEC_AXES)[number];\n\nexport interface FeaturePaths {\n dir: string;\n intent: string;\n delta: string;\n specsDir: string;\n adrsDir: string;\n}\n\nexport function featurePaths(featureDir: FeatureDir): FeaturePaths {\n return {\n dir: featureDir,\n intent: join(featureDir, \"intent.md\"),\n delta: join(featureDir, \"delta.likec4\"),\n specsDir: join(featureDir, \"specs\"),\n adrsDir: join(featureDir, \"adrs\"),\n };\n}\n\nexport interface FeatureSpecPaths {\n dir: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract delta — the event axis's sibling of `openapi`. */\n asyncapi: string;\n}\n\nexport function featureSpecPaths(featureDir: FeatureDir, service: PathableService): FeatureSpecPaths {\n const dir = join(featureDir, \"specs\", service);\n return {\n dir,\n spec: join(dir, \"spec.md\"),\n archSpec: join(dir, \"arch.spec.md\"),\n openapi: join(dir, \"openapi.yaml\"),\n asyncapi: join(dir, \"asyncapi.yaml\"),\n };\n}\n\nexport function landscapePath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"landscape.likec4\");\n}\n\n/** The fleet's authorization vocabulary — beside the fleet map, for the same reason. */\nexport function permissionsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"permissions.yaml\");\n}\n\n/** The fleet's capability vocabulary — beside the other two fleet documents, for the same reason. */\nexport function capabilitiesPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"capabilities.yaml\");\n}\n\n/**\n * The fleet's ARCHITECTURAL OBLIGATION vocabulary — beside the landscape it tags\n * and the two vocabularies it sits with, for their reason: it is a fact about\n * the fleet that no single service owns.\n */\nexport function obligationsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"obligations.yaml\");\n}\n\n/**\n * The FLEET's decision records — `architecture/adrs/`, beside the landscape and\n * the two vocabularies.\n *\n * ADRs used to exist at two altitudes only, `services/<id>/adrs/` and\n * `features/<FEAT>/adrs/`, so a decision about the fleet itself — \"event\n * publishers use a transactional outbox\", \"cross-service calls carry a circuit\n * breaker\" — had no home but one arbitrary service's directory, where the next\n * reader of the other fifty services never finds it. This is not a new axis: it\n * is the same `ADRS_DIR` name one level up, spelled through the same constant so\n * the two altitudes cannot drift into `adrs/` and `decisions/`.\n *\n * PRESENCE-TRACKED AND NOTHING MORE, exactly like the service directory it\n * mirrors: `loam list` counts the files (`repo.ts`'s `fleetAdrCount`) and no\n * check anywhere reads one. A fleet with no fleet-level ADRs owes nothing and\n * must produce no finding — which is also why `loam init` does NOT scaffold the\n * directory. git does not carry an empty one, so it would vanish on the first\n * clone and come back as a diff on the next `init`; and an empty `adrs/` in a\n * fresh repo reads as an obligation nobody has met, when there is no obligation\n * at all.\n */\nexport function fleetAdrsDir(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", ADRS_DIR);\n}\n\n/**\n * The GENERATED subsystem views — beside the fleet map because the LikeC4\n * renderer merges the whole `architecture/` project, and a view can only\n * `include` elements the landscape beside it defines. `loam subsystem sync`\n * owns the bytes (`core/repo/tree/views.ts` renders them); nothing in loam\n * ever parses the file, and staleness against the tree is a byte compare.\n */\nexport function subsystemViewsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"subsystems.likec4\");\n}\n\nexport function featuresDir(docsDir: DocsDir): string {\n return join(docsDir, \"features\");\n}\n\n/**\n * The root of the tree that IS the fleet — where an unfiled service and a\n * root-level subsystem both sit. The `subsystem` verbs join validated names\n * under it; the enumerating walk spells the same join for itself\n * (`repo/tree/walk.ts`), because it cannot import this module.\n */\nexport function servicesDir(docsDir: DocsDir): string {\n return join(docsDir, \"services\");\n}\n\n/**\n * The agent contract's filename, and where it sits in a docs repo.\n *\n * Both spellings are exported because the two questions are different: `init`,\n * `doctor` and `validate` want a path under a docsDir they already hold, while\n * `docs.ts` builds the scaffold as `[relative path, bytes]` pairs and needs the\n * bare name. Four modules spelled the literal out for themselves, and one of\n * them — `init` — uses it to decide whether a directory IS a docs repo at all,\n * so a rename that reached three of the four would leave `init` recognising a\n * file nothing else writes any more.\n *\n * `openspec-inventory.ts`'s \"AGENTS.md\" is deliberately NOT this constant: it\n * names a file in a foreign OpenSpec tree that happens to share the name.\n */\nexport const AGENTS_FILENAME = \"AGENTS.md\";\n\nexport function agentsPath(docsDir: DocsDir): string {\n return join(docsDir, AGENTS_FILENAME);\n}\n\n/**\n * Where shipped features live. `features/archive/` is spelled here and nowhere\n * else for the same reason every other artifact path is: `archive` and\n * `unarchive` move directories in and out of it, and a layout string that two\n * commands re-derive is a layout two commands can disagree about. The\n * `features/archive/…` strings in refusal PROSE are not this path — they are\n * what a reader types into `ls`, and they stay spelled out.\n */\nexport function archiveDir(docsDir: DocsDir): string {\n return join(featuresDir(docsDir), ARCHIVE_DIR);\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/core/repo/paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where every artifact lives. Artifact filenames are spelled here and nowhere\n * else, so a rename is one edit rather than a grep.\n *\n * This is also the module that carries loam's one path guarantee.\n * `unfiledServicePaths(docsDir, service)` spells `<docsDir>/services/<service>/`,\n * so both parts are caller-controlled path input, and `node:path` cannot help:\n * `join(...paths: string[])` accepts every string there is. The guarantee IS\n * the parameter brands and nothing else — `PathableService` for the id (a name\n * whose provenance is the repository, unconstructible from document text),\n * `DocsDir`/`FeatureDir` for the roots (a resolved config or validated\n * `--docs`; a directory an enumeration read), and `ServiceDir` for\n * `servicePathsAt` (the enumeration's own resolved directory, narrower still)\n * — the `kernel/ids/` package holds the only casts, so an unchecked value at\n * these call sites does not compile.\n * Code that spells `services/<id>/` or a feature layout with a bare join is\n * outside the guarantee — `commands/new.ts` and the openspec migrator both do,\n * held instead by `resolveInside` at the write.\n */\nimport { join } from \"node:path\";\nimport { serviceDirOf, type DocsDir, type FeatureDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport type { SubsystemName } from \"../kernel/ids/subsystem.js\";\n\n/** Directory under features/ holding shipped features. Never a feature itself. */\nexport const ARCHIVE_DIR = \"archive\";\n\n/* ------------------------------------------------------------------ */\n/* Paths — artifact filenames are spelled here and nowhere else */\n/* ------------------------------------------------------------------ */\n\n/**\n * The living artifact filenames, as one table, because three questions read\n * them: `servicePathsAt` below spells the paths, the tree walk classifies\n * a directory as a service by whether any of these names sit in it, and\n * `core/diff/base-state.ts` classifies the SAME way over a base git ref's\n * tree listing (exported for that reader — a filename respelled there would\n * be the drift this table exists to prevent). The walk cannot import this\n * module — `repo → repo/tree` is the package direction, and an edge back\n * would be the cycle `scripts/package-graph.mjs` refuses — so `repo.ts`\n * injects `isServiceArtifactName` into the walk request instead.\n */\nexport const ARTIFACT_FILES = {\n model: \"model.likec4\",\n spec: \"spec.md\",\n archSpec: \"arch.spec.md\",\n openapi: \"openapi.yaml\",\n asyncapi: \"asyncapi.yaml\",\n runbook: \"runbook.md\",\n health: \"health.yaml\",\n steps: \"steps.yaml\",\n} as const;\nconst ADRS_DIR = \"adrs\";\n\n/** Does a directory entry name a living service artifact? The classification half of the table above. */\nexport function isServiceArtifactName(name: string, kind: \"file\" | \"dir\"): boolean {\n if (kind === \"dir\") return name === ADRS_DIR;\n return Object.values(ARTIFACT_FILES).some((file) => file === name);\n}\n\nexport interface ServicePaths {\n dir: ServiceDir;\n model: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract — the event axis's sibling of `openapi`. */\n asyncapi: string;\n runbook: string;\n health: string;\n /**\n * The step catalogue — which phrases this service's suite has DECIDED to\n * define. Authored and opt-in; its CONTENTS are read by `loam steps` alone and\n * nothing in `validate` consults them\n * (`core/gherkin/steps/catalogue.ts` records why).\n *\n * Its EXISTENCE is read more widely, and that is not the same statement: the\n * filename is in `ARTIFACT_FILES`, so `isServiceArtifactName` classifies it\n * and the tree walk treats a directory holding one as a service. That is\n * correct — it is a service artifact — and it is worth writing down, because\n * it means a `steps.yaml` written at the wrong path mints a service.\n */\n steps: string;\n adrsDir: string;\n}\n\n/**\n * The artifact paths of a service whose directory is already KNOWN — the\n * enumeration's `dir`, at whatever depth the tree walk found it. This is the\n * spelling every reader of an existing service must use: joining\n * `services/<id>/` at the root is only true for a fleet nobody has filed, and\n * a moved service would silently grade as absent through it.\n */\nexport function servicePathsAt(dir: ServiceDir): ServicePaths {\n return {\n dir,\n model: join(dir, ARTIFACT_FILES.model),\n spec: join(dir, ARTIFACT_FILES.spec),\n archSpec: join(dir, ARTIFACT_FILES.archSpec),\n openapi: join(dir, ARTIFACT_FILES.openapi),\n asyncapi: join(dir, ARTIFACT_FILES.asyncapi),\n runbook: join(dir, ARTIFACT_FILES.runbook),\n health: join(dir, ARTIFACT_FILES.health),\n steps: join(dir, ARTIFACT_FILES.steps),\n adrsDir: join(dir, ADRS_DIR),\n };\n}\n\n/**\n * The UNFILED spelling — `<docsDir>/services/<id>/`, the tree's root level.\n * This is where creation lands (`adopt`'s brief, a new service materialised by\n * `archive`, the OpenSpec migration) and the honest fallback for a service the\n * enumeration does not answer; for a service that EXISTS, resolve through the\n * enumeration instead (`servicePathsAt(entry.dir)`, or\n * `locateServicePaths` in `service-target.ts`, which chooses between the two).\n * Renamed from `servicePaths` when the tree landed, precisely so the compiler\n * would put every remaining caller in front of a reviewer: a root join that\n * survives unreviewed is a moved service silently grading as absent.\n */\nexport function unfiledServicePaths(docsDir: DocsDir, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(docsDir, \"services\", service)));\n}\n\n/**\n * The creation spelling INSIDE a subsystem — `<subsystemDir>/<id>/`, the\n * target `adopt --subsystem` briefs so an adoption need not land unfiled and\n * cost a second command. The same two provenances the unfiled spelling\n * combines, one level down: the subsystem directory came off the tree walk's\n * readdir, and the id passed the service grammar before it reached here.\n */\nexport function servicePathsUnder(subsystemDir: string, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(subsystemDir, service)));\n}\n\n/**\n * The directory a SUBSYSTEM occupies (or will occupy) under a parent — the\n * spelling `subsystem new` creates and `subsystem rename` renames into. The\n * same guarantee-by-brand as the service spellings above: the parent came off\n * the tree walk's readdir (or is `servicesDir` itself), and demanding\n * `SubsystemName` here is what makes the validator unskippable — a raw argv\n * string at this call site does not compile, so no future verb can hoist the\n * join above the check that earns it.\n */\nexport function subsystemPathUnder(parentDir: string, name: SubsystemName): string {\n return join(parentDir, name);\n}\n\n/**\n * The pair of requirement-carrying spec files, living and delta alike: the\n * business spec and the architecture spec. Same grammar, same delta algebra,\n * same merge — everything that walks \"the spec files\" of a service walks this\n * list, so the two axes cannot drift apart in what handles them. `key` indexes\n * ServicePaths/FeatureSpecPaths; `label` is how the axis names itself in prose.\n */\nexport const SPEC_AXES = [\n { key: \"spec\", file: \"spec.md\", label: \"requirements\" },\n { key: \"archSpec\", file: \"arch.spec.md\", label: \"arch requirements\" },\n] as const;\nexport type SpecAxis = (typeof SPEC_AXES)[number];\n\nexport interface FeaturePaths {\n dir: string;\n intent: string;\n delta: string;\n specsDir: string;\n adrsDir: string;\n}\n\nexport function featurePaths(featureDir: FeatureDir): FeaturePaths {\n return {\n dir: featureDir,\n intent: join(featureDir, \"intent.md\"),\n delta: join(featureDir, \"delta.likec4\"),\n specsDir: join(featureDir, \"specs\"),\n adrsDir: join(featureDir, \"adrs\"),\n };\n}\n\nexport interface FeatureSpecPaths {\n dir: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract delta — the event axis's sibling of `openapi`. */\n asyncapi: string;\n}\n\nexport function featureSpecPaths(featureDir: FeatureDir, service: PathableService): FeatureSpecPaths {\n const dir = join(featureDir, \"specs\", service);\n return {\n dir,\n spec: join(dir, \"spec.md\"),\n archSpec: join(dir, \"arch.spec.md\"),\n openapi: join(dir, \"openapi.yaml\"),\n asyncapi: join(dir, \"asyncapi.yaml\"),\n };\n}\n\nexport function landscapePath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", LANDSCAPE_FILE);\n}\n\n/** The fleet's authorization vocabulary — beside the fleet map, for the same reason. */\nexport function permissionsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"permissions.yaml\");\n}\n\n/** The fleet's capability vocabulary — beside the other two fleet documents, for the same reason. */\nexport function capabilitiesPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"capabilities.yaml\");\n}\n\n/**\n * The fleet's ARCHITECTURAL OBLIGATION vocabulary — beside the landscape it tags\n * and the two vocabularies it sits with, for their reason: it is a fact about\n * the fleet that no single service owns.\n */\nexport function obligationsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"obligations.yaml\");\n}\n\n/**\n * The FLEET's decision records — `architecture/adrs/`, beside the landscape and\n * the two vocabularies.\n *\n * ADRs used to exist at two altitudes only, `services/<id>/adrs/` and\n * `features/<FEAT>/adrs/`, so a decision about the fleet itself — \"event\n * publishers use a transactional outbox\", \"cross-service calls carry a circuit\n * breaker\" — had no home but one arbitrary service's directory, where the next\n * reader of the other fifty services never finds it. This is not a new axis: it\n * is the same `ADRS_DIR` name one level up, spelled through the same constant so\n * the two altitudes cannot drift into `adrs/` and `decisions/`.\n *\n * PRESENCE-TRACKED AND NOTHING MORE, exactly like the service directory it\n * mirrors: `loam list` counts the files (`repo.ts`'s `fleetAdrCount`) and no\n * check anywhere reads one. A fleet with no fleet-level ADRs owes nothing and\n * must produce no finding — which is also why `loam init` does NOT scaffold the\n * directory. git does not carry an empty one, so it would vanish on the first\n * clone and come back as a diff on the next `init`; and an empty `adrs/` in a\n * fresh repo reads as an obligation nobody has met, when there is no obligation\n * at all.\n */\nexport function fleetAdrsDir(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", ADRS_DIR);\n}\n\nconst SUBSYSTEM_VIEWS_FILE = \"subsystems.likec4\";\nconst SERVICE_VIEWS_FILE = \"services.likec4\";\n\n/**\n * The documents inside `architecture/` that LOAM GENERATES — ONE list, because\n * four questions read it and a name in one reader and missing from another fails\n * silently both ways: the path functions below, the reservation below, the project\n * loads that must EXCLUDE these files (`core/c4/project/architecture.ts` and\n * `commands/validate/fleet/views/projects.ts` — a views-only document does not\n * parse standalone, so loading one blanks the project it lands in), and `loam\n * archive`'s withhold-and-name — each of which hard-coded \"the generated one\"\n * while there was only one. The VALUE is what the reservation below prints.\n */\nexport const GENERATED_ARCHITECTURE_FILES: Readonly<Record<string, string>> = {\n [SUBSYSTEM_VIEWS_FILE]:\n \"`subsystems.likec4` is generated by `loam subsystem sync`, which overwrites it wholesale — and nothing in loam ever parses it, so anything landing there is invisible from the moment it archives\",\n [SERVICE_VIEWS_FILE]:\n \"`services.likec4` is generated by `loam subsystem sync`, one overview view per service, and is overwritten wholesale — and nothing in loam ever parses it, so anything landing there is invisible from the moment it archives\",\n};\n\n/**\n * The two GENERATED views files — beside the fleet map because the LikeC4\n * renderer merges the whole `architecture/` project, and a view can only\n * `include` elements the landscape beside it defines. `loam subsystem sync` owns\n * both files' bytes (`render/views.ts`, `render/service/views.ts`); loam parses\n * neither, and staleness against the tree is a content compare.\n */\nexport function subsystemViewsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", SUBSYSTEM_VIEWS_FILE);\n}\n\nexport function serviceViewsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", SERVICE_VIEWS_FILE);\n}\n\n/**\n * Names inside `architecture/` that LOAM owns, and what goes wrong if a\n * feature's own document claims one. `features/<FEAT>/deployment/<name>.likec4`\n * merges as a whole-file COPY straight into `architecture/` — unlike a flow,\n * which lands under `usecases/` — so its `<name>` is free to be any of them. A\n * generated name fails silently both ways (the table above says how); a copy over\n * `landscape.likec4` would discard the living map the archive SPLICES; and\n * `usecases/` is the flow tree the scan walks, where a deployment document parses\n * and is then looked for by a reader asking another question. Returns the reason,\n * or null when the name is the author's — over the rel a walk produced, never argv.\n */\nexport function reservedArchitectureName(rel: string): string | null {\n const parts = rel.split(\"/\");\n if (parts[0] === USECASE_DIR) {\n return `\\`${USECASE_DIR}/\\` is the use-case tree — a flow belongs in \\`features/<FEAT>/usecases/\\`, which merges there`;\n }\n // Own key only: a bare lookup answers `constructor` with something inherited.\n if (Object.hasOwn(GENERATED_ARCHITECTURE_FILES, rel)) return GENERATED_ARCHITECTURE_FILES[rel]!;\n if (rel === LANDSCAPE_FILE) {\n return \"`landscape.likec4` is the living fleet map, which the archive SPLICES into rather than replaces\";\n }\n return null;\n}\n\n/** The living map's file name, and the flow tree's directory — spelled once, read by the reservation. */\nconst LANDSCAPE_FILE = \"landscape.likec4\";\nconst USECASE_DIR = \"usecases\";\n\nexport function featuresDir(docsDir: DocsDir): string {\n return join(docsDir, \"features\");\n}\n\n/**\n * The root of the tree that IS the fleet — where an unfiled service and a\n * root-level subsystem both sit. The `subsystem` verbs join validated names\n * under it; the enumerating walk spells the same join for itself\n * (`repo/tree/walk.ts`), because it cannot import this module.\n */\nexport function servicesDir(docsDir: DocsDir): string {\n return join(docsDir, \"services\");\n}\n\n/**\n * The agent contract's filename, and where it sits in a docs repo.\n *\n * Both spellings are exported because the two questions are different: `init`,\n * `doctor` and `validate` want a path under a docsDir they already hold, while\n * `docs.ts` builds the scaffold as `[relative path, bytes]` pairs and needs the\n * bare name. Four modules spelled the literal out for themselves, and one of\n * them — `init` — uses it to decide whether a directory IS a docs repo at all,\n * so a rename that reached three of the four would leave `init` recognising a\n * file nothing else writes any more.\n *\n * `openspec-inventory.ts`'s \"AGENTS.md\" is deliberately NOT this constant: it\n * names a file in a foreign OpenSpec tree that happens to share the name.\n */\nexport const AGENTS_FILENAME = \"AGENTS.md\";\n\nexport function agentsPath(docsDir: DocsDir): string {\n return join(docsDir, AGENTS_FILENAME);\n}\n\n/**\n * Where shipped features live. `features/archive/` is spelled here and nowhere\n * else for the same reason every other artifact path is: `archive` and\n * `unarchive` move directories in and out of it, and a layout string that two\n * commands re-derive is a layout two commands can disagree about. The\n * `features/archive/…` strings in refusal PROSE are not this path — they are\n * what a reader types into `ls`, and they stay spelled out.\n */\nexport function archiveDir(docsDir: DocsDir): string {\n return join(featuresDir(docsDir), ARCHIVE_DIR);\n}\n\n/**\n * The LikeC4 project file — the one thing in a docs repo loam writes for a\n * tool other than itself — and it lives at two altitudes.\n *\n * At the ROOT, `loam init --create` writes the project `fleet` (`core/docs.ts`\n * composes those bytes), which holds the fleet map and every service model that\n * EXTENDS it. Beside a model that STANDS ALONE — one declaring its own\n * `specification` block, which can only be parsed on its own — `loam subsystem\n * sync` writes a project of the service's own, and adds that directory to the\n * root's `exclude`; without both, such a model is either a duplicate of the map\n * or a box on it with nothing renderable inside.\n * `core/repo/tree/render/projects.ts` composes the per-service bytes and records\n * what was measured; `core/c4/root-project/exclude.ts` owns reading the root's\n * `exclude` list and deciding what an entry covers; `core/c4/service-model/renderer.ts`\n * owns the list `loam subsystem sync` writes back.\n *\n * Spelled here and NOT in `ARTIFACT_FILES`, and the distinction is\n * load-bearing: a filename in that table classifies a directory as a service,\n * and this file is the renderer's — a stray one must mint nothing. The root's\n * NAME sits beside the filename because the per-service rule needs it too (a\n * service id equal to the root project's name would be silently renamed by\n * the renderer), and two spellings of the word are how the scaffold and the\n * rule drift apart.\n */\nexport const LIKEC4_PROJECT_FILENAME = \"likec4.config.json\";\n\n/** The root project's name, exactly as `loam init --create` scaffolds it. */\nexport const LIKEC4_ROOT_PROJECT = \"fleet\";\n\nexport function rootProjectPath(docsDir: DocsDir): string {\n return join(docsDir, LIKEC4_PROJECT_FILENAME);\n}\n\n/**\n * The two files the per-service project question is asked of — the model that\n * needs rendering and the project file that renders it — in one spelling for\n * the writer (`subsystem sync`) and the grader (`validate --all`) alike. Handed\n * INTO `render/projects.ts` rather than imported there, because that package\n * cannot import this module: `render/stale.ts` records the cycle.\n */\nexport function serviceRenderPaths(dir: ServiceDir): { model: string; project: string } {\n return { model: join(dir, ARTIFACT_FILES.model), project: join(dir, LIKEC4_PROJECT_FILENAME) };\n}\n"]}