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

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 (270) hide show
  1. package/CHANGELOG.md +947 -0
  2. package/MIGRATING-from-OpenSpec.md +7 -1
  3. package/QUICKSTART.md +8 -3
  4. package/README.md +20 -13
  5. package/ROADMAP.md +329 -22
  6. package/SCHEMA.md +472 -24
  7. package/WORKFLOW.md +23 -10
  8. package/dist/commands/adopt/render.js +47 -8
  9. package/dist/commands/adopt/render.js.map +1 -1
  10. package/dist/commands/archive/plan/landscape.js +45 -1
  11. package/dist/commands/archive/plan/landscape.js.map +1 -1
  12. package/dist/commands/archive/plan/model/extending.js +85 -0
  13. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  14. package/dist/commands/archive/run.js +5 -2
  15. package/dist/commands/archive/run.js.map +1 -1
  16. package/dist/commands/context/print.js +11 -1
  17. package/dist/commands/context/print.js.map +1 -1
  18. package/dist/commands/doctor.js +46 -0
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/explore.js +12 -2
  21. package/dist/commands/explore.js.map +1 -1
  22. package/dist/commands/list/views.js +5 -1
  23. package/dist/commands/list/views.js.map +1 -1
  24. package/dist/commands/seed/plan.js +97 -3
  25. package/dist/commands/seed/plan.js.map +1 -1
  26. package/dist/commands/seed/seed.js +7 -2
  27. package/dist/commands/seed/seed.js.map +1 -1
  28. package/dist/commands/show/feature.js +2 -1
  29. package/dist/commands/show/feature.js.map +1 -1
  30. package/dist/commands/show/marks.js +3 -5
  31. package/dist/commands/show/marks.js.map +1 -1
  32. package/dist/commands/show/service.js +39 -18
  33. package/dist/commands/show/service.js.map +1 -1
  34. package/dist/commands/subsystem/sync.js +184 -39
  35. package/dist/commands/subsystem/sync.js.map +1 -1
  36. package/dist/commands/subsystem/txn/exclude.js +132 -0
  37. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  38. package/dist/commands/subsystem/txn/report.js +124 -0
  39. package/dist/commands/subsystem/txn/report.js.map +1 -0
  40. package/dist/commands/subsystem/txn/txn.js +29 -3
  41. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  42. package/dist/commands/subsystem/txn/views.js +113 -16
  43. package/dist/commands/subsystem/txn/views.js.map +1 -1
  44. package/dist/commands/validate/arch-coverage.js +16 -9
  45. package/dist/commands/validate/arch-coverage.js.map +1 -1
  46. package/dist/commands/validate/checks/fleet-shape.js +30 -77
  47. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  48. package/dist/commands/validate/checks/vocabulary.js +4 -19
  49. package/dist/commands/validate/checks/vocabulary.js.map +1 -1
  50. package/dist/commands/validate/feature.js +1 -1
  51. package/dist/commands/validate/feature.js.map +1 -1
  52. package/dist/commands/validate/fleet/census.js +11 -10
  53. package/dist/commands/validate/fleet/census.js.map +1 -1
  54. package/dist/commands/validate/fleet/landscape.js +128 -114
  55. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  56. package/dist/commands/validate/fleet/load.js +66 -0
  57. package/dist/commands/validate/fleet/load.js.map +1 -1
  58. package/dist/commands/validate/fleet/map/attest.js +103 -0
  59. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  60. package/dist/commands/validate/fleet/map/bindings.js +96 -0
  61. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  62. package/dist/commands/validate/fleet/map/consumers.js +298 -0
  63. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  64. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  65. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  66. package/dist/commands/validate/fleet/map/usecases.js +56 -0
  67. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  68. package/dist/commands/validate/fleet/usecases/steps.js +49 -17
  69. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
  70. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  71. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  72. package/dist/commands/validate/fleet/views/ids.js +13 -9
  73. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  74. package/dist/commands/validate/fleet/views/projects.js +278 -0
  75. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  76. package/dist/commands/validate/fleet/views/root-documents.js +97 -0
  77. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  78. package/dist/commands/validate/fleet/views/stale.js +2 -2
  79. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  80. package/dist/commands/validate/service/model/diverged.js +226 -0
  81. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  82. package/dist/commands/validate/service/model/grade.js +163 -0
  83. package/dist/commands/validate/service/model/grade.js.map +1 -0
  84. package/dist/commands/validate/service/service.js +110 -34
  85. package/dist/commands/validate/service/service.js.map +1 -1
  86. package/dist/commands/validate/service/specs.js +39 -5
  87. package/dist/commands/validate/service/specs.js.map +1 -1
  88. package/dist/commands/validate/service/spine.js +41 -3
  89. package/dist/commands/validate/service/spine.js.map +1 -1
  90. package/dist/commands/validate/service/usecases/tags.js +113 -0
  91. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  92. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  93. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  94. package/dist/commands/validate/validate.js +10 -24
  95. package/dist/commands/validate/validate.js.map +1 -1
  96. package/dist/core/agent/agents-md/artifacts.js +1 -0
  97. package/dist/core/agent/agents-md/artifacts.js.map +1 -1
  98. package/dist/core/agent/agents-md/command-map.js +18 -2
  99. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  100. package/dist/core/agent/agents-md/cycle.js +24 -9
  101. package/dist/core/agent/agents-md/cycle.js.map +1 -1
  102. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  103. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  104. package/dist/core/agent/agents-md/map/subsystem.js +51 -2
  105. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  106. package/dist/core/agent/agents-md/map/usecases.js +45 -3
  107. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  108. package/dist/core/agent/agents-md.js +14 -4
  109. package/dist/core/agent/agents-md.js.map +1 -1
  110. package/dist/core/agent/support/report.js +39 -5
  111. package/dist/core/agent/support/report.js.map +1 -1
  112. package/dist/core/agent/workflows/adopt.js +125 -6
  113. package/dist/core/agent/workflows/adopt.js.map +1 -1
  114. package/dist/core/agent/workflows/check.js +35 -19
  115. package/dist/core/agent/workflows/check.js.map +1 -1
  116. package/dist/core/agent/workflows/closing.js +3 -1
  117. package/dist/core/agent/workflows/closing.js.map +1 -1
  118. package/dist/core/brief/brief.js +63 -11
  119. package/dist/core/brief/brief.js.map +1 -1
  120. package/dist/core/brief/checks.js +138 -3
  121. package/dist/core/brief/checks.js.map +1 -1
  122. package/dist/core/brief/landscape.js +212 -129
  123. package/dist/core/brief/landscape.js.map +1 -1
  124. package/dist/core/brief/map/extend.js +81 -0
  125. package/dist/core/brief/map/extend.js.map +1 -0
  126. package/dist/core/brief/map/owed.js +284 -0
  127. package/dist/core/brief/map/owed.js.map +1 -0
  128. package/dist/core/brief/targets.js +30 -26
  129. package/dist/core/brief/targets.js.map +1 -1
  130. package/dist/core/brief/unchecked.js +12 -2
  131. package/dist/core/brief/unchecked.js.map +1 -1
  132. package/dist/core/c4/likec4.js +23 -1
  133. package/dist/core/c4/likec4.js.map +1 -1
  134. package/dist/core/c4/parsed/deployment.js +5 -3
  135. package/dist/core/c4/parsed/deployment.js.map +1 -1
  136. package/dist/core/c4/parsed/view-ids.js +39 -1
  137. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  138. package/dist/core/c4/prefetch/fleet.js +142 -0
  139. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  140. package/dist/core/c4/project/architecture.js +52 -4
  141. package/dist/core/c4/project/architecture.js.map +1 -1
  142. package/dist/core/c4/project/batch.js +140 -0
  143. package/dist/core/c4/project/batch.js.map +1 -0
  144. package/dist/core/c4/project/documents.js +9 -4
  145. package/dist/core/c4/project/documents.js.map +1 -1
  146. package/dist/core/c4/project/load.js +77 -38
  147. package/dist/core/c4/project/load.js.map +1 -1
  148. package/dist/core/c4/project/staged.js +46 -12
  149. package/dist/core/c4/project/staged.js.map +1 -1
  150. package/dist/core/c4/resolve/attested.js +0 -0
  151. package/dist/core/c4/resolve/attested.js.map +1 -0
  152. package/dist/core/c4/resolve/service.js +8 -17
  153. package/dist/core/c4/resolve/service.js.map +1 -1
  154. package/dist/core/c4/resolve/steps.js +18 -0
  155. package/dist/core/c4/resolve/steps.js.map +1 -1
  156. package/dist/core/c4/root-project/exclude.js +352 -0
  157. package/dist/core/c4/root-project/exclude.js.map +1 -0
  158. package/dist/core/c4/service-model/documents.js +47 -0
  159. package/dist/core/c4/service-model/documents.js.map +1 -0
  160. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  161. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  162. package/dist/core/c4/service-model/load.js +165 -0
  163. package/dist/core/c4/service-model/load.js.map +1 -0
  164. package/dist/core/c4/service-model/renderer.js +128 -0
  165. package/dist/core/c4/service-model/renderer.js.map +1 -0
  166. package/dist/core/c4/service-model/shape.js +126 -0
  167. package/dist/core/c4/service-model/shape.js.map +1 -0
  168. package/dist/core/c4/service-model/slice.js +188 -0
  169. package/dist/core/c4/service-model/slice.js.map +1 -0
  170. package/dist/core/c4/source-scan.js +26 -0
  171. package/dist/core/c4/source-scan.js.map +1 -1
  172. package/dist/core/c4/splice/authored-source.js +60 -13
  173. package/dist/core/c4/splice/authored-source.js.map +1 -1
  174. package/dist/core/c4/splice/contract.js.map +1 -1
  175. package/dist/core/c4/splice/identity/declared.js +87 -0
  176. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  177. package/dist/core/c4/splice/identity/edges.js +17 -0
  178. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  179. package/dist/core/c4/splice/identity/existing.js +80 -0
  180. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  181. package/dist/core/c4/splice/identity/interior.js +80 -0
  182. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  183. package/dist/core/c4/splice/identity/owner.js +50 -0
  184. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  185. package/dist/core/c4/splice/landscape-merge.js +154 -100
  186. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  187. package/dist/core/c4/splice/model/merge.js +140 -0
  188. package/dist/core/c4/splice/model/merge.js.map +1 -0
  189. package/dist/core/c4/splice/placement.js +14 -4
  190. package/dist/core/c4/splice/placement.js.map +1 -1
  191. package/dist/core/c4/workspace.js +2 -1
  192. package/dist/core/c4/workspace.js.map +1 -1
  193. package/dist/core/coherence/coherence.js +7 -0
  194. package/dist/core/coherence/coherence.js.map +1 -1
  195. package/dist/core/delta/delta.js +9 -0
  196. package/dist/core/delta/delta.js.map +1 -1
  197. package/dist/core/deployment/delta.js +4 -0
  198. package/dist/core/deployment/delta.js.map +1 -1
  199. package/dist/core/diff/base-state.js +21 -5
  200. package/dist/core/diff/base-state.js.map +1 -1
  201. package/dist/core/diff/victims.js +10 -1
  202. package/dist/core/diff/victims.js.map +1 -1
  203. package/dist/core/docs.js +31 -22
  204. package/dist/core/docs.js.map +1 -1
  205. package/dist/core/doctor/config.js +7 -3
  206. package/dist/core/doctor/config.js.map +1 -1
  207. package/dist/core/doctor/doctor.js +48 -9
  208. package/dist/core/doctor/doctor.js.map +1 -1
  209. package/dist/core/doctor/report.js.map +1 -1
  210. package/dist/core/doctor/reports/scan.js +242 -0
  211. package/dist/core/doctor/reports/scan.js.map +1 -0
  212. package/dist/core/explain/families.js +1 -1
  213. package/dist/core/explain/families.js.map +1 -1
  214. package/dist/core/explain/refusals.js +1 -1
  215. package/dist/core/explain/refusals.js.map +1 -1
  216. package/dist/core/explore/explore.js +44 -9
  217. package/dist/core/explore/explore.js.map +1 -1
  218. package/dist/core/fleet-context.js +47 -0
  219. package/dist/core/fleet-context.js.map +1 -1
  220. package/dist/core/gate/partners.js +5 -9
  221. package/dist/core/gate/partners.js.map +1 -1
  222. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  223. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  224. package/dist/core/pack/living.js +47 -13
  225. package/dist/core/pack/living.js.map +1 -1
  226. package/dist/core/pack/pack.js +9 -6
  227. package/dist/core/pack/pack.js.map +1 -1
  228. package/dist/core/projection/arch-slice.js +2 -2
  229. package/dist/core/projection/arch-slice.js.map +1 -1
  230. package/dist/core/repo/paths.js +40 -0
  231. package/dist/core/repo/paths.js.map +1 -1
  232. package/dist/core/repo/tree/render/projects.js +182 -0
  233. package/dist/core/repo/tree/render/projects.js.map +1 -0
  234. package/dist/core/repo/tree/render/stale.js +2 -3
  235. package/dist/core/repo/tree/render/stale.js.map +1 -1
  236. package/dist/core/repo/tree/render/views.js +51 -3
  237. package/dist/core/repo/tree/render/views.js.map +1 -1
  238. package/dist/core/repo/tree/walk.js +45 -2
  239. package/dist/core/repo/tree/walk.js.map +1 -1
  240. package/dist/core/scaffold/landscape.js +219 -9
  241. package/dist/core/scaffold/landscape.js.map +1 -1
  242. package/dist/core/staging/writes.js.map +1 -1
  243. package/dist/core/usecases/delta/claims.js +1 -0
  244. package/dist/core/usecases/delta/claims.js.map +1 -1
  245. package/dist/core/usecases/delta/overlay.js +4 -0
  246. package/dist/core/usecases/delta/overlay.js.map +1 -1
  247. package/dist/core/usecases/fleet.js +18 -6
  248. package/dist/core/usecases/fleet.js.map +1 -1
  249. package/dist/core/usecases/operations.js +7 -0
  250. package/dist/core/usecases/operations.js.map +1 -1
  251. package/dist/core/usecases/service/exclude.js +119 -0
  252. package/dist/core/usecases/service/exclude.js.map +1 -0
  253. package/dist/core/usecases/service/flows.js +161 -0
  254. package/dist/core/usecases/service/flows.js.map +1 -0
  255. package/dist/core/vocabulary/maturity.js +10 -0
  256. package/dist/core/vocabulary/maturity.js.map +1 -1
  257. package/examples/README.md +23 -0
  258. package/examples/docs/architecture/landscape.likec4 +41 -0
  259. package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
  260. package/examples/docs/likec4.config.json +0 -1
  261. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  262. package/examples/docs/services/order-service/arch.spec.md +13 -6
  263. package/examples/docs/services/order-service/model.likec4 +53 -67
  264. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  265. package/examples/docs/services/payment-service/model.likec4 +32 -33
  266. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  267. package/examples/docs/services/platform/identity-service/model.likec4 +29 -33
  268. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  269. package/examples/docs/services/platform/notification-service/model.likec4 +39 -59
  270. package/package.json +1 -1
