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

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 (1026) hide show
  1. package/CHANGELOG.md +2548 -86
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +494 -0
  8. package/SCHEMA.md +2355 -256
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +317 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +161 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +251 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +72 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +194 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +236 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +250 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +219 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +334 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +47 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +282 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +88 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  398. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  399. package/dist/core/c4/parsed/specification.js +78 -0
  400. package/dist/core/c4/parsed/specification.js.map +1 -0
  401. package/dist/core/c4/parsed/values.js +57 -0
  402. package/dist/core/c4/parsed/values.js.map +1 -0
  403. package/dist/core/c4/parsed/view-ids.js +75 -0
  404. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  405. package/dist/core/c4/project/architecture.js +45 -0
  406. package/dist/core/c4/project/architecture.js.map +1 -0
  407. package/dist/core/c4/project/documents.js +58 -0
  408. package/dist/core/c4/project/documents.js.map +1 -0
  409. package/dist/core/c4/project/load.js +183 -0
  410. package/dist/core/c4/project/load.js.map +1 -0
  411. package/dist/core/c4/resolve/service.js +113 -0
  412. package/dist/core/c4/resolve/service.js.map +1 -0
  413. package/dist/core/c4/seed/fleet-file.js +201 -0
  414. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  415. package/dist/core/c4/seed/items.js +91 -0
  416. package/dist/core/c4/seed/items.js.map +1 -0
  417. package/dist/core/c4/seed/stamp.js +48 -0
  418. package/dist/core/c4/seed/stamp.js.map +1 -0
  419. package/dist/core/c4/seed/template.js +109 -0
  420. package/dist/core/c4/seed/template.js.map +1 -0
  421. package/dist/core/c4/source-mask.js +82 -0
  422. package/dist/core/c4/source-mask.js.map +1 -0
  423. package/dist/core/c4/source-scan.js +227 -0
  424. package/dist/core/c4/source-scan.js.map +1 -0
  425. package/dist/core/c4/splice/authored-source.js +101 -0
  426. package/dist/core/c4/splice/authored-source.js.map +1 -0
  427. package/dist/core/c4/splice/contract.js +10 -0
  428. package/dist/core/c4/splice/contract.js.map +1 -0
  429. package/dist/core/c4/splice/delta-blocks.js +126 -0
  430. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  431. package/dist/core/c4/splice/identity/edges.js +36 -0
  432. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  433. package/dist/core/c4/splice/landscape-merge.js +269 -0
  434. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  435. package/dist/core/c4/splice/placement.js +194 -0
  436. package/dist/core/c4/splice/placement.js.map +1 -0
  437. package/dist/core/c4/workspace.js +172 -0
  438. package/dist/core/c4/workspace.js.map +1 -0
  439. package/dist/core/capabilities/authoring/notes.js +141 -0
  440. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  441. package/dist/core/capabilities/capabilities.js +124 -0
  442. package/dist/core/capabilities/capabilities.js.map +1 -0
  443. package/dist/core/capabilities/delta/doc.js +50 -0
  444. package/dist/core/capabilities/delta/doc.js.map +1 -0
  445. package/dist/core/capabilities/delta/overlay.js +129 -0
  446. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  447. package/dist/core/capabilities/delta/summary.js +57 -0
  448. package/dist/core/capabilities/delta/summary.js.map +1 -0
  449. package/dist/core/capabilities/delta/tree.js +43 -0
  450. package/dist/core/capabilities/delta/tree.js.map +1 -0
  451. package/dist/core/capabilities/delta/uncovered.js +222 -0
  452. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  453. package/dist/core/capabilities/findings.js +342 -0
  454. package/dist/core/capabilities/findings.js.map +1 -0
  455. package/dist/core/capabilities/realizes/findings.js +181 -0
  456. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  457. package/dist/core/capabilities/realizes/join.js +143 -0
  458. package/dist/core/capabilities/realizes/join.js.map +1 -0
  459. package/dist/core/capabilities/rollup.js +199 -0
  460. package/dist/core/capabilities/rollup.js.map +1 -0
  461. package/dist/core/capabilities/tree.js +126 -0
  462. package/dist/core/capabilities/tree.js.map +1 -0
  463. package/dist/core/capabilities/usecase-join.js +277 -0
  464. package/dist/core/capabilities/usecase-join.js.map +1 -0
  465. package/dist/core/coherence/authoring/scaffold.js +177 -0
  466. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  467. package/dist/core/coherence/authoring/sentinels.js +74 -0
  468. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  469. package/dist/core/coherence/coherence.js +251 -0
  470. package/dist/core/coherence/coherence.js.map +1 -0
  471. package/dist/core/coherence/declared.js +269 -0
  472. package/dist/core/coherence/declared.js.map +1 -0
  473. package/dist/core/coherence/events/declared.js +206 -0
  474. package/dist/core/coherence/events/declared.js.map +1 -0
  475. package/dist/core/coherence/events/events.js +204 -0
  476. package/dist/core/coherence/events/events.js.map +1 -0
  477. package/dist/core/coherence/events/grades.js +166 -0
  478. package/dist/core/coherence/events/grades.js.map +1 -0
  479. package/dist/core/coherence/events/lookups.js +122 -0
  480. package/dist/core/coherence/events/lookups.js.map +1 -0
  481. package/dist/core/coherence/events/merged.js +40 -0
  482. package/dist/core/coherence/events/merged.js.map +1 -0
  483. package/dist/core/coherence/living.js +198 -0
  484. package/dist/core/coherence/living.js.map +1 -0
  485. package/dist/core/coherence/lookups.js +155 -0
  486. package/dist/core/coherence/lookups.js.map +1 -0
  487. package/dist/core/coherence/pending.js +39 -0
  488. package/dist/core/coherence/pending.js.map +1 -0
  489. package/dist/core/conflict-markers.js +60 -0
  490. package/dist/core/conflict-markers.js.map +1 -0
  491. package/dist/core/delta/claims.js +73 -0
  492. package/dist/core/delta/claims.js.map +1 -0
  493. package/dist/core/delta/delta.js +296 -0
  494. package/dist/core/delta/delta.js.map +1 -0
  495. package/dist/core/delta/document.js +177 -0
  496. package/dist/core/delta/document.js.map +1 -0
  497. package/dist/core/delta/scope.js +48 -0
  498. package/dist/core/delta/scope.js.map +1 -0
  499. package/dist/core/delta/select.js +237 -0
  500. package/dist/core/delta/select.js.map +1 -0
  501. package/dist/core/dependencies/dependencies.js +141 -0
  502. package/dist/core/dependencies/dependencies.js.map +1 -0
  503. package/dist/core/dependencies/facts.js +165 -0
  504. package/dist/core/dependencies/facts.js.map +1 -0
  505. package/dist/core/dependencies/fanin.js +88 -0
  506. package/dist/core/dependencies/fanin.js.map +1 -0
  507. package/dist/core/dependencies/graph.js +106 -0
  508. package/dist/core/dependencies/graph.js.map +1 -0
  509. package/dist/core/diff/base-git.js +148 -0
  510. package/dist/core/diff/base-git.js.map +1 -0
  511. package/dist/core/diff/base-state.js +209 -0
  512. package/dist/core/diff/base-state.js.map +1 -0
  513. package/dist/core/diff/current-state.js +96 -0
  514. package/dist/core/diff/current-state.js.map +1 -0
  515. package/dist/core/diff/scope/changed-paths.js +188 -0
  516. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  517. package/dist/core/diff/semantic.js +243 -0
  518. package/dist/core/diff/semantic.js.map +1 -0
  519. package/dist/core/diff/victims.js +247 -0
  520. package/dist/core/diff/victims.js.map +1 -0
  521. package/dist/core/docs.js +7 -64
  522. package/dist/core/docs.js.map +1 -1
  523. package/dist/core/doctor/agents.js +222 -0
  524. package/dist/core/doctor/agents.js.map +1 -0
  525. package/dist/core/doctor/config.js +135 -0
  526. package/dist/core/doctor/config.js.map +1 -0
  527. package/dist/core/doctor/doctor.js +275 -0
  528. package/dist/core/doctor/doctor.js.map +1 -0
  529. package/dist/core/doctor/report.js +13 -0
  530. package/dist/core/doctor/report.js.map +1 -0
  531. package/dist/core/doctor/residue.js +110 -0
  532. package/dist/core/doctor/residue.js.map +1 -0
  533. package/dist/core/document/apply.js +77 -0
  534. package/dist/core/document/apply.js.map +1 -0
  535. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  536. package/dist/core/document/frontmatter.js.map +1 -0
  537. package/dist/core/document/grammar/directives.js +143 -0
  538. package/dist/core/document/grammar/directives.js.map +1 -0
  539. package/dist/core/document/parse.js +263 -0
  540. package/dist/core/document/parse.js.map +1 -0
  541. package/dist/core/document/scenarios.js +221 -0
  542. package/dist/core/document/scenarios.js.map +1 -0
  543. package/dist/core/document/spec.js +241 -0
  544. package/dist/core/document/spec.js.map +1 -0
  545. package/dist/core/{config.js → envelope/config.js} +79 -18
  546. package/dist/core/envelope/config.js.map +1 -0
  547. package/dist/core/envelope/json.js +175 -0
  548. package/dist/core/envelope/json.js.map +1 -0
  549. package/dist/core/{version.js → envelope/version.js} +3 -3
  550. package/dist/core/envelope/version.js.map +1 -0
  551. package/dist/core/explain/families.js +175 -0
  552. package/dist/core/explain/families.js.map +1 -0
  553. package/dist/core/explain/fix-tables.js +178 -0
  554. package/dist/core/explain/fix-tables.js.map +1 -0
  555. package/dist/core/explain/lookup.js +181 -0
  556. package/dist/core/explain/lookup.js.map +1 -0
  557. package/dist/core/explain/refusals.js +49 -0
  558. package/dist/core/explain/refusals.js.map +1 -0
  559. package/dist/core/explain/terms.js +144 -0
  560. package/dist/core/explain/terms.js.map +1 -0
  561. package/dist/core/explore/describe.js +105 -0
  562. package/dist/core/explore/describe.js.map +1 -0
  563. package/dist/core/{explore.js → explore/explore.js} +50 -101
  564. package/dist/core/explore/explore.js.map +1 -0
  565. package/dist/core/fleet-context.js +120 -78
  566. package/dist/core/fleet-context.js.map +1 -1
  567. package/dist/core/gate/checks.js +201 -0
  568. package/dist/core/gate/checks.js.map +1 -0
  569. package/dist/core/gate/partners.js +205 -0
  570. package/dist/core/gate/partners.js.map +1 -0
  571. package/dist/core/gate/report.js +47 -0
  572. package/dist/core/gate/report.js.map +1 -0
  573. package/dist/core/gate/verification.js +113 -0
  574. package/dist/core/gate/verification.js.map +1 -0
  575. package/dist/core/gherkin/digest.js +60 -0
  576. package/dist/core/gherkin/digest.js.map +1 -0
  577. package/dist/core/gherkin/emit.js +177 -0
  578. package/dist/core/gherkin/emit.js.map +1 -0
  579. package/dist/core/gherkin/read.js +76 -0
  580. package/dist/core/gherkin/read.js.map +1 -0
  581. package/dist/core/gherkin/stale.js +234 -0
  582. package/dist/core/gherkin/stale.js.map +1 -0
  583. package/dist/core/gherkin/stamp.js +77 -0
  584. package/dist/core/gherkin/stamp.js.map +1 -0
  585. package/dist/core/gherkin/steps/catalogue.js +154 -0
  586. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  587. package/dist/core/gherkin/steps/inventory.js +98 -0
  588. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  589. package/dist/core/gherkin/steps/phrase.js +72 -0
  590. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  591. package/dist/core/glossary/delta.js +106 -0
  592. package/dist/core/glossary/delta.js.map +1 -0
  593. package/dist/core/glossary/tree.js +86 -0
  594. package/dist/core/glossary/tree.js.map +1 -0
  595. package/dist/core/kernel/concurrency.js.map +1 -0
  596. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  597. package/dist/core/kernel/document-bytes.js.map +1 -0
  598. package/dist/core/kernel/fences.js +42 -0
  599. package/dist/core/kernel/fences.js.map +1 -0
  600. package/dist/core/kernel/ids/capability.js +72 -0
  601. package/dist/core/kernel/ids/capability.js.map +1 -0
  602. package/dist/core/kernel/ids/dirs.js +33 -0
  603. package/dist/core/kernel/ids/dirs.js.map +1 -0
  604. package/dist/core/kernel/ids/feature.js +75 -0
  605. package/dist/core/kernel/ids/feature.js.map +1 -0
  606. package/dist/core/kernel/ids/service.js +156 -0
  607. package/dist/core/kernel/ids/service.js.map +1 -0
  608. package/dist/core/kernel/ids/subsystem.js +63 -0
  609. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  610. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  611. package/dist/core/kernel/path-safety.js.map +1 -0
  612. package/dist/core/kernel/records.js.map +1 -0
  613. package/dist/core/links/backlinks.js +68 -0
  614. package/dist/core/links/backlinks.js.map +1 -0
  615. package/dist/core/links/case.js +74 -0
  616. package/dist/core/links/case.js.map +1 -0
  617. package/dist/core/links/corpus.js +104 -0
  618. package/dist/core/links/corpus.js.map +1 -0
  619. package/dist/core/links/findings.js +110 -0
  620. package/dist/core/links/findings.js.map +1 -0
  621. package/dist/core/links/parse.js +236 -0
  622. package/dist/core/links/parse.js.map +1 -0
  623. package/dist/core/mcp/argv.js +111 -0
  624. package/dist/core/mcp/argv.js.map +1 -0
  625. package/dist/core/mcp/author-tools.js +72 -0
  626. package/dist/core/mcp/author-tools.js.map +1 -0
  627. package/dist/core/mcp/framing.js +46 -0
  628. package/dist/core/mcp/framing.js.map +1 -0
  629. package/dist/core/mcp/protocol.js +280 -0
  630. package/dist/core/mcp/protocol.js.map +1 -0
  631. package/dist/core/mcp/tools.js +314 -0
  632. package/dist/core/mcp/tools.js.map +1 -0
  633. package/dist/core/obligations/obligations.js +106 -0
  634. package/dist/core/obligations/obligations.js.map +1 -0
  635. package/dist/core/openapi/baseline/gate.js +166 -0
  636. package/dist/core/openapi/baseline/gate.js.map +1 -0
  637. package/dist/core/openapi/baseline/plan.js +112 -0
  638. package/dist/core/openapi/baseline/plan.js.map +1 -0
  639. package/dist/core/openapi/baseline/record.js +175 -0
  640. package/dist/core/openapi/baseline/record.js.map +1 -0
  641. package/dist/core/openapi/depth.js +122 -0
  642. package/dist/core/openapi/depth.js.map +1 -0
  643. package/dist/core/openapi/digest.js +189 -0
  644. package/dist/core/openapi/digest.js.map +1 -0
  645. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  646. package/dist/core/openapi/doc.js.map +1 -0
  647. package/dist/core/openapi/merge/components.js +234 -0
  648. package/dist/core/openapi/merge/components.js.map +1 -0
  649. package/dist/core/openapi/merge/error.js +26 -0
  650. package/dist/core/openapi/merge/error.js.map +1 -0
  651. package/dist/core/openapi/merge/markers.js +208 -0
  652. package/dist/core/openapi/merge/markers.js.map +1 -0
  653. package/dist/core/openapi/merge/merge.js +256 -0
  654. package/dist/core/openapi/merge/merge.js.map +1 -0
  655. package/dist/core/openapi/merge/pin.js +128 -0
  656. package/dist/core/openapi/merge/pin.js.map +1 -0
  657. package/dist/core/openspec/decide/capabilities.js +244 -0
  658. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  659. package/dist/core/openspec/decide/dispositions.js +63 -0
  660. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  661. package/dist/core/openspec/decide/renames.js +179 -0
  662. package/dist/core/openspec/decide/renames.js.map +1 -0
  663. package/dist/core/openspec/decide/verdict.js +84 -0
  664. package/dist/core/openspec/decide/verdict.js.map +1 -0
  665. package/dist/core/openspec/inventory.js +221 -0
  666. package/dist/core/openspec/inventory.js.map +1 -0
  667. package/dist/core/openspec/model/mapping.js +76 -0
  668. package/dist/core/openspec/model/mapping.js.map +1 -0
  669. package/dist/core/openspec/model/model.js +65 -0
  670. package/dist/core/openspec/model/model.js.map +1 -0
  671. package/dist/core/openspec/scan/changes.js +235 -0
  672. package/dist/core/openspec/scan/changes.js.map +1 -0
  673. package/dist/core/openspec/scan/config.js +200 -0
  674. package/dist/core/openspec/scan/config.js.map +1 -0
  675. package/dist/core/openspec/scan/shape.js +260 -0
  676. package/dist/core/openspec/scan/shape.js.map +1 -0
  677. package/dist/core/openspec/scan/walk.js +133 -0
  678. package/dist/core/openspec/scan/walk.js.map +1 -0
  679. package/dist/core/openspec/scan/workspace.js +71 -0
  680. package/dist/core/openspec/scan/workspace.js.map +1 -0
  681. package/dist/core/owners/codeowners.js +141 -0
  682. package/dist/core/owners/codeowners.js.map +1 -0
  683. package/dist/core/pack/features.js +120 -0
  684. package/dist/core/pack/features.js.map +1 -0
  685. package/dist/core/pack/joins.js +112 -0
  686. package/dist/core/pack/joins.js.map +1 -0
  687. package/dist/core/pack/living.js +118 -0
  688. package/dist/core/pack/living.js.map +1 -0
  689. package/dist/core/pack/pack.js +76 -0
  690. package/dist/core/pack/pack.js.map +1 -0
  691. package/dist/core/permissions/permissions.js +87 -0
  692. package/dist/core/permissions/permissions.js.map +1 -0
  693. package/dist/core/projection/api.js +84 -0
  694. package/dist/core/projection/api.js.map +1 -0
  695. package/dist/core/projection/arch-slice.js +63 -0
  696. package/dist/core/projection/arch-slice.js.map +1 -0
  697. package/dist/core/projection/events.js +34 -0
  698. package/dist/core/projection/events.js.map +1 -0
  699. package/dist/core/provenance/findings.js +208 -0
  700. package/dist/core/provenance/findings.js.map +1 -0
  701. package/dist/core/provenance/git.js +225 -0
  702. package/dist/core/provenance/git.js.map +1 -0
  703. package/dist/core/provenance/gitq/moves.js +182 -0
  704. package/dist/core/provenance/gitq/moves.js.map +1 -0
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  707. package/dist/core/provenance/sample/scope.js +238 -0
  708. package/dist/core/provenance/sample/scope.js.map +1 -0
  709. package/dist/core/provenance/sample/sections.js +61 -0
  710. package/dist/core/provenance/sample/sections.js.map +1 -0
  711. package/dist/core/provenance/sources.js +263 -0
  712. package/dist/core/provenance/sources.js.map +1 -0
  713. package/dist/core/provenance/stamp.js +232 -0
  714. package/dist/core/provenance/stamp.js.map +1 -0
  715. package/dist/core/provenance/walk.js +157 -0
  716. package/dist/core/provenance/walk.js.map +1 -0
  717. package/dist/core/repo/authored/paths.js +192 -0
  718. package/dist/core/repo/authored/paths.js.map +1 -0
  719. package/dist/core/repo/entries.js +126 -0
  720. package/dist/core/repo/entries.js.map +1 -0
  721. package/dist/core/repo/paths.js +236 -0
  722. package/dist/core/repo/paths.js.map +1 -0
  723. package/dist/core/repo/repo.js +248 -0
  724. package/dist/core/repo/repo.js.map +1 -0
  725. package/dist/core/repo/service-target.js +153 -0
  726. package/dist/core/repo/service-target.js.map +1 -0
  727. package/dist/core/repo/state.js +58 -0
  728. package/dist/core/repo/state.js.map +1 -0
  729. package/dist/core/repo/tree/find.js +52 -0
  730. package/dist/core/repo/tree/find.js.map +1 -0
  731. package/dist/core/repo/tree/fs.js +64 -0
  732. package/dist/core/repo/tree/fs.js.map +1 -0
  733. package/dist/core/repo/tree/marker.js +61 -0
  734. package/dist/core/repo/tree/marker.js.map +1 -0
  735. package/dist/core/repo/tree/views.js +172 -0
  736. package/dist/core/repo/tree/views.js.map +1 -0
  737. package/dist/core/repo/tree/walk.js +234 -0
  738. package/dist/core/repo/tree/walk.js.map +1 -0
  739. package/dist/core/results.js +99 -2
  740. package/dist/core/results.js.map +1 -1
  741. package/dist/core/scaffold/landscape.js +131 -0
  742. package/dist/core/scaffold/landscape.js.map +1 -0
  743. package/dist/core/scaffold/readme.js +84 -0
  744. package/dist/core/scaffold/readme.js.map +1 -0
  745. package/dist/core/staging/commit.js +241 -0
  746. package/dist/core/staging/commit.js.map +1 -0
  747. package/dist/core/staging/interrupted.js +96 -0
  748. package/dist/core/staging/interrupted.js.map +1 -0
  749. package/dist/core/staging/lock.js +206 -0
  750. package/dist/core/staging/lock.js.map +1 -0
  751. package/dist/core/staging/recovery/finding.js +35 -0
  752. package/dist/core/staging/recovery/finding.js.map +1 -0
  753. package/dist/core/staging/recovery/intent.js +98 -0
  754. package/dist/core/staging/recovery/intent.js.map +1 -0
  755. package/dist/core/staging/recovery/recover.js +126 -0
  756. package/dist/core/staging/recovery/recover.js.map +1 -0
  757. package/dist/core/staging/recovery/residue.js +62 -0
  758. package/dist/core/staging/recovery/residue.js.map +1 -0
  759. package/dist/core/staging/snapshot.js +202 -0
  760. package/dist/core/staging/snapshot.js.map +1 -0
  761. package/dist/core/staging/txn/forward.js +171 -0
  762. package/dist/core/staging/txn/forward.js.map +1 -0
  763. package/dist/core/staging/txn/journal.js +139 -0
  764. package/dist/core/staging/txn/journal.js.map +1 -0
  765. package/dist/core/staging/txn/transaction.js +75 -0
  766. package/dist/core/staging/txn/transaction.js.map +1 -0
  767. package/dist/core/staging/writes.js +113 -0
  768. package/dist/core/staging/writes.js.map +1 -0
  769. package/dist/core/status/actions/execution.js +49 -0
  770. package/dist/core/status/actions/execution.js.map +1 -0
  771. package/dist/core/status/contracts.js +102 -0
  772. package/dist/core/status/contracts.js.map +1 -0
  773. package/dist/core/status/feature/artifacts.js +171 -0
  774. package/dist/core/status/feature/artifacts.js.map +1 -0
  775. package/dist/core/status/feature/feature.js +207 -0
  776. package/dist/core/status/feature/feature.js.map +1 -0
  777. package/dist/core/status/feature/next.js +150 -0
  778. package/dist/core/status/feature/next.js.map +1 -0
  779. package/dist/core/status/feature/state.js +21 -0
  780. package/dist/core/status/feature/state.js.map +1 -0
  781. package/dist/core/status/fleet/fleet.js +187 -0
  782. package/dist/core/status/fleet/fleet.js.map +1 -0
  783. package/dist/core/status/fleet/next.js +207 -0
  784. package/dist/core/status/fleet/next.js.map +1 -0
  785. package/dist/core/status/interrupted.js +97 -0
  786. package/dist/core/status/interrupted.js.map +1 -0
  787. package/dist/core/status/report.js +31 -0
  788. package/dist/core/status/report.js.map +1 -0
  789. package/dist/core/status/scan.js +41 -0
  790. package/dist/core/status/scan.js.map +1 -0
  791. package/dist/core/status/verification.js +201 -0
  792. package/dist/core/status/verification.js.map +1 -0
  793. package/dist/core/usecases/capability.js +169 -0
  794. package/dist/core/usecases/capability.js.map +1 -0
  795. package/dist/core/usecases/delta/claims.js +116 -0
  796. package/dist/core/usecases/delta/claims.js.map +1 -0
  797. package/dist/core/usecases/delta/flows.js +103 -0
  798. package/dist/core/usecases/delta/flows.js.map +1 -0
  799. package/dist/core/usecases/delta/overlay.js +235 -0
  800. package/dist/core/usecases/delta/overlay.js.map +1 -0
  801. package/dist/core/usecases/fleet.js +137 -0
  802. package/dist/core/usecases/fleet.js.map +1 -0
  803. package/dist/core/usecases/operations.js +111 -0
  804. package/dist/core/usecases/operations.js.map +1 -0
  805. package/dist/core/usecases/place.js +44 -0
  806. package/dist/core/usecases/place.js.map +1 -0
  807. package/dist/core/usecases/touch.js +101 -0
  808. package/dist/core/usecases/touch.js.map +1 -0
  809. package/dist/core/verify/answers.js +173 -0
  810. package/dist/core/verify/answers.js.map +1 -0
  811. package/dist/core/verify/build.js +150 -0
  812. package/dist/core/verify/build.js.map +1 -0
  813. package/dist/core/verify/checklist.js +201 -0
  814. package/dist/core/verify/checklist.js.map +1 -0
  815. package/dist/core/verify/claims/identity.js +61 -0
  816. package/dist/core/verify/claims/identity.js.map +1 -0
  817. package/dist/core/verify/cross/diff.js +97 -0
  818. package/dist/core/verify/cross/diff.js.map +1 -0
  819. package/dist/core/verify/evidence/contract.js +169 -0
  820. package/dist/core/verify/evidence/contract.js.map +1 -0
  821. package/dist/core/verify/file.js +257 -0
  822. package/dist/core/verify/file.js.map +1 -0
  823. package/dist/core/verify/pins/lint.js +157 -0
  824. package/dist/core/verify/pins/lint.js.map +1 -0
  825. package/dist/core/verify/pins/pin.js +95 -0
  826. package/dist/core/verify/pins/pin.js.map +1 -0
  827. package/dist/core/verify/record.js +142 -0
  828. package/dist/core/verify/record.js.map +1 -0
  829. package/dist/core/verify/store/commit.js +89 -0
  830. package/dist/core/verify/store/commit.js.map +1 -0
  831. package/dist/core/verify/store/render.js +57 -0
  832. package/dist/core/verify/store/render.js.map +1 -0
  833. package/dist/core/vocabulary/health.js +91 -0
  834. package/dist/core/vocabulary/health.js.map +1 -0
  835. package/dist/core/vocabulary/issue.js +55 -0
  836. package/dist/core/vocabulary/issue.js.map +1 -0
  837. package/dist/core/vocabulary/maturity.js +145 -0
  838. package/dist/core/vocabulary/maturity.js.map +1 -0
  839. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  840. package/dist/core/vocabulary/report.js.map +1 -0
  841. package/dist/core/vocabulary/steps.js +266 -0
  842. package/dist/core/vocabulary/steps.js.map +1 -0
  843. package/dist/core/workspace/discover.js +239 -0
  844. package/dist/core/workspace/discover.js.map +1 -0
  845. package/dist/core/workspace/render.js +52 -0
  846. package/dist/core/workspace/render.js.map +1 -0
  847. package/examples/README.md +189 -0
  848. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  849. package/examples/docs/architecture/capabilities.yaml +31 -0
  850. package/examples/docs/architecture/landscape.likec4 +280 -0
  851. package/examples/docs/architecture/obligations.yaml +23 -0
  852. package/examples/docs/architecture/permissions.yaml +50 -0
  853. package/examples/docs/architecture/subsystems.likec4 +11 -0
  854. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  855. package/examples/docs/capabilities/checkout/spec.md +55 -0
  856. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  857. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  858. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  859. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  860. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  863. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  864. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  865. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  866. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  867. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  868. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  869. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  870. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  871. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  872. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  873. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  874. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  875. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  876. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  877. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  878. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  879. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  882. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  883. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  884. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  885. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  886. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  887. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  888. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  889. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  890. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  891. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  896. package/examples/docs/glossary/order.md +15 -0
  897. package/examples/docs/glossary/payments/authorization.md +14 -0
  898. package/examples/docs/likec4.config.json +9 -0
  899. package/examples/docs/loam.json +3 -0
  900. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  901. package/examples/docs/services/checkout-web/spec.md +91 -0
  902. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  903. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  904. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  905. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  906. package/examples/docs/services/order-service/arch.spec.md +127 -0
  907. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  908. package/examples/docs/services/order-service/health.yaml +57 -0
  909. package/examples/docs/services/order-service/model.likec4 +138 -0
  910. package/examples/docs/services/order-service/openapi.yaml +257 -0
  911. package/examples/docs/services/order-service/runbook.md +62 -0
  912. package/examples/docs/services/order-service/spec.md +134 -0
  913. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  914. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  915. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  916. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  917. package/examples/docs/services/payment-service/health.yaml +39 -0
  918. package/examples/docs/services/payment-service/model.likec4 +67 -0
  919. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  920. package/examples/docs/services/payment-service/runbook.md +37 -0
  921. package/examples/docs/services/payment-service/spec.md +88 -0
  922. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  923. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  924. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  925. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  926. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  927. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  928. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  929. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  930. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  931. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  932. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  933. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  934. package/examples/docs/services/platform/subsystem.yaml +2 -0
  935. package/examples/fleet.yaml +57 -0
  936. package/package.json +19 -6
  937. package/dist/commands/adopt.js +0 -240
  938. package/dist/commands/adopt.js.map +0 -1
  939. package/dist/commands/archive.js +0 -1313
  940. package/dist/commands/archive.js.map +0 -1
  941. package/dist/commands/delta.js +0 -388
  942. package/dist/commands/delta.js.map +0 -1
  943. package/dist/commands/docs-repo-gate.js.map +0 -1
  944. package/dist/commands/format.js +0 -17
  945. package/dist/commands/format.js.map +0 -1
  946. package/dist/commands/gherkin.js +0 -309
  947. package/dist/commands/gherkin.js.map +0 -1
  948. package/dist/commands/init.js.map +0 -1
  949. package/dist/commands/list.js +0 -295
  950. package/dist/commands/list.js.map +0 -1
  951. package/dist/commands/migrate-openspec.js +0 -950
  952. package/dist/commands/migrate-openspec.js.map +0 -1
  953. package/dist/commands/new.js +0 -413
  954. package/dist/commands/new.js.map +0 -1
  955. package/dist/commands/rebase.js +0 -356
  956. package/dist/commands/rebase.js.map +0 -1
  957. package/dist/commands/show.js.map +0 -1
  958. package/dist/commands/status.js +0 -262
  959. package/dist/commands/status.js.map +0 -1
  960. package/dist/commands/unarchive.js.map +0 -1
  961. package/dist/commands/validate.js +0 -1773
  962. package/dist/commands/validate.js.map +0 -1
  963. package/dist/commands/verify.js +0 -775
  964. package/dist/commands/verify.js.map +0 -1
  965. package/dist/commands/vouch.js +0 -421
  966. package/dist/commands/vouch.js.map +0 -1
  967. package/dist/core/agent.js +0 -2173
  968. package/dist/core/agent.js.map +0 -1
  969. package/dist/core/agents-stamp.js.map +0 -1
  970. package/dist/core/arch.js +0 -130
  971. package/dist/core/arch.js.map +0 -1
  972. package/dist/core/asyncapi.js.map +0 -1
  973. package/dist/core/brief.js +0 -755
  974. package/dist/core/brief.js.map +0 -1
  975. package/dist/core/coherence.js +0 -577
  976. package/dist/core/coherence.js.map +0 -1
  977. package/dist/core/concurrency.js.map +0 -1
  978. package/dist/core/config.js.map +0 -1
  979. package/dist/core/delta.js +0 -476
  980. package/dist/core/delta.js.map +0 -1
  981. package/dist/core/dependencies.js +0 -356
  982. package/dist/core/dependencies.js.map +0 -1
  983. package/dist/core/doctor.js +0 -596
  984. package/dist/core/doctor.js.map +0 -1
  985. package/dist/core/document-bytes.js.map +0 -1
  986. package/dist/core/explore.js.map +0 -1
  987. package/dist/core/frontmatter.js.map +0 -1
  988. package/dist/core/gherkin.js +0 -463
  989. package/dist/core/gherkin.js.map +0 -1
  990. package/dist/core/health.js +0 -67
  991. package/dist/core/health.js.map +0 -1
  992. package/dist/core/ids.js +0 -122
  993. package/dist/core/ids.js.map +0 -1
  994. package/dist/core/issue.js +0 -9
  995. package/dist/core/issue.js.map +0 -1
  996. package/dist/core/json.js +0 -74
  997. package/dist/core/json.js.map +0 -1
  998. package/dist/core/likec4.js +0 -489
  999. package/dist/core/likec4.js.map +0 -1
  1000. package/dist/core/maturity.js +0 -66
  1001. package/dist/core/maturity.js.map +0 -1
  1002. package/dist/core/openapi-merge.js +0 -552
  1003. package/dist/core/openapi-merge.js.map +0 -1
  1004. package/dist/core/openapi.js.map +0 -1
  1005. package/dist/core/openspec-inventory.js +0 -1440
  1006. package/dist/core/openspec-inventory.js.map +0 -1
  1007. package/dist/core/path-safety.js.map +0 -1
  1008. package/dist/core/provenance.js +0 -787
  1009. package/dist/core/provenance.js.map +0 -1
  1010. package/dist/core/records.js.map +0 -1
  1011. package/dist/core/repo.js +0 -438
  1012. package/dist/core/repo.js.map +0 -1
  1013. package/dist/core/report.js.map +0 -1
  1014. package/dist/core/spec.js +0 -505
  1015. package/dist/core/spec.js.map +0 -1
  1016. package/dist/core/staging.js +0 -890
  1017. package/dist/core/staging.js.map +0 -1
  1018. package/dist/core/status.js +0 -1132
  1019. package/dist/core/status.js.map +0 -1
  1020. package/dist/core/steps.js +0 -72
  1021. package/dist/core/steps.js.map +0 -1
  1022. package/dist/core/verify.js +0 -807
  1023. package/dist/core/verify.js.map +0 -1
  1024. package/dist/core/version.js.map +0 -1
  1025. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1026. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Turning a name a caller typed into a name loam may join into a path.
