@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +37 -34
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +103 -23
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +106 -46
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +21 -107
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +36 -14
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +55 -52
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/protocol.js +1 -1
- package/dist/core/agent/protocol.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/support/report.js +46 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +147 -16
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/docs.js +31 -22
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/delta.js +10 -7
- package/dist/core/glossary/delta.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +40 -10
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +31 -7
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +70 -12
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"views.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/views.ts"],"names":[],"mappings":"AAkEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAuB9C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,GACV,GAAG,cAAc,+EAA+E;IAChG,iGAAiG;IACjG,gFAAgF;IAChF,0FAA0F,CAAC;AAE7F;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAa,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACtG,MAAM,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,KAAK,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,IAAI,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7F,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,OAA2B,EAAE,KAAwB,EAAE,MAAe;IACvF,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IACxB,MAAM,GAAG,GAAG,CAAC,UAAU,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,GAAG,IAAI,EAAE,cAAc,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;IAChG,IAAI,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,oBAAoB,mBAAmB,EAAE,CAAC,CAAC;IAChE,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;IAC1D,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,kBAAkB,GAAG,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,CAAC;IACjF,yEAAyE;IACzE,8EAA8E;IAC9E,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,GAAG,CAAC,IAAI,CAAC,eAAe,GAAG,SAAS,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC;IACpD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * `architecture/services.likec4` — one generated LikeC4 view per service,\n * showing that service, its own interior, and its links to everything else the\n * fleet map draws.\n *\n * GENERATED, never authored, exactly like the subsystem views beside it:\n * authored bytes stay authored (archive's verbatim-splice promise), `loam\n * subsystem sync` owns this file outright, and staleness against the tree is a\n * content compare and never a parse — a views-only document does not parse\n * standalone, so nothing in loam ever loads it. docs/DESIGN.md rule 26 does not\n * change with a second file: what a view SHOWS stays unread in every document,\n * this one included.\n *\n * ## The recipe, and why the obvious one is wrong\n *\n * Measured at likec4 1.59.2 on a 120-service and a 58-service fleet. Each view\n * is written as\n *\n * view service_<id> of <fqn> {\n * title '<subsystem path> / <service id>'\n * global style services // only when the map declares the group\n * include * // the service and its own interior\n * include <peer> // one line per peer, id-sorted\n * exclude * -> * // drop the peer-to-peer cross traffic\n * include -> <fqn> // its inbound edges\n * include <fqn> -> // its outbound edges\n * include <fqn>.* -> <fqn>.* // its internal wiring\n * }\n *\n * Three things in that shape are measurements rather than taste, and each one\n * fails SILENTLY — zero errors, a wrong picture — if it is tidied away:\n *\n * 1. **The peers are named, not wildcarded.** `./peers.ts` carries the\n * measurement: `include <fqn> -> *` collapses every counterpart to its\n * GROUP box and yields zero drill-down on any map that nests services.\n * 2. **The edge predicates are UNARY.** `include -> <fqn>` and `include <fqn>\n * ->`, never the binary `include * -> <fqn>` / `include <fqn> -> *`.\n * Measured on `payments.svc0` of the 58-service fleet, swapping the two\n * unary lines for their binary forms takes the view from 11 nodes to 15:\n * the four parent GROUP boxes of the named peers come back as nodes of\n * their own, so the view regrows exactly what rule 1 removed. Zero errors\n * either way, which is what makes it worth a comment.\n * 3. **`exclude * -> *` sits between the includes and the edge lines.** Without\n * it the named peers bring their own cross traffic with them: on the same\n * subject, 24 edges instead of 9, on a view about one service.\n *\n * `of <fqn>` is the navigation half and costs one keyword: LikeC4 computes\n * `navigateTo` into a view declared `of` an element from every OTHER view that\n * draws that element, with no authored links. That is what makes the fleet map,\n * every subsystem view and every other service view click-through into a\n * service. Two views of one element are legal — the first document\n * alphabetically wins the default target and both stay reachable, because the\n * subject's own node cycles to the other — so a team's authored element view in\n * `landscape.likec4` keeps precedence over this file, which sorts later.\n *\n * ## What arrives from outside\n *\n * Everything about the TREE — which services exist, what each one's element is,\n * what its title reads — arrives composed, in `ServiceViewSubject`. This package\n * may not import `../views.ts` or `../text.ts`: the parent imports it, and the\n * reverse edge would be the package cycle `scripts/package-graph.mjs` refuses.\n * So the title arrives already through `likec4String` rather than escaped a\n * second time here; a second escaper is how one of the two forgets an\n * apostrophe and ships a fleet-wide parse error.\n */\nimport type { Rel } from \"../../../../c4/likec4.js\";\nimport { GENERATED_MARK } from \"./header.js\";\nimport { peersOf } from \"./peers.js\";\nimport { serviceViewId } from \"./view-id.js\";\n\n/**\n * The global style group these views reference when the map declares one, the\n * exact counterpart of `SUBSYSTEM_STYLE_GROUP` and reserved on the same terms:\n * presence of the DECLARATION is the whole opt-in, because referencing an\n * undeclared style id is a parse error that blanks the entire `architecture/`\n * project in the renderer. The name mirrors the file it styles.\n */\nexport const SERVICE_STYLE_GROUP = \"services\";\n\n/** One service this file draws — resolved against the map, and already escaped. */\nexport interface ServiceViewSubject {\n /** The `services/<id>/` directory id: the sort key and the view id's source. */\n id: string;\n /** The landscape element it resolves to — the view's `of`, and its subject. */\n fqn: string;\n /** `<subsystem path> / <service id>`, already through `likec4String`. */\n title: string;\n}\n\n/** Everything the file render reads, as one record — see the banner on why it arrives composed. */\nexport interface ServiceViewsInput {\n subjects: readonly ServiceViewSubject[];\n /** The map's edges, from which each subject's peer set is computed. */\n relationships: readonly Rel[];\n /** Emit `global style services` — true only when the map DECLARES that group. */\n styled: boolean;\n /** The one file-level comment the render may write, or null. */\n disclosure: string | null;\n}\n\n/**\n * The header every generated file opens with — stable bytes, part of the\n * content-compare contract, naming the regenerator so a reader who found the\n * file before the docs knows what owns it. `loam subsystem sync` regenerates\n * BOTH generated files, which is why one command is named here and why one\n * finding code (`subsystem.views-stale`) honestly covers both.\n *\n * It OPENS with the shared `GENERATED_MARK` rather than spelling it, because\n * the same bytes are what `loam subsystem sync` reads back off disk to decide\n * whether the file at this reserved path is loam's to rewrite at all\n * (`./header.js`). A second literal here is how a file loam wrote stops being\n * recognised as loam's.\n */\nconst HEADER =\n `${GENERATED_MARK} — one overview view per service under services/, drawn from the fleet map.\\n` +\n \"// Do not edit: `loam subsystem sync` regenerates this file from the directories and the map,\\n\" +\n \"// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\\n\" +\n \"// A navigation aid, not the fleet's architecture: author your own diagrams elsewhere.\\n\";\n\n/**\n * Render the whole file, or null when no service resolves to a map element —\n * the file must then be ABSENT, so a fleet whose map draws nothing never\n * carries a generated artifact that says nothing.\n *\n * Services sorted by id and peers sorted by id, one `include` per line: the\n * output is byte-reproducible on any machine from the same committed bytes (no\n * timestamps, no absolute paths, no readdir-order dependence), and two\n * concurrent changes to different services touch different lines, so git merges\n * the file without intervention.\n */\nexport function renderServiceViews(input: ServiceViewsInput): string | null {\n if (input.subjects.length === 0) return null;\n const lines: string[] = [HEADER, ...(input.disclosure === null ? [] : [input.disclosure]), \"views {\"];\n const subjects = [...input.subjects].sort((a, b) => (a.id < b.id ? -1 : 1));\n for (const [i, subject] of subjects.entries()) {\n if (i > 0) lines.push(\"\");\n lines.push(...viewLines(subject, peersOf(subject.fqn, input.relationships), input.styled));\n }\n lines.push(\"}\");\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * One view, in the order the parser demands: metadata FIRST, and that is a hard\n * constraint rather than a taste — measured, an `include` line followed by\n * `title` is a parse error (\"Expecting token of type '}' but found `title`\"),\n * and one parse error in this file blanks the whole `architecture/` project in\n * the renderer. `global style` sits after `title` and before anything that\n * includes, where the same pin measured it parsing with zero errors.\n */\nfunction viewLines(subject: ServiceViewSubject, peers: readonly string[], styled: boolean): string[] {\n const { fqn } = subject;\n const out = [` view ${serviceViewId(subject.id)} of ${fqn} {`, ` title '${subject.title}'`];\n if (styled) out.push(` global style ${SERVICE_STYLE_GROUP}`);\n out.push(\" include *\");\n for (const peer of peers) out.push(` include ${peer}`);\n out.push(\" exclude * -> *\", ` include -> ${fqn}`, ` include ${fqn} ->`);\n // The interior wiring, last. Measured: `include <fqn>.* -> <fqn>.*` on a\n // CHILDLESS element is legal — `likec4 validate` reports valid and the export\n // carries zero errors — so one generated line serves both model shapes: an\n // EXTENDING model's containers live in the root project and are drawn, a\n // STANDALONE model's are not in this project and the line matches nothing.\n out.push(` include ${fqn}.* -> ${fqn}.*`, \" }\");\n return out;\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is the generated file on disk what this tree and this map render to?
|
|
3
|
+
*
|
|
4
|
+
* One function, because the question now has three askers — `validate --all`
|
|
5
|
+
* (which turns a disagreement into `subsystem.views-stale`), `loam subsystem
|
|
6
|
+
* sync` and the write verbs (which repair it), and `loam archive` (which must
|
|
7
|
+
* not report a repo current when it has just made the file stale). Three
|
|
8
|
+
* private compares would be three chances to answer differently about one
|
|
9
|
+
* file, and the module that mints the bytes is the one that owes the answer.
|
|
10
|
+
*
|
|
11
|
+
* The PATH arrives as a parameter rather than a `DocsDir`: `repo/paths.ts`
|
|
12
|
+
* owns the spelling of every file in a docs repo, and importing it here would
|
|
13
|
+
* close the package cycle `repo → repo/tree → repo/tree/render → repo` that
|
|
14
|
+
* `scripts/package-graph.mjs` exists to refuse. `walk.ts` pays the same price
|
|
15
|
+
* for the same reason and says so.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { renderServiceOverviews, renderSubsystemViews, viewsAgree } from "./views.js";
|
|
20
|
+
export async function viewsState(path, tree, map) {
|
|
21
|
+
return stateOf(path, renderSubsystemViews(tree, map));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The same question about the OTHER generated file, `architecture/services.likec4`.
|
|
25
|
+
*
|
|
26
|
+
* A second exported function rather than a `which` parameter, and a second call
|
|
27
|
+
* at every asker rather than one that answers about both: the two files go stale
|
|
28
|
+
* for different reasons and are repaired in one command, so a caller that wants
|
|
29
|
+
* both asks twice and names each file in its own finding —
|
|
30
|
+
* `subsystem.views-stale` carries the path precisely so one code can honestly
|
|
31
|
+
* cover two files. Folding them into one state would lose which one drifted,
|
|
32
|
+
* which is the only thing the message has to say.
|
|
33
|
+
*/
|
|
34
|
+
export async function serviceViewsState(path, tree, map) {
|
|
35
|
+
return stateOf(path, renderServiceOverviews(tree, map));
|
|
36
|
+
}
|
|
37
|
+
/** Read the file, or record its absence, and compare — the half both askers share. */
|
|
38
|
+
async function stateOf(path, expected) {
|
|
39
|
+
const actual = existsSync(path) ? await readFile(path, "utf8") : null;
|
|
40
|
+
return { actual, expected, agrees: viewsAgree(actual, expected) };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=stale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/stale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAiB,sBAAsB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgBrG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAe,EAAE,GAAa;IAC3E,OAAO,OAAO,CAAC,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,IAAe,EAAE,GAAa;IAClF,OAAO,OAAO,CAAC,IAAI,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,sFAAsF;AACtF,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,QAAuB;IAC1D,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AACpE,CAAC","sourcesContent":["/**\n * Is the generated file on disk what this tree and this map render to?\n *\n * One function, because the question now has three askers — `validate --all`\n * (which turns a disagreement into `subsystem.views-stale`), `loam subsystem\n * sync` and the write verbs (which repair it), and `loam archive` (which must\n * not report a repo current when it has just made the file stale). Three\n * private compares would be three chances to answer differently about one\n * file, and the module that mints the bytes is the one that owes the answer.\n *\n * The PATH arrives as a parameter rather than a `DocsDir`: `repo/paths.ts`\n * owns the spelling of every file in a docs repo, and importing it here would\n * close the package cycle `repo → repo/tree → repo/tree/render → repo` that\n * `scripts/package-graph.mjs` exists to refuse. `walk.ts` pays the same price\n * for the same reason and says so.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { FleetTree } from \"../walk.js\";\nimport { type MapFacts, renderServiceOverviews, renderSubsystemViews, viewsAgree } from \"./views.js\";\n\n/**\n * The three states, kept apart: absent, must-be-absent, and\n * present-but-different are three different sentences, and a fleet whose views\n * file does not exist is not a fleet whose views file is blank.\n */\nexport interface ViewsState {\n /** The bytes on disk, or null when the file is not there. */\n actual: string | null;\n /** The bytes the tree renders to, or null when the file must be absent. */\n expected: string | null;\n /** Do the two say the same thing? Content, not bytes — `viewsAgree` says why. */\n agrees: boolean;\n}\n\nexport async function viewsState(path: string, tree: FleetTree, map: MapFacts): Promise<ViewsState> {\n return stateOf(path, renderSubsystemViews(tree, map));\n}\n\n/**\n * The same question about the OTHER generated file, `architecture/services.likec4`.\n *\n * A second exported function rather than a `which` parameter, and a second call\n * at every asker rather than one that answers about both: the two files go stale\n * for different reasons and are repaired in one command, so a caller that wants\n * both asks twice and names each file in its own finding —\n * `subsystem.views-stale` carries the path precisely so one code can honestly\n * cover two files. Folding them into one state would lose which one drifted,\n * which is the only thing the message has to say.\n */\nexport async function serviceViewsState(path: string, tree: FleetTree, map: MapFacts): Promise<ViewsState> {\n return stateOf(path, renderServiceOverviews(tree, map));\n}\n\n/** Read the file, or record its absence, and compare — the half both askers share. */\nasync function stateOf(path: string, expected: string | null): Promise<ViewsState> {\n const actual = existsSync(path) ? await readFile(path, \"utf8\") : null;\n return { actual, expected, agrees: viewsAgree(actual, expected) };\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One string, safely spelled as a LikeC4 single-quoted literal.
|
|
3
|
+
*
|
|
4
|
+
* The marker reader validates that a `title` is a string and nothing else
|
|
5
|
+
* (`../marker.ts`), so every measurement below is reachable from a
|
|
6
|
+
* `subsystem.yaml` a human may legally write, against the pinned likec4:
|
|
7
|
+
*
|
|
8
|
+
* - a raw `'` is 2 parse errors, and a parse error in ONE document of the
|
|
9
|
+
* `architecture/` project blanks the model for every document in it — a
|
|
10
|
+
* fleet would lose its whole map over one apostrophe in a marker;
|
|
11
|
+
* - a trailing lone `\` is 3 errors (it escapes the closing quote);
|
|
12
|
+
* - `C:\path` written raw is 0 errors and a quietly wrong `C:path` label;
|
|
13
|
+
* - `Ops' } view evil { include x` is 0 errors and INJECTS a view — and an
|
|
14
|
+
* injected id equal to an authored one silently replaces that author's
|
|
15
|
+
* view in the rendered project. That one is the reason this function is
|
|
16
|
+
* not optional.
|
|
17
|
+
*
|
|
18
|
+
* Normalize, then escape, and in that order: every run of control or space
|
|
19
|
+
* characters collapses to one space, because the file's contract is
|
|
20
|
+
* line-oriented (`../views.ts`'s header states the git-merge property in
|
|
21
|
+
* lines) and a literal newline inside an emitted string would split
|
|
22
|
+
* `title '…'` in two. Then `\` before `'`, because doubling the backslashes
|
|
23
|
+
* afterwards would double the ones this function itself inserted.
|
|
24
|
+
*/
|
|
25
|
+
export function likec4String(text) {
|
|
26
|
+
return text
|
|
27
|
+
.replace(/[\p{Cc}\p{Zs}\p{Zl}\p{Zp}]+/gu, " ")
|
|
28
|
+
.trim()
|
|
29
|
+
.split("\\")
|
|
30
|
+
.join("\\\\")
|
|
31
|
+
.split("'")
|
|
32
|
+
.join("\\'");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* ONE subsystem's label: the marker's `title`, or the DIRECTORY NAME when the
|
|
36
|
+
* marker carries none.
|
|
37
|
+
*
|
|
38
|
+
* The fallback is not a nicety. Without a title LikeC4 shows the view id, and
|
|
39
|
+
* the id is hex-escaped for injectivity (`subsystemViewId` says why), so every
|
|
40
|
+
* subsystem whose directory name holds a hyphen — the common case — rendered
|
|
41
|
+
* as `subsystem_platform__order_2dflow`. A directory name is something a human
|
|
42
|
+
* chose; the escaped id never is.
|
|
43
|
+
*
|
|
44
|
+
* A blank or whitespace-only title counts as absent: measured, `title ''`
|
|
45
|
+
* collapses to null at the model layer, so emitting one would put back exactly
|
|
46
|
+
* the unlabelled view this exists to remove.
|
|
47
|
+
*
|
|
48
|
+
* A `/` inside the label becomes `∕` (U+2215), and that substitution is the
|
|
49
|
+
* price of `viewTitle` below composing a path out of these. LikeC4 splits a
|
|
50
|
+
* view title on `/` into browser folders and displays only the last segment —
|
|
51
|
+
* measured: `Payments / refunds` renders as `refunds` with `Payments` demoted
|
|
52
|
+
* to a folder — so an authored slash left alone would silently hide
|
|
53
|
+
* half of what somebody wrote AND invent a folder level the tree does not
|
|
54
|
+
* have. Since loam is the one choosing to compose with `/`, loam owns that
|
|
55
|
+
* consequence rather than passing it on. The whole title stays visible and the
|
|
56
|
+
* folder path stays exactly the subsystem tree. A directory name cannot
|
|
57
|
+
* contain `/`, so the fallback never needs it.
|
|
58
|
+
*/
|
|
59
|
+
export function subsystemLabel(sub) {
|
|
60
|
+
const segment = (text) => likec4String(text).split("/").join("∕");
|
|
61
|
+
const authored = segment(sub.meta.title ?? "");
|
|
62
|
+
return authored === "" ? segment(sub.name) : authored;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The title a view carries: every marked ancestor's label and its own, joined
|
|
66
|
+
* with ` / ` — `Platform / Order flow` for `services/platform/order-flow`.
|
|
67
|
+
*
|
|
68
|
+
* LikeC4 reads that as a path: the browser shows one folder per ancestor and
|
|
69
|
+
* the leaf's own label as the title, so the view list mirrors the directory
|
|
70
|
+
* tree a human already navigates, and two subsystems whose leaf labels are
|
|
71
|
+
* equal are told apart by their parents instead of appearing twice under one
|
|
72
|
+
* name. Nothing is invented — the chain is the tree — and a subsystem directly
|
|
73
|
+
* under `services/` has a chain of one and reads exactly as its own label.
|
|
74
|
+
*
|
|
75
|
+
* The cost, stated because it is real: renaming a PARENT's title rewrites the
|
|
76
|
+
* bytes of every view beneath it, so one marker edit restales more of the file
|
|
77
|
+
* than it used to. The alternative — a fixed `loam subsystems /` prefix —
|
|
78
|
+
* buys the same folder without the churn and puts loam's own name in a
|
|
79
|
+
* reader's UI; the tree's own words won.
|
|
80
|
+
*/
|
|
81
|
+
export function viewTitle(chain) {
|
|
82
|
+
return chain.map(subsystemLabel).join(" / ");
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/text.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI;SACR,OAAO,CAAC,+BAA+B,EAAE,GAAG,CAAC;SAC7C,IAAI,EAAE;SACN,KAAK,CAAC,IAAI,CAAC;SACX,IAAI,CAAC,MAAM,CAAC;SACZ,KAAK,CAAC,GAAG,CAAC;SACV,IAAI,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,cAAc,CAAC,GAAmB;IAChD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC/C,OAAO,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAgC;IACxD,OAAO,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC","sourcesContent":["/**\n * Somebody else's words, made safe to write into a `.likec4` file.\n *\n * The generated views file carries FREE TEXT for the first time — a\n * subsystem's `title` and `description`, straight out of `subsystem.yaml` —\n * and this module is the only place in loam where a person's prose becomes\n * syntax. It sits in its own module beside the body renderer because both the\n * file-level render (`../views.ts`, the view's own `title` line) and the box\n * render (`./body.ts`, a group's label) must escape the same way; two\n * spellings would be two chances to differ, and the one that forgot would ship\n * a fleet-wide parse error.\n */\nimport type { SubsystemEntry } from \"../walk.js\";\n\n/**\n * One string, safely spelled as a LikeC4 single-quoted literal.\n *\n * The marker reader validates that a `title` is a string and nothing else\n * (`../marker.ts`), so every measurement below is reachable from a\n * `subsystem.yaml` a human may legally write, against the pinned likec4:\n *\n * - a raw `'` is 2 parse errors, and a parse error in ONE document of the\n * `architecture/` project blanks the model for every document in it — a\n * fleet would lose its whole map over one apostrophe in a marker;\n * - a trailing lone `\\` is 3 errors (it escapes the closing quote);\n * - `C:\\path` written raw is 0 errors and a quietly wrong `C:path` label;\n * - `Ops' } view evil { include x` is 0 errors and INJECTS a view — and an\n * injected id equal to an authored one silently replaces that author's\n * view in the rendered project. That one is the reason this function is\n * not optional.\n *\n * Normalize, then escape, and in that order: every run of control or space\n * characters collapses to one space, because the file's contract is\n * line-oriented (`../views.ts`'s header states the git-merge property in\n * lines) and a literal newline inside an emitted string would split\n * `title '…'` in two. Then `\\` before `'`, because doubling the backslashes\n * afterwards would double the ones this function itself inserted.\n */\nexport function likec4String(text: string): string {\n return text\n .replace(/[\\p{Cc}\\p{Zs}\\p{Zl}\\p{Zp}]+/gu, \" \")\n .trim()\n .split(\"\\\\\")\n .join(\"\\\\\\\\\")\n .split(\"'\")\n .join(\"\\\\'\");\n}\n\n/**\n * ONE subsystem's label: the marker's `title`, or the DIRECTORY NAME when the\n * marker carries none.\n *\n * The fallback is not a nicety. Without a title LikeC4 shows the view id, and\n * the id is hex-escaped for injectivity (`subsystemViewId` says why), so every\n * subsystem whose directory name holds a hyphen — the common case — rendered\n * as `subsystem_platform__order_2dflow`. A directory name is something a human\n * chose; the escaped id never is.\n *\n * A blank or whitespace-only title counts as absent: measured, `title ''`\n * collapses to null at the model layer, so emitting one would put back exactly\n * the unlabelled view this exists to remove.\n *\n * A `/` inside the label becomes `∕` (U+2215), and that substitution is the\n * price of `viewTitle` below composing a path out of these. LikeC4 splits a\n * view title on `/` into browser folders and displays only the last segment —\n * measured: `Payments / refunds` renders as `refunds` with `Payments` demoted\n * to a folder — so an authored slash left alone would silently hide\n * half of what somebody wrote AND invent a folder level the tree does not\n * have. Since loam is the one choosing to compose with `/`, loam owns that\n * consequence rather than passing it on. The whole title stays visible and the\n * folder path stays exactly the subsystem tree. A directory name cannot\n * contain `/`, so the fallback never needs it.\n */\nexport function subsystemLabel(sub: SubsystemEntry): string {\n const segment = (text: string): string => likec4String(text).split(\"/\").join(\"∕\");\n const authored = segment(sub.meta.title ?? \"\");\n return authored === \"\" ? segment(sub.name) : authored;\n}\n\n/**\n * The title a view carries: every marked ancestor's label and its own, joined\n * with ` / ` — `Platform / Order flow` for `services/platform/order-flow`.\n *\n * LikeC4 reads that as a path: the browser shows one folder per ancestor and\n * the leaf's own label as the title, so the view list mirrors the directory\n * tree a human already navigates, and two subsystems whose leaf labels are\n * equal are told apart by their parents instead of appearing twice under one\n * name. Nothing is invented — the chain is the tree — and a subsystem directly\n * under `services/` has a chain of one and reads exactly as its own label.\n *\n * The cost, stated because it is real: renaming a PARENT's title rewrites the\n * bytes of every view beneath it, so one marker edit restales more of the file\n * than it used to. The alternative — a fixed `loam subsystems /` prefix —\n * buys the same folder without the churn and puts loam's own name in a\n * reader's UI; the tree's own words won.\n */\nexport function viewTitle(chain: readonly SubsystemEntry[]): string {\n return chain.map(subsystemLabel).join(\" / \");\n}\n"]}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated views file: `architecture/subsystems.likec4`, one LikeC4 view
|
|
3
|
+
* per subsystem enumerating the services beneath it. GENERATED, never
|
|
4
|
+
* authored — authored bytes stay authored (archive's verbatim-splice promise),
|
|
5
|
+
* so the tree is mirrored into a file of its own that `loam subsystem sync`
|
|
6
|
+
* owns outright, and staleness against the tree is a byte compare
|
|
7
|
+
* (`subsystem.views-stale` in `validate --all`), never a parse: a spike
|
|
8
|
+
* confirmed a views-only document does NOT parse standalone (its `include`
|
|
9
|
+
* lines reference elements the landscape defines), so nothing in loam ever
|
|
10
|
+
* loads this file (and rule 26 does not change that: this file is GENERATED and
|
|
11
|
+
* holds STATIC views, whose contents loam may not read in any document) — only
|
|
12
|
+
* the LikeC4 renderer, which merges the whole `architecture/` project per
|
|
13
|
+
* likec4.config.json, resolves it.
|
|
14
|
+
*
|
|
15
|
+
* Each view opens with the LABEL a human reads — `title`, and `description`
|
|
16
|
+
* when the marker carries one — because this file exists only to be rendered
|
|
17
|
+
* and an unlabelled view is shown by its id, which is hex-escaped for
|
|
18
|
+
* injectivity and reads as `subsystem_platform__order_2dflow`. Both
|
|
19
|
+
* strings are somebody else's words and go through `likec4String`, which
|
|
20
|
+
* records what a raw one does.
|
|
21
|
+
*
|
|
22
|
+
* The output is deterministic and line-oriented, and both halves are the
|
|
23
|
+
* contract: subsystems sorted by path, members sorted by id, ONE `include`
|
|
24
|
+
* per line — so two concurrent moves into different groups touch different
|
|
25
|
+
* lines and git merges the file without intervention, exactly as the
|
|
26
|
+
* service-grouped landscape splice already achieves. Byte-reproducible on any
|
|
27
|
+
* machine: no timestamps, no absolute paths, no readdir-order dependence (the
|
|
28
|
+
* walk sorts, and every list is re-sorted here). Plain lexicographic sorts on
|
|
29
|
+
* purpose — the ids' own grammar is ASCII, an INVALID name is still ordered
|
|
30
|
+
* deterministically, and `compareIds`' numeric tokenizing lives in `repo/`,
|
|
31
|
+
* which this package must not import (`walk.ts` says why the edge only points
|
|
32
|
+
* the other way).
|
|
33
|
+
*
|
|
34
|
+
* Membership is TRANSITIVE: a subsystem's view includes every service beneath
|
|
35
|
+
* it at any depth, so a parent's view is the whole group's map rather than an
|
|
36
|
+
* empty frame around its children. An empty view body is legal LikeC4 (same
|
|
37
|
+
* spike), so an empty subsystem renders as one — `subsystem new` must be
|
|
38
|
+
* usable before anything moves in.
|
|
39
|
+
*
|
|
40
|
+
* Include lines name the landscape ELEMENT id, resolved through the same
|
|
41
|
+
* element→service join `validate` uses (`serviceResolver`: explicit
|
|
42
|
+
* `metadata { service }` binding first, then a title naming a real service) —
|
|
43
|
+
* because element ids in real landscapes are not service ids, and a view of
|
|
44
|
+
* raw service ids would silently render empty for every such fleet. A member
|
|
45
|
+
* with no landscape element is omitted from the includes: the omission is
|
|
46
|
+
* already an error (`landscape.service-unmodelled`), and rendering only what
|
|
47
|
+
* the committed landscape resolves keeps the output a function of committed
|
|
48
|
+
* bytes alone.
|
|
49
|
+
*/
|
|
50
|
+
import {} from "../../../c4/likec4.js";
|
|
51
|
+
import { renderContext, viewBody } from "./body.js";
|
|
52
|
+
import { GENERATED_MARK } from "./service/header.js";
|
|
53
|
+
import { escapeViewIdSegment } from "./service/view-id.js";
|
|
54
|
+
import { renderServiceViews, SERVICE_STYLE_GROUP, } from "./service/views.js";
|
|
55
|
+
import { likec4String, viewTitle } from "./text.js";
|
|
56
|
+
/**
|
|
57
|
+
* The global style group a generated view references when the map declares
|
|
58
|
+
* one: `global { styleGroup subsystems { … } }` (or the single-rule form
|
|
59
|
+
* `global { style subsystems … }` — LikeC4 files both under one id table).
|
|
60
|
+
* Presence is the opt-in and it is read off a DECLARATION, never a view: a
|
|
61
|
+
* generated view cannot carry an inline `style` rule of its own (the file is
|
|
62
|
+
* regenerated wholesale), and the one mechanism LikeC4 offers a view for
|
|
63
|
+
* borrowing a repository's palette is a reference to a global style by id.
|
|
64
|
+
* Referencing one that is not declared is a parse error that blanks the whole
|
|
65
|
+
* `architecture/` project in the renderer, which is why the line is written
|
|
66
|
+
* only when the id is declared and why its absence leaves the file exactly
|
|
67
|
+
* what it was before this constant existed.
|
|
68
|
+
*
|
|
69
|
+
* One conventional name rather than "the single declared group": a second
|
|
70
|
+
* group somebody declares for a hand-drawn view would otherwise silently
|
|
71
|
+
* switch the generated views' styling OFF and restale the file on a change
|
|
72
|
+
* that touched no subsystem — behaviour flipping on an unrelated declaration
|
|
73
|
+
* is the shape `body.ts` calls "a picture that looks the same whether or not
|
|
74
|
+
* the two agree". The name mirrors the file it styles.
|
|
75
|
+
*/
|
|
76
|
+
export const SUBSYSTEM_STYLE_GROUP = "subsystems";
|
|
77
|
+
/**
|
|
78
|
+
* The header every generated file opens with — stable bytes, part of the
|
|
79
|
+
* byte-compare contract, naming the regenerator so a reader who found the
|
|
80
|
+
* file first knows what owns it.
|
|
81
|
+
*
|
|
82
|
+
* It OPENS with the shared `GENERATED_MARK` rather than spelling it, because
|
|
83
|
+
* the same bytes are what `loam subsystem sync` reads back off disk to decide
|
|
84
|
+
* whether the file standing at this reserved path is loam's to rewrite at all
|
|
85
|
+
* (`./service/header.ts` carries the whole reason). A second literal here is
|
|
86
|
+
* how a file loam wrote stops being recognised as loam's.
|
|
87
|
+
*/
|
|
88
|
+
const HEADER = `${GENERATED_MARK} — the subsystem tree under services/, one view per subsystem.\n` +
|
|
89
|
+
"// Do not edit: `loam subsystem sync` regenerates this file from the directories,\n" +
|
|
90
|
+
"// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\n" +
|
|
91
|
+
"// A scoping aid, not the fleet's architecture: author your own diagrams elsewhere.\n";
|
|
92
|
+
/**
|
|
93
|
+
* The one file-level comment the render may write: the map declares global
|
|
94
|
+
* style groups, and none is the conventional one. Written between `HEADER`
|
|
95
|
+
* and `views {` — outside every view body, graded by nothing — so a team
|
|
96
|
+
* that already declared a palette under another name reads the fix in the
|
|
97
|
+
* diff of the sync that regenerated the file, instead of wondering why the
|
|
98
|
+
* generated views ignore it. Silent when the map declares no group at all: a
|
|
99
|
+
* fleet with no palette is owed no sentence about one. Ids go unescaped
|
|
100
|
+
* because LikeC4's grammar admits only identifier bytes in a style id.
|
|
101
|
+
*
|
|
102
|
+
* The group is a PARAMETER because there are two generated files now and each
|
|
103
|
+
* reserves its own name (`subsystems`, `services`); a map declaring one and not
|
|
104
|
+
* the other must get the sentence in exactly the file that went unstyled.
|
|
105
|
+
*/
|
|
106
|
+
function stylesDisclosure(declared, group) {
|
|
107
|
+
if (declared.length === 0 || declared.includes(group))
|
|
108
|
+
return null;
|
|
109
|
+
return (`// styles: the project declares global style group(s) ${[...declared].sort().join(", ")}, ` +
|
|
110
|
+
`and none named \`${group}\` — these views carry the renderer's defaults until one is.`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Render the views file for a walked tree, or null when the tree has no
|
|
114
|
+
* subsystems — the file must then be ABSENT, so a flat fleet never carries a
|
|
115
|
+
* generated artifact that says nothing.
|
|
116
|
+
*/
|
|
117
|
+
export function renderSubsystemViews(tree, map) {
|
|
118
|
+
if (tree.subsystems.length === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const ctx = renderContext(tree, map.elements);
|
|
121
|
+
const declared = map.globalStyles ?? [];
|
|
122
|
+
const styled = declared.includes(SUBSYSTEM_STYLE_GROUP);
|
|
123
|
+
const disclosure = stylesDisclosure(declared, SUBSYSTEM_STYLE_GROUP);
|
|
124
|
+
const lines = [HEADER, ...(disclosure === null ? [] : [disclosure]), "views {"];
|
|
125
|
+
const subsystems = [...tree.subsystems].sort((a, b) => (pathKey(a) < pathKey(b) ? -1 : 1));
|
|
126
|
+
for (const [i, sub] of subsystems.entries()) {
|
|
127
|
+
if (i > 0)
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(` // services/${pathKey(sub)}`);
|
|
130
|
+
const body = viewBody(ctx, sub);
|
|
131
|
+
if (body.disclosure !== null)
|
|
132
|
+
lines.push(` // ${body.disclosure}`);
|
|
133
|
+
// `of <ancestor>` when — and only when — the map holds an element that owns
|
|
134
|
+
// exactly this subsystem's members (`../render/body.ts` argues the
|
|
135
|
+
// "exactly"). It is the same one keyword the per-service views spend, and it
|
|
136
|
+
// buys the same thing: LikeC4 computes `navigateTo` into this view from
|
|
137
|
+
// every other view that draws that element, so the fleet map's group boxes
|
|
138
|
+
// become click-through and a neighbouring group box inside one subsystem
|
|
139
|
+
// view opens THAT subsystem's view. Measured at 58 services in 11 groups,
|
|
140
|
+
// 12 of 14 nodes navigable. With no owning ancestor there is no element to
|
|
141
|
+
// be `of`, so there is no navigation — honest, and one more reason to nest
|
|
142
|
+
// the map that loam already discloses one line above.
|
|
143
|
+
lines.push(` view ${subsystemViewId(sub)}${body.of === null ? "" : ` of ${body.of}`} {`);
|
|
144
|
+
// Metadata FIRST, and that ordering is a hard constraint rather than a
|
|
145
|
+
// taste: measured, an `include` OR a `group { … }` line followed by
|
|
146
|
+
// `title` is a parse error ("Expecting token of type '}' but found
|
|
147
|
+
// `title`"), and one parse error in this file blanks the whole
|
|
148
|
+
// `architecture/` project in the renderer.
|
|
149
|
+
lines.push(` title '${viewTitle(titleChain(tree, sub.path))}'`);
|
|
150
|
+
const description = likec4String(sub.meta.description ?? "");
|
|
151
|
+
if (description !== "")
|
|
152
|
+
lines.push(` description '${description}'`);
|
|
153
|
+
// After the label, before anything that includes: measured at the pin
|
|
154
|
+
// (test/likec4-view-shape.test.ts), a `global style <id>` line here
|
|
155
|
+
// parses with zero errors, while the same line before `title` is the
|
|
156
|
+
// parse error the comment above records. The palette is the map's own —
|
|
157
|
+
// the group is declared in the same project this file renders into — so
|
|
158
|
+
// the reference resolves exactly when the presence test says it does.
|
|
159
|
+
if (styled)
|
|
160
|
+
lines.push(` global style ${SUBSYSTEM_STYLE_GROUP}`);
|
|
161
|
+
lines.push(...body.lines);
|
|
162
|
+
lines.push(" }");
|
|
163
|
+
}
|
|
164
|
+
lines.push("}");
|
|
165
|
+
return lines.join("\n") + "\n";
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The GENERATED per-service overviews — `architecture/services.likec4`, one
|
|
169
|
+
* view per service the map resolves. Null when it resolves none, so the file is
|
|
170
|
+
* then absent exactly as `subsystems.likec4` is for a tree with no subsystems.
|
|
171
|
+
*
|
|
172
|
+
* This function is the composer and nothing else: `./service/` owns the view
|
|
173
|
+
* shape and its measurements, and everything about the TREE is resolved here,
|
|
174
|
+
* because that package may not import `./body.ts` or `./text.ts` (the edge runs
|
|
175
|
+
* the other way — `./service/views.ts` says why) and duplicating either would
|
|
176
|
+
* put a second element→service join and a second string escaper in the tree.
|
|
177
|
+
*
|
|
178
|
+
* Emission is gated on the SAME join the subsystem views use: a service the map
|
|
179
|
+
* does not draw is omitted rather than invented, because that omission is
|
|
180
|
+
* already `landscape.service-unmodelled` and rendering only what the committed
|
|
181
|
+
* map resolves keeps the output a function of committed bytes alone.
|
|
182
|
+
*/
|
|
183
|
+
export function renderServiceOverviews(tree, map) {
|
|
184
|
+
const ctx = renderContext(tree, map.elements);
|
|
185
|
+
const declared = map.globalStyles ?? [];
|
|
186
|
+
const subjects = [];
|
|
187
|
+
for (const service of tree.services) {
|
|
188
|
+
const fqn = ctx.elementOf.get(service.id);
|
|
189
|
+
if (fqn === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
subjects.push({ id: service.id, fqn, title: serviceViewTitle(tree, service.subsystem, service.id) });
|
|
192
|
+
}
|
|
193
|
+
return renderServiceViews({
|
|
194
|
+
subjects,
|
|
195
|
+
relationships: map.relationships,
|
|
196
|
+
styled: declared.includes(SERVICE_STYLE_GROUP),
|
|
197
|
+
disclosure: stylesDisclosure(declared, SERVICE_STYLE_GROUP),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A per-service view's title: its subsystem path and then its own id, joined
|
|
202
|
+
* with ` / ` — `Platform / order-flow / checkout-web`, or bare `checkout-web`
|
|
203
|
+
* for a service filed directly under `services/`.
|
|
204
|
+
*
|
|
205
|
+
* LikeC4 reads that as a folder path (`./text.ts` measured it), so a service's
|
|
206
|
+
* overview lands in the SAME browser folder as its subsystem's view rather than
|
|
207
|
+
* in a flat list of fifty-eight titles — which is the whole readability answer
|
|
208
|
+
* at fleet scale, and it costs the same helpers the subsystem views already use.
|
|
209
|
+
*
|
|
210
|
+
* The leaf is the service's `services/<id>/` DIRECTORY id and deliberately not
|
|
211
|
+
* the map element's title: it is the name every loam command addresses the
|
|
212
|
+
* service by, it cannot drift when somebody renames a box on the map, and a
|
|
213
|
+
* directory name cannot contain `/`, so it needs no folder-separator
|
|
214
|
+
* substitution of its own. It still goes through `likec4String` — an id is a
|
|
215
|
+
* directory name, and a directory name is somebody else's word.
|
|
216
|
+
*/
|
|
217
|
+
function serviceViewTitle(tree, subsystem, id) {
|
|
218
|
+
return [viewTitle(titleChain(tree, subsystem)), likec4String(id)].filter((part) => part !== "").join(" / ");
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* The MARKED subsystems along a path, outermost first — the chain `viewTitle`
|
|
222
|
+
* composes a folder path out of, for a subsystem's own view and for the view of
|
|
223
|
+
* a service filed beneath it alike.
|
|
224
|
+
*
|
|
225
|
+
* Marked, because an intermediate directory holding no `subsystem.yaml` is
|
|
226
|
+
* `subsystem.unmarked`, an error the walk already names: it is not a group,
|
|
227
|
+
* so it contributes no folder level here either. Read off the walked tree
|
|
228
|
+
* rather than off the path segments, so what the title says and what the
|
|
229
|
+
* finding says about the same directory cannot disagree.
|
|
230
|
+
*/
|
|
231
|
+
function titleChain(tree, path) {
|
|
232
|
+
return tree.subsystems
|
|
233
|
+
.filter((s) => s.path.length <= path.length && s.path.every((name, i) => path[i] === name))
|
|
234
|
+
.sort((a, b) => a.path.length - b.path.length);
|
|
235
|
+
}
|
|
236
|
+
/** The sort key and the comment spelling: the subsystem's path from services/, joined. */
|
|
237
|
+
function pathKey(sub) {
|
|
238
|
+
return sub.path.join("/");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* A LikeC4 identifier for the view, derived from the subsystem's path so
|
|
242
|
+
* nesting reads in the name — and INJECTIVE over every path the name grammar
|
|
243
|
+
* admits, because the renderer refuses the whole `architecture/` project over
|
|
244
|
+
* a duplicate view id.
|
|
245
|
+
*
|
|
246
|
+
* The per-BYTE encoding, the collision it was written against, and the reason
|
|
247
|
+
* segments join on `__` all live with the encoder in `./service/view-id.ts` —
|
|
248
|
+
* which is also where the per-service ids are minted, so the two generated
|
|
249
|
+
* files cannot drift into two spellings of the same escape. That module sits
|
|
250
|
+
* BELOW this one on purpose: it is the leaf, and the import may only run
|
|
251
|
+
* downward (`./service/views.ts`'s banner records the package direction).
|
|
252
|
+
*
|
|
253
|
+
* EXPORTED here still, with its prefix, because `subsystem.view-id-collision`
|
|
254
|
+
* has to ask the generator what it mints rather than re-deriving it. A check
|
|
255
|
+
* carrying its own copy of the escaping would go quiet the day the encoding
|
|
256
|
+
* changed, and going quiet is the failure this whole family exists to prevent.
|
|
257
|
+
*/
|
|
258
|
+
export const SUBSYSTEM_VIEW_PREFIX = "subsystem_";
|
|
259
|
+
export function subsystemViewId(sub) {
|
|
260
|
+
return `${SUBSYSTEM_VIEW_PREFIX}${sub.path.map(escapeViewIdSegment).join("__")}`;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Does the file on disk already SAY what `renderSubsystemViews` renders?
|
|
264
|
+
*
|
|
265
|
+
* The generator above only ever emits LF. `core.autocrlf=true` is Git for
|
|
266
|
+
* Windows' installer default and a docs repo ships no `.gitattributes`, so an
|
|
267
|
+
* ordinary Windows clone hands the file back with CRLF and not one fact
|
|
268
|
+
* changed. Four call sites asked this question with a byte equality and all
|
|
269
|
+
* four answered "stale" on such a clone: `loam validate` reported an error,
|
|
270
|
+
* `loam subsystem sync` reported `updated` and rewrote with LF, git then showed
|
|
271
|
+
* the file permanently modified, and the next checkout restored the CRLF. That
|
|
272
|
+
* is a loop rather than a diagnosis, which is why the comparison lives here
|
|
273
|
+
* now — the module that mints the bytes is the one that owes an answer about
|
|
274
|
+
* them, and three near-copies of this rule would go stale one at a time.
|
|
275
|
+
*
|
|
276
|
+
* Still never a parse: a views-only document does not parse standalone, and no
|
|
277
|
+
* check anywhere reads this file's content. The narrowing is from BYTES to
|
|
278
|
+
* CONTENT, which is what `subsystem.views-stale`'s own message ("does not match
|
|
279
|
+
* the tree") already claims. The symmetric cost is accepted and is the one
|
|
280
|
+
* `core/verify/pins/pin.ts` documents for `pinnedDigest`: a pure line-ending
|
|
281
|
+
* change is invisible, correctly, because it is not a change to what the file
|
|
282
|
+
* says.
|
|
283
|
+
*
|
|
284
|
+
* `null` is carried through as itself rather than collapsing into `""`: absent,
|
|
285
|
+
* must-be-absent and present-but-different are three distinct states with three
|
|
286
|
+
* distinct messages, and a fleet whose views file does not exist is not a fleet
|
|
287
|
+
* whose views file is blank.
|
|
288
|
+
*/
|
|
289
|
+
export function viewsAgree(actual, expected) {
|
|
290
|
+
const lf = (text) => (text === null ? null : text.replace(/\r\n|\r/g, "\n"));
|
|
291
|
+
return lf(actual) === lf(expected);
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"views.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/views.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The generated views file: `architecture/subsystems.likec4`, one LikeC4 view\n * per subsystem enumerating the services beneath it. GENERATED, never\n * authored — authored bytes stay authored (archive's verbatim-splice promise),\n * so the tree is mirrored into a file of its own that `loam subsystem sync`\n * owns outright, and staleness against the tree is a byte compare\n * (`subsystem.views-stale` in `validate --all`), never a parse: a spike\n * confirmed a views-only document does NOT parse standalone (its `include`\n * lines reference elements the landscape defines), so nothing in loam ever\n * loads this file (and rule 26 does not change that: this file is GENERATED and\n * holds STATIC views, whose contents loam may not read in any document) — only\n * the LikeC4 renderer, which merges the whole `architecture/` project per\n * likec4.config.json, resolves it.\n *\n * Each view opens with the LABEL a human reads — `title`, and `description`\n * when the marker carries one — because this file exists only to be rendered\n * and an unlabelled view is shown by its id, which is hex-escaped for\n * injectivity and reads as `subsystem_platform__order_2dflow`. Both\n * strings are somebody else's words and go through `likec4String`, which\n * records what a raw one does.\n *\n * The output is deterministic and line-oriented, and both halves are the\n * contract: subsystems sorted by path, members sorted by id, ONE `include`\n * per line — so two concurrent moves into different groups touch different\n * lines and git merges the file without intervention, exactly as the\n * service-grouped landscape splice already achieves. Byte-reproducible on any\n * machine: no timestamps, no absolute paths, no readdir-order dependence (the\n * walk sorts, and every list is re-sorted here). Plain lexicographic sorts on\n * purpose — the ids' own grammar is ASCII, an INVALID name is still ordered\n * deterministically, and `compareIds`' numeric tokenizing lives in `repo/`,\n * which this package must not import (`walk.ts` says why the edge only points\n * the other way).\n *\n * Membership is TRANSITIVE: a subsystem's view includes every service beneath\n * it at any depth, so a parent's view is the whole group's map rather than an\n * empty frame around its children. An empty view body is legal LikeC4 (same\n * spike), so an empty subsystem renders as one — `subsystem new` must be\n * usable before anything moves in.\n *\n * Include lines name the landscape ELEMENT id, resolved through the same\n * element→service join `validate` uses (`serviceResolver`: explicit\n * `metadata { service }` binding first, then a title naming a real service) —\n * because element ids in real landscapes are not service ids, and a view of\n * raw service ids would silently render empty for every such fleet. A member\n * with no landscape element is omitted from the includes: the omission is\n * already an error (`landscape.service-unmodelled`), and rendering only what\n * the committed landscape resolves keeps the output a function of committed\n * bytes alone.\n */\nimport { type LoadedDoc } from \"../../../c4/likec4.js\";\nimport { renderContext, viewBody } from \"./body.js\";\nimport { GENERATED_MARK } from \"./service/header.js\";\nimport { escapeViewIdSegment } from \"./service/view-id.js\";\nimport {\n renderServiceViews,\n SERVICE_STYLE_GROUP,\n type ServiceViewSubject,\n} from \"./service/views.js\";\nimport { likec4String, viewTitle } from \"./text.js\";\nimport type { FleetTree, SubsystemEntry } from \"../walk.js\";\n\n/**\n * The global style group a generated view references when the map declares\n * one: `global { styleGroup subsystems { … } }` (or the single-rule form\n * `global { style subsystems … }` — LikeC4 files both under one id table).\n * Presence is the opt-in and it is read off a DECLARATION, never a view: a\n * generated view cannot carry an inline `style` rule of its own (the file is\n * regenerated wholesale), and the one mechanism LikeC4 offers a view for\n * borrowing a repository's palette is a reference to a global style by id.\n * Referencing one that is not declared is a parse error that blanks the whole\n * `architecture/` project in the renderer, which is why the line is written\n * only when the id is declared and why its absence leaves the file exactly\n * what it was before this constant existed.\n *\n * One conventional name rather than \"the single declared group\": a second\n * group somebody declares for a hand-drawn view would otherwise silently\n * switch the generated views' styling OFF and restale the file on a change\n * that touched no subsystem — behaviour flipping on an unrelated declaration\n * is the shape `body.ts` calls \"a picture that looks the same whether or not\n * the two agree\". The name mirrors the file it styles.\n */\nexport const SUBSYSTEM_STYLE_GROUP = \"subsystems\";\n\n/**\n * What the render reads off the parsed map: the elements the member join\n * resolves, the RELATIONSHIPS a per-service view computes its peer set from,\n * and the global style ids a view may reference. A record rather than three\n * threaded parameters because all three come out of ONE parsed document, and\n * passing them apart would make an inconsistent pair representable. Every\n * `LoadedDoc` is structurally a `MapFacts`, so a caller holding the document\n * passes the document.\n *\n * `relationships` is REQUIRED, and that is the type doing work rather than a\n * shape falling out of `LoadedDoc`. It was optional once, on the reasoning that\n * the `LoadedDoc` literals standing in for a map that is absent or did not parse\n * have no edges to offer — and the consequence of omitting it is SILENT: every\n * per-service view renders with NO peers, the service and its own interior and\n * nothing it touches, with no error anywhere. Two independent readers found the\n * same caller composing a literal without the field beside a document that\n * carried one (`commands/subsystem/txn/views.ts`'s landscape-only fallback), so\n * documenting the hazard was measurably not enough: a caller with no edges must\n * now say `relationships: []` and mean it, and a caller holding a parsed\n * document passes the document.\n *\n * `globalStyles` stays optional because `LoadedDoc` declares it so and its\n * absence is not silent in the same way: a missing palette costs one `global\n * style` line, which the file-level disclosure comment then names.\n */\nexport type MapFacts = Pick<LoadedDoc, \"elements\" | \"relationships\" | \"globalStyles\">;\n\n/**\n * The header every generated file opens with — stable bytes, part of the\n * byte-compare contract, naming the regenerator so a reader who found the\n * file first knows what owns it.\n *\n * It OPENS with the shared `GENERATED_MARK` rather than spelling it, because\n * the same bytes are what `loam subsystem sync` reads back off disk to decide\n * whether the file standing at this reserved path is loam's to rewrite at all\n * (`./service/header.ts` carries the whole reason). A second literal here is\n * how a file loam wrote stops being recognised as loam's.\n */\nconst HEADER =\n `${GENERATED_MARK} — the subsystem tree under services/, one view per subsystem.\\n` +\n \"// Do not edit: `loam subsystem sync` regenerates this file from the directories,\\n\" +\n \"// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\\n\" +\n \"// A scoping aid, not the fleet's architecture: author your own diagrams elsewhere.\\n\";\n\n/**\n * The one file-level comment the render may write: the map declares global\n * style groups, and none is the conventional one. Written between `HEADER`\n * and `views {` — outside every view body, graded by nothing — so a team\n * that already declared a palette under another name reads the fix in the\n * diff of the sync that regenerated the file, instead of wondering why the\n * generated views ignore it. Silent when the map declares no group at all: a\n * fleet with no palette is owed no sentence about one. Ids go unescaped\n * because LikeC4's grammar admits only identifier bytes in a style id.\n *\n * The group is a PARAMETER because there are two generated files now and each\n * reserves its own name (`subsystems`, `services`); a map declaring one and not\n * the other must get the sentence in exactly the file that went unstyled.\n */\nfunction stylesDisclosure(declared: readonly string[], group: string): string | null {\n if (declared.length === 0 || declared.includes(group)) return null;\n return (\n `// styles: the project declares global style group(s) ${[...declared].sort().join(\", \")}, ` +\n `and none named \\`${group}\\` — these views carry the renderer's defaults until one is.`\n );\n}\n\n/**\n * Render the views file for a walked tree, or null when the tree has no\n * subsystems — the file must then be ABSENT, so a flat fleet never carries a\n * generated artifact that says nothing.\n */\nexport function renderSubsystemViews(tree: FleetTree, map: MapFacts): string | null {\n if (tree.subsystems.length === 0) return null;\n const ctx = renderContext(tree, map.elements);\n const declared = map.globalStyles ?? [];\n const styled = declared.includes(SUBSYSTEM_STYLE_GROUP);\n const disclosure = stylesDisclosure(declared, SUBSYSTEM_STYLE_GROUP);\n const lines: string[] = [HEADER, ...(disclosure === null ? [] : [disclosure]), \"views {\"];\n const subsystems = [...tree.subsystems].sort((a, b) => (pathKey(a) < pathKey(b) ? -1 : 1));\n for (const [i, sub] of subsystems.entries()) {\n if (i > 0) lines.push(\"\");\n lines.push(` // services/${pathKey(sub)}`);\n const body = viewBody(ctx, sub);\n if (body.disclosure !== null) lines.push(` // ${body.disclosure}`);\n // `of <ancestor>` when — and only when — the map holds an element that owns\n // exactly this subsystem's members (`../render/body.ts` argues the\n // \"exactly\"). It is the same one keyword the per-service views spend, and it\n // buys the same thing: LikeC4 computes `navigateTo` into this view from\n // every other view that draws that element, so the fleet map's group boxes\n // become click-through and a neighbouring group box inside one subsystem\n // view opens THAT subsystem's view. Measured at 58 services in 11 groups,\n // 12 of 14 nodes navigable. With no owning ancestor there is no element to\n // be `of`, so there is no navigation — honest, and one more reason to nest\n // the map that loam already discloses one line above.\n lines.push(` view ${subsystemViewId(sub)}${body.of === null ? \"\" : ` of ${body.of}`} {`);\n // Metadata FIRST, and that ordering is a hard constraint rather than a\n // taste: measured, an `include` OR a `group { … }` line followed by\n // `title` is a parse error (\"Expecting token of type '}' but found\n // `title`\"), and one parse error in this file blanks the whole\n // `architecture/` project in the renderer.\n lines.push(` title '${viewTitle(titleChain(tree, sub.path))}'`);\n const description = likec4String(sub.meta.description ?? \"\");\n if (description !== \"\") lines.push(` description '${description}'`);\n // After the label, before anything that includes: measured at the pin\n // (test/likec4-view-shape.test.ts), a `global style <id>` line here\n // parses with zero errors, while the same line before `title` is the\n // parse error the comment above records. The palette is the map's own —\n // the group is declared in the same project this file renders into — so\n // the reference resolves exactly when the presence test says it does.\n if (styled) lines.push(` global style ${SUBSYSTEM_STYLE_GROUP}`);\n lines.push(...body.lines);\n lines.push(\" }\");\n }\n lines.push(\"}\");\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * The GENERATED per-service overviews — `architecture/services.likec4`, one\n * view per service the map resolves. Null when it resolves none, so the file is\n * then absent exactly as `subsystems.likec4` is for a tree with no subsystems.\n *\n * This function is the composer and nothing else: `./service/` owns the view\n * shape and its measurements, and everything about the TREE is resolved here,\n * because that package may not import `./body.ts` or `./text.ts` (the edge runs\n * the other way — `./service/views.ts` says why) and duplicating either would\n * put a second element→service join and a second string escaper in the tree.\n *\n * Emission is gated on the SAME join the subsystem views use: a service the map\n * does not draw is omitted rather than invented, because that omission is\n * already `landscape.service-unmodelled` and rendering only what the committed\n * map resolves keeps the output a function of committed bytes alone.\n */\nexport function renderServiceOverviews(tree: FleetTree, map: MapFacts): string | null {\n const ctx = renderContext(tree, map.elements);\n const declared = map.globalStyles ?? [];\n const subjects: ServiceViewSubject[] = [];\n for (const service of tree.services) {\n const fqn = ctx.elementOf.get(service.id);\n if (fqn === undefined) continue;\n subjects.push({ id: service.id, fqn, title: serviceViewTitle(tree, service.subsystem, service.id) });\n }\n return renderServiceViews({\n subjects,\n relationships: map.relationships,\n styled: declared.includes(SERVICE_STYLE_GROUP),\n disclosure: stylesDisclosure(declared, SERVICE_STYLE_GROUP),\n });\n}\n\n/**\n * A per-service view's title: its subsystem path and then its own id, joined\n * with ` / ` — `Platform / order-flow / checkout-web`, or bare `checkout-web`\n * for a service filed directly under `services/`.\n *\n * LikeC4 reads that as a folder path (`./text.ts` measured it), so a service's\n * overview lands in the SAME browser folder as its subsystem's view rather than\n * in a flat list of fifty-eight titles — which is the whole readability answer\n * at fleet scale, and it costs the same helpers the subsystem views already use.\n *\n * The leaf is the service's `services/<id>/` DIRECTORY id and deliberately not\n * the map element's title: it is the name every loam command addresses the\n * service by, it cannot drift when somebody renames a box on the map, and a\n * directory name cannot contain `/`, so it needs no folder-separator\n * substitution of its own. It still goes through `likec4String` — an id is a\n * directory name, and a directory name is somebody else's word.\n */\nfunction serviceViewTitle(tree: FleetTree, subsystem: string[], id: string): string {\n return [viewTitle(titleChain(tree, subsystem)), likec4String(id)].filter((part) => part !== \"\").join(\" / \");\n}\n\n/**\n * The MARKED subsystems along a path, outermost first — the chain `viewTitle`\n * composes a folder path out of, for a subsystem's own view and for the view of\n * a service filed beneath it alike.\n *\n * Marked, because an intermediate directory holding no `subsystem.yaml` is\n * `subsystem.unmarked`, an error the walk already names: it is not a group,\n * so it contributes no folder level here either. Read off the walked tree\n * rather than off the path segments, so what the title says and what the\n * finding says about the same directory cannot disagree.\n */\nfunction titleChain(tree: FleetTree, path: readonly string[]): SubsystemEntry[] {\n return tree.subsystems\n .filter((s) => s.path.length <= path.length && s.path.every((name, i) => path[i] === name))\n .sort((a, b) => a.path.length - b.path.length);\n}\n\n/** The sort key and the comment spelling: the subsystem's path from services/, joined. */\nfunction pathKey(sub: SubsystemEntry): string {\n return sub.path.join(\"/\");\n}\n\n/**\n * A LikeC4 identifier for the view, derived from the subsystem's path so\n * nesting reads in the name — and INJECTIVE over every path the name grammar\n * admits, because the renderer refuses the whole `architecture/` project over\n * a duplicate view id.\n *\n * The per-BYTE encoding, the collision it was written against, and the reason\n * segments join on `__` all live with the encoder in `./service/view-id.ts` —\n * which is also where the per-service ids are minted, so the two generated\n * files cannot drift into two spellings of the same escape. That module sits\n * BELOW this one on purpose: it is the leaf, and the import may only run\n * downward (`./service/views.ts`'s banner records the package direction).\n *\n * EXPORTED here still, with its prefix, because `subsystem.view-id-collision`\n * has to ask the generator what it mints rather than re-deriving it. A check\n * carrying its own copy of the escaping would go quiet the day the encoding\n * changed, and going quiet is the failure this whole family exists to prevent.\n */\nexport const SUBSYSTEM_VIEW_PREFIX = \"subsystem_\";\n\nexport function subsystemViewId(sub: SubsystemEntry): string {\n return `${SUBSYSTEM_VIEW_PREFIX}${sub.path.map(escapeViewIdSegment).join(\"__\")}`;\n}\n\n/**\n * Does the file on disk already SAY what `renderSubsystemViews` renders?\n *\n * The generator above only ever emits LF. `core.autocrlf=true` is Git for\n * Windows' installer default and a docs repo ships no `.gitattributes`, so an\n * ordinary Windows clone hands the file back with CRLF and not one fact\n * changed. Four call sites asked this question with a byte equality and all\n * four answered \"stale\" on such a clone: `loam validate` reported an error,\n * `loam subsystem sync` reported `updated` and rewrote with LF, git then showed\n * the file permanently modified, and the next checkout restored the CRLF. That\n * is a loop rather than a diagnosis, which is why the comparison lives here\n * now — the module that mints the bytes is the one that owes an answer about\n * them, and three near-copies of this rule would go stale one at a time.\n *\n * Still never a parse: a views-only document does not parse standalone, and no\n * check anywhere reads this file's content. The narrowing is from BYTES to\n * CONTENT, which is what `subsystem.views-stale`'s own message (\"does not match\n * the tree\") already claims. The symmetric cost is accepted and is the one\n * `core/verify/pins/pin.ts` documents for `pinnedDigest`: a pure line-ending\n * change is invisible, correctly, because it is not a change to what the file\n * says.\n *\n * `null` is carried through as itself rather than collapsing into `\"\"`: absent,\n * must-be-absent and present-but-different are three distinct states with three\n * distinct messages, and a fleet whose views file does not exist is not a fleet\n * whose views file is blank.\n */\nexport function viewsAgree(actual: string | null, expected: string | null): boolean {\n const lf = (text: string | null): string | null => (text === null ? null : text.replace(/\\r\\n|\\r/g, \"\\n\"));\n return lf(actual) === lf(expected);\n}\n"]}
|