package/WORKFLOW.md CHANGED
@@ -336,7 +336,12 @@ following it behaves unexpectedly. Its version-matched body comes from
336
336
 
337
337
  The agent preserves the original symptom before investigating, collects the binary version,
338
338
  `doctor --json`, relevant `status --json` and the smallest safe reproduction, then writes
339
- `loam-reports/YYYY-MM-DD-<slug>.md`. It classifies the evidence as a loam defect, project-data
339
+ `loam-reports/NNN-YYYY-MM-DD-<slug>.md` — `NNN` the next ordinal, at least three digits and
340
+ zero-padded, which `loam doctor` reports, and a
341
+ `Status: open` header line the team later edits to `sent`, `fixed in <version>` or
342
+ `superseded by <NNN>`. `doctor` reads that line from the header field block above the first `##`
343
+ heading, so a report quoting the protocol's template counts as whatever its own header says. It
344
+ classifies the evidence as a loam defect, project-data
340
345
  problem, agent-workflow problem, host/infrastructure problem or inconclusive. A report contains
341
346
  stable envelope/finding codes and short relevant excerpts, not a full environment or document
342
347
  dump. Secrets and home paths are redacted. A writer is never repeated just to reproduce a failure,
@@ -429,7 +434,12 @@ Every command takes `--json`, and the contract is built so an agent never has to
429
434
  that service's repository: the living requirements verbatim, both contracts, the fleet edges one
430
435
  hop out, the permission and capability joins, and every feature in flight over the service — one
431
436
  deterministic payload, so two runs over the same state are byte-diffable. `--feature <FEAT>`
432
- narrows the in-flight section to the feature being implemented.
437
+ narrows the in-flight section to the feature being implemented. Its `landscape` object carries
438
+ `broken` beside `parses` — the `architecture/` documents that failed, docs-relative POSIX, deduped
439
+ and sorted, `[]` while `parses` is true — spelled exactly as `landscape.invalid` and the adopt
440
+ brief spell them. `parses` answers for the `architecture/` PROJECT, so the document at fault is
441
+ very often a sibling beside a landscape that reads perfectly, which is why the key exists.
442
+ `loam explore --json` carries the same key with the same meaning.
433
443
  - **`loam show <FEAT> --json` is the review hand-off.** Its `review` object joins the intent summary,
434
444
  exact C4 objects, API/event slices, dependencies, artifact states, verification, blockers,
435
445
  advisories, use cases and executable next actions without making the reviewer reopen each file.