3
+ *
4
+ * `unfiledServicePaths(docsDir, name)` spells `<docsDir>/services/<name>/`,
5
+ * and its parameter is `PathableService`: an unchecked name at that call site
6
+ * no longer compiles, and this module is where a name a caller typed EARNS the
7
+ * type. The history is why it exists. Six commands guard their argument with
8
+ * `assertServiceId` at the boundary; `validate` never did, and both of its
9
+ * entry points — `--service <id>` and the positional `<target>` — reached
10
+ * the path builder with whatever argv held. `--service ../../outside/services/x`
11
+ * resolved ABOVE the docs repo, and where a `spec.md` happened to sit there
12
+ * loam opened it, graded it, and reported its frontmatter through `--json`.
13
+ *
14
+ * The grammar alone is not the answer, and this is the whole reason this module
15
+ * exists rather than a call to `assertServiceId`. `services/Payment Service/`
16
+ * is a directory `loam list` shows, `validate --all` grades, and
17
+ * `service.id-invalid` calls an error nobody can fix without a rename — so it
18
+ * is exactly the directory somebody points `validate --service` at. Refusing on
19
+ * the grammar would make the one service loam complains about the one service
20
+ * loam cannot look at.
21
+ *
22
+ * So the enumeration answers first and the grammar answers second:
23
+ *
24
+ * in `services/` → the enumeration's OWN id, which is a name loam
25
+ * already reads off disk every time it lists the fleet.
26
+ * Legal or not, it exists, and joining it lands where
27
+ * the directory is.
28
+ * not there, legal → the caller's name. Nothing exists yet; the run
29
+ * continues and grades it `service.unknown`, with the
30
+ * near-miss hints that make a typo diagnosable.
31
+ * not there, illegal → refused, before any path is built.
32
+ *
33
+ * `show` and `status` already resolve against the enumeration this way. What
34
+ * they do not do is fall through to the grammar, which is why they answer
35
+ * "no such service" to a name that could never BE a service — a worse sentence
36
+ * for the same input.
37
+ */
38
+ import { listServices } from "./repo.js";
39
+ import { parseServiceId } from "../kernel/ids/service.js";
40
+ import { serviceTreePath } from "../kernel/ids/dirs.js";
41
+ import { servicePathsAt, unfiledServicePaths } from "./paths.js";
42
+ /**
43
+ * The enumerated services, or the empty list when `services/` cannot be
44
+ * enumerated at all. The swallow is deliberate and spelled ONCE: every
45
+ * repository-aware resolver wants "which services exist" as context, and an
46
+ * unenumerable `services/` is that repository's own diagnosis
47
+ * (`services-missing`), never the asking check's. Six call sites each carried
48
+ * this try/catch in their own spelling before it lived here — which is exactly
49
+ * the shape docs/CODE-STYLE.md warns about: an errno fix lands in one copy and
50
+ * the other five keep the old behaviour. The existence probes that used to ask
51
+ * `existsSync(services/<id>/)` ask membership HERE for the same tolerance:
52
+ * `validate --feature` must run in a docs repo with no `services/` at all,
53
+ * where enumerating is a refusal but "does this service exist" still has the
54
+ * honest answer no.
55
+ */
56
+ export async function enumeratedServices(docsDir, fleet) {
57
+ try {
58
+ return await listServices(docsDir, fleet);
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ }
64
+ /** The id projection of `enumeratedServices` — most membership asks want only the names. */
65
+ export async function enumeratedServiceIds(docsDir, fleet) {
66
+ return (await enumeratedServices(docsDir, fleet)).map((s) => s.id);
67
+ }
68
+ /**
69
+ * The artifact paths of a service wherever it lives: the enumeration's own
70
+ * directory when the id exists anywhere in the tree, the unfiled root
71
+ * spelling otherwise — so an absent service still probes, grades
72
+ * (`service.unknown`) and CREATES exactly as it did when the tree was flat.
73
+ * This is the one bridge the ~40 former `servicePaths` call sites resolve
74
+ * through — and it is NOT free: without a `fleet` context every call is a
75
+ * fresh fleet enumeration (one readdir per directory walked plus a
76
+ * frontmatter read and several existence probes per service), which measured
77
+ * ~13 ms per call on a 120-service fleet. A call site inside a loop must
78
+ * thread the command's `FleetContext`; a caller that already holds a
79
+ * `ServiceEntry` should spell `servicePathsAt(entry.dir)` directly and pay
80
+ * nothing.
81
+ */
82
+ /**
83
+ * Where a service sits under `services/`, repo-relative and WITHOUT a trailing
84
+ * slash — for a message that names an existing directory to a reader.
85
+ *
86
+ * The join it replaces is `services/${id}`, which is right for an unfiled
87
+ * service and wrong for every filed one; `paths.ts` states the same rule for
88
+ * artifact paths and this is its display sibling. A name the enumeration does
89
+ * not answer to falls back to the unfiled spelling, which is the only honest
90
+ * answer: there is no directory to point at, and that is usually what the
91
+ * message is about.
92
+ */
93
+ export async function serviceTreePathOf(docsDir, service, fleet) {
94
+ const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);
95
+ return entry === undefined ? `services/${service}` : serviceTreePath(entry);
96
+ }
97
+ export async function locateServicePaths(docsDir, service, fleet) {
98
+ const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);
99
+ return entry === undefined ? unfiledServicePaths(docsDir, service) : servicePathsAt(entry.dir);
100
+ }
101
+ export async function resolveServiceTarget(docsDir, name, label, fleet) {
102
+ // The enumeration is asked first, and its answer is the value that travels
103
+ // on: `entry.id` rather than `name`. They are equal as strings, and only one
104
+ // of them carries the fact that a `readdir` produced it. The resolved `dir`
105
+ // travels too, so a caller that goes on to read artifacts can spell
106
+ // `servicePathsAt(dir)` without a second enumeration; it is absent exactly
107
+ // when the name resolved by grammar alone — nothing exists to point at.
108
+ const entry = (await listServices(docsDir, fleet)).find((s) => s.id === name);
109
+ if (entry !== undefined)
110
+ return { ok: true, id: entry.id, dir: entry.dir };
111
+ return parseServiceId(name, label);
112
+ }
113
+ /**
114
+ * The same resolution for a name a DOCUMENT declared — an edge target's
115
+ * `metadata { service '…' }` binding, or its title standing in for one — where
116
+ * the caller needs an answer instead of a refusal: which enumerated directory,
117
+ * if any, does this text name?
118
+ *
119
+ * The enumeration is the fleet's `services/` plus whatever directory names the
120
+ * caller has already read for itself — a feature's own `specs/<svc>/`, the only
121
+ * place a service the feature INTRODUCES has a directory at all. There is
122
+ * deliberately no grammar arm here, in either direction: `services/Payment
123
+ * Service/` keeps resolving for the banner's reason, and a legal-looking name
124
+ * that matches no directory has no living file anywhere, so the caller's
125
+ * business is to grade it absent — never to build a path from it.
126
+ *
127
+ * The index is built on the first ask, not at construction: coherence asks per
128
+ * op-edge and features routinely carry none, so the enumeration only runs once
129
+ * an answer matters. A repo whose `services/` cannot be enumerated resolves
130
+ * only the caller's own names — every living probe would have found nothing
131
+ * there anyway, and `services-missing` is that repository's diagnosis, not
132
+ * this document's.
133
+ */
134
+ export function enumeratedServiceIndex(docsDir, known, fleet) {
135
+ let index;
136
+ return async (name) => {
137
+ if (index === undefined) {
138
+ index = new Map();
139
+ for (const id of known)
140
+ index.set(id, id);
141
+ try {
142
+ for (const s of await listServices(docsDir, fleet))
143
+ index.set(s.id, s.id);
144
+ }
145
+ catch {
146
+ // The docblock's last paragraph: no enumerable services/ means the
147
+ // fleet half of the index is empty, not that the question is an error.
148
+ }
149
+ }
150
+ return index.get(name);
151
+ };
152
+ }
153
+ //# sourceMappingURL=service-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-target.js","sourceRoot":"","sources":["../../../src/core/repo/service-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAA2C,MAAM,0BAA0B,CAAC;AAEnG,OAAO,EAAE,eAAe,EAAiC,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAOpF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAgB,EAChB,KAAoB;IAEpB,IAAI,CAAC;QACH,OAAO,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,KAAoB;IAEpB,OAAO,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,OAAe,EACf,KAAoB;IAEpB,MAAM,KAAK,GAAG,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACvF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAgB,EAChB,OAAwB,EACxB,KAAoB;IAEpB,MAAM,KAAK,GAAG,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACvF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,IAAY,EACZ,KAAa,EACb,KAAoB;IAEpB,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,oEAAoE;IACpE,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,KAAK,GAAG,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IAC9E,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IAC3E,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAgB,EAChB,KAA8B,EAC9B,KAAoB;IAEpB,IAAI,KAA4C,CAAC;IACjD,OAAO,KAAK,EAAE,IAAY,EAAqC,EAAE;QAC/D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;YACxC,KAAK,MAAM,EAAE,IAAI,KAAK;gBAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC;gBACH,KAAK,MAAM,CAAC,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;YAAC,MAAM,CAAC;gBACP,mEAAmE;gBACnE,uEAAuE;YACzE,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Turning a name a caller typed into a name loam may join into a path.\n *\n * `unfiledServicePaths(docsDir, name)` spells `<docsDir>/services/<name>/`,\n * and its parameter is `PathableService`: an unchecked name at that call site\n * no longer compiles, and this module is where a name a caller typed EARNS the\n * type. The history is why it exists. Six commands guard their argument with\n * `assertServiceId` at the boundary; `validate` never did, and both of its\n * entry points — `--service <id>` and the positional `<target>` — reached\n * the path builder with whatever argv held. `--service ../../outside/services/x`\n * resolved ABOVE the docs repo, and where a `spec.md` happened to sit there\n * loam opened it, graded it, and reported its frontmatter through `--json`.\n *\n * The grammar alone is not the answer, and this is the whole reason this module\n * exists rather than a call to `assertServiceId`. `services/Payment Service/`\n * is a directory `loam list` shows, `validate --all` grades, and\n * `service.id-invalid` calls an error nobody can fix without a rename — so it\n * is exactly the directory somebody points `validate --service` at. Refusing on\n * the grammar would make the one service loam complains about the one service\n * loam cannot look at.\n *\n * So the enumeration answers first and the grammar answers second:\n *\n * in `services/` → the enumeration's OWN id, which is a name loam\n * already reads off disk every time it lists the fleet.\n * Legal or not, it exists, and joining it lands where\n * the directory is.\n * not there, legal → the caller's name. Nothing exists yet; the run\n * continues and grades it `service.unknown`, with the\n * near-miss hints that make a typo diagnosable.\n * not there, illegal → refused, before any path is built.\n *\n * `show` and `status` already resolve against the enumeration this way. What\n * they do not do is fall through to the grammar, which is why they answer\n * \"no such service\" to a name that could never BE a service — a worse sentence\n * for the same input.\n */\nimport { listServices } from \"./repo.js\";\nimport { parseServiceId, type PathableService, type RawServiceId } from \"../kernel/ids/service.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { serviceTreePath, type DocsDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport { servicePathsAt, unfiledServicePaths, type ServicePaths } from \"./paths.js\";\nimport type { ServiceEntry } from \"./entries.js\";\n\nexport type ServiceTarget =\n | { readonly ok: true; readonly id: RawServiceId; readonly dir?: ServiceDir }\n | { readonly ok: false; readonly problem: string };\n\n/**\n * The enumerated services, or the empty list when `services/` cannot be\n * enumerated at all. The swallow is deliberate and spelled ONCE: every\n * repository-aware resolver wants \"which services exist\" as context, and an\n * unenumerable `services/` is that repository's own diagnosis\n * (`services-missing`), never the asking check's. Six call sites each carried\n * this try/catch in their own spelling before it lived here — which is exactly\n * the shape docs/CODE-STYLE.md warns about: an errno fix lands in one copy and\n * the other five keep the old behaviour. The existence probes that used to ask\n * `existsSync(services/<id>/)` ask membership HERE for the same tolerance:\n * `validate --feature` must run in a docs repo with no `services/` at all,\n * where enumerating is a refusal but \"does this service exist\" still has the\n * honest answer no.\n */\nexport async function enumeratedServices(\n docsDir: DocsDir,\n fleet?: FleetContext,\n): Promise<ServiceEntry[]> {\n try {\n return await listServices(docsDir, fleet);\n } catch {\n return [];\n }\n}\n\n/** The id projection of `enumeratedServices` — most membership asks want only the names. */\nexport async function enumeratedServiceIds(\n docsDir: DocsDir,\n fleet?: FleetContext,\n): Promise<RawServiceId[]> {\n return (await enumeratedServices(docsDir, fleet)).map((s) => s.id);\n}\n\n/**\n * The artifact paths of a service wherever it lives: the enumeration's own\n * directory when the id exists anywhere in the tree, the unfiled root\n * spelling otherwise — so an absent service still probes, grades\n * (`service.unknown`) and CREATES exactly as it did when the tree was flat.\n * This is the one bridge the ~40 former `servicePaths` call sites resolve\n * through — and it is NOT free: without a `fleet` context every call is a\n * fresh fleet enumeration (one readdir per directory walked plus a\n * frontmatter read and several existence probes per service), which measured\n * ~13 ms per call on a 120-service fleet. A call site inside a loop must\n * thread the command's `FleetContext`; a caller that already holds a\n * `ServiceEntry` should spell `servicePathsAt(entry.dir)` directly and pay\n * nothing.\n */\n/**\n * Where a service sits under `services/`, repo-relative and WITHOUT a trailing\n * slash — for a message that names an existing directory to a reader.\n *\n * The join it replaces is `services/${id}`, which is right for an unfiled\n * service and wrong for every filed one; `paths.ts` states the same rule for\n * artifact paths and this is its display sibling. A name the enumeration does\n * not answer to falls back to the unfiled spelling, which is the only honest\n * answer: there is no directory to point at, and that is usually what the\n * message is about.\n */\nexport async function serviceTreePathOf(\n docsDir: DocsDir,\n service: string,\n fleet?: FleetContext,\n): Promise<string> {\n const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);\n return entry === undefined ? `services/${service}` : serviceTreePath(entry);\n}\n\nexport async function locateServicePaths(\n docsDir: DocsDir,\n service: PathableService,\n fleet?: FleetContext,\n): Promise<ServicePaths> {\n const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);\n return entry === undefined ? unfiledServicePaths(docsDir, service) : servicePathsAt(entry.dir);\n}\n\nexport async function resolveServiceTarget(\n docsDir: DocsDir,\n name: string,\n label: string,\n fleet?: FleetContext,\n): Promise<ServiceTarget> {\n // The enumeration is asked first, and its answer is the value that travels\n // on: `entry.id` rather than `name`. They are equal as strings, and only one\n // of them carries the fact that a `readdir` produced it. The resolved `dir`\n // travels too, so a caller that goes on to read artifacts can spell\n // `servicePathsAt(dir)` without a second enumeration; it is absent exactly\n // when the name resolved by grammar alone — nothing exists to point at.\n const entry = (await listServices(docsDir, fleet)).find((s) => s.id === name);\n if (entry !== undefined) return { ok: true, id: entry.id, dir: entry.dir };\n return parseServiceId(name, label);\n}\n\n/**\n * The same resolution for a name a DOCUMENT declared — an edge target's\n * `metadata { service '…' }` binding, or its title standing in for one — where\n * the caller needs an answer instead of a refusal: which enumerated directory,\n * if any, does this text name?\n *\n * The enumeration is the fleet's `services/` plus whatever directory names the\n * caller has already read for itself — a feature's own `specs/<svc>/`, the only\n * place a service the feature INTRODUCES has a directory at all. There is\n * deliberately no grammar arm here, in either direction: `services/Payment\n * Service/` keeps resolving for the banner's reason, and a legal-looking name\n * that matches no directory has no living file anywhere, so the caller's\n * business is to grade it absent — never to build a path from it.\n *\n * The index is built on the first ask, not at construction: coherence asks per\n * op-edge and features routinely carry none, so the enumeration only runs once\n * an answer matters. A repo whose `services/` cannot be enumerated resolves\n * only the caller's own names — every living probe would have found nothing\n * there anyway, and `services-missing` is that repository's diagnosis, not\n * this document's.\n */\nexport function enumeratedServiceIndex(\n docsDir: DocsDir,\n known: readonly RawServiceId[],\n fleet?: FleetContext,\n): (name: string) => Promise<RawServiceId | undefined> {\n let index: Map<string, RawServiceId> | undefined;\n return async (name: string): Promise<RawServiceId | undefined> => {\n if (index === undefined) {\n index = new Map<string, RawServiceId>();\n for (const id of known) index.set(id, id);\n try {\n for (const s of await listServices(docsDir, fleet)) index.set(s.id, s.id);\n } catch {\n // The docblock's last paragraph: no enumerable services/ means the\n // fleet half of the index is empty, not that the question is an error.\n }\n }\n return index.get(name);\n };\n}\n"]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Is there a docs repo there at all?
3
+ *
4
+ * Three answers, not two: missing, present-but-empty, and ok. The middle one
5
+ * exists because a freshly cloned docs repo whose `services/` git never tracked
6
+ * looks identical to a directory nobody wired — and the fix differs.
7
+ */
8
+ import { existsSync, statSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ export function docsRepoState(docsDir) {
11
+ const path = docsDir;
12
+ let isDir = false;
13
+ try {
14
+ isDir = statSync(path).isDirectory();
15
+ }
16
+ catch {
17
+ isDir = false;
18
+ }
19
+ if (!isDir)
20
+ return { kind: "missing", path };
21
+ return { kind: existsSync(join(path, "services")) ? "ok" : "no-services", path };
22
+ }
23
+ /**
24
+ * The docs repo is not there. Thrown rather than returned because every caller
25
+ * of the enumeration wants the same thing — to stop — and a `[]` return is the
26
+ * bug this closes. `message` is written for a human at a terminal: the CLI's
27
+ * top-level handler prints it verbatim, and commands that catch it report
28
+ * `repository-unavailable`.
29
+ */
30
+ export class DocsRepoUnavailableError extends Error {
31
+ state;
32
+ constructor(state, message) {
33
+ super(message);
34
+ this.state = state;
35
+ this.name = "DocsRepoUnavailableError";
36
+ }
37
+ }
38
+ /**
39
+ * Refuse to enumerate a docs repo that is not one. `allow` names the states the
40
+ * caller can honestly survive: features/ may be absent from a real docs repo
41
+ * (nothing is in flight yet), so feature enumeration only insists the docs repo
42
+ * EXISTS, while service enumeration insists it is shaped like a docs repo — a
43
+ * `services/` directory is what makes it one.
44
+ */
45
+ export function requireDocsRepo(docsDir, allow) {
46
+ const state = docsRepoState(docsDir);
47
+ if (allow.includes(state.kind))
48
+ return;
49
+ if (state.kind === "missing") {
50
+ throw new DocsRepoUnavailableError(state, `The configured docs repo does not exist: ${state.path}. ` +
51
+ "Fix `docsDir` in loam.json, clone the docs repo there, " +
52
+ "or run `loam init --docs <dir> --create` to make a new one.");
53
+ }
54
+ throw new DocsRepoUnavailableError(state, `${state.path} is not a docs repo — it has no services/ directory. ` +
55
+ "Point `docsDir` in loam.json at the shared docs repo, " +
56
+ "or run `loam init --docs <dir> --create` to make one.");
57
+ }
58
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/core/repo/state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA8BjC,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC;IACrB,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,KAAK,CAAC;IAChB,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7C,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAEtC,KAAK;IADhB,YACW,KAAoB,EAC7B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;qBAHN,KAAK;QAId,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,KAAqB;IACpE,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO;IACvC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,wBAAwB,CAChC,KAAK,EACL,4CAA4C,KAAK,CAAC,IAAI,IAAI;YACxD,yDAAyD;YACzD,6DAA6D,CAChE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,wBAAwB,CAChC,KAAK,EACL,GAAG,KAAK,CAAC,IAAI,uDAAuD;QAClE,wDAAwD;QACxD,uDAAuD,CAC1D,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Is there a docs repo there at all?\n *\n * Three answers, not two: missing, present-but-empty, and ok. The middle one\n * exists because a freshly cloned docs repo whose `services/` git never tracked\n * looks identical to a directory nobody wired — and the fix differs.\n */\nimport { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/* ------------------------------------------------------------------ */\n/* Is there a docs repo there at all? */\n/* ------------------------------------------------------------------ */\n\n/**\n * What is actually at `docsDir`. Three answers, because they point at three\n * different fixes and the enumeration below used to give all of them the same\n * one — an empty list:\n *\n * - `missing` — nothing there (or not a directory): `docsDir` in loam.json\n * is wrong, or the docs repo was never cloned;\n * - `no-services` — a directory, but with no `services/`: it is some other\n * directory, most often the service repo itself after a typo;\n * - `ok` — a docs repo. `services/` may still be EMPTY, and that is a\n * legitimate state: a docs repo before the first `loam adopt`.\n *\n * \"Empty fleet\" and \"wrong path\" are the same output only if nobody asks this\n * question, and a green `loam list` over a docsDir that does not exist is worse\n * than a red one: it says the fleet is fine.\n */\nexport type DocsRepoKind = \"missing\" | \"no-services\" | \"ok\";\n\nexport interface DocsRepoState {\n kind: DocsRepoKind;\n /** The path examined, so a caller can quote it without re-deriving it. */\n path: string;\n}\n\nexport function docsRepoState(docsDir: string): DocsRepoState {\n const path = docsDir;\n let isDir = false;\n try {\n isDir = statSync(path).isDirectory();\n } catch {\n isDir = false;\n }\n if (!isDir) return { kind: \"missing\", path };\n return { kind: existsSync(join(path, \"services\")) ? \"ok\" : \"no-services\", path };\n}\n\n/**\n * The docs repo is not there. Thrown rather than returned because every caller\n * of the enumeration wants the same thing — to stop — and a `[]` return is the\n * bug this closes. `message` is written for a human at a terminal: the CLI's\n * top-level handler prints it verbatim, and commands that catch it report\n * `repository-unavailable`.\n */\nexport class DocsRepoUnavailableError extends Error {\n constructor(\n readonly state: DocsRepoState,\n message: string,\n ) {\n super(message);\n this.name = \"DocsRepoUnavailableError\";\n }\n}\n\n/**\n * Refuse to enumerate a docs repo that is not one. `allow` names the states the\n * caller can honestly survive: features/ may be absent from a real docs repo\n * (nothing is in flight yet), so feature enumeration only insists the docs repo\n * EXISTS, while service enumeration insists it is shaped like a docs repo — a\n * `services/` directory is what makes it one.\n */\nexport function requireDocsRepo(docsDir: string, allow: DocsRepoKind[]): void {\n const state = docsRepoState(docsDir);\n if (allow.includes(state.kind)) return;\n if (state.kind === \"missing\") {\n throw new DocsRepoUnavailableError(\n state,\n `The configured docs repo does not exist: ${state.path}. ` +\n \"Fix `docsDir` in loam.json, clone the docs repo there, \" +\n \"or run `loam init --docs <dir> --create` to make a new one.\",\n );\n }\n throw new DocsRepoUnavailableError(\n state,\n `${state.path} is not a docs repo — it has no services/ directory. ` +\n \"Point `docsDir` in loam.json at the shared docs repo, \" +\n \"or run `loam init --docs <dir> --create` to make one.\",\n );\n}\n"]}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Name resolution over a walked tree — the questions every `loam subsystem`
3
+ * verb asks before it acts: what does this name denote, what sits beneath a
4
+ * subsystem, and which names come close to a miss. One module so `move`,
5
+ * `rm`, `new` and `history` cannot grow four private spellings of "the flat
6
+ * namespace" — the walk already enforces one namespace, and resolution must
7
+ * read it the same way.
8
+ */
9
+ import { closeIds } from "../../c4/arch.js";
10
+ /**
11
+ * Resolve one name against the flat namespace. Services win a tie on
12
+ * purpose: a tie IS `subsystem.name-collision`, already an error finding,
13
+ * and while it stands the service reading keeps every id-addressed command
14
+ * coherent.
15
+ */
16
+ export function findInTree(tree, name) {
17
+ const service = tree.services.find((s) => s.id === name);
18
+ if (service !== undefined)
19
+ return { kind: "service", service };
20
+ const subsystem = tree.subsystems.find((s) => s.name === name);
21
+ if (subsystem !== undefined)
22
+ return { kind: "subsystem", subsystem };
23
+ return null;
24
+ }
25
+ /** Every name the flat namespace holds — service ids and subsystem names together. */
26
+ export function treeNames(tree) {
27
+ return [...tree.services.map((s) => s.id), ...tree.subsystems.map((s) => s.name)];
28
+ }
29
+ /** The near-miss hints a refusal offers, over whichever pool the verb resolves against. */
30
+ export function nearestTreeNames(name, pool) {
31
+ return closeIds(name, pool);
32
+ }
33
+ /**
34
+ * Does the subsystem at `subPath` contain `path` — itself, or anywhere beneath
35
+ * it? The move-into-own-subtree refusal. The parameter is the name chain, not
36
+ * a `SubsystemEntry`, because a chain is all this comparison reads — the one
37
+ * caller outside this module (`move`) has a chain it just composed and no
38
+ * entry to hand, and a structural parameter it never reads would force a
39
+ * placeholder there.
40
+ */
41
+ export function withinSubsystem(subPath, path) {
42
+ return subPath.length <= path.length && subPath.every((name, i) => path[i] === name);
43
+ }
44
+ /** Services anywhere beneath a subsystem — the transitive membership the views and counts share. */
45
+ export function servicesUnder(tree, sub) {
46
+ return tree.services.filter((s) => sub.path.every((name, i) => s.subsystem[i] === name));
47
+ }
48
+ /** Child subsystems anywhere beneath one — `rm`'s other half of "empty". */
49
+ export function subsystemsUnder(tree, sub) {
50
+ return tree.subsystems.filter((c) => c !== sub && withinSubsystem(sub.path, c.path));
51
+ }
52
+ //# sourceMappingURL=find.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/find.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAS5C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe,EAAE,IAAY;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IACrE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAc;IAC3D,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,OAAiB,EAAE,IAAc;IAC/D,OAAO,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AACvF,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,IAAe,EAAE,GAAmB;IAChE,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,IAAe,EAAE,GAAmB;IAClE,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACvF,CAAC","sourcesContent":["/**\n * Name resolution over a walked tree — the questions every `loam subsystem`\n * verb asks before it acts: what does this name denote, what sits beneath a\n * subsystem, and which names come close to a miss. One module so `move`,\n * `rm`, `new` and `history` cannot grow four private spellings of \"the flat\n * namespace\" — the walk already enforces one namespace, and resolution must\n * read it the same way.\n */\nimport { closeIds } from \"../../c4/arch.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"./walk.js\";\n\n/** What a name denotes in the tree — a service, a subsystem, or nothing. */\nexport type TreeHit =\n | { kind: \"service\"; service: WalkedService }\n | { kind: \"subsystem\"; subsystem: SubsystemEntry }\n | null;\n\n/**\n * Resolve one name against the flat namespace. Services win a tie on\n * purpose: a tie IS `subsystem.name-collision`, already an error finding,\n * and while it stands the service reading keeps every id-addressed command\n * coherent.\n */\nexport function findInTree(tree: FleetTree, name: string): TreeHit {\n const service = tree.services.find((s) => s.id === name);\n if (service !== undefined) return { kind: \"service\", service };\n const subsystem = tree.subsystems.find((s) => s.name === name);\n if (subsystem !== undefined) return { kind: \"subsystem\", subsystem };\n return null;\n}\n\n/** Every name the flat namespace holds — service ids and subsystem names together. */\nexport function treeNames(tree: FleetTree): string[] {\n return [...tree.services.map((s) => s.id), ...tree.subsystems.map((s) => s.name)];\n}\n\n/** The near-miss hints a refusal offers, over whichever pool the verb resolves against. */\nexport function nearestTreeNames(name: string, pool: string[]): string[] {\n return closeIds(name, pool);\n}\n\n/**\n * Does the subsystem at `subPath` contain `path` — itself, or anywhere beneath\n * it? The move-into-own-subtree refusal. The parameter is the name chain, not\n * a `SubsystemEntry`, because a chain is all this comparison reads — the one\n * caller outside this module (`move`) has a chain it just composed and no\n * entry to hand, and a structural parameter it never reads would force a\n * placeholder there.\n */\nexport function withinSubsystem(subPath: string[], path: string[]): boolean {\n return subPath.length <= path.length && subPath.every((name, i) => path[i] === name);\n}\n\n/** Services anywhere beneath a subsystem — the transitive membership the views and counts share. */\nexport function servicesUnder(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return tree.services.filter((s) => sub.path.every((name, i) => s.subsystem[i] === name));\n}\n\n/** Child subsystems anywhere beneath one — `rm`'s other half of \"empty\". */\nexport function subsystemsUnder(tree: FleetTree, sub: SubsystemEntry): SubsystemEntry[] {\n return tree.subsystems.filter((c) => c !== sub && withinSubsystem(sub.path, c.path));\n}\n"]}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Directory-entry questions the enumerations share, symlinks followed.
3
+ *
4
+ * Moved here from `repo/repo.ts` when the tree walk arrived: the walk and the
5
+ * feature enumeration must answer "what IS this entry" identically, and the
6
+ * package direction is `repo → repo/tree` (never back — `walk.ts` says why),
7
+ * so the shared spelling lives on this side of the edge.
8
+ */
9
+ import { existsSync, statSync } from "node:fs";
10
+ import { readdir } from "node:fs/promises";
11
+ import { join } from "node:path";
12
+ /**
13
+ * What a directory entry IS, following a symlink to find out.
14
+ *
15
+ * `readdir(withFileTypes)` does not follow symlinks: for a symlink `Dirent`
16
+ * every `isDirectory()`/`isFile()` is false, whatever it points at. That made a
17
+ * symlinked `services/<id>/` or `features/<id>/` vanish from every enumeration
18
+ * at once — `list`, `validate --all`, the fleet gate — with no diagnostic
19
+ * anywhere, which is the one outcome a shared docs repo cannot survive: the
20
+ * fleet gate reported a service that is not there as fine because it never saw
21
+ * it. Composing a docs repo out of symlinks (a worktree, a submodule, one
22
+ * service's directory shared between two checkouts) is legitimate, so they are
23
+ * FOLLOWED rather than refused. A dangling link stats as nothing and is skipped
24
+ * exactly as an absent directory is — it is not a service, and saying so would
25
+ * mean inventing an entry to hang the complaint on.
26
+ */
27
+ export function entryIs(dir, e, want) {
28
+ if (!e.isSymbolicLink())
29
+ return want === "dir" ? e.isDirectory() : e.isFile();
30
+ try {
31
+ const s = statSync(join(dir, e.name));
32
+ return want === "dir" ? s.isDirectory() : s.isFile();
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Markdown files sitting directly in `dir`, sorted, as full paths — the ADR
40
+ * corpus, and the only place that rule is spelled. Symlinks are resolved
41
+ * through `entryIs`, which is the whole reason this is shared: `show` once kept
42
+ * a second copy that tested `Dirent.isFile()` itself — always false for a
43
+ * symlink, whatever it points at — so a docs repo composed of symlinks (the
44
+ * layout `entryIs` above exists to support) had `loam list` and `loam show`
45
+ * reporting different ADR counts for the same service. One rule, one answer.
46
+ *
47
+ * The list, not the count, is the primitive: the link check reads these same
48
+ * files, and a corpus that disagreed with the count would grade documents the
49
+ * fleet listing says are not there.
50
+ */
51
+ export async function markdownFiles(dir) {
52
+ if (!existsSync(dir))
53
+ return [];
54
+ const entries = await readdir(dir, { withFileTypes: true });
55
+ return entries
56
+ .filter((e) => e.name.endsWith(".md") && entryIs(dir, e, "file"))
57
+ .map((e) => join(dir, e.name))
58
+ .sort();
59
+ }
60
+ /** How many markdown files sit directly in `dir` — the ADR count, over the corpus above. */
61
+ export async function countMarkdown(dir) {
62
+ return (await markdownFiles(dir)).length;
63
+ }
64
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,CAAS,EAAE,IAAoB;IAClE,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE;QAAE,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9E,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;SAChE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;SAC7B,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,OAAO,CAAC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * Directory-entry questions the enumerations share, symlinks followed.\n *\n * Moved here from `repo/repo.ts` when the tree walk arrived: the walk and the\n * feature enumeration must answer \"what IS this entry\" identically, and the\n * package direction is `repo → repo/tree` (never back — `walk.ts` says why),\n * so the shared spelling lives on this side of the edge.\n */\nimport { existsSync, statSync, type Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * What a directory entry IS, following a symlink to find out.\n *\n * `readdir(withFileTypes)` does not follow symlinks: for a symlink `Dirent`\n * every `isDirectory()`/`isFile()` is false, whatever it points at. That made a\n * symlinked `services/<id>/` or `features/<id>/` vanish from every enumeration\n * at once — `list`, `validate --all`, the fleet gate — with no diagnostic\n * anywhere, which is the one outcome a shared docs repo cannot survive: the\n * fleet gate reported a service that is not there as fine because it never saw\n * it. Composing a docs repo out of symlinks (a worktree, a submodule, one\n * service's directory shared between two checkouts) is legitimate, so they are\n * FOLLOWED rather than refused. A dangling link stats as nothing and is skipped\n * exactly as an absent directory is — it is not a service, and saying so would\n * mean inventing an entry to hang the complaint on.\n */\nexport function entryIs(dir: string, e: Dirent, want: \"dir\" | \"file\"): boolean {\n if (!e.isSymbolicLink()) return want === \"dir\" ? e.isDirectory() : e.isFile();\n try {\n const s = statSync(join(dir, e.name));\n return want === \"dir\" ? s.isDirectory() : s.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Markdown files sitting directly in `dir`, sorted, as full paths — the ADR\n * corpus, and the only place that rule is spelled. Symlinks are resolved\n * through `entryIs`, which is the whole reason this is shared: `show` once kept\n * a second copy that tested `Dirent.isFile()` itself — always false for a\n * symlink, whatever it points at — so a docs repo composed of symlinks (the\n * layout `entryIs` above exists to support) had `loam list` and `loam show`\n * reporting different ADR counts for the same service. One rule, one answer.\n *\n * The list, not the count, is the primitive: the link check reads these same\n * files, and a corpus that disagreed with the count would grade documents the\n * fleet listing says are not there.\n */\nexport async function markdownFiles(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.name.endsWith(\".md\") && entryIs(dir, e, \"file\"))\n .map((e) => join(dir, e.name))\n .sort();\n}\n\n/** How many markdown files sit directly in `dir` — the ADR count, over the corpus above. */\nexport async function countMarkdown(dir: string): Promise<number> {\n return (await markdownFiles(dir)).length;\n}\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The subsystem marker: `subsystem.yaml`, the one file that makes a directory
3
+ * under `services/` a GROUP rather than a service.
4
+ *
5
+ * Presence classifies; content is metadata. The walk decides what a directory
6
+ * IS from the marker's existence alone, so a marker that cannot be read still
7
+ * classifies its directory as a subsystem — anything else would let a corrupt
8
+ * byte reinterpret a whole subtree as a service and swallow the fleet beneath
9
+ * it, the exact silent-shrink the three-way classification exists to refuse.
10
+ * An unreadable marker is reported as exactly one `subsystem.invalid` finding
11
+ * instead (`walk.ts` emits it from the `invalid` field here).
12
+ *
13
+ * The defensive-read shape is `core/permissions/permissions.ts`'s: every wrong
14
+ * shape is an `invalid` sentence naming what is wrong, never a throw — this is
15
+ * a read model, and a marker somebody hand-edited badly must not take down the
16
+ * enumeration that would report it.
17
+ */
18
+ import { readFile } from "node:fs/promises";
19
+ import { parse as parseYaml } from "yaml";
20
+ import { isRecord } from "../../kernel/records.js";
21
+ /** The marker's filename — spelled here and nowhere else. */
22
+ export const SUBSYSTEM_MARKER = "subsystem.yaml";
23
+ /**
24
+ * Read one marker. An EMPTY file is a valid marker with no metadata — the
25
+ * marker's job is done by existing, and `title`/`description`/`owner` are all
26
+ * optional. A `members` key is invalid on purpose: placement is recorded
27
+ * exactly once, in the directory itself, and a membership list would be a
28
+ * second copy that merges, drifts and lies (SCHEMA.md's marker-not-manifest
29
+ * rule).
30
+ */
31
+ export async function readSubsystemMarker(path) {
32
+ let doc;
33
+ try {
34
+ doc = parseYaml(await readFile(path, "utf8"));
35
+ }
36
+ catch (e) {
37
+ return { invalid: e instanceof Error ? e.message : String(e) };
38
+ }
39
+ if (doc === null || doc === undefined)
40
+ return {};
41
+ if (!isRecord(doc))
42
+ return { invalid: "the document is not a mapping" };
43
+ if (doc["members"] !== undefined) {
44
+ return {
45
+ invalid: "it declares `members` — a subsystem's members are the service directories inside it, " +
46
+ "recorded exactly once, in the directory itself; a list here would be a second copy that drifts",
47
+ };
48
+ }
49
+ for (const field of ["title", "description", "owner"]) {
50
+ const value = doc[field];
51
+ if (value !== undefined && typeof value !== "string") {
52
+ return { invalid: `\`${field}\` is not a string` };
53
+ }
54
+ }
55
+ return {
56
+ ...(typeof doc["title"] === "string" ? { title: doc["title"] } : {}),
57
+ ...(typeof doc["description"] === "string" ? { description: doc["description"] } : {}),
58
+ ...(typeof doc["owner"] === "string" ? { owner: doc["owner"] } : {}),
59
+ };
60
+ }
61
+ //# sourceMappingURL=marker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marker.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAUjD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY;IACpD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;IACxE,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO;YACL,OAAO,EACL,uFAAuF;gBACvF,gGAAgG;SACnG,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAU,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,oBAAoB,EAAE,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO;QACL,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,OAAO,GAAG,CAAC,aAAa,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The subsystem marker: `subsystem.yaml`, the one file that makes a directory\n * under `services/` a GROUP rather than a service.\n *\n * Presence classifies; content is metadata. The walk decides what a directory\n * IS from the marker's existence alone, so a marker that cannot be read still\n * classifies its directory as a subsystem — anything else would let a corrupt\n * byte reinterpret a whole subtree as a service and swallow the fleet beneath\n * it, the exact silent-shrink the three-way classification exists to refuse.\n * An unreadable marker is reported as exactly one `subsystem.invalid` finding\n * instead (`walk.ts` emits it from the `invalid` field here).\n *\n * The defensive-read shape is `core/permissions/permissions.ts`'s: every wrong\n * shape is an `invalid` sentence naming what is wrong, never a throw — this is\n * a read model, and a marker somebody hand-edited badly must not take down the\n * enumeration that would report it.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseYaml } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\n\n/** The marker's filename — spelled here and nowhere else. */\nexport const SUBSYSTEM_MARKER = \"subsystem.yaml\";\n\nexport interface SubsystemMeta {\n title?: string;\n description?: string;\n owner?: string;\n /** Why the file could not be read as subsystem metadata, when it could not. */\n invalid?: string;\n}\n\n/**\n * Read one marker. An EMPTY file is a valid marker with no metadata — the\n * marker's job is done by existing, and `title`/`description`/`owner` are all\n * optional. A `members` key is invalid on purpose: placement is recorded\n * exactly once, in the directory itself, and a membership list would be a\n * second copy that merges, drifts and lies (SCHEMA.md's marker-not-manifest\n * rule).\n */\nexport async function readSubsystemMarker(path: string): Promise<SubsystemMeta> {\n let doc: unknown;\n try {\n doc = parseYaml(await readFile(path, \"utf8\"));\n } catch (e) {\n return { invalid: e instanceof Error ? e.message : String(e) };\n }\n if (doc === null || doc === undefined) return {};\n if (!isRecord(doc)) return { invalid: \"the document is not a mapping\" };\n if (doc[\"members\"] !== undefined) {\n return {\n invalid:\n \"it declares `members` — a subsystem's members are the service directories inside it, \" +\n \"recorded exactly once, in the directory itself; a list here would be a second copy that drifts\",\n };\n }\n for (const field of [\"title\", \"description\", \"owner\"] as const) {\n const value = doc[field];\n if (value !== undefined && typeof value !== \"string\") {\n return { invalid: `\\`${field}\\` is not a string` };\n }\n }\n return {\n ...(typeof doc[\"title\"] === \"string\" ? { title: doc[\"title\"] } : {}),\n ...(typeof doc[\"description\"] === \"string\" ? { description: doc[\"description\"] } : {}),\n ...(typeof doc[\"owner\"] === \"string\" ? { owner: doc[\"owner\"] } : {}),\n };\n}\n"]}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * The generated views file: `architecture/subsystems.likec4`, one LikeC4 view
3
+ * per subsystem enumerating the services beneath it. GENERATED, never
4
+ * authored — authored bytes stay authored (archive's verbatim-splice promise),
5
+ * so the tree is mirrored into a file of its own that `loam subsystem sync`
6
+ * owns outright, and staleness against the tree is a byte compare
7
+ * (`subsystem.views-stale` in `validate --all`), never a parse: a spike
8
+ * confirmed a views-only document does NOT parse standalone (its `include`
9
+ * lines reference elements the landscape defines), so nothing in loam ever
10
+ * loads this file (and rule 26 does not change that: this file is GENERATED and
11
+ * holds STATIC views, whose contents loam may not read in any document) — only
12
+ * the LikeC4 renderer, which merges the whole `architecture/` project per
13
+ * likec4.config.json, resolves it.
14
+ *
15
+ * The output is deterministic and line-oriented, and both halves are the
16
+ * contract: subsystems sorted by path, members sorted by id, ONE `include`
17
+ * per line — so two concurrent moves into different groups touch different
18
+ * lines and git merges the file without intervention, exactly as the
19
+ * service-grouped landscape splice already achieves. Byte-reproducible on any
20
+ * machine: no timestamps, no absolute paths, no readdir-order dependence (the
21
+ * walk sorts, and every list is re-sorted here). Plain lexicographic sorts on
22
+ * purpose — the ids' own grammar is ASCII, an INVALID name is still ordered
23
+ * deterministically, and `compareIds`' numeric tokenizing lives in `repo/`,
24
+ * which this package must not import (`walk.ts` says why the edge only points
25
+ * the other way).
26
+ *
27
+ * Membership is TRANSITIVE: a subsystem's view includes every service beneath
28
+ * it at any depth, so a parent's view is the whole group's map rather than an
29
+ * empty frame around its children. An empty view body is legal LikeC4 (same
30
+ * spike), so an empty subsystem renders as one — `subsystem new` must be
31
+ * usable before anything moves in.
32
+ *
33
+ * Include lines name the landscape ELEMENT id, resolved through the same
34
+ * element→service join `validate` uses (`serviceResolver`: explicit
35
+ * `metadata { service }` binding first, then a title naming a real service) —
36
+ * because element ids in real landscapes are not service ids, and a view of
37
+ * raw service ids would silently render empty for every such fleet. A member
38
+ * with no landscape element is omitted from the includes: the omission is
39
+ * already an error (`landscape.service-unmodelled`), and rendering only what
40
+ * the committed landscape resolves keeps the output a function of committed
41
+ * bytes alone.
42
+ */
43
+ import {} from "../../c4/likec4.js";
44
+ import { serviceResolver } from "../../c4/resolve/service.js";
45
+ import { servicesUnder } from "./find.js";
46
+ /**
47
+ * The header every generated file opens with — stable bytes, part of the
48
+ * byte-compare contract, naming the regenerator so a reader who found the
49
+ * file first knows what owns it.
50
+ */
51
+ const HEADER = "// generated by loam — the subsystem tree under services/, one view per subsystem.\n" +
52
+ "// Do not edit: `loam subsystem sync` regenerates this file from the directories,\n" +
53
+ "// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\n";
54
+ /**
55
+ * Render the views file for a walked tree, or null when the tree has no
56
+ * subsystems — the file must then be ABSENT, so a flat fleet never carries a
57
+ * generated artifact that says nothing.
58
+ */
59
+ export function renderSubsystemViews(tree, elements) {
60
+ if (tree.subsystems.length === 0)
61
+ return null;
62
+ const elementOf = elementByService(tree, elements);
63
+ const lines = [HEADER, "views {"];
64
+ const subsystems = [...tree.subsystems].sort((a, b) => (pathKey(a) < pathKey(b) ? -1 : 1));
65
+ for (const [i, sub] of subsystems.entries()) {
66
+ if (i > 0)
67
+ lines.push("");
68
+ lines.push(` // services/${pathKey(sub)}`);
69
+ lines.push(` view ${subsystemViewId(sub)} {`);
70
+ for (const member of membersOf(tree, sub)) {
71
+ const element = elementOf.get(member.id);
72
+ if (element !== undefined)
73
+ lines.push(` include ${element}`);
74
+ }
75
+ lines.push(" }");
76
+ }
77
+ lines.push("}");
78
+ return lines.join("\n") + "\n";
79
+ }
80
+ /**
81
+ * The landscape element standing for each service id: for every element,
82
+ * resolve it through the shared join, and let the SHALLOWEST element win
83
+ * (document order breaking ties). Shallowest, because `serviceResolver`
84
+ * resolves a service's own containers to the service too, and a view should
85
+ * include the service-level box, not whichever nested container happened to
86
+ * be declared first.
87
+ */
88
+ function elementByService(tree, elements) {
89
+ const known = new Set(tree.services.map((s) => s.id));
90
+ const resolve = serviceResolver(elements, known);
91
+ const depth = (id) => id.split(".").length;
92
+ const out = new Map();
93
+ for (const e of elements) {
94
+ const service = resolve(e.id);
95
+ if (!known.has(service))
96
+ continue;
97
+ const held = out.get(service);
98
+ if (held === undefined || depth(e.id) < depth(held))
99
+ out.set(service, e.id);
100
+ }
101
+ return out;
102
+ }
103
+ /** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */
104
+ function membersOf(tree, sub) {
105
+ return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));
106
+ }
107
+ /** The sort key and the comment spelling: the subsystem's path from services/, joined. */
108
+ function pathKey(sub) {
109
+ return sub.path.join("/");
110
+ }
111
+ /**
112
+ * A LikeC4 identifier for the view, derived from the subsystem's path so
113
+ * nesting reads in the name — and INJECTIVE over every path the name grammar
114
+ * admits, because the renderer refuses the whole `architecture/` project over
115
+ * a duplicate view id. The naive folding (every non-identifier byte and the
116
+ * join separator alike becoming `_`) collided three DISTINCT healthy names
117
+ * onto one identifier — `a_b`, and `b` under `a`, both rendered
118
+ * `subsystem_a_b` — so `validate --all` was green while the renderer refused
119
+ * the fleet map; `subsystem.name-collision` never fires because the flat
120
+ * namespace really is intact. Hence the encoding: an identifier byte
121
+ * (`[A-Za-z0-9]`) passes through, any other byte becomes `_` + two lowercase
122
+ * hex digits (`.` → `_2e`, `-` → `_2d`, `_` itself → `_5f` — the underscore
123
+ * must be escaped precisely because it is BOTH legal in a name and the
124
+ * separator), and segments join on `__`. Underscore runs then decode
125
+ * uniquely — one is an escape, two a separator, three a separator followed
126
+ * by an escape — so no two distinct legal paths share an identifier. A name
127
+ * `subsystem.name-invalid` already flagged may hold a code point above 0xff
128
+ * whose longer hex could in principle re-collide; it is deterministic still,
129
+ * and that directory is already an error finding by name.
130
+ *
131
+ * EXPORTED, with its prefix, because `subsystem.view-id-collision` has to ask
132
+ * the generator what it mints rather than re-deriving it. A check carrying its
133
+ * own copy of the escaping above would go quiet the day the encoding changed,
134
+ * and going quiet is the failure this whole family exists to prevent.
135
+ */
136
+ export const SUBSYSTEM_VIEW_PREFIX = "subsystem_";
137
+ export function subsystemViewId(sub) {
138
+ const segment = (name) => [...name].map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0).toString(16).padStart(2, "0")}`)).join("");
139
+ return `subsystem_${sub.path.map(segment).join("__")}`;
140
+ }
141
+ /**
142
+ * Does the file on disk already SAY what `renderSubsystemViews` renders?
143
+ *
144
+ * The generator above only ever emits LF. `core.autocrlf=true` is Git for
145
+ * Windows' installer default and a docs repo ships no `.gitattributes`, so an
146
+ * ordinary Windows clone hands the file back with CRLF and not one fact
147
+ * changed. Four call sites asked this question with a byte equality and all
148
+ * four answered "stale" on such a clone: `loam validate` reported an error,
149
+ * `loam subsystem sync` reported `updated` and rewrote with LF, git then showed
150
+ * the file permanently modified, and the next checkout restored the CRLF. That
151
+ * is a loop rather than a diagnosis, which is why the comparison lives here
152
+ * now — the module that mints the bytes is the one that owes an answer about
153
+ * them, and three near-copies of this rule would go stale one at a time.
154
+ *
155
+ * Still never a parse: a views-only document does not parse standalone, and no
156
+ * check anywhere reads this file's content. The narrowing is from BYTES to
157
+ * CONTENT, which is what `subsystem.views-stale`'s own message ("does not match
158
+ * the tree") already claims. The symmetric cost is accepted and is the one
159
+ * `core/verify/pins/pin.ts` documents for `pinnedDigest`: a pure line-ending
160
+ * change is invisible, correctly, because it is not a change to what the file
161
+ * says.
162
+ *
163
+ * `null` is carried through as itself rather than collapsing into `""`: absent,
164
+ * must-be-absent and present-but-different are three distinct states with three
165
+ * distinct messages, and a fleet whose views file does not exist is not a fleet
166
+ * whose views file is blank.
167
+ */
168
+ export function viewsAgree(actual, expected) {
169
+ const lf = (text) => (text === null ? null : text.replace(/\r\n|\r/g, "\n"));
170
+ return lf(actual) === lf(expected);
171
+ }
172
+ //# sourceMappingURL=views.js.map