@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9

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 (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -1 +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"]}
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;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAc,EAAE,SAAiB;IAC5E,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,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/**\n * The near-miss tail a refusal appends to its \"no such name\" sentence, over\n * whichever pool the verb resolves against: the close names when there are\n * any, else `otherwise` — the verb's own pointer at what exists, or \"\" for\n * none. Eight refusals across `subsystem`, `adopt --subsystem` and\n * `list --subsystem` end this way, and each spelled the conditional itself;\n * the lead sentence and the fallback stay theirs, because those are what\n * differ.\n */\nexport function closeNamesHint(name: string, pool: string[], otherwise: string): string {\n const close = closeIds(name, pool);\n return close.length > 0 ? ` Close names: ${close.join(\", \")}.` : otherwise;\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"]}
@@ -6,7 +6,7 @@
6
6
  * package direction is `repo → repo/tree` (never back — `walk.ts` says why),
7
7
  * so the shared spelling lives on this side of the edge.
8
8
  */
9
- import { existsSync, statSync } from "node:fs";
9
+ import { existsSync, realpathSync, statSync } from "node:fs";
10
10
  import { readdir } from "node:fs/promises";
11
11
  import { join } from "node:path";
12
12
  /**
@@ -61,4 +61,30 @@ export async function markdownFiles(dir) {
61
61
  export async function countMarkdown(dir) {
62
62
  return (await markdownFiles(dir)).length;
63
63
  }
64
+ /**
65
+ * Mark a directory as descended-into; false when it (or another name for it)
66
+ * already was. The symlink-cycle guard every walk that follows links through
67
+ * `entryIs` carries — the service tree (`./walk.ts`), the capability tree, the
68
+ * glossary, the generated-gherkin scan — and for one reason: a followed cycle
69
+ * without a visited set recurses until the stack ends. A path realpath cannot
70
+ * resolve is one whose children cannot be read either, so it is never entered.
71
+ */
72
+ export function markDescended(dir, visited) {
73
+ let real;
74
+ try {
75
+ real = realpathSync(dir);
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ if (visited.has(real))
81
+ return false;
82
+ visited.add(real);
83
+ return true;
84
+ }
85
+ /** Sorted non-dot subdirectory names, links followed — a tree walk's deterministic child order. */
86
+ export async function subdirNames(dir) {
87
+ const entries = await readdir(dir, { withFileTypes: true });
88
+ return entries.filter((e) => !e.name.startsWith(".") && entryIs(dir, e, "dir")).map((e) => e.name).sort();
89
+ }
64
90
  //# sourceMappingURL=fs.js.map
@@ -1 +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"]}
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;AAC1E,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;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,OAAoB;IAC7D,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5G,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, realpathSync, 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\n/**\n * Mark a directory as descended-into; false when it (or another name for it)\n * already was. The symlink-cycle guard every walk that follows links through\n * `entryIs` carries — the service tree (`./walk.ts`), the capability tree, the\n * glossary, the generated-gherkin scan — and for one reason: a followed cycle\n * without a visited set recurses until the stack ends. A path realpath cannot\n * resolve is one whose children cannot be read either, so it is never entered.\n */\nexport function markDescended(dir: string, visited: Set<string>): boolean {\n let real: string;\n try {\n real = realpathSync(dir);\n } catch {\n return false;\n }\n if (visited.has(real)) return false;\n visited.add(real);\n return true;\n}\n\n/** Sorted non-dot subdirectory names, links followed — a tree walk's deterministic child order. */\nexport async function subdirNames(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => !e.name.startsWith(\".\") && entryIs(dir, e, \"dir\")).map((e) => e.name).sort();\n}\n"]}
@@ -52,20 +52,59 @@ export function renderContext(tree, elements) {
52
52
  export function viewBody(ctx, sub) {
53
53
  const members = memberElements(ctx, sub);
54
54
  if (members.length === 0)
55
- return { lines: [], disclosure: null };
56
- if (members.every((id) => !id.includes(".")))
57
- return { lines: groupedBody(ctx, sub, " "), disclosure: null };
58
- const lines = [...ownedAncestors(ctx, sub), ...members].map((id) => ` include ${id}`);
59
- if (owningAncestor(ctx, sub) !== null)
60
- return { lines, disclosure: null };
55
+ return { lines: [], disclosure: null, of: null };
56
+ if (members.every((id) => !id.includes("."))) {
57
+ return { lines: [...groupedBody(ctx, sub, " "), ...boundaryLines(members)], disclosure: null, of: null };
58
+ }
59
+ const includes = [...ownedAncestors(ctx, sub), ...members].map((id) => ` include ${id}`);
60
+ const lines = [...includes, ...boundaryLines(members)];
61
+ const owner = owningAncestor(ctx, sub);
62
+ if (owner !== null)
63
+ return { lines, disclosure: null, of: owner };
61
64
  const shared = sharedAncestor(ctx, members);
62
65
  return {
63
66
  lines,
67
+ of: null,
64
68
  disclosure: shared === null
65
69
  ? "model: no boundary — these members do not share a containing element."
66
70
  : `model: no boundary — ${shared} also holds services outside this subsystem.`,
67
71
  };
68
72
  }
73
+ /**
74
+ * What this group TOUCHES: for each member, every edge it has in either
75
+ * direction, so the view answers "what does payments talk to" and not only
76
+ * "who is in payments".
77
+ *
78
+ * **The measured trap, and it is why these lines are appended by the caller of
79
+ * `groupedBody` rather than inside it:** placed INSIDE the `group { }` block of
80
+ * the flat-map body shape, this exact predicate parses and validates with ZERO
81
+ * errors and draws a false picture. Measured at likec4 1.59.2 on a flat map of
82
+ * `alpha, beta` grouped and `outsider, store` outside it: at view level the two
83
+ * neighbours are drawn with no parent, correctly outside the box; moved inside
84
+ * the `group { }`, both are drawn as CHILDREN of that group — the diagram then
85
+ * claims `outsider` and `store` are part of the subsystem. A maintainer tidying
86
+ * the body by moving these lines next to the includes they refer to would ship
87
+ * that, and nothing anywhere would report it. They belong at view level, always.
88
+ *
89
+ * The WILDCARD is correct here, and that is not an inconsistency with the
90
+ * per-service views, which measure it as a defect (`./service/peers.ts`): at
91
+ * subsystem altitude a counterpart collapsing to its own group box is exactly
92
+ * the sentence "payments touches identity", and — once those group boxes are
93
+ * themselves views' subjects (`of <ancestor>`) — it is a click-through into
94
+ * that subsystem. At service altitude the same collapse is a lie about who
95
+ * calls whom.
96
+ *
97
+ * MEMBERS only, never `ownedAncestors`: `-> *` on an authored container pulls
98
+ * in every edge any descendant of it has, and in the `// model: no boundary`
99
+ * shape that container also holds services outside this subsystem — the view
100
+ * would draw traffic that is nobody-here's. Measured at 58 services in 11
101
+ * groups: 13-15 nodes and 31-39 edges per subsystem view, of which 11-12 nodes
102
+ * carry drill-down — the members into their own service views, the neighbouring
103
+ * group boxes into THEIR subsystem views.
104
+ */
105
+ function boundaryLines(members) {
106
+ return ["", ...members.map((id) => ` include ${id} -> *, * -> ${id}`)];
107
+ }
69
108
  /**
70
109
  * The flat-model body: this subsystem as a box, its child subsystems as boxes
71
110
  * inside it, its own services as leaves. Recursive, and the recursion is the
@@ -1 +1 @@
1
- {"version":3,"file":"body.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/body.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What one subsystem's generated view SHOWS: which boxes it draws around the\n * services beneath a directory, and the one thing it may say about the map.\n *\n * Split from `../views.ts` when that file crossed the 400-line limit: the file\n * render (header, view ids, staleness comparison) and the body render (the\n * boxes) are two subjects, and the seam is exactly where the second one\n * consults the LANDSCAPE. Everything here reads element ids and nothing else —\n * no view is ever parsed (docs/DESIGN.md rule 26), and the generated file\n * stays a pure function of the tree plus the committed map.\n */\nimport { type Elem } from \"../../../c4/likec4.js\";\nimport { serviceResolver } from \"../../../c4/resolve/service.js\";\nimport { servicesUnder, withinSubsystem } from \"../find.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"../walk.js\";\nimport { subsystemLabel } from \"./text.js\";\n\n/**\n * Everything a view's body needs from the landscape, resolved ONCE: the\n * element standing for each service, every element id the map declares, and\n * whether an element is a service's own box. A record rather than four\n * threaded parameters because the body render recurses down the subsystem\n * subtree and would otherwise sit on the parameter limit at every hop\n * (`docs/CODE-STYLE.md`'s own prescription).\n */\nexport interface RenderContext {\n tree: FleetTree;\n elementOf: Map<string, string>;\n elementIds: ReadonlySet<string>;\n isServiceElement: (id: string) => boolean;\n}\n\nexport function renderContext(tree: FleetTree, elements: Elem[]): RenderContext {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const resolve = serviceResolver(elements, known);\n return {\n tree,\n elementOf: elementByService(tree, elements, resolve),\n elementIds: new Set(elements.map((e) => e.id)),\n isServiceElement: (id: string): boolean => known.has(resolve(id)),\n };\n}\n\n/** A view's body lines, and the one comment the render may write above it. */\nexport interface ViewBody {\n lines: string[];\n disclosure: string | null;\n}\n\n/**\n * Three shapes, and which one applies is a fact about the landscape rather\n * than a setting:\n *\n * 1. **The model draws this group.** Some ancestor element holds exactly this\n * subsystem's members and nothing else, so the view includes it beside the\n * leaves and the renderer nests them inside the box the AUTHOR drew, with\n * the author's own title. Measured: including the ancestor alone renders\n * one empty box and the leaves alone render flat — both lines are needed.\n * 2. **The model is flat.** No member element is nested at all, so there is\n * no authored box to agree with, and the view wraps the members in a\n * `group '<title>'` of its own — a real named box in the renderer, derived\n * from the DIRECTORY the services already sit in, mirroring the subtree so\n * a parent subsystem shows its children as boxes rather than as one pile.\n * Nothing is invented: the grouping is the tree, and the tree is what this\n * file has always mirrored.\n * 3. **The model nests them, differently.** The members ARE nested but no\n * ancestor owns exactly them — the directory tree and the map disagree\n * about this group. loam draws no box (a synthetic one would silently\n * replace the author's boundary with the folder's, and the picture would\n * look the same whether or not the two agree) and writes ONE comment line\n * saying so, outside the view body. That comment is the whole of loam's\n * answer to the drift: it grades nothing, it fails nothing, and it appears\n * in the diff of the very commit that caused it — placement stays a fact\n * nothing branches on.\n */\nexport function viewBody(ctx: RenderContext, sub: SubsystemEntry): ViewBody {\n const members = memberElements(ctx, sub);\n if (members.length === 0) return { lines: [], disclosure: null };\n if (members.every((id) => !id.includes(\".\"))) return { lines: groupedBody(ctx, sub, \" \"), disclosure: null };\n const lines = [...ownedAncestors(ctx, sub), ...members].map((id) => ` include ${id}`);\n if (owningAncestor(ctx, sub) !== null) return { lines, disclosure: null };\n const shared = sharedAncestor(ctx, members);\n return {\n lines,\n disclosure:\n shared === null\n ? \"model: no boundary — these members do not share a containing element.\"\n : `model: no boundary — ${shared} also holds services outside this subsystem.`,\n };\n}\n\n/**\n * The flat-model body: this subsystem as a box, its child subsystems as boxes\n * inside it, its own services as leaves. Recursive, and the recursion is the\n * point — a parent subsystem's view is otherwise one undifferentiated pile of\n * every service beneath it.\n *\n * An empty child renders an empty named box, which is what the tree says: the\n * group exists and holds nothing yet.\n */\nfunction groupedBody(ctx: RenderContext, sub: SubsystemEntry, indent: string): string[] {\n const inner = `${indent} `;\n const out = [`${indent}group '${subsystemLabel(sub)}' {`];\n for (const child of childSubsystems(ctx, sub)) out.push(...groupedBody(ctx, child, inner));\n for (const id of directMemberElements(ctx, sub)) out.push(`${inner}include ${id}`);\n out.push(`${indent}}`);\n return out;\n}\n\n/** The child subsystems one level down, ordered by name. */\nfunction childSubsystems(ctx: RenderContext, sub: SubsystemEntry): SubsystemEntry[] {\n return ctx.tree.subsystems\n .filter((c) => c.path.length === sub.path.length + 1 && withinSubsystem(sub.path, c.path))\n .sort((a, b) => (a.name < b.name ? -1 : 1));\n}\n\n/** The drawn elements of the services filed in THIS directory, not in a child. */\nfunction directMemberElements(ctx: RenderContext, sub: SubsystemEntry): string[] {\n return drawnElements(\n ctx,\n membersOf(ctx.tree, sub).filter((s) => s.subsystem.length === sub.path.length),\n );\n}\n\n/** The drawn elements of every service beneath this subsystem, ordered by service id. */\nfunction memberElements(ctx: RenderContext, sub: SubsystemEntry): string[] {\n return drawnElements(ctx, membersOf(ctx.tree, sub));\n}\n\n/**\n * The element ids these services resolve to. A service the map does not draw\n * is omitted rather than invented: the omission is already an error\n * (`landscape.service-unmodelled`), and rendering only what the committed\n * documents resolve keeps the output a function of committed bytes alone.\n */\nfunction drawnElements(ctx: RenderContext, services: WalkedService[]): string[] {\n return services.map((s) => ctx.elementOf.get(s.id)).filter((id): id is string => id !== undefined);\n}\n\n/**\n * The boxes a nested-model view draws: this subsystem's own owning ancestor\n * when it has one, plus the owning ancestor of every subsystem beneath it — so\n * a parent view keeps its children's boundaries instead of flattening them\n * into one box. Sorted and de-duplicated, because two subsystems resolve to\n * one element only in a tree that already reports a collision.\n */\nfunction ownedAncestors(ctx: RenderContext, sub: SubsystemEntry): string[] {\n const found = new Set<string>();\n for (const candidate of ctx.tree.subsystems) {\n if (!withinSubsystem(sub.path, candidate.path)) continue;\n const owner = owningAncestor(ctx, candidate);\n if (owner !== null) found.add(owner);\n }\n return [...found].sort();\n}\n\n/**\n * The deepest ancestor element that holds exactly this subsystem's members —\n * or null when no element does.\n *\n * \"Exactly\" is the whole guard, and dropping it was measured on loam's own\n * `examples/docs`: the members' longest common ancestor there is a group of\n * five services while the subsystem holds two, so an unguarded include would\n * generate a box labelled for the group with two of its five children drawn\n * inside it — a diagram asserting something false, in a file the reader is\n * told not to edit. Tightness is judged over ELEMENTS rather than over\n * documented services: mid-adoption a group commonly holds systems no\n * `services/<id>/` exists for yet, and counting only the documented ones would\n * draw that same false box for the whole of an adoption.\n *\n * An ancestor that is itself a service's element is refused separately: one\n * service's box containing another's is a picture of nothing.\n */\nfunction owningAncestor(ctx: RenderContext, sub: SubsystemEntry): string | null {\n const members = memberElements(ctx, sub);\n if (members.length === 0) return null;\n const owned = new Set(members);\n for (const candidate of ancestorChain(members)) {\n if (!ctx.elementIds.has(candidate) || ctx.isServiceElement(candidate)) continue;\n if (ownsExactly(ctx, candidate, owned)) return candidate;\n }\n return null;\n}\n\n/** The deepest common ancestor the map declares at all — the disclosure's subject. */\nfunction sharedAncestor(ctx: RenderContext, members: string[]): string | null {\n return ancestorChain(members).find((id) => ctx.elementIds.has(id)) ?? null;\n}\n\n/** The common proper ancestors of every member id, deepest first. */\nfunction ancestorChain(members: string[]): string[] {\n const segments = members.map((id) => id.split(\".\"));\n let common = Math.min(...segments.map((s) => s.length - 1));\n for (let i = 0; i < common; i++) {\n if (!segments.every((s) => s[i] === segments[0]![i])) {\n common = i;\n break;\n }\n }\n const out: string[] = [];\n for (let depth = common; depth > 0; depth--) out.push(segments[0]!.slice(0, depth).join(\".\"));\n return out;\n}\n\n/**\n * Does every element beneath `ancestor` belong to this subsystem — as a\n * member, as one of a member's own containers, or as a container of one?\n */\nfunction ownsExactly(ctx: RenderContext, ancestor: string, members: ReadonlySet<string>): boolean {\n const prefix = `${ancestor}.`;\n for (const id of ctx.elementIds) {\n if (!id.startsWith(prefix)) continue;\n const claimed = [...members].some((m) => id === m || id.startsWith(`${m}.`) || m.startsWith(`${id}.`));\n if (!claimed) return false;\n }\n return true;\n}\n\n/**\n * The landscape element standing for each service id: for every element,\n * resolve it through the shared join, and let the SHALLOWEST element win\n * (document order breaking ties). Shallowest, because `serviceResolver`\n * resolves a service's own containers to the service too, and a view should\n * include the service-level box, not whichever nested container happened to be\n * declared first.\n */\nfunction elementByService(\n tree: FleetTree,\n elements: Elem[],\n resolve: (id: string) => string,\n): Map<string, string> {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const depth = (id: string): number => id.split(\".\").length;\n const out = new Map<string, string>();\n for (const e of elements) {\n const service = resolve(e.id);\n if (!known.has(service)) continue;\n const held = out.get(service);\n if (held === undefined || depth(e.id) < depth(held)) out.set(service, e.id);\n }\n return out;\n}\n\n/** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */\nfunction membersOf(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));\n}\n"]}
1
+ {"version":3,"file":"body.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAa,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAiB3C,MAAM,UAAU,aAAa,CAAC,IAAe,EAAE,QAAgB;IAC7D,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACjD,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC;QACpD,UAAU,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9C,gBAAgB,EAAE,CAAC,EAAU,EAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;KAClE,CAAC;AACJ,CAAC;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAkB,EAAE,GAAmB;IAC9D,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC3E,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC9G,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IAC5F,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAClE,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC5C,OAAO;QACL,KAAK;QACL,EAAE,EAAE,IAAI;QACR,UAAU,EACR,MAAM,KAAK,IAAI;YACb,CAAC,CAAC,uEAAuE;YACzE,CAAC,CAAC,wBAAwB,MAAM,8CAA8C;KACnF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,SAAS,aAAa,CAAC,OAA0B;IAC/C,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,GAAkB,EAAE,GAAmB,EAAE,MAAc;IAC1E,MAAM,KAAK,GAAG,GAAG,MAAM,IAAI,CAAC;IAC5B,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,UAAU,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1D,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3F,KAAK,MAAM,EAAE,IAAI,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,WAAW,EAAE,EAAE,CAAC,CAAC;IACnF,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;IACvB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4DAA4D;AAC5D,SAAS,eAAe,CAAC,GAAkB,EAAE,GAAmB;IAC9D,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU;SACvB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;SACzF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,kFAAkF;AAClF,SAAS,oBAAoB,CAAC,GAAkB,EAAE,GAAmB;IACnE,OAAO,aAAa,CAClB,GAAG,EACH,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAC/E,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAS,cAAc,CAAC,GAAkB,EAAE,GAAmB;IAC7D,OAAO,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,GAAkB,EAAE,QAAyB;IAClE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;AACrG,CAAC;AAED;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,GAAkB,EAAE,GAAmB;IAC7D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC;YAAE,SAAS;QACzD,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,IAAI;YAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,cAAc,CAAC,GAAkB,EAAE,GAAmB;IAC7D,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC/B,KAAK,MAAM,SAAS,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,gBAAgB,CAAC,SAAS,CAAC;YAAE,SAAS;QAChF,IAAI,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sFAAsF;AACtF,SAAS,cAAc,CAAC,GAAkB,EAAE,OAAiB;IAC3D,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC;AAC7E,CAAC;AAED,qEAAqE;AACrE,SAAS,aAAa,CAAC,OAAiB;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,GAAG,CAAC,CAAC;YACX,MAAM;QACR,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,KAAK,GAAG,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE;QAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9F,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,GAAkB,EAAE,QAAgB,EAAE,OAA4B;IACrF,MAAM,MAAM,GAAG,GAAG,QAAQ,GAAG,CAAC;IAC9B,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QACrC,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QACvG,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CACvB,IAAe,EACf,QAAgB,EAChB,OAA+B;IAE/B,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QAClC,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+FAA+F;AAC/F,SAAS,SAAS,CAAC,IAAe,EAAE,GAAmB;IACrD,OAAO,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["/**\n * What one subsystem's generated view SHOWS: which boxes it draws around the\n * services beneath a directory, and the one thing it may say about the map.\n *\n * Split from `../views.ts` when that file crossed the 400-line limit: the file\n * render (header, view ids, staleness comparison) and the body render (the\n * boxes) are two subjects, and the seam is exactly where the second one\n * consults the LANDSCAPE. Everything here reads element ids and nothing else —\n * no view is ever parsed (docs/DESIGN.md rule 26), and the generated file\n * stays a pure function of the tree plus the committed map.\n */\nimport { type Elem } from \"../../../c4/likec4.js\";\nimport { serviceResolver } from \"../../../c4/resolve/service.js\";\nimport { servicesUnder, withinSubsystem } from \"../find.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"../walk.js\";\nimport { subsystemLabel } from \"./text.js\";\n\n/**\n * Everything a view's body needs from the landscape, resolved ONCE: the\n * element standing for each service, every element id the map declares, and\n * whether an element is a service's own box. A record rather than four\n * threaded parameters because the body render recurses down the subsystem\n * subtree and would otherwise sit on the parameter limit at every hop\n * (`docs/CODE-STYLE.md`'s own prescription).\n */\nexport interface RenderContext {\n tree: FleetTree;\n elementOf: Map<string, string>;\n elementIds: ReadonlySet<string>;\n isServiceElement: (id: string) => boolean;\n}\n\nexport function renderContext(tree: FleetTree, elements: Elem[]): RenderContext {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const resolve = serviceResolver(elements, known);\n return {\n tree,\n elementOf: elementByService(tree, elements, resolve),\n elementIds: new Set(elements.map((e) => e.id)),\n isServiceElement: (id: string): boolean => known.has(resolve(id)),\n };\n}\n\n/** A view's body lines, the one comment the render may write above it, and its subject. */\nexport interface ViewBody {\n lines: string[];\n disclosure: string | null;\n /**\n * The element this view is declared `of`, or null when the map holds none\n * that owns exactly this subsystem. It is computed here rather than by the\n * file render because `owningAncestor` below already decides it — the same\n * answer decides which of the three body shapes applies, and two callers\n * deriving it separately could draw the boundary of one element while\n * declaring the view of another.\n */\n of: string | null;\n}\n\n/**\n * Three shapes, and which one applies is a fact about the landscape rather\n * than a setting:\n *\n * 1. **The model draws this group.** Some ancestor element holds exactly this\n * subsystem's members and nothing else, so the view includes it beside the\n * leaves and the renderer nests them inside the box the AUTHOR drew, with\n * the author's own title. Measured: including the ancestor alone renders\n * one empty box and the leaves alone render flat — both lines are needed.\n * 2. **The model is flat.** No member element is nested at all, so there is\n * no authored box to agree with, and the view wraps the members in a\n * `group '<title>'` of its own — a real named box in the renderer, derived\n * from the DIRECTORY the services already sit in, mirroring the subtree so\n * a parent subsystem shows its children as boxes rather than as one pile.\n * Nothing is invented: the grouping is the tree, and the tree is what this\n * file has always mirrored.\n * 3. **The model nests them, differently.** The members ARE nested but no\n * ancestor owns exactly them — the directory tree and the map disagree\n * about this group. loam draws no box (a synthetic one would silently\n * replace the author's boundary with the folder's, and the picture would\n * look the same whether or not the two agree) and writes ONE comment line\n * saying so, outside the view body. That comment is the whole of loam's\n * answer to the drift: it grades nothing, it fails nothing, and it appears\n * in the diff of the very commit that caused it — placement stays a fact\n * nothing branches on.\n */\nexport function viewBody(ctx: RenderContext, sub: SubsystemEntry): ViewBody {\n const members = memberElements(ctx, sub);\n if (members.length === 0) return { lines: [], disclosure: null, of: null };\n if (members.every((id) => !id.includes(\".\"))) {\n return { lines: [...groupedBody(ctx, sub, \" \"), ...boundaryLines(members)], disclosure: null, of: null };\n }\n const includes = [...ownedAncestors(ctx, sub), ...members].map((id) => ` include ${id}`);\n const lines = [...includes, ...boundaryLines(members)];\n const owner = owningAncestor(ctx, sub);\n if (owner !== null) return { lines, disclosure: null, of: owner };\n const shared = sharedAncestor(ctx, members);\n return {\n lines,\n of: null,\n disclosure:\n shared === null\n ? \"model: no boundary — these members do not share a containing element.\"\n : `model: no boundary — ${shared} also holds services outside this subsystem.`,\n };\n}\n\n/**\n * What this group TOUCHES: for each member, every edge it has in either\n * direction, so the view answers \"what does payments talk to\" and not only\n * \"who is in payments\".\n *\n * **The measured trap, and it is why these lines are appended by the caller of\n * `groupedBody` rather than inside it:** placed INSIDE the `group { }` block of\n * the flat-map body shape, this exact predicate parses and validates with ZERO\n * errors and draws a false picture. Measured at likec4 1.59.2 on a flat map of\n * `alpha, beta` grouped and `outsider, store` outside it: at view level the two\n * neighbours are drawn with no parent, correctly outside the box; moved inside\n * the `group { }`, both are drawn as CHILDREN of that group — the diagram then\n * claims `outsider` and `store` are part of the subsystem. A maintainer tidying\n * the body by moving these lines next to the includes they refer to would ship\n * that, and nothing anywhere would report it. They belong at view level, always.\n *\n * The WILDCARD is correct here, and that is not an inconsistency with the\n * per-service views, which measure it as a defect (`./service/peers.ts`): at\n * subsystem altitude a counterpart collapsing to its own group box is exactly\n * the sentence \"payments touches identity\", and — once those group boxes are\n * themselves views' subjects (`of <ancestor>`) — it is a click-through into\n * that subsystem. At service altitude the same collapse is a lie about who\n * calls whom.\n *\n * MEMBERS only, never `ownedAncestors`: `-> *` on an authored container pulls\n * in every edge any descendant of it has, and in the `// model: no boundary`\n * shape that container also holds services outside this subsystem — the view\n * would draw traffic that is nobody-here's. Measured at 58 services in 11\n * groups: 13-15 nodes and 31-39 edges per subsystem view, of which 11-12 nodes\n * carry drill-down — the members into their own service views, the neighbouring\n * group boxes into THEIR subsystem views.\n */\nfunction boundaryLines(members: readonly string[]): string[] {\n return [\"\", ...members.map((id) => ` include ${id} -> *, * -> ${id}`)];\n}\n\n/**\n * The flat-model body: this subsystem as a box, its child subsystems as boxes\n * inside it, its own services as leaves. Recursive, and the recursion is the\n * point — a parent subsystem's view is otherwise one undifferentiated pile of\n * every service beneath it.\n *\n * An empty child renders an empty named box, which is what the tree says: the\n * group exists and holds nothing yet.\n */\nfunction groupedBody(ctx: RenderContext, sub: SubsystemEntry, indent: string): string[] {\n const inner = `${indent} `;\n const out = [`${indent}group '${subsystemLabel(sub)}' {`];\n for (const child of childSubsystems(ctx, sub)) out.push(...groupedBody(ctx, child, inner));\n for (const id of directMemberElements(ctx, sub)) out.push(`${inner}include ${id}`);\n out.push(`${indent}}`);\n return out;\n}\n\n/** The child subsystems one level down, ordered by name. */\nfunction childSubsystems(ctx: RenderContext, sub: SubsystemEntry): SubsystemEntry[] {\n return ctx.tree.subsystems\n .filter((c) => c.path.length === sub.path.length + 1 && withinSubsystem(sub.path, c.path))\n .sort((a, b) => (a.name < b.name ? -1 : 1));\n}\n\n/** The drawn elements of the services filed in THIS directory, not in a child. */\nfunction directMemberElements(ctx: RenderContext, sub: SubsystemEntry): string[] {\n return drawnElements(\n ctx,\n membersOf(ctx.tree, sub).filter((s) => s.subsystem.length === sub.path.length),\n );\n}\n\n/** The drawn elements of every service beneath this subsystem, ordered by service id. */\nfunction memberElements(ctx: RenderContext, sub: SubsystemEntry): string[] {\n return drawnElements(ctx, membersOf(ctx.tree, sub));\n}\n\n/**\n * The element ids these services resolve to. A service the map does not draw\n * is omitted rather than invented: the omission is already an error\n * (`landscape.service-unmodelled`), and rendering only what the committed\n * documents resolve keeps the output a function of committed bytes alone.\n */\nfunction drawnElements(ctx: RenderContext, services: WalkedService[]): string[] {\n return services.map((s) => ctx.elementOf.get(s.id)).filter((id): id is string => id !== undefined);\n}\n\n/**\n * The boxes a nested-model view draws: this subsystem's own owning ancestor\n * when it has one, plus the owning ancestor of every subsystem beneath it — so\n * a parent view keeps its children's boundaries instead of flattening them\n * into one box. Sorted and de-duplicated, because two subsystems resolve to\n * one element only in a tree that already reports a collision.\n */\nfunction ownedAncestors(ctx: RenderContext, sub: SubsystemEntry): string[] {\n const found = new Set<string>();\n for (const candidate of ctx.tree.subsystems) {\n if (!withinSubsystem(sub.path, candidate.path)) continue;\n const owner = owningAncestor(ctx, candidate);\n if (owner !== null) found.add(owner);\n }\n return [...found].sort();\n}\n\n/**\n * The deepest ancestor element that holds exactly this subsystem's members —\n * or null when no element does.\n *\n * \"Exactly\" is the whole guard, and dropping it was measured on loam's own\n * `examples/docs`: the members' longest common ancestor there is a group of\n * five services while the subsystem holds two, so an unguarded include would\n * generate a box labelled for the group with two of its five children drawn\n * inside it — a diagram asserting something false, in a file the reader is\n * told not to edit. Tightness is judged over ELEMENTS rather than over\n * documented services: mid-adoption a group commonly holds systems no\n * `services/<id>/` exists for yet, and counting only the documented ones would\n * draw that same false box for the whole of an adoption.\n *\n * An ancestor that is itself a service's element is refused separately: one\n * service's box containing another's is a picture of nothing.\n */\nfunction owningAncestor(ctx: RenderContext, sub: SubsystemEntry): string | null {\n const members = memberElements(ctx, sub);\n if (members.length === 0) return null;\n const owned = new Set(members);\n for (const candidate of ancestorChain(members)) {\n if (!ctx.elementIds.has(candidate) || ctx.isServiceElement(candidate)) continue;\n if (ownsExactly(ctx, candidate, owned)) return candidate;\n }\n return null;\n}\n\n/** The deepest common ancestor the map declares at all — the disclosure's subject. */\nfunction sharedAncestor(ctx: RenderContext, members: string[]): string | null {\n return ancestorChain(members).find((id) => ctx.elementIds.has(id)) ?? null;\n}\n\n/** The common proper ancestors of every member id, deepest first. */\nfunction ancestorChain(members: string[]): string[] {\n const segments = members.map((id) => id.split(\".\"));\n let common = Math.min(...segments.map((s) => s.length - 1));\n for (let i = 0; i < common; i++) {\n if (!segments.every((s) => s[i] === segments[0]![i])) {\n common = i;\n break;\n }\n }\n const out: string[] = [];\n for (let depth = common; depth > 0; depth--) out.push(segments[0]!.slice(0, depth).join(\".\"));\n return out;\n}\n\n/**\n * Does every element beneath `ancestor` belong to this subsystem — as a\n * member, as one of a member's own containers, or as a container of one?\n */\nfunction ownsExactly(ctx: RenderContext, ancestor: string, members: ReadonlySet<string>): boolean {\n const prefix = `${ancestor}.`;\n for (const id of ctx.elementIds) {\n if (!id.startsWith(prefix)) continue;\n const claimed = [...members].some((m) => id === m || id.startsWith(`${m}.`) || m.startsWith(`${id}.`));\n if (!claimed) return false;\n }\n return true;\n}\n\n/**\n * The landscape element standing for each service id: for every element,\n * resolve it through the shared join, and let the SHALLOWEST element win\n * (document order breaking ties). Shallowest, because `serviceResolver`\n * resolves a service's own containers to the service too, and a view should\n * include the service-level box, not whichever nested container happened to be\n * declared first.\n */\nfunction elementByService(\n tree: FleetTree,\n elements: Elem[],\n resolve: (id: string) => string,\n): Map<string, string> {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const depth = (id: string): number => id.split(\".\").length;\n const out = new Map<string, string>();\n for (const e of elements) {\n const service = resolve(e.id);\n if (!known.has(service)) continue;\n const held = out.get(service);\n if (held === undefined || depth(e.id) < depth(held)) out.set(service, e.id);\n }\n return out;\n}\n\n/** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */\nfunction membersOf(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));\n}\n"]}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The one line that says a file under `architecture/` is LOAM'S — the opening
3
+ * comment both generated views files carry, and the predicate that reads it
4
+ * back off a file on disk.
5
+ *
6
+ * WHY A MARK EXISTS AT ALL. `architecture/subsystems.likec4` and
7
+ * `architecture/services.likec4` are filenames loam RESERVES inside a directory
8
+ * the team owns, and `loam subsystem sync` rewrites them wholesale. A docs repo
9
+ * that already kept a hand-authored diagram at one of those paths — plausible
10
+ * for `services.likec4` in particular, which loam reserved only now — would
11
+ * have lost it on the first sync after the upgrade, with no warning and nothing
12
+ * to restore it from. That is the one promise the rest of the tree keeps
13
+ * everywhere: authored bytes stay authored, which is what archive's verbatim
14
+ * splice is for. So the writer asks the file itself who wrote it, and refuses
15
+ * every path where the answer is not loam.
16
+ *
17
+ * IT LIVES HERE, in the leaf package, for the reason `./view-id.ts` does: the
18
+ * parent (`../views.ts`) may import a child and never the reverse, so the one
19
+ * spelling BOTH generated files must open with has to sit below both. Two
20
+ * literals for one mark is how the day comes that a file loam wrote is not
21
+ * recognised as loam's and the writer refuses to regenerate its own output.
22
+ *
23
+ * SO DOES THE READ, AND SO DOES THE SENTENCE, and that is the correction this
24
+ * module carries. The guard first shipped inside `loam subsystem sync` alone,
25
+ * and there are FOUR other writers of exactly these two paths: the shared commit
26
+ * window behind `subsystem new`, `rm`, `move` and `rename`
27
+ * (`commands/subsystem/txn/txn.ts`) and `loam seed`
28
+ * (`commands/seed/plan.ts`). Reproduced before this module grew: with a
29
+ * hand-authored `architecture/services.likec4` in the tree, `loam subsystem new
30
+ * platform` printed "architecture/services.likec4 updated" and destroyed it —
31
+ * the file's digest changed and its first line became loam's header. A guard
32
+ * that lives in one of five writers is not a guard. So the reader
33
+ * (`readGeneratedFile`, `authoredGeneratedPaths`) and the refusal sentence
34
+ * (`authoredRefusal`) sit beside the mark itself, below every writer, and a
35
+ * sixth writer added tomorrow has one place to reach for rather than a fourth
36
+ * spelling to invent.
37
+ */
38
+ import { existsSync } from "node:fs";
39
+ import { readFile } from "node:fs/promises";
40
+ /**
41
+ * The first bytes of every generated views file. Part of the content-compare
42
+ * contract, so it is not prose that may be reworded: the rest of each header is,
43
+ * and deliberately, because only this much identifies the owner.
44
+ */
45
+ export const GENERATED_MARK = "// generated by loam";
46
+ /**
47
+ * Was the text now at a generated path written by loam?
48
+ *
49
+ * A LEADING BYTE-ORDER MARK is stripped first, and that is not tidiness: a
50
+ * Windows editor that opens a generated file and saves it back adds one without
51
+ * changing a single fact in the file, and without this the writer would call its
52
+ * own output authored and refuse to regenerate it for good — a refusal no
53
+ * re-running could ever clear. CRLF needs no such treatment, for the reason
54
+ * `viewsAgree` records one file over: the mark is the head of the first line and
55
+ * the line ending is behind it.
56
+ *
57
+ * The test is the MARK and not the whole header, because the header's remaining
58
+ * lines differ between the two files and are prose. It is also deliberately not
59
+ * a test of the whole file: a hand EDIT of a generated file keeps the header, and
60
+ * that file is loam's to repair — `subsystem.views-stale` names it and one sync
61
+ * puts it back. Only a file that never carried the mark is somebody else's.
62
+ */
63
+ export function isGenerated(text) {
64
+ return text.replace(/^\uFEFF/, "").startsWith(GENERATED_MARK);
65
+ }
66
+ /**
67
+ * Read one generated path and answer both questions about it at once \u2014 what it
68
+ * says, and whether loam may write it.
69
+ *
70
+ * ONE READ, because the only caller that needs the text is the one that also
71
+ * compares it (`subsystem sync`'s `planFile`), and asking the disk twice for the
72
+ * same small file so that two helpers could each own half the answer is how the
73
+ * two halves come to disagree about which file they were talking about.
74
+ *
75
+ * ABSENT IS NOT AUTHORED. There is nothing to lose at a path that holds
76
+ * nothing, and a fleet being scaffolded must still get its first generated file
77
+ * \u2014 a guard that refused an absent file would refuse every repository forever.
78
+ */
79
+ export async function readGeneratedFile(path) {
80
+ if (!existsSync(path))
81
+ return { text: null, authored: false };
82
+ const text = await readFile(path, "utf8");
83
+ return { text, authored: !isGenerated(text) };
84
+ }
85
+ /**
86
+ * Which of these generated paths hold a file loam did not write \u2014 the question
87
+ * every writer that rewrites the PAIR wholesale asks before it stages anything.
88
+ *
89
+ * The paths come back in the order they were given, so a caller's message names
90
+ * the files in a stable order rather than in whichever order the reads finished.
91
+ */
92
+ export async function authoredGeneratedPaths(paths) {
93
+ // Independent reads of at most two small files: `Promise.all`, per the house
94
+ // rule, and the order of the input is preserved by the map rather than by the
95
+ // scheduler.
96
+ const reads = await Promise.all(paths.map(async (path) => ({ path, read: await readGeneratedFile(path) })));
97
+ return reads.filter((r) => r.read.authored).map((r) => r.path);
98
+ }
99
+ /**
100
+ * The one sentence a refusal owes about ONE authored file, composed here and
101
+ * parameterised by the file, because FIVE writers refuse for this reason and a
102
+ * reader who met the sentence from `loam subsystem sync` must recognise it
103
+ * verbatim from `loam subsystem new`, `rm`, `move`, `rename` and `loam seed`.
104
+ *
105
+ * It names three things a person cannot get anywhere else: which file was left
106
+ * alone, WHY loam thinks the file is not its own (the mark it looked for), and
107
+ * the fact that nothing was written to it. The last clause closes the loop the
108
+ * refusal otherwise leaves open \u2014 `loam validate --all` will keep reporting
109
+ * `subsystem.views-stale` for that path and keep naming `loam subsystem sync` as
110
+ * the repair, so a person who is not told why the writers decline would be sent
111
+ * back and forth between the two forever.
112
+ *
113
+ * It deliberately does NOT name a command to re-run. It used to say "re-run
114
+ * `loam subsystem sync`", which was true of the one caller it then had and wrong
115
+ * for the four that were added: the command to re-run is the one the person
116
+ * typed, and the sentence a caller appends says what that command did instead.
117
+ */
118
+ export function authoredRefusal(file) {
119
+ return (`${file} left as it is \u2014 its first line is not \`${GENERATED_MARK}\`, so it is an AUTHORED file standing at a ` +
120
+ "path loam generates and rewrites wholesale. Nothing was written to it and not one of its bytes changed. " +
121
+ "Rename it to something loam does not reserve (every other `.likec4` under architecture/ renders in the same " +
122
+ "project, so the diagram is not lost) or delete it, then re-run. Until then `loam validate --all` reports " +
123
+ "`subsystem.views-stale` for that path and every loam command that writes the file keeps refusing.");
124
+ }
125
+ //# sourceMappingURL=header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAChE,CAAC;AAUD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAClD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,KAAwB;IACnE,6EAA6E;IAC7E,8EAA8E;IAC9E,aAAa;IACb,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAC3E,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,CACL,GAAG,IAAI,iDAAiD,cAAc,8CAA8C;QACpH,0GAA0G;QAC1G,8GAA8G;QAC9G,2GAA2G;QAC3G,mGAAmG,CACpG,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The one line that says a file under `architecture/` is LOAM'S — the opening\n * comment both generated views files carry, and the predicate that reads it\n * back off a file on disk.\n *\n * WHY A MARK EXISTS AT ALL. `architecture/subsystems.likec4` and\n * `architecture/services.likec4` are filenames loam RESERVES inside a directory\n * the team owns, and `loam subsystem sync` rewrites them wholesale. A docs repo\n * that already kept a hand-authored diagram at one of those paths — plausible\n * for `services.likec4` in particular, which loam reserved only now — would\n * have lost it on the first sync after the upgrade, with no warning and nothing\n * to restore it from. That is the one promise the rest of the tree keeps\n * everywhere: authored bytes stay authored, which is what archive's verbatim\n * splice is for. So the writer asks the file itself who wrote it, and refuses\n * every path where the answer is not loam.\n *\n * IT LIVES HERE, in the leaf package, for the reason `./view-id.ts` does: the\n * parent (`../views.ts`) may import a child and never the reverse, so the one\n * spelling BOTH generated files must open with has to sit below both. Two\n * literals for one mark is how the day comes that a file loam wrote is not\n * recognised as loam's and the writer refuses to regenerate its own output.\n *\n * SO DOES THE READ, AND SO DOES THE SENTENCE, and that is the correction this\n * module carries. The guard first shipped inside `loam subsystem sync` alone,\n * and there are FOUR other writers of exactly these two paths: the shared commit\n * window behind `subsystem new`, `rm`, `move` and `rename`\n * (`commands/subsystem/txn/txn.ts`) and `loam seed`\n * (`commands/seed/plan.ts`). Reproduced before this module grew: with a\n * hand-authored `architecture/services.likec4` in the tree, `loam subsystem new\n * platform` printed \"architecture/services.likec4 updated\" and destroyed it —\n * the file's digest changed and its first line became loam's header. A guard\n * that lives in one of five writers is not a guard. So the reader\n * (`readGeneratedFile`, `authoredGeneratedPaths`) and the refusal sentence\n * (`authoredRefusal`) sit beside the mark itself, below every writer, and a\n * sixth writer added tomorrow has one place to reach for rather than a fourth\n * spelling to invent.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\n\n/**\n * The first bytes of every generated views file. Part of the content-compare\n * contract, so it is not prose that may be reworded: the rest of each header is,\n * and deliberately, because only this much identifies the owner.\n */\nexport const GENERATED_MARK = \"// generated by loam\";\n\n/**\n * Was the text now at a generated path written by loam?\n *\n * A LEADING BYTE-ORDER MARK is stripped first, and that is not tidiness: a\n * Windows editor that opens a generated file and saves it back adds one without\n * changing a single fact in the file, and without this the writer would call its\n * own output authored and refuse to regenerate it for good — a refusal no\n * re-running could ever clear. CRLF needs no such treatment, for the reason\n * `viewsAgree` records one file over: the mark is the head of the first line and\n * the line ending is behind it.\n *\n * The test is the MARK and not the whole header, because the header's remaining\n * lines differ between the two files and are prose. It is also deliberately not\n * a test of the whole file: a hand EDIT of a generated file keeps the header, and\n * that file is loam's to repair — `subsystem.views-stale` names it and one sync\n * puts it back. Only a file that never carried the mark is somebody else's.\n */\nexport function isGenerated(text: string): boolean {\n return text.replace(/^\\uFEFF/, \"\").startsWith(GENERATED_MARK);\n}\n\n/** What a writer of a generated path learns by reading it, in one read. */\nexport interface GeneratedFileRead {\n /** The bytes as text, or null when nothing stands at the path. */\n text: string | null;\n /** SOMEBODY ELSE wrote this: a file is there and its first line is not the mark. */\n authored: boolean;\n}\n\n/**\n * Read one generated path and answer both questions about it at once \\u2014 what it\n * says, and whether loam may write it.\n *\n * ONE READ, because the only caller that needs the text is the one that also\n * compares it (`subsystem sync`'s `planFile`), and asking the disk twice for the\n * same small file so that two helpers could each own half the answer is how the\n * two halves come to disagree about which file they were talking about.\n *\n * ABSENT IS NOT AUTHORED. There is nothing to lose at a path that holds\n * nothing, and a fleet being scaffolded must still get its first generated file\n * \\u2014 a guard that refused an absent file would refuse every repository forever.\n */\nexport async function readGeneratedFile(path: string): Promise<GeneratedFileRead> {\n if (!existsSync(path)) return { text: null, authored: false };\n const text = await readFile(path, \"utf8\");\n return { text, authored: !isGenerated(text) };\n}\n\n/**\n * Which of these generated paths hold a file loam did not write \\u2014 the question\n * every writer that rewrites the PAIR wholesale asks before it stages anything.\n *\n * The paths come back in the order they were given, so a caller's message names\n * the files in a stable order rather than in whichever order the reads finished.\n */\nexport async function authoredGeneratedPaths(paths: readonly string[]): Promise<string[]> {\n // Independent reads of at most two small files: `Promise.all`, per the house\n // rule, and the order of the input is preserved by the map rather than by the\n // scheduler.\n const reads = await Promise.all(\n paths.map(async (path) => ({ path, read: await readGeneratedFile(path) })),\n );\n return reads.filter((r) => r.read.authored).map((r) => r.path);\n}\n\n/**\n * The one sentence a refusal owes about ONE authored file, composed here and\n * parameterised by the file, because FIVE writers refuse for this reason and a\n * reader who met the sentence from `loam subsystem sync` must recognise it\n * verbatim from `loam subsystem new`, `rm`, `move`, `rename` and `loam seed`.\n *\n * It names three things a person cannot get anywhere else: which file was left\n * alone, WHY loam thinks the file is not its own (the mark it looked for), and\n * the fact that nothing was written to it. The last clause closes the loop the\n * refusal otherwise leaves open \\u2014 `loam validate --all` will keep reporting\n * `subsystem.views-stale` for that path and keep naming `loam subsystem sync` as\n * the repair, so a person who is not told why the writers decline would be sent\n * back and forth between the two forever.\n *\n * It deliberately does NOT name a command to re-run. It used to say \"re-run\n * `loam subsystem sync`\", which was true of the one caller it then had and wrong\n * for the four that were added: the command to re-run is the one the person\n * typed, and the sentence a caller appends says what that command did instead.\n */\nexport function authoredRefusal(file: string): string {\n return (\n `${file} left as it is \\u2014 its first line is not \\`${GENERATED_MARK}\\`, so it is an AUTHORED file standing at a ` +\n \"path loam generates and rewrites wholesale. Nothing was written to it and not one of its bytes changed. \" +\n \"Rename it to something loam does not reserve (every other `.likec4` under architecture/ renders in the same \" +\n \"project, so the diagram is not lost) or delete it, then re-run. Until then `loam validate --all` reports \" +\n \"`subsystem.views-stale` for that path and every loam command that writes the file keeps refusing.\"\n );\n}\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Every element on the far side of an edge touching this service, sorted and
3
+ * de-duplicated.
4
+ *
5
+ * "Touching" is the subject OR any descendant of it: an edge into
6
+ * `svc.api` is an edge into the service, and the view that hid it would be
7
+ * lying about the service's surface. The far endpoint is dropped when it is
8
+ * itself the subject or one of its descendants — that is INTERIOR, already
9
+ * drawn by the view's own `include *`, and naming it again would put a second
10
+ * include line in the file for a node that is already there.
11
+ *
12
+ * Sorted and de-duplicated because the generated file is a pure function of
13
+ * committed bytes: document order is the parse's, not the author's, and two
14
+ * edges between the same pair must not emit the same include twice.
15
+ */
16
+ export function peersOf(fqn, relationships) {
17
+ const prefix = `${fqn}.`;
18
+ const own = (id) => id === fqn || id.startsWith(prefix);
19
+ const found = new Set();
20
+ for (const rel of relationships) {
21
+ if (own(rel.source) && !own(rel.target))
22
+ found.add(rel.target);
23
+ if (own(rel.target) && !own(rel.source))
24
+ found.add(rel.source);
25
+ }
26
+ return [...found].sort();
27
+ }
28
+ //# sourceMappingURL=peers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peers.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/peers.ts"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,aAA6B;IAChE,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,MAAM,GAAG,GAAG,CAAC,EAAU,EAAW,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * The NEIGHBOUR SET of one service: every element the map draws an edge to or\n * from, computed by loam rather than asked of the renderer.\n *\n * This module exists because of a measurement, and the measurement is the whole\n * reason the per-service view is not three wildcard lines. At likec4 1.59.2, on\n * a 120-service map whose services are nested in `group` boxes, the obvious\n * recipe\n *\n * include *\n * include <fqn> -> *\n * include * -> <fqn>\n *\n * drew the GROUP BOXES as the counterparts — `growth`, `catalog`, `support`,\n * `notifications` — instead of the peer services, and NONE of them carried\n * drill-down. LikeC4 resolves a counterpart at the highest ancestor that is not\n * also an ancestor of the subject, so on any map that groups its services —\n * which is exactly what a fleet past about thirty services needs at fleet\n * level — the wildcard form says \"payments/service-0 talks to the growth group\"\n * and navigates nowhere. It looks perfect on a five-service FLAT fixture, which\n * is how it would have shipped.\n *\n * Re-measured here at 1.59.2 on the 58-service / 11-group fleet, subject\n * `payments.svc0` with 8 real peers: the wildcard recipe draws 10 nodes of which\n * four are group boxes; naming the peers draws 11 — the subject, its `api`, its\n * `db` and all 8 peers, each at its own level, each carrying `navigateTo` into\n * that peer's own generated view (6 of the 8 are services and have one; the\n * customer and the broker are not).\n *\n * The counterpart is taken AS THE MAP DECLARES IT — an edge landing on\n * `kafka.orderEvents` names the topic, not the broker — because that is the\n * truer picture and it is the one the map already draws. Anything the render\n * substituted here would be loam re-drawing somebody's architecture.\n */\nimport type { Rel } from \"../../../../c4/likec4.js\";\n\n/**\n * Every element on the far side of an edge touching this service, sorted and\n * de-duplicated.\n *\n * \"Touching\" is the subject OR any descendant of it: an edge into\n * `svc.api` is an edge into the service, and the view that hid it would be\n * lying about the service's surface. The far endpoint is dropped when it is\n * itself the subject or one of its descendants — that is INTERIOR, already\n * drawn by the view's own `include *`, and naming it again would put a second\n * include line in the file for a node that is already there.\n *\n * Sorted and de-duplicated because the generated file is a pure function of\n * committed bytes: document order is the parse's, not the author's, and two\n * edges between the same pair must not emit the same include twice.\n */\nexport function peersOf(fqn: string, relationships: readonly Rel[]): string[] {\n const prefix = `${fqn}.`;\n const own = (id: string): boolean => id === fqn || id.startsWith(prefix);\n const found = new Set<string>();\n for (const rel of relationships) {\n if (own(rel.source) && !own(rel.target)) found.add(rel.target);\n if (own(rel.target) && !own(rel.source)) found.add(rel.source);\n }\n return [...found].sort();\n}\n"]}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The identifier loam mints for a per-service overview view, and the ONE
3
+ * encoder every generated view id is spelled through.
4
+ *
5
+ * The encoder lives HERE, in the child package, rather than beside the
6
+ * subsystem render that has used it since it was written — and the direction is
7
+ * forced rather than chosen. `../views.ts` imports this package to compose the
8
+ * per-service file, so an import the other way would be the package cycle
9
+ * `scripts/package-graph.mjs` refuses; that script's own advice for exactly this
10
+ * shape is "move the helper; do not import the weight", and an id encoder is the
11
+ * leaf while the two renders are the weight. One copy is also the point: the
12
+ * injectivity argument below holds across BOTH prefixes only while they share an
13
+ * encoder, and a second spelling would be a second chance to collide.
14
+ *
15
+ * INJECTIVITY is a correctness rule and not tidiness. The renderer refuses the
16
+ * whole `architecture/` project over a duplicate view id, and the naive folding
17
+ * (every non-identifier byte and the join separator alike becoming `_`) collided
18
+ * three DISTINCT healthy names onto one identifier — `a_b`, and `b` under `a`,
19
+ * both rendering `subsystem_a_b` — so `validate --all` was green while the
20
+ * renderer refused the fleet map, and `subsystem.name-collision` never fired
21
+ * because the flat namespace really was intact.
22
+ *
23
+ * Hence the encoding: an identifier byte (`[A-Za-z0-9]`) passes through, any
24
+ * other byte becomes `_` + two lowercase hex digits (`.` → `_2e`, `-` → `_2d`,
25
+ * `_` itself → `_5f` — the underscore must be escaped precisely because it is
26
+ * BOTH legal in a name and the separator between path segments). Underscore runs
27
+ * then decode uniquely — one is an escape, two a separator, three a separator
28
+ * followed by an escape — so no two distinct legal names share an identifier. A
29
+ * name `subsystem.name-invalid` already flagged may hold a code point above 0xff
30
+ * whose longer hex could in principle re-collide; it is deterministic still, and
31
+ * that directory is already an error finding by name.
32
+ *
33
+ * The prefix is EXPORTED, with the minting function, because the collision check
34
+ * (`subsystem.view-id-collision`) has to ask the generator what it mints rather
35
+ * than re-derive it. A check carrying its own copy of the escaping goes quiet the
36
+ * day the encoding changes, and going quiet is the failure that whole family
37
+ * exists to prevent.
38
+ */
39
+ /**
40
+ * The reserved prefix for a generated per-service overview. Distinct from
41
+ * `subsystem_` so the two generated files can never mint the same id for
42
+ * different subjects, and it cannot match the shape LikeC4 mints for an unnamed
43
+ * view (`^view_[0-9a-z]+$`, `core/c4/parsed/view-ids.ts`), so a generated id is
44
+ * never mistaken for a synthesized one.
45
+ */
46
+ export const SERVICE_VIEW_PREFIX = "service_";
47
+ /** One name, folded to identifier bytes injectively — the banner argues why this shape. */
48
+ export function escapeViewIdSegment(name) {
49
+ return [...name]
50
+ .map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0).toString(16).padStart(2, "0")}`))
51
+ .join("");
52
+ }
53
+ /**
54
+ * The view id for one service, from its `services/<id>/` DIRECTORY name.
55
+ *
56
+ * The directory id rather than the map element's title or its fqn, for the same
57
+ * reason the title uses it: it is the name every loam command already addresses
58
+ * the service by, a directory name cannot hold a `/` or a `.`, and it cannot
59
+ * drift when somebody renames a box on the map. Two services cannot share a
60
+ * directory id — the walk's flat namespace already refuses that as
61
+ * `subsystem.name-collision` — so injectivity of the encoder is injectivity of
62
+ * the whole id.
63
+ */
64
+ export function serviceViewId(id) {
65
+ return `${SERVICE_VIEW_PREFIX}${escapeViewIdSegment(id)}`;
66
+ }
67
+ //# sourceMappingURL=view-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-id.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/view-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAE9C,2FAA2F;AAC3F,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,CAAC,GAAG,IAAI,CAAC;SACb,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;SACnG,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,GAAG,mBAAmB,GAAG,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAC;AAC5D,CAAC","sourcesContent":["/**\n * The identifier loam mints for a per-service overview view, and the ONE\n * encoder every generated view id is spelled through.\n *\n * The encoder lives HERE, in the child package, rather than beside the\n * subsystem render that has used it since it was written — and the direction is\n * forced rather than chosen. `../views.ts` imports this package to compose the\n * per-service file, so an import the other way would be the package cycle\n * `scripts/package-graph.mjs` refuses; that script's own advice for exactly this\n * shape is \"move the helper; do not import the weight\", and an id encoder is the\n * leaf while the two renders are the weight. One copy is also the point: the\n * injectivity argument below holds across BOTH prefixes only while they share an\n * encoder, and a second spelling would be a second chance to collide.\n *\n * INJECTIVITY is a correctness rule and not tidiness. The renderer refuses the\n * whole `architecture/` project over a duplicate view id, and the naive folding\n * (every non-identifier byte and the join separator alike becoming `_`) collided\n * three DISTINCT healthy names onto one identifier — `a_b`, and `b` under `a`,\n * both rendering `subsystem_a_b` — so `validate --all` was green while the\n * renderer refused the fleet map, and `subsystem.name-collision` never fired\n * because the flat namespace really was intact.\n *\n * Hence the encoding: an identifier byte (`[A-Za-z0-9]`) passes through, any\n * other byte becomes `_` + two lowercase hex digits (`.` → `_2e`, `-` → `_2d`,\n * `_` itself → `_5f` — the underscore must be escaped precisely because it is\n * BOTH legal in a name and the separator between path segments). Underscore runs\n * then decode uniquely — one is an escape, two a separator, three a separator\n * followed by an escape — so no two distinct legal names share an identifier. A\n * name `subsystem.name-invalid` already flagged may hold a code point above 0xff\n * whose longer hex could in principle re-collide; it is deterministic still, and\n * that directory is already an error finding by name.\n *\n * The prefix is EXPORTED, with the minting function, because the collision check\n * (`subsystem.view-id-collision`) has to ask the generator what it mints rather\n * than re-derive it. A check carrying its own copy of the escaping goes quiet the\n * day the encoding changes, and going quiet is the failure that whole family\n * exists to prevent.\n */\n\n/**\n * The reserved prefix for a generated per-service overview. Distinct from\n * `subsystem_` so the two generated files can never mint the same id for\n * different subjects, and it cannot match the shape LikeC4 mints for an unnamed\n * view (`^view_[0-9a-z]+$`, `core/c4/parsed/view-ids.ts`), so a generated id is\n * never mistaken for a synthesized one.\n */\nexport const SERVICE_VIEW_PREFIX = \"service_\";\n\n/** One name, folded to identifier bytes injectively — the banner argues why this shape. */\nexport function escapeViewIdSegment(name: string): string {\n return [...name]\n .map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0)!.toString(16).padStart(2, \"0\")}`))\n .join(\"\");\n}\n\n/**\n * The view id for one service, from its `services/<id>/` DIRECTORY name.\n *\n * The directory id rather than the map element's title or its fqn, for the same\n * reason the title uses it: it is the name every loam command already addresses\n * the service by, a directory name cannot hold a `/` or a `.`, and it cannot\n * drift when somebody renames a box on the map. Two services cannot share a\n * directory id — the walk's flat namespace already refuses that as\n * `subsystem.name-collision` — so injectivity of the encoder is injectivity of\n * the whole id.\n */\nexport function serviceViewId(id: string): string {\n return `${SERVICE_VIEW_PREFIX}${escapeViewIdSegment(id)}`;\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import { GENERATED_MARK } from "./header.js";
2
+ import { peersOf } from "./peers.js";
3
+ import { serviceViewId } from "./view-id.js";
4
+ /**
5
+ * The global style group these views reference when the map declares one, the
6
+ * exact counterpart of `SUBSYSTEM_STYLE_GROUP` and reserved on the same terms:
7
+ * presence of the DECLARATION is the whole opt-in, because referencing an
8
+ * undeclared style id is a parse error that blanks the entire `architecture/`
9
+ * project in the renderer. The name mirrors the file it styles.
10
+ */
11
+ export const SERVICE_STYLE_GROUP = "services";
12
+ /**
13
+ * The header every generated file opens with — stable bytes, part of the
14
+ * content-compare contract, naming the regenerator so a reader who found the
15
+ * file before the docs knows what owns it. `loam subsystem sync` regenerates
16
+ * BOTH generated files, which is why one command is named here and why one
17
+ * finding code (`subsystem.views-stale`) honestly covers both.
18
+ *
19
+ * It OPENS with the shared `GENERATED_MARK` rather than spelling it, because
20
+ * the same bytes are what `loam subsystem sync` reads back off disk to decide
21
+ * whether the file at this reserved path is loam's to rewrite at all
22
+ * (`./header.js`). A second literal here is how a file loam wrote stops being
23
+ * recognised as loam's.
24
+ */
25
+ const HEADER = `${GENERATED_MARK} — one overview view per service under services/, drawn from the fleet map.\n` +
26
+ "// Do not edit: `loam subsystem sync` regenerates this file from the directories and the map,\n" +
27
+ "// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\n" +
28
+ "// A navigation aid, not the fleet's architecture: author your own diagrams elsewhere.\n";
29
+ /**
30
+ * Render the whole file, or null when no service resolves to a map element —
31
+ * the file must then be ABSENT, so a fleet whose map draws nothing never
32
+ * carries a generated artifact that says nothing.
33
+ *
34
+ * Services sorted by id and peers sorted by id, one `include` per line: the
35
+ * output is byte-reproducible on any machine from the same committed bytes (no
36
+ * timestamps, no absolute paths, no readdir-order dependence), and two
37
+ * concurrent changes to different services touch different lines, so git merges
38
+ * the file without intervention.
39
+ */
40
+ export function renderServiceViews(input) {
41
+ if (input.subjects.length === 0)
42
+ return null;
43
+ const lines = [HEADER, ...(input.disclosure === null ? [] : [input.disclosure]), "views {"];
44
+ const subjects = [...input.subjects].sort((a, b) => (a.id < b.id ? -1 : 1));
45
+ for (const [i, subject] of subjects.entries()) {
46
+ if (i > 0)
47
+ lines.push("");
48
+ lines.push(...viewLines(subject, peersOf(subject.fqn, input.relationships), input.styled));
49
+ }
50
+ lines.push("}");
51
+ return lines.join("\n") + "\n";
52
+ }
53
+ /**
54
+ * One view, in the order the parser demands: metadata FIRST, and that is a hard
55
+ * constraint rather than a taste — measured, an `include` line followed by
56
+ * `title` is a parse error ("Expecting token of type '}' but found `title`"),
57
+ * and one parse error in this file blanks the whole `architecture/` project in
58
+ * the renderer. `global style` sits after `title` and before anything that
59
+ * includes, where the same pin measured it parsing with zero errors.
60
+ */
61
+ function viewLines(subject, peers, styled) {
62
+ const { fqn } = subject;
63
+ const out = [` view ${serviceViewId(subject.id)} of ${fqn} {`, ` title '${subject.title}'`];
64
+ if (styled)
65
+ out.push(` global style ${SERVICE_STYLE_GROUP}`);
66
+ out.push(" include *");
67
+ for (const peer of peers)
68
+ out.push(` include ${peer}`);
69
+ out.push(" exclude * -> *", ` include -> ${fqn}`, ` include ${fqn} ->`);
70
+ // The interior wiring, last. Measured: `include <fqn>.* -> <fqn>.*` on a
71
+ // CHILDLESS element is legal — `likec4 validate` reports valid and the export
72
+ // carries zero errors — so one generated line serves both model shapes: an
73
+ // EXTENDING model's containers live in the root project and are drawn, a
74
+ // STANDALONE model's are not in this project and the line matches nothing.
75
+ out.push(` include ${fqn}.* -> ${fqn}.*`, " }");
76
+ return out;
77
+ }
78
+ //# sourceMappingURL=views.js.map