@@ -549,16 +559,19 @@ refuses it until a person authors the content, or deliberately overrides with `-
549
559
  requirements-only feature you delete the scaffolded `delta.likec4` yourself — `new` says so on the
550
560
  way out.
551
561
 
552
- **`loam adopt`** — the brief names the target paths, the grammar of each, what the landscape already
553
- says, the checks that follow, and the ones that do not exist. The default human view now opens with
562
+ **`loam adopt`** — the brief names the target paths, the grammar of each, what the fleet map already
563
+ says — the whole `architecture/` project, not the landscape file alone — the checks that follow, and
564
+ the ones that do not exist. The default human view now opens with
554
565
  an orientation block before any of that: how many of those targets are required and still
555
566
  outstanding, named, and what each flag in the artifact table means — `MISSING` required and absent,
556
- lowercase `missing` optional and absent, `present` a document to diff rather than replace, and
557
- `UNDRAWN` the one row that is the whole system's shared map, which this boundary owes an element
558
- rather than a file. `--targets` drops the half that is identical for every service — the code walk,
559
- the frontmatter rules, the 37 named checks and the fifteen statements of what nothing checks — and
560
- replaces it with a `full` field naming the one run that carries them, which is 42,873 bytes down to
561
- 17,805 on the example fleet.
567
+ lowercase `missing` optional and absent, `present` a document to diff rather than replace, and two
568
+ rows for the one target that is the whole system's shared map: `UNDRAWN` while nothing in the map
569
+ resolves to this boundary, and `EDGELESS` while an element does and no edge touches it. The map owes
570
+ an element in the first state and edges in the second, and `landscape.instruction` says which.
571
+ `--targets` drops the half that is identical for every service — the code walk,
572
+ the frontmatter rules, the 52 named checks and the seventeen statements of what nothing checks — and
573
+ replaces it with a `full` field naming the one run that carries them, which is 54,833 bytes down to
574
+ 20,062 on the example fleet (measured 2026-09-04).
562
575
 
563
576
  **`loam diff`** — the base state is read out of the docs repo's own git history
564
577
  (`rev-parse`/`ls-tree`/`show`, read-only, no checkout), so it refuses (`repository-unavailable`)
@@ -55,7 +55,19 @@ export function render(b, warnings, full) {
55
55
  for (const t of b.targets) {
56
56
  // `edit` is the shared fleet map: the file exists, but "present" would read
57
57
  // as "nothing to do here", which is the opposite of why it is on the list.
58
- const flag = t.action === "edit" ? "UNDRAWN" : t.exists ? "present" : t.required ? "MISSING" : "missing";
58
+ // Two words for it, because the two states want different work: `UNDRAWN`
59
+ // when nothing resolves to the service, `EDGELESS` when an element does
60
+ // and no edge touches it — a reader acting on `UNDRAWN` there would look
61
+ // for a box that is already on the map.
62
+ const flag = t.action === "edit"
63
+ ? b.landscape.modelled === true
64
+ ? "EDGELESS"
65
+ : "UNDRAWN"
66
+ : t.exists
67
+ ? "present"
68
+ : t.required
69
+ ? "MISSING"
70
+ : "missing";
59
71
  console.log(` ${t.artifact.padEnd(width)} ${t.action.padEnd(6)} ${flag.padEnd(7)} ${t.purpose}`);
60
72
  }
61
73
  for (const t of b.targets)
@@ -81,8 +93,8 @@ export function render(b, warnings, full) {
81
93
  console.log("");
82
94
  console.log(wrap("Leave everything `status: draft`. Vouching is somebody reading the code and saying the document matches it — it is not yours to do, and a status with no digest behind it is a claim with nothing behind it.", " "));
83
95
  }
84
- /** The table's four flags, compressed to one terminal line for the scroll-back reader. */
85
- const LEGEND_LINE = "MISSING required · missing optional · present diff it · UNDRAWN add to the map";
96
+ /** The table's five flags, compressed to one terminal line for the scroll-back reader. */
97
+ const LEGEND_LINE = "MISSING required · missing optional · present diff it · UNDRAWN add to the map · EDGELESS an element exists, no edge does";
86
98
  /**
87
99
  * A required target whose work is still outstanding.
88
100
  *
@@ -91,7 +103,8 @@ const LEGEND_LINE = "MISSING required · missing optional · present diff it ·
91
103
  * exists, so `exists` is true while nothing in it resolves to this boundary —
92
104
  * which `loam validate --all` grades `landscape.service-unmodelled`, an ERROR.
93
105
  * `action` carries that distinction and nothing else does: `edit` is only ever
94
- * the undrawn fleet map (see `BriefTarget.action`, core/brief/targets.ts).
106
+ * the fleet map still owed — undrawn, or drawn with no edge touching it (see
107
+ * `BriefTarget.action`, core/brief/targets.ts).
95
108
  */
96
109
  function outstanding(t) {
97
110
  return t.required && (!t.exists || t.action === "edit");
@@ -121,7 +134,7 @@ function printOrientation(b) {
121
134
  // capitals were emphasis; they are the required/optional distinction, and
122
135
  // nothing on the page said so. `UNDRAWN` needs more than a gloss — it is the
123
136
  // one row that is not this boundary's file at all.
124
- console.log(bullet("The flags in that table: `MISSING` in capitals is required and not there, lowercase `missing` is optional and not there, `present` means the file exists — diff it, never replace it. `UNDRAWN` marks the one row that is not this boundary's own file: `architecture/landscape.likec4` is the whole system's map and already holds every other boundary, so ADD to it rather than rewriting it. Until an element in it resolves to this directory, `loam validate --all` reports `landscape.service-unmodelled` (error).", " "));
137
+ console.log(bullet("The flags in that table: `MISSING` in capitals is required and not there, lowercase `missing` is optional and not there, `present` means the file exists — diff it, never replace it. `UNDRAWN` and `EDGELESS` mark the one row that is not this boundary's own file: `architecture/landscape.likec4` is the whole system's map and already holds every other boundary, so ADD to it rather than rewriting it. Until an element in it resolves to this directory, `loam validate --all` reports `landscape.service-unmodelled` (error); `EDGELESS` means an element already does and no edge touches it, so what the map lacks is the calls in and out of it, never a second element.", " "));
125
138
  console.log("");
126
139
  // What the rest of the page is, so a reader who stops here still knows what
127
140
  // they scrolled past — and where the same content lives for an agent, which
@@ -215,9 +228,15 @@ function printLandscape(b) {
215
228
  return;
216
229
  }
217
230
  if (!l.parses) {
218
- console.log(" architecture/landscape.likec4 does not parse — bind by hand, and fix it first.");
231
+ // Not "architecture/landscape.likec4 does not parse": the map is the whole
232
+ // `architecture/` project, so the file that broke is usually a sibling —
233
+ // and this line accused the landscape of errors `validate --all` blamed on
234
+ // a palette. The instruction printed directly below names the documents
235
+ // that actually failed, so the headline says the project and nothing more.
236
+ console.log(" The fleet map (architecture/) does not parse — fix that first; the files are named below.");
219
237
  console.log("");
220
238
  console.log(wrap(l.instruction ?? "", " "));
239
+ printExpects(l);
221
240
  return;
222
241
  }
223
242
  if (l.elements.length === 0) {
@@ -234,10 +253,30 @@ function printLandscape(b) {
234
253
  console.log(` ← ${e.from} ${e.op ?? `"${e.title ?? ""}"`}`);
235
254
  for (const e of l.outbound)
236
255
  console.log(` → ${e.to} ${e.op ?? `"${e.title ?? ""}"`}`);
237
- if (l.expects.length > 0) {
256
+ // The edgeless state: an element and no edge lines above it. The modelled
257
+ // branch used to return here without ever printing an instruction, which
258
+ // is how the text view briefed "the map owes nothing" over two empty lists.
259
+ if (l.instruction !== null) {
238
260
  console.log("");
239
- console.log(wrap(`openapi.yaml must define ${l.expects.map((o) => `'${o}'`).join(", ")} — the fleet already calls ${l.expects.length === 1 ? "it" : "them"}, and a contract that omits ${l.expects.length === 1 ? "it" : "them"} fails spine.op-undefined the moment it lands.`, " "));
261
+ console.log(wrap(l.instruction, " "));
240
262
  }
263
+ printExpects(l);
264
+ }
265
+ /**
266
+ * What the fleet already calls on this service, and why the contract owes it.
267
+ *
268
+ * Printed in the unparseable branch as well as the healthy one: the landscape
269
+ * FILE still answers who calls this service when a sibling under `architecture/`
270
+ * breaks the project, and it is what decides whether `openapi.yaml` is flagged
271
+ * required — so a reader who is told the map is unreadable still gets the
272
+ * evidence behind that row.
273
+ */
274
+ function printExpects(l) {
275
+ if (l.expects.length === 0)
276
+ return;
277
+ const it = l.expects.length === 1 ? "it" : "them";
278
+ console.log("");
279
+ console.log(wrap(`openapi.yaml must define ${l.expects.map((o) => `'${o}'`).join(", ")} — the fleet already calls ${it}, and a contract that omits ${it} fails spine.op-undefined the moment it lands.`, " "));
241
280
  }
242
281
  /**
243
282
  * Wrap prose so the brief is readable in a terminal. Continuation lines are
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/commands/adopt/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The brief as a person reads it.\n *\n * A module of its own because the brief is the one loam output an agent is\n * expected to FOLLOW rather than parse: `--json` carries the same eight targets,\n * and this decides only how they read. The wrapping matters for that reason —\n * a continuation line indented past its marker cannot be misread as a second\n * bullet, which is what a hand-followed checklist gets wrong first.\n */\nimport { SEVERITY_MARK } from \"../../core/vocabulary/report.js\";\nimport { type Brief } from \"../../core/brief/brief.js\";\nimport { VIA_ALL, type BriefCheck } from \"../../core/brief/checks.js\";\nimport type { BriefTarget } from \"../../core/brief/targets.js\";\n\n/* ------------------------------------------------------------------ */\n/* Text */\n/* ------------------------------------------------------------------ */\n\n/**\n * `full` is the `--targets` pointer — the sentence naming the run that carries\n * the invariant half of the brief. Its PRESENCE is what narrows this view, so\n * the two views cannot disagree about what `--targets` means: exactly the\n * sections `--targets` omits from `--json` are the sections skipped here.\n * `undefined` is the default brief, unchanged byte for byte.\n */\nexport function render(b: Brief, warnings: string[], full?: string): void {\n console.log(`adopt ${b.service} — write the baseline into ${b.path}/\\n`);\n // In the header, before the work: a warning printed after eight artifact\n // shapes is a warning read after the agent has started writing.\n for (const w of warnings) {\n console.log(wrap(`! ${w}`, \" \"));\n console.log(\"\");\n }\n if (full === undefined) {\n // First, above even the never-overwrite rule, because the rule is an\n // instruction and this is the answer to \"what am I looking at\": measured on\n // the example fleet, this view is over 650 lines and 5,600 words to an\n // unpaged terminal, and the two rows that actually orient a person —\n // `model.likec4 create MISSING`, `spec.md create MISSING` — land past line\n // 450, behind four hundred lines of prose. `--json` is explicitly the agent\n // contract, so nothing was served by leaving the human view unintroduced.\n // `--targets` skips it — bullet three describes sections that view omits,\n // and that view has its own pointer for the same job.\n printOrientation(b);\n console.log(wrap(b.rule, \" \"));\n\n // Before the artifact table, because the table is the output and this is the\n // input: an agent that meets the file list first starts writing files.\n printWalk(b);\n }\n\n console.log(\"\\n artifacts\\n\");\n // The legend again, next to the thing it decodes. A reader who scrolls back\n // to the table two hundred lines later arrives at four flags and no key, and\n // the capital/lowercase distinction is invisible to anyone who has to\n // remember it rather than read it.\n console.log(wrap(LEGEND_LINE, \" \"));\n console.log(\"\");\n const width = Math.max(...b.targets.map((t) => t.artifact.length));\n for (const t of b.targets) {\n // `edit` is the shared fleet map: the file exists, but \"present\" would read\n // as \"nothing to do here\", which is the opposite of why it is on the list.\n const flag = t.action === \"edit\" ? \"UNDRAWN\" : t.exists ? \"present\" : t.required ? \"MISSING\" : \"missing\";\n console.log(` ${t.artifact.padEnd(width)} ${t.action.padEnd(6)} ${flag.padEnd(7)} ${t.purpose}`);\n }\n\n for (const t of b.targets) printShape(t);\n\n printLandscape(b);\n\n if (full === undefined) printChecking(b);\n else {\n // The pointer takes the place of the sections it stands for, in the same\n // position they would have occupied. Anywhere else — a footnote under the\n // closing instructions, say — and it reads as a cross-reference rather\n // than as the notice that this brief is not all of the brief.\n console.log(\"\\n the rest of the brief\\n\");\n console.log(wrap(full, \" \"));\n }\n\n console.log(\"\\n when you are done\\n\");\n console.log(` loam validate --service ${b.service} --json # fix every error`);\n // The fleet run is not a nicety: --service never reports the landscape\n // cross-check, so a baseline can pass the line above with the fleet map\n // never edited at all.\n console.log(` loam validate --all --json # in the docs repo: the fleet map`);\n console.log(` loam vouch --service ${b.service} # a HUMAN, in the service's own repo`);\n console.log(\"\");\n console.log(\n wrap(\n \"Leave everything `status: draft`. Vouching is somebody reading the code and saying the document matches it — it is not yours to do, and a status with no digest behind it is a claim with nothing behind it.\",\n \" \",\n ),\n );\n}\n\n/** The table's four flags, compressed to one terminal line for the scroll-back reader. */\nconst LEGEND_LINE = \"MISSING required · missing optional · present diff it · UNDRAWN add to the map\";\n\n/**\n * A required target whose work is still outstanding.\n *\n * `!t.exists` alone is the wrong test, and the fleet map is the reason: for\n * every repo after its first service `architecture/landscape.likec4` already\n * exists, so `exists` is true while nothing in it resolves to this boundary —\n * which `loam validate --all` grades `landscape.service-unmodelled`, an ERROR.\n * `action` carries that distinction and nothing else does: `edit` is only ever\n * the undrawn fleet map (see `BriefTarget.action`, core/brief/targets.ts).\n */\nfunction outstanding(t: BriefTarget): boolean {\n return t.required && (!t.exists || t.action === \"edit\");\n}\n\n/**\n * The three sentences that make the rest of the page readable: how much of it\n * is owed, what the table's own convention means, and what the four hundred\n * lines below it are.\n *\n * Every number here is computed from the brief that was just assembled — the\n * outstanding count off `targets`, the section sizes off `walk`, `checks` and\n * `unchecked`. A literal would be right until the next check is added and\n * silently wrong forever after, and this block's whole value is that a reader\n * can trust it instead of scrolling to verify it.\n */\nfunction printOrientation(b: Brief): void {\n const owed = b.targets.filter(outstanding);\n console.log(\" read this first\\n\");\n\n // What is actually owed, named. The count is the load-bearing half: \"some of\n // these are required\" is a sentence a reader skips, \"three of the nine, and\n // here they are\" is one they act on.\n console.log(\n bullet(\n owed.length === 0\n ? `None of the ${String(b.targets.length)} artifacts below is both required and outstanding — every required one is already there. What is left is optional, or an existing document to diff your findings against.`\n : `${String(owed.length)} of the ${String(b.targets.length)} artifacts below ${owed.length === 1 ? \"is\" : \"are\"} required and not yet done: ${owed.map((t) => t.artifact).join(\", \")}. Everything else in the table is optional, or already written and wants diffing rather than replacing.`,\n \" \",\n ),\n );\n console.log(\"\");\n\n // The legend, in full. The table prints `MISSING` and `missing` as if the\n // capitals were emphasis; they are the required/optional distinction, and\n // nothing on the page said so. `UNDRAWN` needs more than a gloss — it is the\n // one row that is not this boundary's file at all.\n console.log(\n bullet(\n \"The flags in that table: `MISSING` in capitals is required and not there, lowercase `missing` is optional and not there, `present` means the file exists — diff it, never replace it. `UNDRAWN` marks the one row that is not this boundary's own file: `architecture/landscape.likec4` is the whole system's map and already holds every other boundary, so ADD to it rather than rewriting it. Until an element in it resolves to this directory, `loam validate --all` reports `landscape.service-unmodelled` (error).\",\n \" \",\n ),\n );\n console.log(\"\");\n\n // What the rest of the page is, so a reader who stops here still knows what\n // they scrolled past — and where the same content lives for an agent, which\n // is the reader this long body was written for.\n console.log(\n bullet(\n `The rest of this page, in order: the ${String(b.walk.length)}-stop order to read the code in, then the grammar of each artifact — every rule there is one a later check depends on — then the ${String(b.checks.length)} named checks the result will face and the ${String(b.unchecked.length)} statements of what nothing checks. \\`loam adopt --service ${b.service} --json\\` carries the same targets for an agent, and \\`--targets\\` narrows that to only the part that varies by boundary.`,\n \" \",\n ),\n );\n console.log(\"\");\n}\n\n/**\n * The invariant half: the frontmatter rules, the checks that run, and the\n * checks that do not exist. A function of its own only so `--targets` can skip\n * all three in one place — the three sections are the exact set `--targets`\n * omits from `--json`, and splitting them here is what keeps that a single\n * decision instead of three that can drift apart.\n */\nfunction printChecking(b: Brief): void {\n console.log(\"\\n frontmatter — on every markdown artifact\\n\");\n for (const [field, what] of Object.entries(b.frontmatter.fields)) {\n console.log(` ${field}`);\n console.log(wrap(what, \" \"));\n }\n console.log(`\\n never write by hand: ${b.frontmatter.never.join(\", \")}`);\n console.log(`\\n${wrap(b.frontmatter.why, \" \")}`);\n\n // Attribution matters here: the fleet cross-check runs under `--all`, and a\n // header promising it to `--service` sent agents chasing a finding that\n // invocation never reports.\n console.log(`\\n what \\`loam validate --service ${b.service}\\` then checks\\n`);\n for (const c of b.checks) {\n if (c.via !== VIA_ALL) printCheck(c);\n }\n console.log(`\\n and what only \\`loam validate --all\\` surfaces\\n`);\n for (const c of b.checks) {\n if (c.via === VIA_ALL) printCheck(c);\n }\n\n console.log(\"\\n what nothing checks\\n\");\n for (const u of b.unchecked) console.log(bullet(u, \" \"));\n}\n\n/**\n * The walk, numbered. The numbers are the point: an unordered list of nine\n * places to look reads as nine optional suggestions, and the two stops that fix\n * what the service IS have to happen before the surfaces are enumerated.\n */\nfunction printWalk(b: Brief): void {\n console.log(\"\\n read the code in this order — nothing below is written from anything else\\n\");\n for (const [i, stop] of b.walk.entries()) {\n // The hang is explicit rather than derived: `wrap` infers one from a leading\n // `- `, and a numbered stop whose second line starts at the number's own\n // column reads as the next stop — which for a list whose ORDER is the point\n // is the one misreading that costs something.\n console.log(wrap(`${String(i + 1)}. ${stop.where}`, \" \", 88, \" \"));\n // Two more columns than the stop it belongs to: where-to-look and\n // what-to-take are different sentences, and at one indent the second reads\n // as a continuation of the first.\n console.log(wrap(stop.find, \" \"));\n console.log(` → ${stop.lands.join(\", \")}`);\n console.log(\"\");\n }\n console.log(wrap(b.walkClose, \" \"));\n}\n\nfunction printCheck(c: BriefCheck): void {\n console.log(` ${SEVERITY_MARK[c.severity]} ${c.code}`);\n console.log(wrap(c.what, \" \"));\n}\n\nconst ACTION_NOTE: Record<BriefTarget[\"action\"], string> = {\n create: \"\",\n diff: \" (exists — diff, do not replace)\",\n edit: \" (the whole fleet's file — ADD to it, never rewrite it)\",\n};\n\nfunction printShape(t: BriefTarget): void {\n console.log(`\\n ${t.path}${ACTION_NOTE[t.action]}\\n`);\n for (const rule of t.shape) console.log(bullet(rule, \" \"));\n if (t.example === undefined) return;\n console.log(\"\");\n for (const line of t.example.trimEnd().split(\"\\n\")) console.log(` ${line}`.trimEnd());\n}\n\nfunction printLandscape(b: Brief): void {\n const l = b.landscape;\n console.log(\"\\n what the fleet already says about this service\\n\");\n // The instruction is the same sentence the JSON carries, printed from the\n // same field — the text view and `--json` cannot drift into briefing two\n // different writes.\n if (!l.present) {\n console.log(\" architecture/landscape.likec4 does not exist yet — nothing to bind to.\");\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n if (!l.parses) {\n console.log(\" architecture/landscape.likec4 does not parse — bind by hand, and fix it first.\");\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n if (l.elements.length === 0) {\n console.log(` Nothing in the landscape models '${b.service}'.`);\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n for (const e of l.elements) {\n const bound = e.bound ? \"bound\" : \"matched by title — bind it\";\n console.log(` ${e.id} = ${e.kind} '${e.title}' (${bound})`);\n }\n for (const e of l.inbound) console.log(` ← ${e.from} ${e.op ?? `\"${e.title ?? \"\"}\"`}`);\n for (const e of l.outbound) console.log(` → ${e.to} ${e.op ?? `\"${e.title ?? \"\"}\"`}`);\n if (l.expects.length > 0) {\n console.log(\"\");\n console.log(\n wrap(\n `openapi.yaml must define ${l.expects.map((o) => `'${o}'`).join(\", \")} — the fleet already calls ${l.expects.length === 1 ? \"it\" : \"them\"}, and a contract that omits ${l.expects.length === 1 ? \"it\" : \"them\"} fails spine.op-undefined the moment it lands.`,\n \" \",\n ),\n );\n }\n}\n\n/**\n * Wrap prose so the brief is readable in a terminal. Continuation lines are\n * indented past the marker, so a wrapped bullet cannot be misread as two.\n */\nfunction wrap(text: string, indent: string, width = 88, hangIndent?: string): string {\n const hang = hangIndent ?? (/^\\s*-\\s/.test(text) ? `${indent} ` : indent);\n const out: string[] = [];\n let line = indent;\n let pad = indent;\n for (const word of text.trim().split(/\\s+/)) {\n if (line.length > pad.length && line.length + 1 + word.length > width) {\n out.push(line);\n pad = hang;\n line = hang + word;\n } else {\n line = line.length > pad.length ? `${line} ${word}` : line + word;\n }\n }\n out.push(line);\n return out.join(\"\\n\");\n}\n\n/** A bullet, wrapped with its continuation lines hanging under the text. */\nfunction bullet(text: string, indent: string): string {\n return wrap(`- ${text}`, indent);\n}\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/commands/adopt/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAc,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,OAAO,EAAmB,MAAM,4BAA4B,CAAC;AAGtE,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,CAAQ,EAAE,QAAkB,EAAE,IAAa;IAChE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,8BAA8B,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IACzE,yEAAyE;IACzE,gEAAgE;IAChE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,qEAAqE;QACrE,4EAA4E;QAC5E,uEAAuE;QACvE,qEAAqE;QACrE,2EAA2E;QAC3E,4EAA4E;QAC5E,0EAA0E;QAC1E,0EAA0E;QAC1E,sDAAsD;QACtD,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAEhC,6EAA6E;QAC7E,uEAAuE;QACvE,SAAS,CAAC,CAAC,CAAC,CAAC;IACf,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,6EAA6E;IAC7E,sEAAsE;IACtE,mCAAmC;IACnC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1B,4EAA4E;QAC5E,2EAA2E;QAC3E,0EAA0E;QAC1E,wEAAwE;QACxE,yEAAyE;QACzE,wCAAwC;QACxC,MAAM,IAAI,GACR,CAAC,CAAC,MAAM,KAAK,MAAM;YACjB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,KAAK,IAAI;gBAC7B,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,SAAS;YACb,CAAC,CAAC,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,CAAC,CAAC,QAAQ;oBACV,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,SAAS,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACzG,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;QAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAEzC,cAAc,CAAC,CAAC,CAAC,CAAC;IAElB,IAAI,IAAI,KAAK,SAAS;QAAE,aAAa,CAAC,CAAC,CAAC,CAAC;SACpC,CAAC;QACJ,yEAAyE;QACzE,0EAA0E;QAC1E,uEAAuE;QACvE,8DAA8D;QAC9D,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,OAAO,8BAA8B,CAAC,CAAC;IACpF,uEAAuE;IACvE,wEAAwE;IACxE,uBAAuB;IACvB,OAAO,CAAC,GAAG,CAAC,yFAAyF,CAAC,CAAC;IACvG,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,OAAO,oDAAoD,CAAC,CAAC;IACvG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CACT,IAAI,CACF,8MAA8M,EAC9M,MAAM,CACP,CACF,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,WAAW,GACf,2HAA2H,CAAC;AAE9H;;;;;;;;;;GAUG;AACH,SAAS,WAAW,CAAC,CAAc;IACjC,OAAO,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,gBAAgB,CAAC,CAAQ;IAChC,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAEnC,6EAA6E;IAC7E,4EAA4E;IAC5E,qCAAqC;IACrC,OAAO,CAAC,GAAG,CACT,MAAM,CACJ,IAAI,CAAC,MAAM,KAAK,CAAC;QACf,CAAC,CAAC,eAAe,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,2KAA2K;QACpN,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,oBAAoB,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,+BAA+B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,yGAAyG,EAC/R,MAAM,CACP,CACF,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,0EAA0E;IAC1E,0EAA0E;IAC1E,6EAA6E;IAC7E,mDAAmD;IACnD,OAAO,CAAC,GAAG,CACT,MAAM,CACJ,upBAAupB,EACvpB,MAAM,CACP,CACF,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,4EAA4E;IAC5E,4EAA4E;IAC5E,gDAAgD;IAChD,OAAO,CAAC,GAAG,CACT,MAAM,CACJ,wCAAwC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,oIAAoI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,8CAA8C,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,8DAA8D,CAAC,CAAC,OAAO,2HAA2H,EACle,MAAM,CACP,CACF,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,CAAQ;IAC7B,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;IAC9D,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAEpD,4EAA4E;IAC5E,wEAAwE;IACxE,4BAA4B;IAC5B,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,OAAO,kBAAkB,CAAC,CAAC;IAC/E,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;YAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;YAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,CAAQ;IACzB,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACzC,6EAA6E;QAC7E,yEAAyE;QACzE,4EAA4E;QAC5E,8CAA8C;QAC9C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5E,kEAAkE;QAClE,2EAA2E;QAC3E,kCAAkC;QAClC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,UAAU,CAAC,CAAa;IAC/B,OAAO,CAAC,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,WAAW,GAA0C;IACzD,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,oCAAoC;IAC1C,IAAI,EAAE,2DAA2D;CAClE,CAAC;AAEF,SAAS,UAAU,CAAC,CAAc;IAChC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvD,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO;IACpC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AAC7F,CAAC;AAED,SAAS,cAAc,CAAC,CAAQ;IAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC;IACtB,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;IACpE,0EAA0E;IAC1E,yEAAyE;IACzE,oBAAoB;IACpB,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,4EAA4E,CAAC,CAAC;QAC1F,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACd,2EAA2E;QAC3E,yEAAyE;QACzE,2EAA2E;QAC3E,wEAAwE;QACxE,2EAA2E;QAC3E,OAAO,CAAC,GAAG,CAAC,+FAA+F,CAAC,CAAC;QAC7G,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/C,YAAY,CAAC,CAAC,CAAC,CAAC;QAChB,OAAO;IACT,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC;QAC/D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,QAAQ,KAAK,GAAG,CAAC,CAAC;IACnE,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3F,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1F,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,IAAI,CAAC,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,YAAY,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,YAAY,CAAC,CAAqB;IACzC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACnC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CACT,IAAI,CACF,4BAA4B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,8BAA8B,EAAE,+BAA+B,EAAE,gDAAgD,EACtL,MAAM,CACP,CACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,IAAI,CAAC,IAAY,EAAE,MAAc,EAAE,KAAK,GAAG,EAAE,EAAE,UAAmB;IACzE,MAAM,IAAI,GAAG,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC3E,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,IAAI,GAAG,MAAM,CAAC;IAClB,IAAI,GAAG,GAAG,MAAM,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;YACtE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,GAAG,GAAG,IAAI,CAAC;YACX,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;QACpE,CAAC;IACH,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,4EAA4E;AAC5E,SAAS,MAAM,CAAC,IAAY,EAAE,MAAc;IAC1C,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * The brief as a person reads it.\n *\n * A module of its own because the brief is the one loam output an agent is\n * expected to FOLLOW rather than parse: `--json` carries the same eight targets,\n * and this decides only how they read. The wrapping matters for that reason —\n * a continuation line indented past its marker cannot be misread as a second\n * bullet, which is what a hand-followed checklist gets wrong first.\n */\nimport { SEVERITY_MARK } from \"../../core/vocabulary/report.js\";\nimport { type Brief } from \"../../core/brief/brief.js\";\nimport { VIA_ALL, type BriefCheck } from \"../../core/brief/checks.js\";\nimport type { BriefTarget } from \"../../core/brief/targets.js\";\n\n/* ------------------------------------------------------------------ */\n/* Text */\n/* ------------------------------------------------------------------ */\n\n/**\n * `full` is the `--targets` pointer — the sentence naming the run that carries\n * the invariant half of the brief. Its PRESENCE is what narrows this view, so\n * the two views cannot disagree about what `--targets` means: exactly the\n * sections `--targets` omits from `--json` are the sections skipped here.\n * `undefined` is the default brief, unchanged byte for byte.\n */\nexport function render(b: Brief, warnings: string[], full?: string): void {\n console.log(`adopt ${b.service} — write the baseline into ${b.path}/\\n`);\n // In the header, before the work: a warning printed after eight artifact\n // shapes is a warning read after the agent has started writing.\n for (const w of warnings) {\n console.log(wrap(`! ${w}`, \" \"));\n console.log(\"\");\n }\n if (full === undefined) {\n // First, above even the never-overwrite rule, because the rule is an\n // instruction and this is the answer to \"what am I looking at\": measured on\n // the example fleet, this view is over 650 lines and 5,600 words to an\n // unpaged terminal, and the two rows that actually orient a person —\n // `model.likec4 create MISSING`, `spec.md create MISSING` — land past line\n // 450, behind four hundred lines of prose. `--json` is explicitly the agent\n // contract, so nothing was served by leaving the human view unintroduced.\n // `--targets` skips it — bullet three describes sections that view omits,\n // and that view has its own pointer for the same job.\n printOrientation(b);\n console.log(wrap(b.rule, \" \"));\n\n // Before the artifact table, because the table is the output and this is the\n // input: an agent that meets the file list first starts writing files.\n printWalk(b);\n }\n\n console.log(\"\\n artifacts\\n\");\n // The legend again, next to the thing it decodes. A reader who scrolls back\n // to the table two hundred lines later arrives at four flags and no key, and\n // the capital/lowercase distinction is invisible to anyone who has to\n // remember it rather than read it.\n console.log(wrap(LEGEND_LINE, \" \"));\n console.log(\"\");\n const width = Math.max(...b.targets.map((t) => t.artifact.length));\n for (const t of b.targets) {\n // `edit` is the shared fleet map: the file exists, but \"present\" would read\n // as \"nothing to do here\", which is the opposite of why it is on the list.\n // Two words for it, because the two states want different work: `UNDRAWN`\n // when nothing resolves to the service, `EDGELESS` when an element does\n // and no edge touches it — a reader acting on `UNDRAWN` there would look\n // for a box that is already on the map.\n const flag =\n t.action === \"edit\"\n ? b.landscape.modelled === true\n ? \"EDGELESS\"\n : \"UNDRAWN\"\n : t.exists\n ? \"present\"\n : t.required\n ? \"MISSING\"\n : \"missing\";\n console.log(` ${t.artifact.padEnd(width)} ${t.action.padEnd(6)} ${flag.padEnd(7)} ${t.purpose}`);\n }\n\n for (const t of b.targets) printShape(t);\n\n printLandscape(b);\n\n if (full === undefined) printChecking(b);\n else {\n // The pointer takes the place of the sections it stands for, in the same\n // position they would have occupied. Anywhere else — a footnote under the\n // closing instructions, say — and it reads as a cross-reference rather\n // than as the notice that this brief is not all of the brief.\n console.log(\"\\n the rest of the brief\\n\");\n console.log(wrap(full, \" \"));\n }\n\n console.log(\"\\n when you are done\\n\");\n console.log(` loam validate --service ${b.service} --json # fix every error`);\n // The fleet run is not a nicety: --service never reports the landscape\n // cross-check, so a baseline can pass the line above with the fleet map\n // never edited at all.\n console.log(` loam validate --all --json # in the docs repo: the fleet map`);\n console.log(` loam vouch --service ${b.service} # a HUMAN, in the service's own repo`);\n console.log(\"\");\n console.log(\n wrap(\n \"Leave everything `status: draft`. Vouching is somebody reading the code and saying the document matches it — it is not yours to do, and a status with no digest behind it is a claim with nothing behind it.\",\n \" \",\n ),\n );\n}\n\n/** The table's five flags, compressed to one terminal line for the scroll-back reader. */\nconst LEGEND_LINE =\n \"MISSING required · missing optional · present diff it · UNDRAWN add to the map · EDGELESS an element exists, no edge does\";\n\n/**\n * A required target whose work is still outstanding.\n *\n * `!t.exists` alone is the wrong test, and the fleet map is the reason: for\n * every repo after its first service `architecture/landscape.likec4` already\n * exists, so `exists` is true while nothing in it resolves to this boundary —\n * which `loam validate --all` grades `landscape.service-unmodelled`, an ERROR.\n * `action` carries that distinction and nothing else does: `edit` is only ever\n * the fleet map still owed — undrawn, or drawn with no edge touching it (see\n * `BriefTarget.action`, core/brief/targets.ts).\n */\nfunction outstanding(t: BriefTarget): boolean {\n return t.required && (!t.exists || t.action === \"edit\");\n}\n\n/**\n * The three sentences that make the rest of the page readable: how much of it\n * is owed, what the table's own convention means, and what the four hundred\n * lines below it are.\n *\n * Every number here is computed from the brief that was just assembled — the\n * outstanding count off `targets`, the section sizes off `walk`, `checks` and\n * `unchecked`. A literal would be right until the next check is added and\n * silently wrong forever after, and this block's whole value is that a reader\n * can trust it instead of scrolling to verify it.\n */\nfunction printOrientation(b: Brief): void {\n const owed = b.targets.filter(outstanding);\n console.log(\" read this first\\n\");\n\n // What is actually owed, named. The count is the load-bearing half: \"some of\n // these are required\" is a sentence a reader skips, \"three of the nine, and\n // here they are\" is one they act on.\n console.log(\n bullet(\n owed.length === 0\n ? `None of the ${String(b.targets.length)} artifacts below is both required and outstanding — every required one is already there. What is left is optional, or an existing document to diff your findings against.`\n : `${String(owed.length)} of the ${String(b.targets.length)} artifacts below ${owed.length === 1 ? \"is\" : \"are\"} required and not yet done: ${owed.map((t) => t.artifact).join(\", \")}. Everything else in the table is optional, or already written and wants diffing rather than replacing.`,\n \" \",\n ),\n );\n console.log(\"\");\n\n // The legend, in full. The table prints `MISSING` and `missing` as if the\n // capitals were emphasis; they are the required/optional distinction, and\n // nothing on the page said so. `UNDRAWN` needs more than a gloss — it is the\n // one row that is not this boundary's file at all.\n console.log(\n bullet(\n \"The flags in that table: `MISSING` in capitals is required and not there, lowercase `missing` is optional and not there, `present` means the file exists — diff it, never replace it. `UNDRAWN` and `EDGELESS` mark the one row that is not this boundary's own file: `architecture/landscape.likec4` is the whole system's map and already holds every other boundary, so ADD to it rather than rewriting it. Until an element in it resolves to this directory, `loam validate --all` reports `landscape.service-unmodelled` (error); `EDGELESS` means an element already does and no edge touches it, so what the map lacks is the calls in and out of it, never a second element.\",\n \" \",\n ),\n );\n console.log(\"\");\n\n // What the rest of the page is, so a reader who stops here still knows what\n // they scrolled past — and where the same content lives for an agent, which\n // is the reader this long body was written for.\n console.log(\n bullet(\n `The rest of this page, in order: the ${String(b.walk.length)}-stop order to read the code in, then the grammar of each artifact — every rule there is one a later check depends on — then the ${String(b.checks.length)} named checks the result will face and the ${String(b.unchecked.length)} statements of what nothing checks. \\`loam adopt --service ${b.service} --json\\` carries the same targets for an agent, and \\`--targets\\` narrows that to only the part that varies by boundary.`,\n \" \",\n ),\n );\n console.log(\"\");\n}\n\n/**\n * The invariant half: the frontmatter rules, the checks that run, and the\n * checks that do not exist. A function of its own only so `--targets` can skip\n * all three in one place — the three sections are the exact set `--targets`\n * omits from `--json`, and splitting them here is what keeps that a single\n * decision instead of three that can drift apart.\n */\nfunction printChecking(b: Brief): void {\n console.log(\"\\n frontmatter — on every markdown artifact\\n\");\n for (const [field, what] of Object.entries(b.frontmatter.fields)) {\n console.log(` ${field}`);\n console.log(wrap(what, \" \"));\n }\n console.log(`\\n never write by hand: ${b.frontmatter.never.join(\", \")}`);\n console.log(`\\n${wrap(b.frontmatter.why, \" \")}`);\n\n // Attribution matters here: the fleet cross-check runs under `--all`, and a\n // header promising it to `--service` sent agents chasing a finding that\n // invocation never reports.\n console.log(`\\n what \\`loam validate --service ${b.service}\\` then checks\\n`);\n for (const c of b.checks) {\n if (c.via !== VIA_ALL) printCheck(c);\n }\n console.log(`\\n and what only \\`loam validate --all\\` surfaces\\n`);\n for (const c of b.checks) {\n if (c.via === VIA_ALL) printCheck(c);\n }\n\n console.log(\"\\n what nothing checks\\n\");\n for (const u of b.unchecked) console.log(bullet(u, \" \"));\n}\n\n/**\n * The walk, numbered. The numbers are the point: an unordered list of nine\n * places to look reads as nine optional suggestions, and the two stops that fix\n * what the service IS have to happen before the surfaces are enumerated.\n */\nfunction printWalk(b: Brief): void {\n console.log(\"\\n read the code in this order — nothing below is written from anything else\\n\");\n for (const [i, stop] of b.walk.entries()) {\n // The hang is explicit rather than derived: `wrap` infers one from a leading\n // `- `, and a numbered stop whose second line starts at the number's own\n // column reads as the next stop — which for a list whose ORDER is the point\n // is the one misreading that costs something.\n console.log(wrap(`${String(i + 1)}. ${stop.where}`, \" \", 88, \" \"));\n // Two more columns than the stop it belongs to: where-to-look and\n // what-to-take are different sentences, and at one indent the second reads\n // as a continuation of the first.\n console.log(wrap(stop.find, \" \"));\n console.log(` → ${stop.lands.join(\", \")}`);\n console.log(\"\");\n }\n console.log(wrap(b.walkClose, \" \"));\n}\n\nfunction printCheck(c: BriefCheck): void {\n console.log(` ${SEVERITY_MARK[c.severity]} ${c.code}`);\n console.log(wrap(c.what, \" \"));\n}\n\nconst ACTION_NOTE: Record<BriefTarget[\"action\"], string> = {\n create: \"\",\n diff: \" (exists — diff, do not replace)\",\n edit: \" (the whole fleet's file — ADD to it, never rewrite it)\",\n};\n\nfunction printShape(t: BriefTarget): void {\n console.log(`\\n ${t.path}${ACTION_NOTE[t.action]}\\n`);\n for (const rule of t.shape) console.log(bullet(rule, \" \"));\n if (t.example === undefined) return;\n console.log(\"\");\n for (const line of t.example.trimEnd().split(\"\\n\")) console.log(` ${line}`.trimEnd());\n}\n\nfunction printLandscape(b: Brief): void {\n const l = b.landscape;\n console.log(\"\\n what the fleet already says about this service\\n\");\n // The instruction is the same sentence the JSON carries, printed from the\n // same field — the text view and `--json` cannot drift into briefing two\n // different writes.\n if (!l.present) {\n console.log(\" architecture/landscape.likec4 does not exist yet — nothing to bind to.\");\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n if (!l.parses) {\n // Not \"architecture/landscape.likec4 does not parse\": the map is the whole\n // `architecture/` project, so the file that broke is usually a sibling —\n // and this line accused the landscape of errors `validate --all` blamed on\n // a palette. The instruction printed directly below names the documents\n // that actually failed, so the headline says the project and nothing more.\n console.log(\" The fleet map (architecture/) does not parse — fix that first; the files are named below.\");\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n printExpects(l);\n return;\n }\n if (l.elements.length === 0) {\n console.log(` Nothing in the landscape models '${b.service}'.`);\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n for (const e of l.elements) {\n const bound = e.bound ? \"bound\" : \"matched by title — bind it\";\n console.log(` ${e.id} = ${e.kind} '${e.title}' (${bound})`);\n }\n for (const e of l.inbound) console.log(` ← ${e.from} ${e.op ?? `\"${e.title ?? \"\"}\"`}`);\n for (const e of l.outbound) console.log(` → ${e.to} ${e.op ?? `\"${e.title ?? \"\"}\"`}`);\n // The edgeless state: an element and no edge lines above it. The modelled\n // branch used to return here without ever printing an instruction, which\n // is how the text view briefed \"the map owes nothing\" over two empty lists.\n if (l.instruction !== null) {\n console.log(\"\");\n console.log(wrap(l.instruction, \" \"));\n }\n printExpects(l);\n}\n\n/**\n * What the fleet already calls on this service, and why the contract owes it.\n *\n * Printed in the unparseable branch as well as the healthy one: the landscape\n * FILE still answers who calls this service when a sibling under `architecture/`\n * breaks the project, and it is what decides whether `openapi.yaml` is flagged\n * required — so a reader who is told the map is unreadable still gets the\n * evidence behind that row.\n */\nfunction printExpects(l: Brief[\"landscape\"]): void {\n if (l.expects.length === 0) return;\n const it = l.expects.length === 1 ? \"it\" : \"them\";\n console.log(\"\");\n console.log(\n wrap(\n `openapi.yaml must define ${l.expects.map((o) => `'${o}'`).join(\", \")} — the fleet already calls ${it}, and a contract that omits ${it} fails spine.op-undefined the moment it lands.`,\n \" \",\n ),\n );\n}\n\n/**\n * Wrap prose so the brief is readable in a terminal. Continuation lines are\n * indented past the marker, so a wrapped bullet cannot be misread as two.\n */\nfunction wrap(text: string, indent: string, width = 88, hangIndent?: string): string {\n const hang = hangIndent ?? (/^\\s*-\\s/.test(text) ? `${indent} ` : indent);\n const out: string[] = [];\n let line = indent;\n let pad = indent;\n for (const word of text.trim().split(/\\s+/)) {\n if (line.length > pad.length && line.length + 1 + word.length > width) {\n out.push(line);\n pad = hang;\n line = hang + word;\n } else {\n line = line.length > pad.length ? `${line} ${word}` : line + word;\n }\n }\n out.push(line);\n return out.join(\"\\n\");\n}\n\n/** A bullet, wrapped with its continuation lines hanging under the text. */\nfunction bullet(text: string, indent: string): string {\n return wrap(`- ${text}`, indent);\n}\n"]}
@@ -9,6 +9,12 @@
9
9
  * — `validate` would report it next, and reporting it here is what stops that
