@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":"body.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/body.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What one subsystem's generated view SHOWS: which boxes it draws around the\n * services beneath a directory, and the one thing it may say about the map.\n *\n * Split from `../views.ts` when that file crossed the 400-line limit: the file\n * render (header, view ids, staleness comparison) and the body render (the\n * boxes) are two subjects, and the seam is exactly where the second one\n * consults the LANDSCAPE. Everything here reads element ids and nothing else —\n * no view is ever parsed (docs/DESIGN.md rule 26), and the generated file\n * stays a pure function of the tree plus the committed map.\n */\nimport { type Elem } from \"../../../c4/likec4.js\";\nimport { serviceResolver } from \"../../../c4/resolve/service.js\";\nimport { servicesUnder, withinSubsystem } from \"../find.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"../walk.js\";\nimport { subsystemLabel } from \"./text.js\";\n\n/**\n * Everything a view's body needs from the landscape, resolved ONCE: the\n * element standing for each service, every element id the map declares, and\n * whether an element is a service's own box. A record rather than four\n * threaded parameters because the body render recurses down the subsystem\n * subtree and would otherwise sit on the parameter limit at every hop\n * (`docs/CODE-STYLE.md`'s own prescription).\n */\nexport interface RenderContext {\n tree: FleetTree;\n elementOf: Map<string, string>;\n elementIds: ReadonlySet<string>;\n isServiceElement: (id: string) => boolean;\n}\n\nexport function renderContext(tree: FleetTree, elements: Elem[]): RenderContext {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const resolve = serviceResolver(elements, known);\n return {\n tree,\n elementOf: elementByService(tree, elements, resolve),\n elementIds: new Set(elements.map((e) => e.id)),\n isServiceElement: (id: string): boolean => known.has(resolve(id)),\n };\n}\n\n/** A view's body lines, the one comment the render may write above it, and its subject. */\nexport interface ViewBody {\n lines: string[];\n disclosure: string | null;\n /**\n * The element this view is declared `of`, or null when the map holds none\n * that owns exactly this subsystem. It is computed here rather than by the\n * file render because `owningAncestor` below already decides it — the same\n * answer decides which of the three body shapes applies, and two callers\n * deriving it separately could draw the boundary of one element while\n * declaring the view of another.\n */\n of: string | null;\n}\n\n/**\n * Three shapes, and which one applies is a fact about the landscape rather\n * than a setting:\n *\n * 1. **The model draws this group.** Some ancestor element holds exactly this\n * subsystem's members and nothing else, so the view includes it beside the\n * leaves and the renderer nests them inside the box the AUTHOR drew, with\n * the author's own title. Measured: including the ancestor alone renders\n * one empty box and the leaves alone render flat — both lines are needed.\n * 2. **The model is flat.** No member element is nested at all, so there is\n * no authored box to agree with, and the view wraps the members in a\n * `group '<title>'` of its own — a real named box in the renderer, derived\n * from the DIRECTORY the services already sit in, mirroring the subtree so\n * a parent subsystem shows its children as boxes rather than as one pile.\n * Nothing is invented: the grouping is the tree, and the tree is what this\n * file has always mirrored.\n * 3. **The model nests them, differently.** The members ARE nested but no\n * ancestor owns exactly them — the directory tree and the map disagree\n * about this group. loam draws no box (a synthetic one would silently\n * replace the author's boundary with the folder's, and the picture would\n * look the same whether or not the two agree) and writes ONE comment line\n * saying so, outside the view body. That comment is the whole of loam's\n * answer to the drift: it grades nothing, it fails nothing, and it appears\n * in the diff of the very commit that caused it — placement stays a fact\n * nothing branches on.\n */\nexport function viewBody(ctx: RenderContext, sub: SubsystemEntry): ViewBody {\n const members = memberElements(ctx, sub);\n if (members.length === 0) return { lines: [], disclosure: null, of: null };\n if (members.every((id) => !id.includes(\".\"))) {\n return { lines: [...groupedBody(ctx, sub, \" \"), ...boundaryLines(members)], disclosure: null, of: null };\n }\n const includes = [...ownedAncestors(ctx, sub), ...members].map((id) => ` include ${id}`);\n const lines = [...includes, ...boundaryLines(members)];\n const owner = owningAncestor(ctx, sub);\n if (owner !== null) return { lines, disclosure: null, of: owner };\n const shared = sharedAncestor(ctx, members);\n return {\n lines,\n of: null,\n disclosure:\n shared === null\n ? \"model: no boundary — these members do not share a containing element.\"\n : `model: no boundary — ${shared} also holds services outside this subsystem.`,\n };\n}\n\n/**\n * What this group TOUCHES: for each member, every edge it has in either\n * direction, so the view answers \"what does payments talk to\" and not only\n * \"who is in payments\".\n *\n * **The measured trap, and it is why these lines are appended by the caller of\n * `groupedBody` rather than inside it:** placed INSIDE the `group { }` block of\n * the flat-map body shape, this exact predicate parses and validates with ZERO\n * errors and draws a false picture. Measured at likec4 1.59.2 on a flat map of\n * `alpha, beta` grouped and `outsider, store` outside it: at view level the two\n * neighbours are drawn with no parent, correctly outside the box; moved inside\n * the `group { }`, both are drawn as CHILDREN of that group — the diagram then\n * claims `outsider` and `store` are part of the subsystem. A maintainer tidying\n * the body by moving these lines next to the includes they refer to would ship\n * that, and nothing anywhere would report it. They belong at view level, always.\n *\n * The WILDCARD is correct here, and that is not an inconsistency with the\n * per-service views, which measure it as a defect (`./service/peers.ts`): at\n * subsystem altitude a counterpart collapsing to its own group box is exactly\n * the sentence \"payments touches identity\", and — once those group boxes are\n * themselves views' subjects (`of <ancestor>`) — it is a click-through into\n * that subsystem. At service altitude the same collapse is a lie about who\n * calls whom.\n *\n * MEMBERS only, never `ownedAncestors`: `-> *` on an authored container pulls\n * in every edge any descendant of it has, and in the `// model: no boundary`\n * shape that container also holds services outside this subsystem — the view\n * would draw traffic that is nobody-here's. Measured at 58 services in 11\n * groups: 13-15 nodes and 31-39 edges per subsystem view, of which 11-12 nodes\n * carry drill-down — the members into their own service views, the neighbouring\n * group boxes into THEIR subsystem views.\n */\nfunction boundaryLines(members: readonly string[]): string[] {\n return [\"\", ...members.map((id) => ` include ${id} -> *, * -> ${id}`)];\n}\n\n/**\n * The flat-model body: this subsystem as a box, its child subsystems as boxes\n * inside it, its own services as leaves. Recursive, and the recursion is the\n * point — a parent subsystem's view is otherwise one undifferentiated pile of\n * every service beneath it.\n *\n * An empty child renders an empty named box, which is what the tree says: the\n * group exists and holds nothing yet.\n */\nfunction groupedBody(ctx: RenderContext, sub: SubsystemEntry, indent: string): string[] {\n const inner = `${indent} `;\n const out = [`${indent}group '${subsystemLabel(sub)}' {`];\n for (const child of childSubsystems(ctx, sub)) out.push(...groupedBody(ctx, child, inner));\n for (const id of directMemberElements(ctx, sub)) out.push(`${inner}include ${id}`);\n out.push(`${indent}}`);\n return out;\n}\n\n/** The child subsystems one level down, ordered by name. */\nfunction childSubsystems(ctx: RenderContext, sub: SubsystemEntry): SubsystemEntry[] {\n return ctx.tree.subsystems\n .filter((c) => c.path.length === sub.path.length + 1 && withinSubsystem(sub.path, c.path))\n .sort((a, b) => (a.name < b.name ? -1 : 1));\n}\n\n/** The drawn elements of the services filed in THIS directory, not in a child. */\nfunction directMemberElements(ctx: RenderContext, sub: SubsystemEntry): string[] {\n return drawnElements(\n ctx,\n membersOf(ctx.tree, sub).filter((s) => s.subsystem.length === sub.path.length),\n );\n}\n\n/** The drawn elements of every service beneath this subsystem, ordered by service id. */\nfunction memberElements(ctx: RenderContext, sub: SubsystemEntry): string[] {\n return drawnElements(ctx, membersOf(ctx.tree, sub));\n}\n\n/**\n * The element ids these services resolve to. A service the map does not draw\n * is omitted rather than invented: the omission is already an error\n * (`landscape.service-unmodelled`), and rendering only what the committed\n * documents resolve keeps the output a function of committed bytes alone.\n */\nfunction drawnElements(ctx: RenderContext, services: WalkedService[]): string[] {\n return services.map((s) => ctx.elementOf.get(s.id)).filter((id): id is string => id !== undefined);\n}\n\n/**\n * The boxes a nested-model view draws: this subsystem's own owning ancestor\n * when it has one, plus the owning ancestor of every subsystem beneath it — so\n * a parent view keeps its children's boundaries instead of flattening them\n * into one box. Sorted and de-duplicated, because two subsystems resolve to\n * one element only in a tree that already reports a collision.\n */\nfunction ownedAncestors(ctx: RenderContext, sub: SubsystemEntry): string[] {\n const found = new Set<string>();\n for (const candidate of ctx.tree.subsystems) {\n if (!withinSubsystem(sub.path, candidate.path)) continue;\n const owner = owningAncestor(ctx, candidate);\n if (owner !== null) found.add(owner);\n }\n return [...found].sort();\n}\n\n/**\n * The deepest ancestor element that holds exactly this subsystem's members —\n * or null when no element does.\n *\n * \"Exactly\" is the whole guard, and dropping it was measured on loam's own\n * `examples/docs`: the members' longest common ancestor there is a group of\n * five services while the subsystem holds two, so an unguarded include would\n * generate a box labelled for the group with two of its five children drawn\n * inside it — a diagram asserting something false, in a file the reader is\n * told not to edit. Tightness is judged over ELEMENTS rather than over\n * documented services: mid-adoption a group commonly holds systems no\n * `services/<id>/` exists for yet, and counting only the documented ones would\n * draw that same false box for the whole of an adoption.\n *\n * An ancestor that is itself a service's element is refused separately: one\n * service's box containing another's is a picture of nothing.\n */\nfunction owningAncestor(ctx: RenderContext, sub: SubsystemEntry): string | null {\n const members = memberElements(ctx, sub);\n if (members.length === 0) return null;\n const owned = new Set(members);\n for (const candidate of ancestorChain(members)) {\n if (!ctx.elementIds.has(candidate) || ctx.isServiceElement(candidate)) continue;\n if (ownsExactly(ctx, candidate, owned)) return candidate;\n }\n return null;\n}\n\n/** The deepest common ancestor the map declares at all — the disclosure's subject. */\nfunction sharedAncestor(ctx: RenderContext, members: string[]): string | null {\n return ancestorChain(members).find((id) => ctx.elementIds.has(id)) ?? null;\n}\n\n/** The common proper ancestors of every member id, deepest first. */\nfunction ancestorChain(members: string[]): string[] {\n const segments = members.map((id) => id.split(\".\"));\n let common = Math.min(...segments.map((s) => s.length - 1));\n for (let i = 0; i < common; i++) {\n if (!segments.every((s) => s[i] === segments[0]![i])) {\n common = i;\n break;\n }\n }\n const out: string[] = [];\n for (let depth = common; depth > 0; depth--) out.push(segments[0]!.slice(0, depth).join(\".\"));\n return out;\n}\n\n/**\n * Does every element beneath `ancestor` belong to this subsystem — as a\n * member, as one of a member's own containers, or as a container of one?\n */\nfunction ownsExactly(ctx: RenderContext, ancestor: string, members: ReadonlySet<string>): boolean {\n const prefix = `${ancestor}.`;\n for (const id of ctx.elementIds) {\n if (!id.startsWith(prefix)) continue;\n const claimed = [...members].some((m) => id === m || id.startsWith(`${m}.`) || m.startsWith(`${id}.`));\n if (!claimed) return false;\n }\n return true;\n}\n\n/**\n * The landscape element standing for each service id: for every element,\n * resolve it through the shared join, and let the SHALLOWEST element win\n * (document order breaking ties). Shallowest, because `serviceResolver`\n * resolves a service's own containers to the service too, and a view should\n * include the service-level box, not whichever nested container happened to be\n * declared first.\n */\nfunction elementByService(\n tree: FleetTree,\n elements: Elem[],\n resolve: (id: string) => string,\n): Map<string, string> {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const depth = (id: string): number => id.split(\".\").length;\n const out = new Map<string, string>();\n for (const e of elements) {\n const service = resolve(e.id);\n if (!known.has(service)) continue;\n const held = out.get(service);\n if (held === undefined || depth(e.id) < depth(held)) out.set(service, e.id);\n }\n return out;\n}\n\n/** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */\nfunction membersOf(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));\n}\n"]}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The renderer's project file for ONE service — `services/<…>/<id>/likec4.config.json`
|
|
3
|
+
* — the second thing under `services/` that exists only to be rendered.
|
|
4
|
+
*
|
|
5
|
+
* Why it exists, and WHICH MODELS IT IS FOR. A model that STANDS ALONE declares
|
|
6
|
+
* its own `specification` block, so it is parsed alone and the root project must
|
|
7
|
+
* exclude its directory — a renderer merging it into the map reports every kind
|
|
8
|
+
* and element it declares as a duplicate, blamed on both files. The cost of that
|
|
9
|
+
* exclusion went unstated for a release: an adopted service's model — the
|
|
10
|
+
* artifact `loam adopt` spends the most effort on — was a box on the fleet map
|
|
11
|
+
* with nothing renderable inside it from the docs root, and the documented way
|
|
12
|
+
* to see it was one renderer invocation per service directory. Measured at the
|
|
13
|
+
* 1.59.2 pin: a `likec4.config.json` INSIDE a service directory is registered as
|
|
14
|
+
* a project of its own even though the root excludes the directory (the root's
|
|
15
|
+
* `exclude` does not hide a nested project file), the workspace then loads
|
|
16
|
+
* `fleet` plus one project per service with zero errors, and each service
|
|
17
|
+
* project carries LikeC4's own `index` view of the containers beside whatever
|
|
18
|
+
* view the model authors. One two-key file per such directory is therefore what
|
|
19
|
+
* makes that model renderable from the same root, in the same renderer, beside
|
|
20
|
+
* the map.
|
|
21
|
+
*
|
|
22
|
+
* A model that EXTENDS the map is owed NO file, and that is the same
|
|
23
|
+
* measurement read the other way: it declares no kinds, it lives in the ROOT
|
|
24
|
+
* project beside the map, and a nested project file beside it TAKES it out of
|
|
25
|
+
* there — measured at the 1.59.2 pin, the workspace loads two projects and, where
|
|
26
|
+
* the nested one claims the model, it holds it alone (the map's kinds are not in
|
|
27
|
+
* there with it, so it does not resolve) and `export json --project fleet` loses
|
|
28
|
+
* the service's containers. So the survey below takes the shape as a predicate, the writer
|
|
29
|
+
* creates a file only for the standalone half and removes one beside the
|
|
30
|
+
* extending half, and `service.likec4-config-stray` grades what is left.
|
|
31
|
+
*
|
|
32
|
+
* NEVER COMPARED, NEVER REWRITTEN — the root config's ownership rule
|
|
33
|
+
* (`core/docs.ts`: written once, never re-read, the team owns it), NOT the
|
|
34
|
+
* generated views file's. The bytes are a function of the service id alone,
|
|
35
|
+
* which SCHEMA.md defines as the identity no verb ever changes, so nothing can
|
|
36
|
+
* make the file stale; a compare would only fight the per-project keys LikeC4
|
|
37
|
+
* lets a team add (`title`, styles, a contact), and would restale every service
|
|
38
|
+
* on any change to this rule. Presence is the one question loam owes the file,
|
|
39
|
+
* and presence needs no parse: rule 26 stands, and loam still reads none of it.
|
|
40
|
+
*
|
|
41
|
+
* REMOVED IN EXACTLY ONE STATE: beside a model that EXTENDS the map. That is a
|
|
42
|
+
* correction, and it is measured rather than reasoned. The design note said a
|
|
43
|
+
* nested project beside an extending model was registered EMPTY and therefore
|
|
44
|
+
* harmless; on likec4 1.59.2 two agents measured the opposite (verification
|
|
45
|
+
* 2026-09-04, W1) — the nested project CLAIMS the model, the workspace loads
|
|
46
|
+
* "2 projects", the model is Invalid inside its own one (`Could not resolve
|
|
47
|
+
* reference to Element named 'svc_svc_a'`, because the map is not in there with
|
|
48
|
+
* it), and `export json --project fleet` drops the service's containers. So the
|
|
49
|
+
* file is not a leftover, it is a working renderer taken away from the fleet,
|
|
50
|
+
* and `loam subsystem sync` deletes it — which is what SCHEMA, the message and
|
|
51
|
+
* `/loam-adopt` step 5 had been promising all along.
|
|
52
|
+
*
|
|
53
|
+
* THE EXPORT LOSS IS THE WORSE HALF, NOT THE CERTAIN ONE, and the finding's
|
|
54
|
+
* message says so since it was re-measured (re-verification 2026-09-04, area C
|
|
55
|
+
* item 7). Same pin, two fleets: on the seeded fleet above the containers went
|
|
56
|
+
* and came back when the file was deleted; on `examples/docs` a stray beside the
|
|
57
|
+
* extending order-service left `export json --project fleet` byte-identical —
|
|
58
|
+
* 33 elements, every `marketplace.orderService.*` child present — because there
|
|
59
|
+
* the ROOT project kept the model. What is true in every state measured is the
|
|
60
|
+
* second project itself: the docs root stops being a one-project workspace, and
|
|
61
|
+
* `likec4 validate .` goes from ✓ Valid to ✗ Invalid ("Specify exact project,
|
|
62
|
+
* known: [order-service, fleet]"). A message that asserted the export loss
|
|
63
|
+
* unconditionally was refutable on the repository's own example fleet.
|
|
64
|
+
*
|
|
65
|
+
* A leftover beside a model that is GONE stays. That one really is the team's:
|
|
66
|
+
* a nested project holding no `.likec4` file is silently absent from the
|
|
67
|
+
* renderer's project list (measured), so it costs nothing, and a survey that
|
|
68
|
+
* deleted files in directories it found no model in would be a writer acting on
|
|
69
|
+
* an absence.
|
|
70
|
+
*
|
|
71
|
+
* THE NAME. LikeC4's project-name grammar (measured at the pin) refuses an
|
|
72
|
+
* empty name, the word `default`, and the bytes `.`, `@` and `#`; loam's
|
|
73
|
+
* service-id grammar admits `.`. So every `.` becomes `-`, and a name that
|
|
74
|
+
* would then equal the ROOT project's (`fleet` — a nested project of that name
|
|
75
|
+
* is silently registered as `fleet-1`) or LikeC4's reserved `default` is
|
|
76
|
+
* prefixed `service-`. Readable and typeable after `--project`, and NOT
|
|
77
|
+
* injective: an id spelled a.b beside a service literally named a-b, or
|
|
78
|
+
* `service-fleet` beside `fleet`, share a name and the renderer suffixes the
|
|
79
|
+
* later one `-1`. That is disclosed rather than escaped away, because the
|
|
80
|
+
* picker shows the `title` (measured: `title ?? id`), which is ALWAYS the id
|
|
81
|
+
* verbatim, and a collision costs one suffixed label rather than the blanked
|
|
82
|
+
* workspace that made `subsystemViewId` injective. The root's name arrives as
|
|
83
|
+
* a parameter, like every spelling this package needs from `repo/`: `render/`
|
|
84
|
+
* cannot import `repo/paths.ts` (`stale.ts` records the cycle), and a second
|
|
85
|
+
* copy of the word `fleet` here would be the drift the injection prevents.
|
|
86
|
+
*
|
|
87
|
+
* A DIRECTORY THAT IS NOT A LEGAL SERVICE ID IS OWED NO FILE. The tree walk
|
|
88
|
+
* enumerates every directory on purpose — the fleet is never reported smaller
|
|
89
|
+
* than it is — and `service.id-invalid` names the illegal ones. Folding only
|
|
90
|
+
* `.` is right for every LEGAL id, whose remaining bytes LikeC4 accepts; a
|
|
91
|
+
* name like `pay@1` would produce a project file the renderer silently drops
|
|
92
|
+
* (measured: zero errors, the project simply absent from the list), and
|
|
93
|
+
* because the file is create-only the grade below would then be silent
|
|
94
|
+
* forever while the model stayed unrendered — the exact state this file
|
|
95
|
+
* exists to remove, with a label saying it was fixed. So the survey skips
|
|
96
|
+
* such a service: the writer creates nothing, the grader says nothing, and
|
|
97
|
+
* `service.id-invalid`'s own repair — the rename — is what earns the file on
|
|
98
|
+
* the first sync after it.
|
|
99
|
+
*
|
|
100
|
+
* NO MODEL, NO FILE: a service directory without `model.likec4` — one an
|
|
101
|
+
* archive materialised, a spec-only baseline — is owed nothing, and gets its
|
|
102
|
+
* file on the first `loam subsystem sync` after the model lands. A feature's
|
|
103
|
+
* `delta.likec4` is deliberately out of scope: the directory is transient and
|
|
104
|
+
* `loam archive` moves it where a project file would still be registered, so
|
|
105
|
+
* every shipped feature would keep a dead project in the list forever.
|
|
106
|
+
*/
|
|
107
|
+
import { existsSync } from "node:fs";
|
|
108
|
+
import { dirNameHazard } from "../../../kernel/ids/service.js";
|
|
109
|
+
/** LikeC4's own reserved project name; a file naming it is refused outright. */
|
|
110
|
+
const LIKEC4_RESERVED_PROJECT = "default";
|
|
111
|
+
/**
|
|
112
|
+
* The project name for a service id — the id itself for every id made of
|
|
113
|
+
* letters, digits, `_` and `-`, which is nearly all of them.
|
|
114
|
+
*/
|
|
115
|
+
export function serviceProjectName(id, rootProject) {
|
|
116
|
+
const folded = id.replace(/\./g, "-");
|
|
117
|
+
return folded === rootProject || folded === LIKEC4_RESERVED_PROJECT ? `service-${folded}` : folded;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The exact bytes of a service's project file: two keys, two-space JSON, LF,
|
|
121
|
+
* one trailing newline — the root file's own serialisation. `title` is the id
|
|
122
|
+
* verbatim, always, so whatever the renderer labels a project with is the
|
|
123
|
+
* service's real identity even where `name` had to bend.
|
|
124
|
+
*/
|
|
125
|
+
export function renderServiceProject(id, rootProject) {
|
|
126
|
+
return `${JSON.stringify({ name: serviceProjectName(id, rootProject), title: id }, null, 2)}\n`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* ONE predicate for the writer (`subsystem sync`) and the grader
|
|
130
|
+
* (`validate --all`), on `viewsState`'s precedent — two private spellings of
|
|
131
|
+
* "is this model a project" would be two chances to answer differently about
|
|
132
|
+
* one file, and a grader naming a gap the writer would not fill is a loop no
|
|
133
|
+
* command can clear. Both the gaps AND the count live here for the same
|
|
134
|
+
* reason: a caller re-deriving "has a model" beside this function is the
|
|
135
|
+
* second spelling in another coat.
|
|
136
|
+
*
|
|
137
|
+
* Structural over `{ id, dir }` so a walked service and a full entry both fit,
|
|
138
|
+
* and the two file spellings arrive injected (`repo/paths.ts`'s
|
|
139
|
+
* `serviceRenderPaths`) for the package reason the banner gives. Sorted by id,
|
|
140
|
+
* plain lexicographic like every list this package emits, so the writer's
|
|
141
|
+
* report and the grader's findings come out in one stable order.
|
|
142
|
+
*/
|
|
143
|
+
export function surveyProjects(services, pathsOf, isStandalone) {
|
|
144
|
+
const models = [];
|
|
145
|
+
for (const service of services) {
|
|
146
|
+
// The banner's rule: a name the renderer would refuse earns no file, and
|
|
147
|
+
// `service.id-invalid` — not this survey — is what names the repair.
|
|
148
|
+
if (dirNameHazard(service.id) !== null)
|
|
149
|
+
continue;
|
|
150
|
+
const paths = pathsOf(service.dir);
|
|
151
|
+
if (!existsSync(paths.model))
|
|
152
|
+
continue;
|
|
153
|
+
models.push({
|
|
154
|
+
service,
|
|
155
|
+
path: paths.project,
|
|
156
|
+
// INJECTED rather than read here, and the reason is this package's
|
|
157
|
+
// standing one: `render/` may not import `c4/` (see `./stale.ts` on the
|
|
158
|
+
// cycle), and the shape is a fact about the model's GRAMMAR that
|
|
159
|
+
// `c4/service-model/shape.ts` owns. A predicate crossing the boundary
|
|
160
|
+
// keeps one scanner for the writer and the grader alike.
|
|
161
|
+
standalone: isStandalone(paths.model),
|
|
162
|
+
configured: existsSync(paths.project),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
models.sort((a, b) => (a.service.id < b.service.id ? -1 : a.service.id > b.service.id ? 1 : 0));
|
|
166
|
+
return {
|
|
167
|
+
// A model that EXTENDS the map is owed no project file: it belongs to the
|
|
168
|
+
// root project, and a nested project file beside it CLAIMS it — the model
|
|
169
|
+
// then parses alone, where the map's kinds are not, and the fleet project
|
|
170
|
+
// loses the service's interior. So the gap set narrowed to the standalone
|
|
171
|
+
// shape, and `strays` below is the grade's and the writer's other direction.
|
|
172
|
+
gaps: models.filter((m) => m.standalone && !m.configured).map(({ service, path }) => ({ service, path })),
|
|
173
|
+
// The other direction, and the one the banner's correction is about: the
|
|
174
|
+
// nested project claims the model and the fleet project loses the service's
|
|
175
|
+
// interior, so the file is removed rather than tolerated.
|
|
176
|
+
strays: models.filter((m) => !m.standalone && m.configured).map(({ service, path }) => ({ service, path })),
|
|
177
|
+
models,
|
|
178
|
+
modelled: models.length,
|
|
179
|
+
standalone: models.filter((m) => m.standalone).length,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/projects.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The renderer's project file for ONE service — `services/<…>/<id>/likec4.config.json`\n * — the second thing under `services/` that exists only to be rendered.\n *\n * Why it exists, and WHICH MODELS IT IS FOR. A model that STANDS ALONE declares\n * its own `specification` block, so it is parsed alone and the root project must\n * exclude its directory — a renderer merging it into the map reports every kind\n * and element it declares as a duplicate, blamed on both files. The cost of that\n * exclusion went unstated for a release: an adopted service's model — the\n * artifact `loam adopt` spends the most effort on — was a box on the fleet map\n * with nothing renderable inside it from the docs root, and the documented way\n * to see it was one renderer invocation per service directory. Measured at the\n * 1.59.2 pin: a `likec4.config.json` INSIDE a service directory is registered as\n * a project of its own even though the root excludes the directory (the root's\n * `exclude` does not hide a nested project file), the workspace then loads\n * `fleet` plus one project per service with zero errors, and each service\n * project carries LikeC4's own `index` view of the containers beside whatever\n * view the model authors. One two-key file per such directory is therefore what\n * makes that model renderable from the same root, in the same renderer, beside\n * the map.\n *\n * A model that EXTENDS the map is owed NO file, and that is the same\n * measurement read the other way: it declares no kinds, it lives in the ROOT\n * project beside the map, and a nested project file beside it TAKES it out of\n * there — measured at the 1.59.2 pin, the workspace loads two projects and, where\n * the nested one claims the model, it holds it alone (the map's kinds are not in\n * there with it, so it does not resolve) and `export json --project fleet` loses\n * the service's containers. So the survey below takes the shape as a predicate, the writer\n * creates a file only for the standalone half and removes one beside the\n * extending half, and `service.likec4-config-stray` grades what is left.\n *\n * NEVER COMPARED, NEVER REWRITTEN — the root config's ownership rule\n * (`core/docs.ts`: written once, never re-read, the team owns it), NOT the\n * generated views file's. The bytes are a function of the service id alone,\n * which SCHEMA.md defines as the identity no verb ever changes, so nothing can\n * make the file stale; a compare would only fight the per-project keys LikeC4\n * lets a team add (`title`, styles, a contact), and would restale every service\n * on any change to this rule. Presence is the one question loam owes the file,\n * and presence needs no parse: rule 26 stands, and loam still reads none of it.\n *\n * REMOVED IN EXACTLY ONE STATE: beside a model that EXTENDS the map. That is a\n * correction, and it is measured rather than reasoned. The design note said a\n * nested project beside an extending model was registered EMPTY and therefore\n * harmless; on likec4 1.59.2 two agents measured the opposite (verification\n * 2026-09-04, W1) — the nested project CLAIMS the model, the workspace loads\n * \"2 projects\", the model is Invalid inside its own one (`Could not resolve\n * reference to Element named 'svc_svc_a'`, because the map is not in there with\n * it), and `export json --project fleet` drops the service's containers. So the\n * file is not a leftover, it is a working renderer taken away from the fleet,\n * and `loam subsystem sync` deletes it — which is what SCHEMA, the message and\n * `/loam-adopt` step 5 had been promising all along.\n *\n * THE EXPORT LOSS IS THE WORSE HALF, NOT THE CERTAIN ONE, and the finding's\n * message says so since it was re-measured (re-verification 2026-09-04, area C\n * item 7). Same pin, two fleets: on the seeded fleet above the containers went\n * and came back when the file was deleted; on `examples/docs` a stray beside the\n * extending order-service left `export json --project fleet` byte-identical —\n * 33 elements, every `marketplace.orderService.*` child present — because there\n * the ROOT project kept the model. What is true in every state measured is the\n * second project itself: the docs root stops being a one-project workspace, and\n * `likec4 validate .` goes from ✓ Valid to ✗ Invalid (\"Specify exact project,\n * known: [order-service, fleet]\"). A message that asserted the export loss\n * unconditionally was refutable on the repository's own example fleet.\n *\n * A leftover beside a model that is GONE stays. That one really is the team's:\n * a nested project holding no `.likec4` file is silently absent from the\n * renderer's project list (measured), so it costs nothing, and a survey that\n * deleted files in directories it found no model in would be a writer acting on\n * an absence.\n *\n * THE NAME. LikeC4's project-name grammar (measured at the pin) refuses an\n * empty name, the word `default`, and the bytes `.`, `@` and `#`; loam's\n * service-id grammar admits `.`. So every `.` becomes `-`, and a name that\n * would then equal the ROOT project's (`fleet` — a nested project of that name\n * is silently registered as `fleet-1`) or LikeC4's reserved `default` is\n * prefixed `service-`. Readable and typeable after `--project`, and NOT\n * injective: an id spelled a.b beside a service literally named a-b, or\n * `service-fleet` beside `fleet`, share a name and the renderer suffixes the\n * later one `-1`. That is disclosed rather than escaped away, because the\n * picker shows the `title` (measured: `title ?? id`), which is ALWAYS the id\n * verbatim, and a collision costs one suffixed label rather than the blanked\n * workspace that made `subsystemViewId` injective. The root's name arrives as\n * a parameter, like every spelling this package needs from `repo/`: `render/`\n * cannot import `repo/paths.ts` (`stale.ts` records the cycle), and a second\n * copy of the word `fleet` here would be the drift the injection prevents.\n *\n * A DIRECTORY THAT IS NOT A LEGAL SERVICE ID IS OWED NO FILE. The tree walk\n * enumerates every directory on purpose — the fleet is never reported smaller\n * than it is — and `service.id-invalid` names the illegal ones. Folding only\n * `.` is right for every LEGAL id, whose remaining bytes LikeC4 accepts; a\n * name like `pay@1` would produce a project file the renderer silently drops\n * (measured: zero errors, the project simply absent from the list), and\n * because the file is create-only the grade below would then be silent\n * forever while the model stayed unrendered — the exact state this file\n * exists to remove, with a label saying it was fixed. So the survey skips\n * such a service: the writer creates nothing, the grader says nothing, and\n * `service.id-invalid`'s own repair — the rename — is what earns the file on\n * the first sync after it.\n *\n * NO MODEL, NO FILE: a service directory without `model.likec4` — one an\n * archive materialised, a spec-only baseline — is owed nothing, and gets its\n * file on the first `loam subsystem sync` after the model lands. A feature's\n * `delta.likec4` is deliberately out of scope: the directory is transient and\n * `loam archive` moves it where a project file would still be registered, so\n * every shipped feature would keep a dead project in the list forever.\n */\nimport { existsSync } from \"node:fs\";\nimport type { ServiceDir } from \"../../../kernel/ids/dirs.js\";\nimport { dirNameHazard } from \"../../../kernel/ids/service.js\";\n\n/** LikeC4's own reserved project name; a file naming it is refused outright. */\nconst LIKEC4_RESERVED_PROJECT = \"default\";\n\n/**\n * The project name for a service id — the id itself for every id made of\n * letters, digits, `_` and `-`, which is nearly all of them.\n */\nexport function serviceProjectName(id: string, rootProject: string): string {\n const folded = id.replace(/\\./g, \"-\");\n return folded === rootProject || folded === LIKEC4_RESERVED_PROJECT ? `service-${folded}` : folded;\n}\n\n/**\n * The exact bytes of a service's project file: two keys, two-space JSON, LF,\n * one trailing newline — the root file's own serialisation. `title` is the id\n * verbatim, always, so whatever the renderer labels a project with is the\n * service's real identity even where `name` had to bend.\n */\nexport function renderServiceProject(id: string, rootProject: string): string {\n return `${JSON.stringify({ name: serviceProjectName(id, rootProject), title: id }, null, 2)}\\n`;\n}\n\n/**\n * A service whose model has no project file beside it, and where the file\n * goes. The SERVICE travels whole — a walked service or a full entry,\n * whichever the caller surveyed — rather than its id: the grader spells the\n * finding's path from the entry's own placement, and a join back by id would\n * name the wrong directory the moment two services share a leaf name under\n * different subsystems (`subsystem.name-collision` — a broken tree the walk\n * still enumerates in full, and one this grade is emitted on by design).\n */\nexport interface ProjectGap<T> {\n service: T;\n /** Absolute path of the file `loam subsystem sync` will create. */\n path: string;\n}\n\n/**\n * One surveyed model: everything the writer and the four renderer grades ask\n * about a single `model.likec4`, answered in one walk.\n *\n * The SHAPE is on here rather than re-derived per grade because it decides\n * opposite things for the two of them: a model that stands alone is owed a\n * project file and must be excluded from the root project, and a model that\n * extends the map is owed neither and must NOT be excluded. Two readers of the\n * same bytes would be two chances to answer that backwards, and backwards is\n * not a warning — it is a renderer that shows nothing.\n */\nexport interface SurveyedModel<T> {\n service: T;\n /** Absolute path of the per-service project file, whether or not it exists. */\n path: string;\n /** True when the model declares its own element kinds — the caller's byte scan, injected. */\n standalone: boolean;\n /** True when a `likec4.config.json` already sits beside the model. */\n configured: boolean;\n}\n\n/** What one survey of the tree found: the gaps, the models behind them, and the domain they were counted against. */\nexport interface ProjectSurvey<T> {\n /** Every service owed a file and lacking one, sorted by id. */\n gaps: ProjectGap<T>[];\n /**\n * Every service owed NO file that has one anyway — an extending model with a\n * `likec4.config.json` beside it, sorted by id. The mirror of `gaps`, and the\n * same shape, because the writer does the same thing with it: `subsystem sync`\n * plans a delete where it plans a create, and `service.likec4-config-stray`\n * grades what is left. One predicate for both, so the grader can never name a\n * stray the writer would decline to remove.\n */\n strays: ProjectGap<T>[];\n /** Every model the survey considered, sorted by id — the four renderer grades walk this. */\n models: SurveyedModel<T>[];\n /**\n * Every service the survey considered at all — a model on disk, a legal id\n * — file or no file, whichever shape the model has. It is what the\n * root-missing note is owed to: without the root project file NOTHING under\n * `services/` is written, and the count a person needs there is how many\n * models the run walked past.\n */\n modelled: number;\n /**\n * The subset of those whose model STANDS ALONE — the only services owed a\n * project file at all, and therefore the domain `gaps` was drawn from.\n *\n * Apart from `modelled` because the two stopped meaning the same thing when a\n * model could extend the map, and one subtraction rested on their being\n * equal: `modelled - gaps.length` was \"already had a file\", and it counted\n * every extending model as one — a fleet that had just migrated reported N\n * services holding a file that must not exist. `standalone - gaps.length` is\n * the same subtraction over the domain that is actually owed the file.\n */\n standalone: number;\n}\n\n/**\n * ONE predicate for the writer (`subsystem sync`) and the grader\n * (`validate --all`), on `viewsState`'s precedent — two private spellings of\n * \"is this model a project\" would be two chances to answer differently about\n * one file, and a grader naming a gap the writer would not fill is a loop no\n * command can clear. Both the gaps AND the count live here for the same\n * reason: a caller re-deriving \"has a model\" beside this function is the\n * second spelling in another coat.\n *\n * Structural over `{ id, dir }` so a walked service and a full entry both fit,\n * and the two file spellings arrive injected (`repo/paths.ts`'s\n * `serviceRenderPaths`) for the package reason the banner gives. Sorted by id,\n * plain lexicographic like every list this package emits, so the writer's\n * report and the grader's findings come out in one stable order.\n */\nexport function surveyProjects<T extends { id: string; dir: ServiceDir }>(\n services: readonly T[],\n pathsOf: (dir: ServiceDir) => { model: string; project: string },\n isStandalone: (modelPath: string) => boolean,\n): ProjectSurvey<T> {\n const models: SurveyedModel<T>[] = [];\n for (const service of services) {\n // The banner's rule: a name the renderer would refuse earns no file, and\n // `service.id-invalid` — not this survey — is what names the repair.\n if (dirNameHazard(service.id) !== null) continue;\n const paths = pathsOf(service.dir);\n if (!existsSync(paths.model)) continue;\n models.push({\n service,\n path: paths.project,\n // INJECTED rather than read here, and the reason is this package's\n // standing one: `render/` may not import `c4/` (see `./stale.ts` on the\n // cycle), and the shape is a fact about the model's GRAMMAR that\n // `c4/service-model/shape.ts` owns. A predicate crossing the boundary\n // keeps one scanner for the writer and the grader alike.\n standalone: isStandalone(paths.model),\n configured: existsSync(paths.project),\n });\n }\n models.sort((a, b) => (a.service.id < b.service.id ? -1 : a.service.id > b.service.id ? 1 : 0));\n return {\n // A model that EXTENDS the map is owed no project file: it belongs to the\n // root project, and a nested project file beside it CLAIMS it — the model\n // then parses alone, where the map's kinds are not, and the fleet project\n // loses the service's interior. So the gap set narrowed to the standalone\n // shape, and `strays` below is the grade's and the writer's other direction.\n gaps: models.filter((m) => m.standalone && !m.configured).map(({ service, path }) => ({ service, path })),\n // The other direction, and the one the banner's correction is about: the\n // nested project claims the model and the fleet project loses the service's\n // interior, so the file is removed rather than tolerated.\n strays: models.filter((m) => !m.standalone && m.configured).map(({ service, path }) => ({ service, path })),\n models,\n modelled: models.length,\n standalone: models.filter((m) => m.standalone).length,\n };\n}\n"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one line that says a file under `architecture/` is LOAM'S — the opening
|
|
3
|
+
* comment both generated views files carry, and the predicate that reads it
|
|
4
|
+
* back off a file on disk.
|
|
5
|
+
*
|
|
6
|
+
* WHY A MARK EXISTS AT ALL. `architecture/subsystems.likec4` and
|
|
7
|
+
* `architecture/services.likec4` are filenames loam RESERVES inside a directory
|
|
8
|
+
* the team owns, and `loam subsystem sync` rewrites them wholesale. A docs repo
|
|
9
|
+
* that already kept a hand-authored diagram at one of those paths — plausible
|
|
10
|
+
* for `services.likec4` in particular, which loam reserved only now — would
|
|
11
|
+
* have lost it on the first sync after the upgrade, with no warning and nothing
|
|
12
|
+
* to restore it from. That is the one promise the rest of the tree keeps
|
|
13
|
+
* everywhere: authored bytes stay authored, which is what archive's verbatim
|
|
14
|
+
* splice is for. So the writer asks the file itself who wrote it, and refuses
|
|
15
|
+
* every path where the answer is not loam.
|
|
16
|
+
*
|
|
17
|
+
* IT LIVES HERE, in the leaf package, for the reason `./view-id.ts` does: the
|
|
18
|
+
* parent (`../views.ts`) may import a child and never the reverse, so the one
|
|
19
|
+
* spelling BOTH generated files must open with has to sit below both. Two
|
|
20
|
+
* literals for one mark is how the day comes that a file loam wrote is not
|
|
21
|
+
* recognised as loam's and the writer refuses to regenerate its own output.
|
|
22
|
+
*
|
|
23
|
+
* SO DOES THE READ, AND SO DOES THE SENTENCE, and that is the correction this
|
|
24
|
+
* module carries. The guard first shipped inside `loam subsystem sync` alone,
|
|
25
|
+
* and there are FOUR other writers of exactly these two paths: the shared commit
|
|
26
|
+
* window behind `subsystem new`, `rm`, `move` and `rename`
|
|
27
|
+
* (`commands/subsystem/txn/txn.ts`) and `loam seed`
|
|
28
|
+
* (`commands/seed/plan.ts`). Reproduced before this module grew: with a
|
|
29
|
+
* hand-authored `architecture/services.likec4` in the tree, `loam subsystem new
|
|
30
|
+
* platform` printed "architecture/services.likec4 updated" and destroyed it —
|
|
31
|
+
* the file's digest changed and its first line became loam's header. A guard
|
|
32
|
+
* that lives in one of five writers is not a guard. So the reader
|
|
33
|
+
* (`readGeneratedFile`, `authoredGeneratedPaths`) and the refusal sentence
|
|
34
|
+
* (`authoredRefusal`) sit beside the mark itself, below every writer, and a
|
|
35
|
+
* sixth writer added tomorrow has one place to reach for rather than a fourth
|
|
36
|
+
* spelling to invent.
|
|
37
|
+
*/
|
|
38
|
+
import { existsSync } from "node:fs";
|
|
39
|
+
import { readFile } from "node:fs/promises";
|
|
40
|
+
/**
|
|
41
|
+
* The first bytes of every generated views file. Part of the content-compare
|
|
42
|
+
* contract, so it is not prose that may be reworded: the rest of each header is,
|
|
43
|
+
* and deliberately, because only this much identifies the owner.
|
|
44
|
+
*/
|
|
45
|
+
export const GENERATED_MARK = "// generated by loam";
|
|
46
|
+
/**
|
|
47
|
+
* Was the text now at a generated path written by loam?
|
|
48
|
+
*
|
|
49
|
+
* A LEADING BYTE-ORDER MARK is stripped first, and that is not tidiness: a
|
|
50
|
+
* Windows editor that opens a generated file and saves it back adds one without
|
|
51
|
+
* changing a single fact in the file, and without this the writer would call its
|
|
52
|
+
* own output authored and refuse to regenerate it for good — a refusal no
|
|
53
|
+
* re-running could ever clear. CRLF needs no such treatment, for the reason
|
|
54
|
+
* `viewsAgree` records one file over: the mark is the head of the first line and
|
|
55
|
+
* the line ending is behind it.
|
|
56
|
+
*
|
|
57
|
+
* The test is the MARK and not the whole header, because the header's remaining
|
|
58
|
+
* lines differ between the two files and are prose. It is also deliberately not
|
|
59
|
+
* a test of the whole file: a hand EDIT of a generated file keeps the header, and
|
|
60
|
+
* that file is loam's to repair — `subsystem.views-stale` names it and one sync
|
|
61
|
+
* puts it back. Only a file that never carried the mark is somebody else's.
|
|
62
|
+
*/
|
|
63
|
+
export function isGenerated(text) {
|
|
64
|
+
return text.replace(/^\uFEFF/, "").startsWith(GENERATED_MARK);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read one generated path and answer both questions about it at once \u2014 what it
|
|
68
|
+
* says, and whether loam may write it.
|
|
69
|
+
*
|
|
70
|
+
* ONE READ, because the only caller that needs the text is the one that also
|
|
71
|
+
* compares it (`subsystem sync`'s `planFile`), and asking the disk twice for the
|
|
72
|
+
* same small file so that two helpers could each own half the answer is how the
|
|
73
|
+
* two halves come to disagree about which file they were talking about.
|
|
74
|
+
*
|
|
75
|
+
* ABSENT IS NOT AUTHORED. There is nothing to lose at a path that holds
|
|
76
|
+
* nothing, and a fleet being scaffolded must still get its first generated file
|
|
77
|
+
* \u2014 a guard that refused an absent file would refuse every repository forever.
|
|
78
|
+
*/
|
|
79
|
+
export async function readGeneratedFile(path) {
|
|
80
|
+
if (!existsSync(path))
|
|
81
|
+
return { text: null, authored: false };
|
|
82
|
+
const text = await readFile(path, "utf8");
|
|
83
|
+
return { text, authored: !isGenerated(text) };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Which of these generated paths hold a file loam did not write \u2014 the question
|
|
87
|
+
* every writer that rewrites the PAIR wholesale asks before it stages anything.
|
|
88
|
+
*
|
|
89
|
+
* The paths come back in the order they were given, so a caller's message names
|
|
90
|
+
* the files in a stable order rather than in whichever order the reads finished.
|
|
91
|
+
*/
|
|
92
|
+
export async function authoredGeneratedPaths(paths) {
|
|
93
|
+
// Independent reads of at most two small files: `Promise.all`, per the house
|
|
94
|
+
// rule, and the order of the input is preserved by the map rather than by the
|
|
95
|
+
// scheduler.
|
|
96
|
+
const reads = await Promise.all(paths.map(async (path) => ({ path, read: await readGeneratedFile(path) })));
|
|
97
|
+
return reads.filter((r) => r.read.authored).map((r) => r.path);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The one sentence a refusal owes about ONE authored file, composed here and
|
|
101
|
+
* parameterised by the file, because FIVE writers refuse for this reason and a
|
|
102
|
+
* reader who met the sentence from `loam subsystem sync` must recognise it
|
|
103
|
+
* verbatim from `loam subsystem new`, `rm`, `move`, `rename` and `loam seed`.
|
|
104
|
+
*
|
|
105
|
+
* It names three things a person cannot get anywhere else: which file was left
|
|
106
|
+
* alone, WHY loam thinks the file is not its own (the mark it looked for), and
|
|
107
|
+
* the fact that nothing was written to it. The last clause closes the loop the
|
|
108
|
+
* refusal otherwise leaves open \u2014 `loam validate --all` will keep reporting
|
|
109
|
+
* `subsystem.views-stale` for that path and keep naming `loam subsystem sync` as
|
|
110
|
+
* the repair, so a person who is not told why the writers decline would be sent
|
|
111
|
+
* back and forth between the two forever.
|
|
112
|
+
*
|
|
113
|
+
* It deliberately does NOT name a command to re-run. It used to say "re-run
|
|
114
|
+
* `loam subsystem sync`", which was true of the one caller it then had and wrong
|
|
115
|
+
* for the four that were added: the command to re-run is the one the person
|
|
116
|
+
* typed, and the sentence a caller appends says what that command did instead.
|
|
117
|
+
*/
|
|
118
|
+
export function authoredRefusal(file) {
|
|
119
|
+
return (`${file} left as it is \u2014 its first line is not \`${GENERATED_MARK}\`, so it is an AUTHORED file standing at a ` +
|
|
120
|
+
"path loam generates and rewrites wholesale. Nothing was written to it and not one of its bytes changed. " +
|
|
121
|
+
"Rename it to something loam does not reserve (every other `.likec4` under architecture/ renders in the same " +
|
|
122
|
+
"project, so the diagram is not lost) or delete it, then re-run. Until then `loam validate --all` reports " +
|
|
123
|
+
"`subsystem.views-stale` for that path and every loam command that writes the file keeps refusing.");
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAChE,CAAC;AAUD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAClD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,KAAwB;IACnE,6EAA6E;IAC7E,8EAA8E;IAC9E,aAAa;IACb,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAC3E,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,CACL,GAAG,IAAI,iDAAiD,cAAc,8CAA8C;QACpH,0GAA0G;QAC1G,8GAA8G;QAC9G,2GAA2G;QAC3G,mGAAmG,CACpG,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The one line that says a file under `architecture/` is LOAM'S — the opening\n * comment both generated views files carry, and the predicate that reads it\n * back off a file on disk.\n *\n * WHY A MARK EXISTS AT ALL. `architecture/subsystems.likec4` and\n * `architecture/services.likec4` are filenames loam RESERVES inside a directory\n * the team owns, and `loam subsystem sync` rewrites them wholesale. A docs repo\n * that already kept a hand-authored diagram at one of those paths — plausible\n * for `services.likec4` in particular, which loam reserved only now — would\n * have lost it on the first sync after the upgrade, with no warning and nothing\n * to restore it from. That is the one promise the rest of the tree keeps\n * everywhere: authored bytes stay authored, which is what archive's verbatim\n * splice is for. So the writer asks the file itself who wrote it, and refuses\n * every path where the answer is not loam.\n *\n * IT LIVES HERE, in the leaf package, for the reason `./view-id.ts` does: the\n * parent (`../views.ts`) may import a child and never the reverse, so the one\n * spelling BOTH generated files must open with has to sit below both. Two\n * literals for one mark is how the day comes that a file loam wrote is not\n * recognised as loam's and the writer refuses to regenerate its own output.\n *\n * SO DOES THE READ, AND SO DOES THE SENTENCE, and that is the correction this\n * module carries. The guard first shipped inside `loam subsystem sync` alone,\n * and there are FOUR other writers of exactly these two paths: the shared commit\n * window behind `subsystem new`, `rm`, `move` and `rename`\n * (`commands/subsystem/txn/txn.ts`) and `loam seed`\n * (`commands/seed/plan.ts`). Reproduced before this module grew: with a\n * hand-authored `architecture/services.likec4` in the tree, `loam subsystem new\n * platform` printed \"architecture/services.likec4 updated\" and destroyed it —\n * the file's digest changed and its first line became loam's header. A guard\n * that lives in one of five writers is not a guard. So the reader\n * (`readGeneratedFile`, `authoredGeneratedPaths`) and the refusal sentence\n * (`authoredRefusal`) sit beside the mark itself, below every writer, and a\n * sixth writer added tomorrow has one place to reach for rather than a fourth\n * spelling to invent.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\n\n/**\n * The first bytes of every generated views file. Part of the content-compare\n * contract, so it is not prose that may be reworded: the rest of each header is,\n * and deliberately, because only this much identifies the owner.\n */\nexport const GENERATED_MARK = \"// generated by loam\";\n\n/**\n * Was the text now at a generated path written by loam?\n *\n * A LEADING BYTE-ORDER MARK is stripped first, and that is not tidiness: a\n * Windows editor that opens a generated file and saves it back adds one without\n * changing a single fact in the file, and without this the writer would call its\n * own output authored and refuse to regenerate it for good — a refusal no\n * re-running could ever clear. CRLF needs no such treatment, for the reason\n * `viewsAgree` records one file over: the mark is the head of the first line and\n * the line ending is behind it.\n *\n * The test is the MARK and not the whole header, because the header's remaining\n * lines differ between the two files and are prose. It is also deliberately not\n * a test of the whole file: a hand EDIT of a generated file keeps the header, and\n * that file is loam's to repair — `subsystem.views-stale` names it and one sync\n * puts it back. Only a file that never carried the mark is somebody else's.\n */\nexport function isGenerated(text: string): boolean {\n return text.replace(/^\\uFEFF/, \"\").startsWith(GENERATED_MARK);\n}\n\n/** What a writer of a generated path learns by reading it, in one read. */\nexport interface GeneratedFileRead {\n /** The bytes as text, or null when nothing stands at the path. */\n text: string | null;\n /** SOMEBODY ELSE wrote this: a file is there and its first line is not the mark. */\n authored: boolean;\n}\n\n/**\n * Read one generated path and answer both questions about it at once \\u2014 what it\n * says, and whether loam may write it.\n *\n * ONE READ, because the only caller that needs the text is the one that also\n * compares it (`subsystem sync`'s `planFile`), and asking the disk twice for the\n * same small file so that two helpers could each own half the answer is how the\n * two halves come to disagree about which file they were talking about.\n *\n * ABSENT IS NOT AUTHORED. There is nothing to lose at a path that holds\n * nothing, and a fleet being scaffolded must still get its first generated file\n * \\u2014 a guard that refused an absent file would refuse every repository forever.\n */\nexport async function readGeneratedFile(path: string): Promise<GeneratedFileRead> {\n if (!existsSync(path)) return { text: null, authored: false };\n const text = await readFile(path, \"utf8\");\n return { text, authored: !isGenerated(text) };\n}\n\n/**\n * Which of these generated paths hold a file loam did not write \\u2014 the question\n * every writer that rewrites the PAIR wholesale asks before it stages anything.\n *\n * The paths come back in the order they were given, so a caller's message names\n * the files in a stable order rather than in whichever order the reads finished.\n */\nexport async function authoredGeneratedPaths(paths: readonly string[]): Promise<string[]> {\n // Independent reads of at most two small files: `Promise.all`, per the house\n // rule, and the order of the input is preserved by the map rather than by the\n // scheduler.\n const reads = await Promise.all(\n paths.map(async (path) => ({ path, read: await readGeneratedFile(path) })),\n );\n return reads.filter((r) => r.read.authored).map((r) => r.path);\n}\n\n/**\n * The one sentence a refusal owes about ONE authored file, composed here and\n * parameterised by the file, because FIVE writers refuse for this reason and a\n * reader who met the sentence from `loam subsystem sync` must recognise it\n * verbatim from `loam subsystem new`, `rm`, `move`, `rename` and `loam seed`.\n *\n * It names three things a person cannot get anywhere else: which file was left\n * alone, WHY loam thinks the file is not its own (the mark it looked for), and\n * the fact that nothing was written to it. The last clause closes the loop the\n * refusal otherwise leaves open \\u2014 `loam validate --all` will keep reporting\n * `subsystem.views-stale` for that path and keep naming `loam subsystem sync` as\n * the repair, so a person who is not told why the writers decline would be sent\n * back and forth between the two forever.\n *\n * It deliberately does NOT name a command to re-run. It used to say \"re-run\n * `loam subsystem sync`\", which was true of the one caller it then had and wrong\n * for the four that were added: the command to re-run is the one the person\n * typed, and the sentence a caller appends says what that command did instead.\n */\nexport function authoredRefusal(file: string): string {\n return (\n `${file} left as it is \\u2014 its first line is not \\`${GENERATED_MARK}\\`, so it is an AUTHORED file standing at a ` +\n \"path loam generates and rewrites wholesale. Nothing was written to it and not one of its bytes changed. \" +\n \"Rename it to something loam does not reserve (every other `.likec4` under architecture/ renders in the same \" +\n \"project, so the diagram is not lost) or delete it, then re-run. Until then `loam validate --all` reports \" +\n \"`subsystem.views-stale` for that path and every loam command that writes the file keeps refusing.\"\n );\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every element on the far side of an edge touching this service, sorted and
|
|
3
|
+
* de-duplicated.
|
|
4
|
+
*
|
|
5
|
+
* "Touching" is the subject OR any descendant of it: an edge into
|
|
6
|
+
* `svc.api` is an edge into the service, and the view that hid it would be
|
|
7
|
+
* lying about the service's surface. The far endpoint is dropped when it is
|
|
8
|
+
* itself the subject or one of its descendants — that is INTERIOR, already
|
|
9
|
+
* drawn by the view's own `include *`, and naming it again would put a second
|
|
10
|
+
* include line in the file for a node that is already there.
|
|
11
|
+
*
|
|
12
|
+
* Sorted and de-duplicated because the generated file is a pure function of
|
|
13
|
+
* committed bytes: document order is the parse's, not the author's, and two
|
|
14
|
+
* edges between the same pair must not emit the same include twice.
|
|
15
|
+
*/
|
|
16
|
+
export function peersOf(fqn, relationships) {
|
|
17
|
+
const prefix = `${fqn}.`;
|
|
18
|
+
const own = (id) => id === fqn || id.startsWith(prefix);
|
|
19
|
+
const found = new Set();
|
|
20
|
+
for (const rel of relationships) {
|
|
21
|
+
if (own(rel.source) && !own(rel.target))
|
|
22
|
+
found.add(rel.target);
|
|
23
|
+
if (own(rel.target) && !own(rel.source))
|
|
24
|
+
found.add(rel.source);
|
|
25
|
+
}
|
|
26
|
+
return [...found].sort();
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=peers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peers.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/peers.ts"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,aAA6B;IAChE,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,MAAM,GAAG,GAAG,CAAC,EAAU,EAAW,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * The NEIGHBOUR SET of one service: every element the map draws an edge to or\n * from, computed by loam rather than asked of the renderer.\n *\n * This module exists because of a measurement, and the measurement is the whole\n * reason the per-service view is not three wildcard lines. At likec4 1.59.2, on\n * a 120-service map whose services are nested in `group` boxes, the obvious\n * recipe\n *\n * include *\n * include <fqn> -> *\n * include * -> <fqn>\n *\n * drew the GROUP BOXES as the counterparts — `growth`, `catalog`, `support`,\n * `notifications` — instead of the peer services, and NONE of them carried\n * drill-down. LikeC4 resolves a counterpart at the highest ancestor that is not\n * also an ancestor of the subject, so on any map that groups its services —\n * which is exactly what a fleet past about thirty services needs at fleet\n * level — the wildcard form says \"payments/service-0 talks to the growth group\"\n * and navigates nowhere. It looks perfect on a five-service FLAT fixture, which\n * is how it would have shipped.\n *\n * Re-measured here at 1.59.2 on the 58-service / 11-group fleet, subject\n * `payments.svc0` with 8 real peers: the wildcard recipe draws 10 nodes of which\n * four are group boxes; naming the peers draws 11 — the subject, its `api`, its\n * `db` and all 8 peers, each at its own level, each carrying `navigateTo` into\n * that peer's own generated view (6 of the 8 are services and have one; the\n * customer and the broker are not).\n *\n * The counterpart is taken AS THE MAP DECLARES IT — an edge landing on\n * `kafka.orderEvents` names the topic, not the broker — because that is the\n * truer picture and it is the one the map already draws. Anything the render\n * substituted here would be loam re-drawing somebody's architecture.\n */\nimport type { Rel } from \"../../../../c4/likec4.js\";\n\n/**\n * Every element on the far side of an edge touching this service, sorted and\n * de-duplicated.\n *\n * \"Touching\" is the subject OR any descendant of it: an edge into\n * `svc.api` is an edge into the service, and the view that hid it would be\n * lying about the service's surface. The far endpoint is dropped when it is\n * itself the subject or one of its descendants — that is INTERIOR, already\n * drawn by the view's own `include *`, and naming it again would put a second\n * include line in the file for a node that is already there.\n *\n * Sorted and de-duplicated because the generated file is a pure function of\n * committed bytes: document order is the parse's, not the author's, and two\n * edges between the same pair must not emit the same include twice.\n */\nexport function peersOf(fqn: string, relationships: readonly Rel[]): string[] {\n const prefix = `${fqn}.`;\n const own = (id: string): boolean => id === fqn || id.startsWith(prefix);\n const found = new Set<string>();\n for (const rel of relationships) {\n if (own(rel.source) && !own(rel.target)) found.add(rel.target);\n if (own(rel.target) && !own(rel.source)) found.add(rel.source);\n }\n return [...found].sort();\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identifier loam mints for a per-service overview view, and the ONE
|
|
3
|
+
* encoder every generated view id is spelled through.
|
|
4
|
+
*
|
|
5
|
+
* The encoder lives HERE, in the child package, rather than beside the
|
|
6
|
+
* subsystem render that has used it since it was written — and the direction is
|
|
7
|
+
* forced rather than chosen. `../views.ts` imports this package to compose the
|
|
8
|
+
* per-service file, so an import the other way would be the package cycle
|
|
9
|
+
* `scripts/package-graph.mjs` refuses; that script's own advice for exactly this
|
|
10
|
+
* shape is "move the helper; do not import the weight", and an id encoder is the
|
|
11
|
+
* leaf while the two renders are the weight. One copy is also the point: the
|
|
12
|
+
* injectivity argument below holds across BOTH prefixes only while they share an
|
|
13
|
+
* encoder, and a second spelling would be a second chance to collide.
|
|
14
|
+
*
|
|
15
|
+
* INJECTIVITY is a correctness rule and not tidiness. The renderer refuses the
|
|
16
|
+
* whole `architecture/` project over a duplicate view id, and the naive folding
|
|
17
|
+
* (every non-identifier byte and the join separator alike becoming `_`) collided
|
|
18
|
+
* three DISTINCT healthy names onto one identifier — `a_b`, and `b` under `a`,
|
|
19
|
+
* both rendering `subsystem_a_b` — so `validate --all` was green while the
|
|
20
|
+
* renderer refused the fleet map, and `subsystem.name-collision` never fired
|
|
21
|
+
* because the flat namespace really was intact.
|
|
22
|
+
*
|
|
23
|
+
* Hence the encoding: an identifier byte (`[A-Za-z0-9]`) passes through, any
|
|
24
|
+
* other byte becomes `_` + two lowercase hex digits (`.` → `_2e`, `-` → `_2d`,
|
|
25
|
+
* `_` itself → `_5f` — the underscore must be escaped precisely because it is
|
|
26
|
+
* BOTH legal in a name and the separator between path segments). Underscore runs
|
|
27
|
+
* then decode uniquely — one is an escape, two a separator, three a separator
|
|
28
|
+
* followed by an escape — so no two distinct legal names share an identifier. A
|
|
29
|
+
* name `subsystem.name-invalid` already flagged may hold a code point above 0xff
|
|
30
|
+
* whose longer hex could in principle re-collide; it is deterministic still, and
|
|
31
|
+
* that directory is already an error finding by name.
|
|
32
|
+
*
|
|
33
|
+
* The prefix is EXPORTED, with the minting function, because the collision check
|
|
34
|
+
* (`subsystem.view-id-collision`) has to ask the generator what it mints rather
|
|
35
|
+
* than re-derive it. A check carrying its own copy of the escaping goes quiet the
|
|
36
|
+
* day the encoding changes, and going quiet is the failure that whole family
|
|
37
|
+
* exists to prevent.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The reserved prefix for a generated per-service overview. Distinct from
|
|
41
|
+
* `subsystem_` so the two generated files can never mint the same id for
|
|
42
|
+
* different subjects, and it cannot match the shape LikeC4 mints for an unnamed
|
|
43
|
+
* view (`^view_[0-9a-z]+$`, `core/c4/parsed/view-ids.ts`), so a generated id is
|
|
44
|
+
* never mistaken for a synthesized one.
|
|
45
|
+
*/
|
|
46
|
+
export const SERVICE_VIEW_PREFIX = "service_";
|
|
47
|
+
/** One name, folded to identifier bytes injectively — the banner argues why this shape. */
|
|
48
|
+
export function escapeViewIdSegment(name) {
|
|
49
|
+
return [...name]
|
|
50
|
+
.map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0).toString(16).padStart(2, "0")}`))
|
|
51
|
+
.join("");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The view id for one service, from its `services/<id>/` DIRECTORY name.
|
|
55
|
+
*
|
|
56
|
+
* The directory id rather than the map element's title or its fqn, for the same
|
|
57
|
+
* reason the title uses it: it is the name every loam command already addresses
|
|
58
|
+
* the service by, a directory name cannot hold a `/` or a `.`, and it cannot
|
|
59
|
+
* drift when somebody renames a box on the map. Two services cannot share a
|
|
60
|
+
* directory id — the walk's flat namespace already refuses that as
|
|
61
|
+
* `subsystem.name-collision` — so injectivity of the encoder is injectivity of
|
|
62
|
+
* the whole id.
|
|
63
|
+
*/
|
|
64
|
+
export function serviceViewId(id) {
|
|
65
|
+
return `${SERVICE_VIEW_PREFIX}${escapeViewIdSegment(id)}`;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=view-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-id.js","sourceRoot":"","sources":["../../../../../../src/core/repo/tree/render/service/view-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAE9C,2FAA2F;AAC3F,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,CAAC,GAAG,IAAI,CAAC;SACb,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;SACnG,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,GAAG,mBAAmB,GAAG,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAC;AAC5D,CAAC","sourcesContent":["/**\n * The identifier loam mints for a per-service overview view, and the ONE\n * encoder every generated view id is spelled through.\n *\n * The encoder lives HERE, in the child package, rather than beside the\n * subsystem render that has used it since it was written — and the direction is\n * forced rather than chosen. `../views.ts` imports this package to compose the\n * per-service file, so an import the other way would be the package cycle\n * `scripts/package-graph.mjs` refuses; that script's own advice for exactly this\n * shape is \"move the helper; do not import the weight\", and an id encoder is the\n * leaf while the two renders are the weight. One copy is also the point: the\n * injectivity argument below holds across BOTH prefixes only while they share an\n * encoder, and a second spelling would be a second chance to collide.\n *\n * INJECTIVITY is a correctness rule and not tidiness. The renderer refuses the\n * whole `architecture/` project over a duplicate view id, and the naive folding\n * (every non-identifier byte and the join separator alike becoming `_`) collided\n * three DISTINCT healthy names onto one identifier — `a_b`, and `b` under `a`,\n * both rendering `subsystem_a_b` — so `validate --all` was green while the\n * renderer refused the fleet map, and `subsystem.name-collision` never fired\n * because the flat namespace really was intact.\n *\n * Hence the encoding: an identifier byte (`[A-Za-z0-9]`) passes through, any\n * other byte becomes `_` + two lowercase hex digits (`.` → `_2e`, `-` → `_2d`,\n * `_` itself → `_5f` — the underscore must be escaped precisely because it is\n * BOTH legal in a name and the separator between path segments). Underscore runs\n * then decode uniquely — one is an escape, two a separator, three a separator\n * followed by an escape — so no two distinct legal names share an identifier. A\n * name `subsystem.name-invalid` already flagged may hold a code point above 0xff\n * whose longer hex could in principle re-collide; it is deterministic still, and\n * that directory is already an error finding by name.\n *\n * The prefix is EXPORTED, with the minting function, because the collision check\n * (`subsystem.view-id-collision`) has to ask the generator what it mints rather\n * than re-derive it. A check carrying its own copy of the escaping goes quiet the\n * day the encoding changes, and going quiet is the failure that whole family\n * exists to prevent.\n */\n\n/**\n * The reserved prefix for a generated per-service overview. Distinct from\n * `subsystem_` so the two generated files can never mint the same id for\n * different subjects, and it cannot match the shape LikeC4 mints for an unnamed\n * view (`^view_[0-9a-z]+$`, `core/c4/parsed/view-ids.ts`), so a generated id is\n * never mistaken for a synthesized one.\n */\nexport const SERVICE_VIEW_PREFIX = \"service_\";\n\n/** One name, folded to identifier bytes injectively — the banner argues why this shape. */\nexport function escapeViewIdSegment(name: string): string {\n return [...name]\n .map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0)!.toString(16).padStart(2, \"0\")}`))\n .join(\"\");\n}\n\n/**\n * The view id for one service, from its `services/<id>/` DIRECTORY name.\n *\n * The directory id rather than the map element's title or its fqn, for the same\n * reason the title uses it: it is the name every loam command already addresses\n * the service by, a directory name cannot hold a `/` or a `.`, and it cannot\n * drift when somebody renames a box on the map. Two services cannot share a\n * directory id — the walk's flat namespace already refuses that as\n * `subsystem.name-collision` — so injectivity of the encoder is injectivity of\n * the whole id.\n */\nexport function serviceViewId(id: string): string {\n return `${SERVICE_VIEW_PREFIX}${escapeViewIdSegment(id)}`;\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { GENERATED_MARK } from "./header.js";
|
|
2
|
+
import { peersOf } from "./peers.js";
|
|
3
|
+
import { serviceViewId } from "./view-id.js";
|
|
4
|
+
/**
|
|
5
|
+
* The global style group these views reference when the map declares one, the
|
|
6
|
+
* exact counterpart of `SUBSYSTEM_STYLE_GROUP` and reserved on the same terms:
|
|
7
|
+
* presence of the DECLARATION is the whole opt-in, because referencing an
|
|
8
|
+
* undeclared style id is a parse error that blanks the entire `architecture/`
|
|
9
|
+
* project in the renderer. The name mirrors the file it styles.
|
|
10
|
+
*/
|
|
11
|
+
export const SERVICE_STYLE_GROUP = "services";
|
|
12
|
+
/**
|
|
13
|
+
* The header every generated file opens with — stable bytes, part of the
|
|
14
|
+
* content-compare contract, naming the regenerator so a reader who found the
|
|
15
|
+
* file before the docs knows what owns it. `loam subsystem sync` regenerates
|
|
16
|
+
* BOTH generated files, which is why one command is named here and why one
|
|
17
|
+
* finding code (`subsystem.views-stale`) honestly covers both.
|
|
18
|
+
*
|
|
19
|
+
* It OPENS with the shared `GENERATED_MARK` rather than spelling it, because
|
|
20
|
+
* the same bytes are what `loam subsystem sync` reads back off disk to decide
|
|
21
|
+
* whether the file at this reserved path is loam's to rewrite at all
|
|
22
|
+
* (`./header.js`). A second literal here is how a file loam wrote stops being
|
|
23
|
+
* recognised as loam's.
|
|
24
|
+
*/
|
|
25
|
+
const HEADER = `${GENERATED_MARK} — one overview view per service under services/, drawn from the fleet map.\n` +
|
|
26
|
+
"// Do not edit: `loam subsystem sync` regenerates this file from the directories and the map,\n" +
|
|
27
|
+
"// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\n" +
|
|
28
|
+
"// A navigation aid, not the fleet's architecture: author your own diagrams elsewhere.\n";
|
|
29
|
+
/**
|
|
30
|
+
* Render the whole file, or null when no service resolves to a map element —
|
|
31
|
+
* the file must then be ABSENT, so a fleet whose map draws nothing never
|
|
32
|
+
* carries a generated artifact that says nothing.
|
|
33
|
+
*
|
|
34
|
+
* Services sorted by id and peers sorted by id, one `include` per line: the
|
|
35
|
+
* output is byte-reproducible on any machine from the same committed bytes (no
|
|
36
|
+
* timestamps, no absolute paths, no readdir-order dependence), and two
|
|
37
|
+
* concurrent changes to different services touch different lines, so git merges
|
|
38
|
+
* the file without intervention.
|
|
39
|
+
*/
|
|
40
|
+
export function renderServiceViews(input) {
|
|
41
|
+
if (input.subjects.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
const lines = [HEADER, ...(input.disclosure === null ? [] : [input.disclosure]), "views {"];
|
|
44
|
+
const subjects = [...input.subjects].sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
45
|
+
for (const [i, subject] of subjects.entries()) {
|
|
46
|
+
if (i > 0)
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push(...viewLines(subject, peersOf(subject.fqn, input.relationships), input.styled));
|
|
49
|
+
}
|
|
50
|
+
lines.push("}");
|
|
51
|
+
return lines.join("\n") + "\n";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One view, in the order the parser demands: metadata FIRST, and that is a hard
|
|
55
|
+
* constraint rather than a taste — measured, an `include` line followed by
|
|
56
|
+
* `title` is a parse error ("Expecting token of type '}' but found `title`"),
|
|
57
|
+
* and one parse error in this file blanks the whole `architecture/` project in
|
|
58
|
+
* the renderer. `global style` sits after `title` and before anything that
|
|
59
|
+
* includes, where the same pin measured it parsing with zero errors.
|
|
60
|
+
*/
|
|
61
|
+
function viewLines(subject, peers, styled) {
|
|
62
|
+
const { fqn } = subject;
|
|
63
|
+
const out = [` view ${serviceViewId(subject.id)} of ${fqn} {`, ` title '${subject.title}'`];
|
|
64
|
+
if (styled)
|
|
65
|
+
out.push(` global style ${SERVICE_STYLE_GROUP}`);
|
|
66
|
+
out.push(" include *");
|
|
67
|
+
for (const peer of peers)
|
|
68
|
+
out.push(` include ${peer}`);
|
|
69
|
+
out.push(" exclude * -> *", ` include -> ${fqn}`, ` include ${fqn} ->`);
|
|
70
|
+
// The interior wiring, last. Measured: `include <fqn>.* -> <fqn>.*` on a
|
|
71
|
+
// CHILDLESS element is legal — `likec4 validate` reports valid and the export
|
|
72
|
+
// carries zero errors — so one generated line serves both model shapes: an
|
|
73
|
+
// EXTENDING model's containers live in the root project and are drawn, a
|
|
74
|
+
// STANDALONE model's are not in this project and the line matches nothing.
|
|
75
|
+
out.push(` include ${fqn}.* -> ${fqn}.*`, " }");
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=views.js.map
|