10
10
  * being a surprise.
11
11
  *
12
+ * The map is not the only document the C4 axis writes. What a delta nests
13
+ * INSIDE a service belongs to that service's `model.likec4` when the model
14
+ * extends the map — the splicer routes it there, this stages the write, and
15
+ * `./model/extending.ts` proves the result parses beside the merged map before
16
+ * anything lands.
17
+ *
12
18
  * `planFlows` at the bottom is the other half of the same axis and shares this
13
19
  * module for that reason rather than for the file cap: both write into
14
20
  * `architecture/`, and the two merges are read together — the map gains the
@@ -17,12 +23,16 @@
17
23
  * there is nothing to merge partially.
18
24
  */
19
25
  import { existsSync } from "node:fs";
26
+ import { join } from "node:path";
20
27
  import { planWrite, readUtf8 } from "../../../core/staging/writes.js";
21
28
  import { planLandscapeMerge } from "../../../core/c4/splice/landscape-merge.js";
29
+ import { spliceModel } from "../../../core/c4/splice/model/merge.js";
22
30
  import { titleOf } from "../../../core/c4/splice/identity/edges.js";
23
31
  import { parseServiceId } from "../../../core/kernel/ids/service.js";
24
32
  import { landscapePath as landscapeFile } from "../../../core/repo/paths.js";
25
33
  import { enumeratedServiceIds } from "../../../core/repo/service-target.js";
34
+ import { extendingModels } from "../../../core/c4/service-model/fleet/extending.js";
35
+ import { modelMergeErrors } from "./model/extending.js";
26
36
  import {} from "./state.js";
27
37
  import { ArchiveFailure } from "./refusal.js";
28
38
  import { featurePaths } from "../../../core/repo/paths.js";
@@ -63,13 +73,18 @@ export async function planLandscape(config, gated, plan, say) {
63
73
  const newEls = delta.elements.filter((e) => e.tags.includes(id));
64
74
  const newRels = delta.relationships.filter((r) => r.tags.includes(id));
65
75
  if (existsSync(landscapePath)) {
76
+ const deltaText = await readUtf8(deltaLikec4);
66
77
  const plan = await planLandscapeMerge({
67
78
  landscapeText: await readUtf8(landscapePath),
68
- deltaText: await readUtf8(deltaLikec4),
79
+ deltaText,
69
80
  deltaElements: delta.elements,
70
81
  newEls,
71
82
  newRels,
72
83
  featureId: id,
84
+ // A service whose model EXTENDS the map owns its own interior, so the
85
+ // merge routes what is nested under it into that model instead of the
86
+ // map (verification 2026-09-04, E1).
87
+ models: await extendingModels(config.docsDir, config.fleet),
73
88
  });
74
89
  // A service can arrive on the ARCHITECTURE axis alone: an element this
75
90
  // merge ADDS, carrying a `metadata { service }` binding, with no
@@ -98,6 +113,35 @@ export async function planLandscape(config, gated, plan, say) {
98
113
  for (const r of plan.addedRels) {
99
114
  say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} "${r.title ?? ""}"`);
100
115
  }
116
+ for (const additions of plan.models) {
117
+ const content = spliceModel({ additions, deltaText, featureId: id });
118
+ // The model's own parse net, and it has to be a PROJECT one: an
119
+ // extending model resolves against the map, and against the map this
120
+ // very archive would leave — so the merged landscape goes into the set
121
+ // beside it. Refused here, at plan time, nothing written, exactly as an
122
+ // unparseable merged landscape is.
123
+ const errors = await modelMergeErrors({
124
+ docsDir: config.docsDir,
125
+ landscape: plan.content,
126
+ model: { path: additions.model.path, content },
127
+ });
128
+ if (errors.length > 0) {
129
+ throw new ArchiveFailure("merge-failed", `the merged ${additions.model.path} would not parse (${errors.length} error(s): ${errors.slice(0, 3).join("; ")}) — ` +
130
+ `nothing was written. This model is read beside architecture/landscape.likec4 and nothing else, so an addition ` +
131
+ `fits only if the map declares its kind and every element it names. Most often it is a kind or tag missing from ` +
132
+ `the map's specification block, or a reference to ANOTHER service's interior, which lives in that service's own ` +
133
+ `model and is not in this project — draw that call service to service on the map instead. Fix the map's ` +
134
+ `specification or the delta, then re-run`);
135
+ }
136
+ writes.push(planWrite(join(config.docsDir, ...additions.model.path.split("/")), content));
137
+ say(` architecture: merged into ${additions.model.path} — ` +
138
+ `+${additions.els.length} element(s), +${additions.rels.length} relationship(s)`);
139
+ for (const e of additions.els)
140
+ say(` + ${e.title} (${e.kind})`);
141
+ for (const r of additions.rels) {
142
+ say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} "${r.title ?? ""}"`);
143
+ }
144
+ }
101
145
  }
102
146
  else {
103
147
  say(`\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);
@@ -1 +1 @@
1
- {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet map merge, the flows drawn over it, and the services this archive\n * brings into existence.\n *\n * The landscape is the one document every feature writes into, so it is spliced\n * rather than rewritten: the authored file keeps its shape and the delta's\n * elements are placed into it. A service arriving on the architecture axis alone\n * has no `model.likec4` yet, and the warning for that is carried out of the plan\n * — `validate` would report it next, and reporting it here is what stops that\n * being a surprise.\n *\n * `planFlows` at the bottom is the other half of the same axis and shares this\n * module for that reason rather than for the file cap: both write into\n * `architecture/`, and the two merges are read together — the map gains the\n * elements, the flow gains the ordered hops over them. It is a whole-file copy\n * where the landscape is a splice, because a flow is a document of its own and\n * there is nothing to merge partially.\n */\nimport { existsSync } from \"node:fs\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { planLandscapeMerge } from \"../../../core/c4/splice/landscape-merge.js\";\nimport { titleOf } from \"../../../core/c4/splice/identity/edges.js\";\nimport { parseServiceId } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../../core/repo/service-target.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport { ArchiveFailure } from \"./refusal.js\";\nimport { featurePaths } from \"../../../core/repo/paths.js\";\nimport { featureFlows, livingFlowPath, USECASE_SUBDIR } from \"../../../core/usecases/delta/flows.js\";\nimport { featureDeployments, livingDeploymentPath } from \"../../../core/deployment/delta.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport async function planLandscape(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { id, featureDir, deltaDoc, deltaServices } = gated;\n const deltaLikec4 = featurePaths(featureDir).delta;\n const { writes, planWarns, architectureServices } = plan;\n const landscapePath = landscapeFile(config.docsDir);\n if (deltaDoc !== undefined) {\n const delta = deltaDoc;\n if (delta.errors.length > 0) {\n // --approve overrides loam's JUDGMENT about coherence, never its ability to\n // read an axis. Skipping here would silently drop one merge axis in the one\n // command engineered against quiet partial merges — same rule as an\n // unparseable landscape or openapi: the plan stops before anything is written.\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\\`loam validate --feature ${id}\\`) or delete the file`,\n );\n }\n // The feature tag is the whole selection mechanism of this merge, and since\n // LikeC4 1.59.0 a specification KIND can carry a tag that every element or\n // edge of that kind then inherits. A delta declaring its own feature tag on\n // a kind makes `newEls`/`newRels` below select the entire document —\n // including the context declarations the scaffold ships commented out\n // precisely so they are NOT merged — and archive would splice somebody\n // else's services into the fleet map at exit 0. Mechanical, so `--approve`\n // does not reach it, and refused before the merge is even planned.\n const kindTagged = [\n ...Object.entries(delta.specification?.elementKindTags ?? {}),\n ...Object.entries(delta.specification?.relationshipKindTags ?? {}),\n ].find(([, tags]) => tags.includes(id));\n if (kindTagged !== undefined) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +\n `'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +\n `additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +\n `written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`,\n );\n }\n const newEls = delta.elements.filter((e) => e.tags.includes(id));\n const newRels = delta.relationships.filter((r) => r.tags.includes(id));\n if (existsSync(landscapePath)) {\n const plan = await planLandscapeMerge({\n landscapeText: await readUtf8(landscapePath),\n deltaText: await readUtf8(deltaLikec4),\n deltaElements: delta.elements,\n newEls,\n newRels,\n featureId: id,\n });\n // A service can arrive on the ARCHITECTURE axis alone: an element this\n // merge ADDS, carrying a `metadata { service }` binding, with no\n // `specs/<svc>/` anywhere in the feature. It is a service the fleet gate\n // will demand a directory for the moment this merge lands, so it owes the\n // same warning as one arriving with a requirement delta — and until it\n // did, the closing \"complete + current\" line printed over a landscape\n // this very archive had just made red. Read off the ADDED elements, not\n // the tagged ones: an element the living landscape already had is not\n // arriving, and one that is never merged is not there to demand anything.\n // A binding is document text, which the service path builders no longer accept.\n // The parse cannot actually filter anything here: an illegal binding is\n // `c4.service-binding-invalid`, a coherence ERROR `--approve` does not\n // override, refused before any merge is planned — so the `ok` test only\n // carries the compiler's proof that document text never reaches a path.\n for (const e of plan.addedEls) {\n const parsed = e.service === undefined ? undefined : parseServiceId(e.service);\n if (parsed?.ok === true) architectureServices.add(parsed.id);\n }\n if (plan.content !== null) writes.push(planWrite(landscapePath, plan.content));\n say(`\\n architecture: merged into landscape.likec4 — +${plan.addedEls.length} element(s), +${plan.addedRels.length} relationship(s)`);\n for (const e of plan.addedEls) say(` + ${e.title} (${e.kind})`);\n for (const r of plan.addedRels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n } else {\n say(`\\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);\n }\n }\n\n // A service this archive BRINGS INTO EXISTENCE arrives without the one file\n // `validate` demands of every service: its own model.likec4. The merge cannot\n // write it — the delta's tagged subtree is a landscape-level box, not a\n // container model, and inventing a plausible one is the kind of quiet fiction\n // the rest of loam exists to prevent — so the archive says so instead, and\n // stops claiming the docs are complete. Non-gating: the feature is coherent\n // and the merge is correct; what is missing is the next step, and refusing\n // here would make onboarding a new service impossible in one command.\n // \"New\" is enumeration membership, not an existsSync of services/<svc>/ at\n // the root: a FILED service exists wherever the tree walk found it, and the\n // root probe would grade every filed service as newly created here.\n const enumerated = new Set<string>(await enumeratedServiceIds(config.docsDir));\n const newServices = [...new Set([...deltaServices, ...architectureServices])].filter(\n (svc) => !enumerated.has(svc),\n );\n for (const svc of newServices) {\n // Two shapes of the same debt: a service with a requirement delta gets its\n // directory from this merge, one that arrives only in the landscape gets no\n // directory at all — and `validate --all` fails the second harder\n // (`landscape.service-unmodelled` names the binding with nothing behind it).\n const creates = deltaServices.some((d) => d === svc)\n ? `this archive creates services/${svc}/, but nothing writes services/${svc}/model.likec4`\n : `this archive puts '${svc}' in the landscape, but the fleet has no services/${svc}/ at all`;\n planWarns.push({\n severity: \"warn\",\n code: \"service.no-model\",\n subject: svc,\n message: `${svc}: ${creates} — 'loam validate --all' will report the service as incomplete until it exists. Run 'loam adopt --service ${svc}' from the service repo, or write the model by hand.`,\n });\n }\n}\n\n/**\n * The flows this feature brings, copied into `architecture/usecases/`.\n *\n * A create-only whole-file copy, and the smallest merge in the command: the\n * document is views-only, it names nothing the landscape merge has not already\n * placed (`planFlowOverlay` in the gate is what proves that, before this runs),\n * and a flow the living tree already holds was refused at the gate\n * (`usecase.flow-exists`, `core/usecases/delta/flows.ts` has the reasoning). So\n * by the time this runs every path here is a file that does not exist, and\n * `planWrite` marks it `exclusive` — a race that created it between the gate and\n * the commit fails the write rather than overwriting somebody.\n *\n * It lives beside `planLandscape` rather than beside the requirement merges\n * because it writes into `architecture/`, and the two merges are read together:\n * the landscape gains the elements, the flow gains the hops over them.\n * `unarchive` needs nothing new — a create is undone by deleting, which the\n * snapshot manifest already records for every write in the plan.\n */\nexport async function planFlows(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const flows = await featureFlows(gated.featureDir);\n if (flows.length === 0) return;\n for (const flow of flows) {\n plan.writes.push(planWrite(livingFlowPath(config.docsDir, flow.rel), await readUtf8(flow.path)));\n say(` use case: ${flow.rel} — created architecture/${USECASE_SUBDIR}/${flow.rel}`);\n }\n}\n\n/**\n * The same copy for the third whole-file axis: the topology a feature brings.\n *\n * Beside `planFlows` rather than folded into it, because the two write to\n * different places — a flow into `architecture/usecases/`, a topology straight\n * into `architecture/` — and one function taking a subdirectory argument would\n * make that difference a parameter instead of a decision each axis states.\n * `unarchive` needs nothing here either: a create is undone by deleting.\n */\nexport async function planDeployments(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const docs = await featureDeployments(gated.featureDir);\n if (docs.length === 0) return;\n for (const doc of docs) {\n plan.writes.push(planWrite(livingDeploymentPath(config.docsDir, doc.rel), await readUtf8(doc.path)));\n say(` deployment: ${doc.rel} — created architecture/${doc.rel}`);\n }\n}\n"]}
1
+ {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet map merge, the flows drawn over it, and the services this archive\n * brings into existence.\n *\n * The landscape is the one document every feature writes into, so it is spliced\n * rather than rewritten: the authored file keeps its shape and the delta's\n * elements are placed into it. A service arriving on the architecture axis alone\n * has no `model.likec4` yet, and the warning for that is carried out of the plan\n * — `validate` would report it next, and reporting it here is what stops that\n * being a surprise.\n *\n * The map is not the only document the C4 axis writes. What a delta nests\n * INSIDE a service belongs to that service's `model.likec4` when the model\n * extends the map — the splicer routes it there, this stages the write, and\n * `./model/extending.ts` proves the result parses beside the merged map before\n * anything lands.\n *\n * `planFlows` at the bottom is the other half of the same axis and shares this\n * module for that reason rather than for the file cap: both write into\n * `architecture/`, and the two merges are read together — the map gains the\n * elements, the flow gains the ordered hops over them. It is a whole-file copy\n * where the landscape is a splice, because a flow is a document of its own and\n * there is nothing to merge partially.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { planLandscapeMerge } from \"../../../core/c4/splice/landscape-merge.js\";\nimport { spliceModel } from \"../../../core/c4/splice/model/merge.js\";\nimport { titleOf } from \"../../../core/c4/splice/identity/edges.js\";\nimport { parseServiceId } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../../core/repo/service-target.js\";\nimport { extendingModels } from \"../../../core/c4/service-model/fleet/extending.js\";\nimport { modelMergeErrors } from \"./model/extending.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport { ArchiveFailure } from \"./refusal.js\";\nimport { featurePaths } from \"../../../core/repo/paths.js\";\nimport { featureFlows, livingFlowPath, USECASE_SUBDIR } from \"../../../core/usecases/delta/flows.js\";\nimport { featureDeployments, livingDeploymentPath } from \"../../../core/deployment/delta.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function planLandscape(\n config: { docsDir: DocsDir; fleet: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { id, featureDir, deltaDoc, deltaServices } = gated;\n const deltaLikec4 = featurePaths(featureDir).delta;\n const { writes, planWarns, architectureServices } = plan;\n const landscapePath = landscapeFile(config.docsDir);\n if (deltaDoc !== undefined) {\n const delta = deltaDoc;\n if (delta.errors.length > 0) {\n // --approve overrides loam's JUDGMENT about coherence, never its ability to\n // read an axis. Skipping here would silently drop one merge axis in the one\n // command engineered against quiet partial merges — same rule as an\n // unparseable landscape or openapi: the plan stops before anything is written.\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\\`loam validate --feature ${id}\\`) or delete the file`,\n );\n }\n // The feature tag is the whole selection mechanism of this merge, and since\n // LikeC4 1.59.0 a specification KIND can carry a tag that every element or\n // edge of that kind then inherits. A delta declaring its own feature tag on\n // a kind makes `newEls`/`newRels` below select the entire document —\n // including the context declarations the scaffold ships commented out\n // precisely so they are NOT merged — and archive would splice somebody\n // else's services into the fleet map at exit 0. Mechanical, so `--approve`\n // does not reach it, and refused before the merge is even planned.\n const kindTagged = [\n ...Object.entries(delta.specification?.elementKindTags ?? {}),\n ...Object.entries(delta.specification?.relationshipKindTags ?? {}),\n ].find(([, tags]) => tags.includes(id));\n if (kindTagged !== undefined) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +\n `'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +\n `additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +\n `written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`,\n );\n }\n const newEls = delta.elements.filter((e) => e.tags.includes(id));\n const newRels = delta.relationships.filter((r) => r.tags.includes(id));\n if (existsSync(landscapePath)) {\n const deltaText = await readUtf8(deltaLikec4);\n const plan = await planLandscapeMerge({\n landscapeText: await readUtf8(landscapePath),\n deltaText,\n deltaElements: delta.elements,\n newEls,\n newRels,\n featureId: id,\n // A service whose model EXTENDS the map owns its own interior, so the\n // merge routes what is nested under it into that model instead of the\n // map (verification 2026-09-04, E1).\n models: await extendingModels(config.docsDir, config.fleet),\n });\n // A service can arrive on the ARCHITECTURE axis alone: an element this\n // merge ADDS, carrying a `metadata { service }` binding, with no\n // `specs/<svc>/` anywhere in the feature. It is a service the fleet gate\n // will demand a directory for the moment this merge lands, so it owes the\n // same warning as one arriving with a requirement delta — and until it\n // did, the closing \"complete + current\" line printed over a landscape\n // this very archive had just made red. Read off the ADDED elements, not\n // the tagged ones: an element the living landscape already had is not\n // arriving, and one that is never merged is not there to demand anything.\n // A binding is document text, which the service path builders no longer accept.\n // The parse cannot actually filter anything here: an illegal binding is\n // `c4.service-binding-invalid`, a coherence ERROR `--approve` does not\n // override, refused before any merge is planned — so the `ok` test only\n // carries the compiler's proof that document text never reaches a path.\n for (const e of plan.addedEls) {\n const parsed = e.service === undefined ? undefined : parseServiceId(e.service);\n if (parsed?.ok === true) architectureServices.add(parsed.id);\n }\n if (plan.content !== null) writes.push(planWrite(landscapePath, plan.content));\n say(`\\n architecture: merged into landscape.likec4 — +${plan.addedEls.length} element(s), +${plan.addedRels.length} relationship(s)`);\n for (const e of plan.addedEls) say(` + ${e.title} (${e.kind})`);\n for (const r of plan.addedRels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n for (const additions of plan.models) {\n const content = spliceModel({ additions, deltaText, featureId: id });\n // The model's own parse net, and it has to be a PROJECT one: an\n // extending model resolves against the map, and against the map this\n // very archive would leave — so the merged landscape goes into the set\n // beside it. Refused here, at plan time, nothing written, exactly as an\n // unparseable merged landscape is.\n const errors = await modelMergeErrors({\n docsDir: config.docsDir,\n landscape: plan.content,\n model: { path: additions.model.path, content },\n });\n if (errors.length > 0) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `the merged ${additions.model.path} would not parse (${errors.length} error(s): ${errors.slice(0, 3).join(\"; \")}) — ` +\n `nothing was written. This model is read beside architecture/landscape.likec4 and nothing else, so an addition ` +\n `fits only if the map declares its kind and every element it names. Most often it is a kind or tag missing from ` +\n `the map's specification block, or a reference to ANOTHER service's interior, which lives in that service's own ` +\n `model and is not in this project — draw that call service to service on the map instead. Fix the map's ` +\n `specification or the delta, then re-run`,\n );\n }\n writes.push(planWrite(join(config.docsDir, ...additions.model.path.split(\"/\")), content));\n say(\n ` architecture: merged into ${additions.model.path} — ` +\n `+${additions.els.length} element(s), +${additions.rels.length} relationship(s)`,\n );\n for (const e of additions.els) say(` + ${e.title} (${e.kind})`);\n for (const r of additions.rels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n }\n } else {\n say(`\\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);\n }\n }\n\n // A service this archive BRINGS INTO EXISTENCE arrives without the one file\n // `validate` demands of every service: its own model.likec4. The merge cannot\n // write it — the delta's tagged subtree is a landscape-level box, not a\n // container model, and inventing a plausible one is the kind of quiet fiction\n // the rest of loam exists to prevent — so the archive says so instead, and\n // stops claiming the docs are complete. Non-gating: the feature is coherent\n // and the merge is correct; what is missing is the next step, and refusing\n // here would make onboarding a new service impossible in one command.\n // \"New\" is enumeration membership, not an existsSync of services/<svc>/ at\n // the root: a FILED service exists wherever the tree walk found it, and the\n // root probe would grade every filed service as newly created here.\n const enumerated = new Set<string>(await enumeratedServiceIds(config.docsDir));\n const newServices = [...new Set([...deltaServices, ...architectureServices])].filter(\n (svc) => !enumerated.has(svc),\n );\n for (const svc of newServices) {\n // Two shapes of the same debt: a service with a requirement delta gets its\n // directory from this merge, one that arrives only in the landscape gets no\n // directory at all — and `validate --all` fails the second harder\n // (`landscape.service-unmodelled` names the binding with nothing behind it).\n const creates = deltaServices.some((d) => d === svc)\n ? `this archive creates services/${svc}/, but nothing writes services/${svc}/model.likec4`\n : `this archive puts '${svc}' in the landscape, but the fleet has no services/${svc}/ at all`;\n planWarns.push({\n severity: \"warn\",\n code: \"service.no-model\",\n subject: svc,\n message: `${svc}: ${creates} — 'loam validate --all' will report the service as incomplete until it exists. Run 'loam adopt --service ${svc}' from the service repo, or write the model by hand.`,\n });\n }\n}\n\n/**\n * The flows this feature brings, copied into `architecture/usecases/`.\n *\n * A create-only whole-file copy, and the smallest merge in the command: the\n * document is views-only, it names nothing the landscape merge has not already\n * placed (`planFlowOverlay` in the gate is what proves that, before this runs),\n * and a flow the living tree already holds was refused at the gate\n * (`usecase.flow-exists`, `core/usecases/delta/flows.ts` has the reasoning). So\n * by the time this runs every path here is a file that does not exist, and\n * `planWrite` marks it `exclusive` — a race that created it between the gate and\n * the commit fails the write rather than overwriting somebody.\n *\n * It lives beside `planLandscape` rather than beside the requirement merges\n * because it writes into `architecture/`, and the two merges are read together:\n * the landscape gains the elements, the flow gains the hops over them.\n * `unarchive` needs nothing new — a create is undone by deleting, which the\n * snapshot manifest already records for every write in the plan.\n */\nexport async function planFlows(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const flows = await featureFlows(gated.featureDir);\n if (flows.length === 0) return;\n for (const flow of flows) {\n plan.writes.push(planWrite(livingFlowPath(config.docsDir, flow.rel), await readUtf8(flow.path)));\n say(` use case: ${flow.rel} — created architecture/${USECASE_SUBDIR}/${flow.rel}`);\n }\n}\n\n/**\n * The same copy for the third whole-file axis: the topology a feature brings.\n *\n * Beside `planFlows` rather than folded into it, because the two write to\n * different places — a flow into `architecture/usecases/`, a topology straight\n * into `architecture/` — and one function taking a subdirectory argument would\n * make that difference a parameter instead of a decision each axis states.\n * `unarchive` needs nothing here either: a create is undone by deleting.\n */\nexport async function planDeployments(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const docs = await featureDeployments(gated.featureDir);\n if (docs.length === 0) return;\n for (const doc of docs) {\n plan.writes.push(planWrite(livingDeploymentPath(config.docsDir, doc.rel), await readUtf8(doc.path)));\n say(` deployment: ${doc.rel} — created architecture/${doc.rel}`);\n }\n}\n"]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Whether the model this merge would leave behind is one LikeC4 can read.
3
+ *
4
+ * It is here because this is the seam that HAS the other document: an extending
5
+ * model is never parsed alone, it is parsed beside the `architecture/` project,
6
+ * and the project the merge must be proven against is the one this very archive
7
+ * would leave behind — the merged map, not the living one. `core/c4/splice` is
8
+ * a pure text-to-text computation — texts in, texts out — so proving the result
9
+ * stays with the command that decides to write.
10
+ *
11
+ * READING the fleet's extending models used to be here too, as the other half
12
+ * of one seam. It moved to `core/c4/service-model/fleet/extending.ts` when the
13
+ * archive stopped being the only reader: `core/c4/project/staged.ts` previews
14
+ * the same merge for every feature-side gate, and `core/` may not import
15
+ * `commands/`. That module's header carries the fail-open the move closes.
16
+ */
17
+ import { copyFile, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
18
+ import { tmpdir } from "node:os";
19
+ import { dirname, join, resolve } from "node:path";
20
+ import { errorText } from "../../../../core/c4/likec4.js";
21
+ import { architectureProjectDocuments } from "../../../../core/c4/project/architecture.js";
22
+ import { asLoadedDoc, loadProject } from "../../../../core/c4/project/load.js";
23
+ import { repoPath } from "../../../../core/envelope/json.js";
24
+ import { landscapePath } from "../../../../core/repo/paths.js";
25
+ /**
26
+ * What LikeC4 says about the model this merge would leave, read the way the
27
+ * per-service grade reads it — the `architecture/` project plus this one file.
28
+ *
29
+ * Empty means clean. Anything else refuses the archive at plan time, with
30
+ * nothing written, for the reason the landscape's own parse net exists:
31
+ * splicing is text surgery, and a document that does not parse takes a whole
32
+ * service's grade down (`c4.invalid`) the moment it lands.
33
+ *
34
+ * Errors carry the path the author wrote rather than the staged copy's, and a
35
+ * staging failure is reported as an error rather than swallowed: a merge that
36
+ * could not be proven is not one that passed.
37
+ */
38
+ export async function modelMergeErrors(proof) {
39
+ const { docsDir, landscape, model } = proof;
40
+ let root;
41
+ try {
42
+ root = await mkdtemp(join(tmpdir(), "loam-model-merge-"));
43
+ const map = resolve(landscapePath(docsDir));
44
+ const authored = new Map();
45
+ const staged = [];
46
+ for (const path of await architectureProjectDocuments(docsDir)) {
47
+ const rel = repoPath(docsDir, path);
48
+ const at = landscape !== null && resolve(path) === map
49
+ ? await write(root, rel, landscape)
50
+ : await copy(root, rel, path);
51
+ authored.set(at, rel);
52
+ staged.push(at);
53
+ }
54
+ const at = await write(root, model.path, model.content);
55
+ authored.set(at, model.path);
56
+ staged.push(at);
57
+ const doc = asLoadedDoc(await loadProject(root, staged));
58
+ return doc.errors.map((e) => {
59
+ const where = e.sourceFsPath === undefined ? undefined : authored.get(resolve(e.sourceFsPath));
60
+ return where === undefined ? errorText(e) : `${where} ${errorText(e)}`;
61
+ });
62
+ }
63
+ catch (err) {
64
+ return [err instanceof Error ? err.message : String(err)];
65
+ }
66
+ finally {
67
+ if (root !== undefined)
68
+ await rm(root, { recursive: true, force: true });
69
+ }
70
+ }
71
+ /** Mirror one authored document into the staged tree at its docs-relative path. */
72
+ async function copy(root, rel, path) {
73
+ const dest = join(root, ...rel.split("/"));
74
+ await mkdir(dirname(dest), { recursive: true });
75
+ await copyFile(path, dest);
76
+ return resolve(dest);
77
+ }
78
+ /** The same for bytes loam holds rather than a file — the merge previews. */
79
+ async function write(root, rel, content) {
80
+ const dest = join(root, ...rel.split("/"));
81
+ await mkdir(dirname(dest), { recursive: true });
82
+ await writeFile(dest, content, "utf8");
83
+ return resolve(dest);
84
+ }
85
+ //# sourceMappingURL=extending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extending.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/model/extending.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Whether the model this merge would leave behind is one LikeC4 can read.\n *\n * It is here because this is the seam that HAS the other document: an extending\n * model is never parsed alone, it is parsed beside the `architecture/` project,\n * and the project the merge must be proven against is the one this very archive\n * would leave behind — the merged map, not the living one. `core/c4/splice` is\n * a pure text-to-text computation — texts in, texts out — so proving the result\n * stays with the command that decides to write.\n *\n * READING the fleet's extending models used to be here too, as the other half\n * of one seam. It moved to `core/c4/service-model/fleet/extending.ts` when the\n * archive stopped being the only reader: `core/c4/project/staged.ts` previews\n * the same merge for every feature-side gate, and `core/` may not import\n * `commands/`. That module's header carries the fail-open the move closes.\n */\nimport { copyFile, mkdir, mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { errorText } from \"../../../../core/c4/likec4.js\";\nimport { architectureProjectDocuments } from \"../../../../core/c4/project/architecture.js\";\nimport { asLoadedDoc, loadProject } from \"../../../../core/c4/project/load.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { repoPath } from \"../../../../core/envelope/json.js\";\nimport { landscapePath } from \"../../../../core/repo/paths.js\";\n\nexport interface ModelProof {\n docsDir: DocsDir;\n /** The merged landscape, or null when this archive leaves the map as it is. */\n landscape: string | null;\n /** The merged model: repo-relative path, and the text the archive would write. */\n model: { path: string; content: string };\n}\n\n/**\n * What LikeC4 says about the model this merge would leave, read the way the\n * per-service grade reads it — the `architecture/` project plus this one file.\n *\n * Empty means clean. Anything else refuses the archive at plan time, with\n * nothing written, for the reason the landscape's own parse net exists:\n * splicing is text surgery, and a document that does not parse takes a whole\n * service's grade down (`c4.invalid`) the moment it lands.\n *\n * Errors carry the path the author wrote rather than the staged copy's, and a\n * staging failure is reported as an error rather than swallowed: a merge that\n * could not be proven is not one that passed.\n */\nexport async function modelMergeErrors(proof: ModelProof): Promise<string[]> {\n const { docsDir, landscape, model } = proof;\n let root: string | undefined;\n try {\n root = await mkdtemp(join(tmpdir(), \"loam-model-merge-\"));\n const map = resolve(landscapePath(docsDir));\n const authored = new Map<string, string>();\n const staged: string[] = [];\n for (const path of await architectureProjectDocuments(docsDir)) {\n const rel = repoPath(docsDir, path);\n const at =\n landscape !== null && resolve(path) === map\n ? await write(root, rel, landscape)\n : await copy(root, rel, path);\n authored.set(at, rel);\n staged.push(at);\n }\n const at = await write(root, model.path, model.content);\n authored.set(at, model.path);\n staged.push(at);\n const doc = asLoadedDoc(await loadProject(root, staged));\n return doc.errors.map((e) => {\n const where = e.sourceFsPath === undefined ? undefined : authored.get(resolve(e.sourceFsPath));\n return where === undefined ? errorText(e) : `${where} ${errorText(e)}`;\n });\n } catch (err) {\n return [err instanceof Error ? err.message : String(err)];\n } finally {\n if (root !== undefined) await rm(root, { recursive: true, force: true });\n }\n}\n\n/** Mirror one authored document into the staged tree at its docs-relative path. */\nasync function copy(root: string, rel: string, path: string): Promise<string> {\n const dest = join(root, ...rel.split(\"/\"));\n await mkdir(dirname(dest), { recursive: true });\n await copyFile(path, dest);\n return resolve(dest);\n}\n\n/** The same for bytes loam holds rather than a file — the merge previews. */\nasync function write(root: string, rel: string, content: string): Promise<string> {\n const dest = join(root, ...rel.split(\"/\"));\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, content, \"utf8\");\n return resolve(dest);\n}\n"]}
@@ -65,7 +65,10 @@ export async function archiveLocked(config, featureId, opts) {
65
65
  await planGlossary(read, gated, planned, say);
66
66
  await planOpenapiContracts(read, gated, planned, say);
67
67
  await planAsyncapiContracts(read, gated, planned, say);
68
- await planLandscape(config, gated, planned, say);
68
+ // `read`, not `config`: the C4 merge now asks which services own their own
69
+ // interior, and that answer comes from the fleet enumeration this run has
70
+ // already paid for.
71
+ await planLandscape(read, gated, planned, say);
69
72
  // The flows this feature brings, after the elements they draw hops over: the
70
73
  // copy is create-only and refused at the gate if the living tree already holds
71
74
  // one (`core/usecases/delta/flows.ts`), and it is ordered after the landscape
@@ -282,7 +285,7 @@ async function subsystemViewsStale(docsDir) {
282
285
  const doc = await loadArchitecture(docsDir);
283
286
  if (doc.errors.length > 0)
284
287
  return false;
285
- const state = await viewsState(subsystemViewsPath(docsDir), await listFleetTree(docsDir), doc.elements);
288
+ const state = await viewsState(subsystemViewsPath(docsDir), await listFleetTree(docsDir), doc);
286
289
  return !state.agrees;
287
290
  }
288
291
  catch {