@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
|
@@ -1,103 +1,318 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `loam subsystem sync` — the
|
|
3
|
-
*
|
|
4
|
-
* differs, remove it when the tree has no subsystems, and say which.
|
|
2
|
+
* `loam subsystem sync` — the writer of everything under the tree that exists
|
|
3
|
+
* only to be RENDERED, which is the renderer's reading of the tree twice over:
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* - `architecture/subsystems.likec4`, the GENERATED views file. Recomputed from
|
|
6
|
+
* the tree on every run, compared, written when it differs, removed when the
|
|
7
|
+
* tree has no subsystems. One of the two repairs for `subsystem.views-stale`.
|
|
8
|
+
* Left exactly as it is when the `architecture/` project does not parse and
|
|
9
|
+
* the file already exists — `action: "blocked"`, below.
|
|
10
|
+
* - `architecture/services.likec4`, the OTHER generated views file: one
|
|
11
|
+
* overview view per service the map draws, so a reader can open a service and
|
|
12
|
+
* see what it touches. Same rules, same commit, same code when it drifts —
|
|
13
|
+
* which is what makes ONE `subsystem.views-stale` honest about two files, and
|
|
14
|
+
* why this command is named as the repair for both. Removed when NO service
|
|
15
|
+
* resolves to a map element, exactly as the subsystem file is removed when
|
|
16
|
+
* the tree has no subsystems. `serviceViews` is its `action`; `action` still
|
|
17
|
+
* means the subsystem file alone.
|
|
18
|
+
* - `services/<…>/<id>/likec4.config.json`, one LikeC4 project file beside
|
|
19
|
+
* every model that STANDS ALONE. Never compared, never rewritten. Created
|
|
20
|
+
* where a standalone model has none, and DELETED where an extending model has
|
|
21
|
+
* one: measured at the 1.59.2 pin, that nested project claims the model out of
|
|
22
|
+
* the root project and the fleet loses the service's interior
|
|
23
|
+
* (`core/repo/tree/render/projects.ts` carries the measurement). This is the
|
|
24
|
+
* repair `service.likec4-config-stray` has always named and, until now, did
|
|
25
|
+
* not perform.
|
|
26
|
+
* - the ROOT `likec4.config.json`'s `exclude` list, and ONLY that key. Derived
|
|
27
|
+
* from the shapes on disk on every run and rewritten when it differs, because
|
|
28
|
+
* the two shapes need opposite answers from it: a standalone model must be
|
|
29
|
+
* excluded (inside the root project every kind it declares is a duplicate)
|
|
30
|
+
* and an extending model must not be (the root project is the only place it
|
|
31
|
+
* parses). `planExclude` below states the gate that keeps an old repository
|
|
32
|
+
* untouched, and `core/c4/service-model/renderer.ts` states which half of the
|
|
33
|
+
* list is loam's and which is the team's. One entry loam did NOT write is
|
|
34
|
+
* taken anyway, and only this one: an entry naming a directory that HIDES an
|
|
35
|
+
* extending model — a subsystem-wide `services/platform/**` survived every
|
|
36
|
+
* sync while `service.model-excluded` named this command as the repair.
|
|
12
37
|
*
|
|
13
|
-
*
|
|
38
|
+
* NEITHER views file is written over a document loam did not write. Both are
|
|
39
|
+
* filenames loam RESERVES inside `architecture/`, a directory the team owns, and
|
|
40
|
+
* both are rewritten whole — so a repository that already kept a hand-authored
|
|
41
|
+
* diagram at one of those paths would have lost it, silently, on the first sync
|
|
42
|
+
* after the release that reserved the name. One predicate answers it for both
|
|
43
|
+
* (`readGeneratedFile`, off the file's own first line), the run reports
|
|
44
|
+
* `refused` for that file, writes nothing for it, and exits non-zero. A hand
|
|
45
|
+
* EDIT of a generated file is a different thing and is still repaired: it keeps
|
|
46
|
+
* the mark, so it is loam's file with wrong bytes in it, which is exactly what
|
|
47
|
+
* `subsystem.views-stale` says.
|
|
48
|
+
*
|
|
49
|
+
* THIS COMMAND IS NOT THE ONLY WRITER, and for a while it was the only one
|
|
50
|
+
* holding that line — reproduced, with a hand-authored
|
|
51
|
+
* `architecture/services.likec4` in the tree: `loam subsystem new platform`
|
|
52
|
+
* printed "architecture/services.likec4 updated" and destroyed it. The predicate
|
|
53
|
+
* and the refusal sentence therefore live below every writer
|
|
54
|
+
* (`core/repo/tree/render/service/header.ts`), and `./txn/txn.ts` and
|
|
55
|
+
* `../seed/plan.ts` ask the same question of the same two paths.
|
|
56
|
+
*
|
|
57
|
+
* WHAT DIFFERS IS THE POLICY, not the question, and the difference is the shape
|
|
58
|
+
* of the operation rather than a second opinion about ownership. Sync REPORTS
|
|
59
|
+
* `refused` for the one file and does the rest: nothing it writes depends on
|
|
60
|
+
* that file, every part of it is idempotent, and a run that also created a
|
|
61
|
+
* project file really did create it. A tree-changing verb and `seed` REFUSE
|
|
62
|
+
* WHOLE, because their generated file has to land in the same commit as the
|
|
63
|
+
* change to the tree — completing half of that is the very state the commit
|
|
64
|
+
* window exists never to leave between two commits, and `subsystem move` would
|
|
65
|
+
* additionally leave git-staged renames behind it.
|
|
66
|
+
*
|
|
67
|
+
* The rules differ because the files answer to different owners. The
|
|
68
|
+
* views files are a pure function of the tree and the committed landscape, so
|
|
69
|
+
* every byte of them is loam's and a hand edit is by definition stale. The
|
|
70
|
+
* project file's bytes are a function of the service id ALONE — the identity
|
|
71
|
+
* no verb ever changes — so nothing can make it stale, and the keys LikeC4
|
|
72
|
+
* lets a team add to it (`title`, styles, a contact) are the team's; a compare
|
|
73
|
+
* would fight them, and a rewrite would take them. `core/repo/tree/render/projects.ts`
|
|
74
|
+
* records the measurement behind the file and the naming rule; here, presence
|
|
75
|
+
* is the one question asked of it, through the SAME predicate `validate --all`
|
|
76
|
+
* grades with (`missingProjects`), so the grader can never name a gap this
|
|
77
|
+
* writer would not fill.
|
|
78
|
+
*
|
|
79
|
+
* Nothing under `services/` is written, and no `exclude` is recomputed, unless
|
|
80
|
+
* the ROOT `likec4.config.json` exists. Without it the tree is not a loadable
|
|
81
|
+
* workspace at all — every model would merge into one project and report every
|
|
82
|
+
* declaration as a duplicate — and a nested project beside a missing root is a
|
|
83
|
+
* promise about a renderer that cannot be opened. `loam doctor` prints the root
|
|
84
|
+
* file; sync says so and writes nothing for the services until it is there.
|
|
85
|
+
*
|
|
86
|
+
* Every write goes through the same lock + journaled transaction every docs
|
|
87
|
+
* writer uses (`commitStaged`), and both kinds land in ONE commit: a
|
|
88
|
+
* predecessor's interrupted commit is recovered first and a sync killed
|
|
89
|
+
* mid-swap is rolled forward by the next writer. Idempotent by construction:
|
|
90
|
+
* the expected views bytes are a pure function of the tree, and a project
|
|
91
|
+
* file, once present, is never a gap again — so a second run reports
|
|
92
|
+
* `current` and writes nothing.
|
|
93
|
+
*
|
|
94
|
+
* The expected views bytes come from `./txn/views.ts` — the same module every
|
|
14
95
|
* writer verb renders from, so sync and the transactions cannot disagree
|
|
15
|
-
* about what the file should say.
|
|
96
|
+
* about what the file should say. The subsystem verbs' own commit window
|
|
97
|
+
* (`./txn/txn.ts`) does NOT write project files, and records why. What this run
|
|
98
|
+
* SAYS it did is `./txn/report.ts`: the third answer about the same three files,
|
|
99
|
+
* and the phase that is not part of the commit.
|
|
16
100
|
*/
|
|
17
101
|
import { existsSync } from "node:fs";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
102
|
+
import { resolve } from "node:path";
|
|
103
|
+
import { readModelShapes } from "../../core/c4/service-model/shape.js";
|
|
104
|
+
import { fail, repoPath } from "../../core/envelope/json.js";
|
|
105
|
+
import { LIKEC4_ROOT_PROJECT, rootProjectPath, serviceRenderPaths } from "../../core/repo/paths.js";
|
|
21
106
|
import { listFleetTree } from "../../core/repo/repo.js";
|
|
107
|
+
import { renderServiceProject, surveyProjects, } from "../../core/repo/tree/render/projects.js";
|
|
22
108
|
import { stageWrites } from "../../core/staging/commit.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
|
|
109
|
+
import { LOCK_WAIT_MS } from "../../core/staging/lock.js";
|
|
110
|
+
import { openCommitWindow } from "../../core/staging/recovery/recover.js";
|
|
26
111
|
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
112
|
+
import { planWrite } from "../../core/staging/writes.js";
|
|
113
|
+
import { readGeneratedFile } from "../../core/repo/tree/render/service/header.js";
|
|
114
|
+
import { NO_EXCLUDE, planExclude } from "./txn/exclude.js";
|
|
115
|
+
import { reportSync } from "./txn/report.js";
|
|
116
|
+
import { expectedViews, planGenerated } from "./txn/views.js";
|
|
29
117
|
export async function runSync(docsDir, json) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!(err instanceof DocsBusyError))
|
|
36
|
-
throw err;
|
|
37
|
-
fail(json, "docs-busy", err.message);
|
|
118
|
+
// A predecessor's journal is recovered (or refused) before the tree is
|
|
119
|
+
// read: the views file itself may be the half-written one.
|
|
120
|
+
const opened = await openCommitWindow(docsDir, LOCK_WAIT_MS);
|
|
121
|
+
if (!opened.ok) {
|
|
122
|
+
fail(json, opened.code, opened.message);
|
|
38
123
|
return;
|
|
39
124
|
}
|
|
125
|
+
const { recovered } = opened;
|
|
40
126
|
try {
|
|
41
|
-
// A predecessor's journal is recovered (or refused) before the tree is
|
|
42
|
-
// read: the views file itself may be the half-written one.
|
|
43
|
-
let recovered;
|
|
44
|
-
try {
|
|
45
|
-
recovered = await recoverInterruptedCommit(docsDir);
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
if (!(err instanceof InterruptedCommitError))
|
|
49
|
-
throw err;
|
|
50
|
-
fail(json, "commit-interrupted", err.message);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
127
|
const tree = await listFleetTree(docsDir);
|
|
128
|
+
// BOTH generated files, from ONE read of the map — `./txn/views.ts` says why
|
|
129
|
+
// the pair is one answer and why the parsed document, never a literal, is
|
|
130
|
+
// what the per-service peer sets are computed from.
|
|
54
131
|
const expected = await expectedViews(docsDir, tree);
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
132
|
+
const views = await planFile(expected.subsystems, expected.known);
|
|
133
|
+
const services = await planFile(expected.services, expected.known);
|
|
134
|
+
// ONE read of every model's bytes for the whole run, and the same scan the
|
|
135
|
+
// grader uses: it decides which directories are owed a project file AND
|
|
136
|
+
// which the root project must exclude, and those are opposite answers for
|
|
137
|
+
// the two shapes (`core/repo/tree/render/projects.ts`).
|
|
138
|
+
const shapes = await readModelShapes(tree.services.map((svc) => serviceRenderPaths(svc.dir).model));
|
|
139
|
+
const plan = planProjects(docsDir, tree.services, shapes);
|
|
140
|
+
const exclude = plan.root ? await planExclude(docsDir, tree.services, plan.models) : NO_EXCLUDE;
|
|
141
|
+
// The plan IS the report: every gap is created and every stray removed in
|
|
142
|
+
// the one commit below, and the early return is the branch where both lists
|
|
143
|
+
// are empty.
|
|
144
|
+
const projects = () => ({
|
|
145
|
+
root: plan.root,
|
|
146
|
+
// SORTED BY PATH, which is what the contract says these keys hold and what
|
|
147
|
+
// the survey's own order is not: `surveyProjects` sorts by service id, so
|
|
148
|
+
// `services/svc-a/…` came out before `services/platform/svc-d/…` and a
|
|
149
|
+
// consumer told the list was "sorted" got an order no path sort produces
|
|
150
|
+
// (re-verification 2026-09-04, area C item 6). The text view prints the
|
|
151
|
+
// same array, so the two orders cannot drift apart.
|
|
152
|
+
created: plan.gaps.map((gap) => repoPath(docsDir, gap.path)).sort(),
|
|
153
|
+
removed: plan.strays.map((stray) => repoPath(docsDir, stray.path)).sort(),
|
|
154
|
+
current: plan.current,
|
|
155
|
+
modelled: plan.modelled,
|
|
156
|
+
exclude,
|
|
157
|
+
});
|
|
158
|
+
// `action` still describes `architecture/subsystems.likec4` ALONE — a run
|
|
159
|
+
// that only created project files, or only rewrote the per-service
|
|
160
|
+
// overviews, answers `current` for it, exactly as before either existed, so
|
|
161
|
+
// nothing reading the key sees a new meaning. The second generated file
|
|
162
|
+
// arrives as a key of its own (`serviceViews`), because a payload key's
|
|
163
|
+
// meaning is frozen and folding two files into one answer would lose which
|
|
164
|
+
// one moved — the only thing either sentence has to say.
|
|
165
|
+
const say = () => {
|
|
166
|
+
reportSync(json, {
|
|
167
|
+
action: views.action,
|
|
168
|
+
serviceViews: services.action,
|
|
169
|
+
subsystems: tree.subsystems.length,
|
|
170
|
+
recovered,
|
|
171
|
+
projects: projects(),
|
|
172
|
+
mapUnreadable: !expected.known,
|
|
173
|
+
});
|
|
174
|
+
// A REFUSAL MUST NOT READ AS A CLEAN RUN. The rest of the sync really did
|
|
175
|
+
// happen — the other generated file, the project files, the root
|
|
176
|
+
// `exclude` — so this is `loam doctor`'s and `loam validate`'s shape and
|
|
177
|
+
// not `fail()`'s: the whole report is emitted, and the exit code says the
|
|
178
|
+
// tree was not left in the state the command was asked to leave it in. A
|
|
179
|
+
// caller that runs `loam subsystem sync` in a hook or a CI step would
|
|
180
|
+
// otherwise be told nothing, forever, about a file it can never repair —
|
|
181
|
+
// and `loam validate --all` would go on reporting `subsystem.views-stale`
|
|
182
|
+
// for that path with this very command named as the fix.
|
|
183
|
+
if (views.action === "refused" || services.action === "refused")
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
};
|
|
186
|
+
const generated = [views.write, services.write].filter((write) => write !== null);
|
|
187
|
+
if (generated.length === 0 && plan.gaps.length === 0 && plan.strays.length === 0 && exclude.write === null) {
|
|
188
|
+
say();
|
|
66
189
|
return;
|
|
67
190
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
191
|
+
const writes = [...generated];
|
|
192
|
+
// `planWrite` marks a non-existent target exclusive — a no-clobber create
|
|
193
|
+
// that fails EEXIST rather than replacing a file another writer landed in
|
|
194
|
+
// the same instant. That IS the create-only rule, enforced by the write
|
|
195
|
+
// path rather than by the `existsSync` the predicate already ran: a file
|
|
196
|
+
// that appears between the check and the swap is somebody's, and stays.
|
|
197
|
+
for (const gap of plan.gaps) {
|
|
198
|
+
writes.push(planWrite(gap.path, renderServiceProject(gap.service.id, LIKEC4_ROOT_PROJECT)));
|
|
199
|
+
}
|
|
200
|
+
// The one file this verb DELETES, and it rides the same journal as the rest,
|
|
201
|
+
// so a run killed mid-swap is rolled forward rather than half-done.
|
|
202
|
+
// `content: null` is the delete the views file already uses.
|
|
203
|
+
for (const stray of plan.strays)
|
|
204
|
+
writes.push({ path: stray.path, content: null });
|
|
205
|
+
// The root project's `exclude`, in the SAME transaction as the files it
|
|
206
|
+
// decides the fate of: a run that wrote the per-service projects and died
|
|
207
|
+
// before the exclude would leave a tree where the renderer loads a
|
|
208
|
+
// standalone model twice — once in the root project, where every kind it
|
|
209
|
+
// declares is a duplicate — which is worse than either end state.
|
|
210
|
+
if (exclude.write !== null)
|
|
211
|
+
writes.push(exclude.write);
|
|
212
|
+
// One lock, one journal, one commit for both kinds of file — a sync killed
|
|
213
|
+
// between the views swap and the project creates would otherwise leave a
|
|
214
|
+
// half-state no later run could tell from a team's deliberate deletion.
|
|
215
|
+
const staged = await stageWrites(writes);
|
|
70
216
|
const committed = await commitStaged({ root: docsDir, command: "subsystem", rerun: "loam subsystem sync", target: "subsystems" }, staged, "synced");
|
|
71
217
|
if (!committed.ok) {
|
|
72
218
|
fail(json, committed.code, committed.message);
|
|
73
219
|
return;
|
|
74
220
|
}
|
|
75
|
-
|
|
221
|
+
say();
|
|
76
222
|
}
|
|
77
223
|
finally {
|
|
78
|
-
await
|
|
224
|
+
await opened.release();
|
|
79
225
|
}
|
|
80
226
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
227
|
+
/**
|
|
228
|
+
* What this run will do to ONE generated file: the action to report, and the
|
|
229
|
+
* write to stage (or null for "leave it alone").
|
|
230
|
+
*
|
|
231
|
+
* ONE function for both generated files, because the rule is one rule and a
|
|
232
|
+
* second spelling of it would be a second chance for `subsystems.likec4` and
|
|
233
|
+
* `services.likec4` to answer differently about the same map. Past the two
|
|
234
|
+
* vetoes below, the compare is `planGenerated` (`./txn/views.ts`), the one the
|
|
235
|
+
* tree-changing verbs make too, which says why it is over CONTENT, not bytes.
|
|
236
|
+
*
|
|
237
|
+
* `blocked` is the map's veto and it is the whole reason `known` is threaded
|
|
238
|
+
* this far. The render cannot be trusted against a file that already exists when
|
|
239
|
+
* `architecture/` does not parse as one project: without the element join and
|
|
240
|
+
* the style census, `subsystems.likec4` is a title and a description — writing
|
|
241
|
+
* it would take the `include` lines, the boundary comment and the palette
|
|
242
|
+
* reference off a file whose only fault is a sibling document that does not
|
|
243
|
+
* parse — and `services.likec4` is NOTHING AT ALL, because every one of its
|
|
244
|
+
* views is gated on a service resolving to a map element, so the tolerant render
|
|
245
|
+
* would delete the whole file. An absent file is not protected: there is nothing
|
|
246
|
+
* to lose and a fleet being scaffolded must still get its first one.
|
|
247
|
+
*
|
|
248
|
+
* `refused` is the OTHER veto and it comes from the file rather than from the
|
|
249
|
+
* map: a file at one of these reserved paths that does not open with loam's mark
|
|
250
|
+
* was written by somebody, and this command writes both files whole. It is
|
|
251
|
+
* checked first, applies to both files through one predicate, and is the one
|
|
252
|
+
* answer here that makes the run exit non-zero — see `runSync`.
|
|
253
|
+
*/
|
|
254
|
+
async function planFile(file, known) {
|
|
255
|
+
// ONE read for both questions — what the file says, and whose it is. The
|
|
256
|
+
// predicate and the reader live beside the mark itself
|
|
257
|
+
// (`core/repo/tree/render/service/header.ts`), below every writer of these two
|
|
258
|
+
// paths, because sync is one of FIVE and was for a while the only one holding
|
|
259
|
+
// the line.
|
|
260
|
+
const { text: actual, authored } = await readGeneratedFile(file.path);
|
|
261
|
+
// OWNERSHIP FIRST, before the map's veto and before the compare, because it is
|
|
262
|
+
// the one question whose wrong answer destroys work. Both of these are
|
|
263
|
+
// filenames loam RESERVES inside a directory the team owns, and every arm
|
|
264
|
+
// below writes the whole file or deletes it: a docs repo that already kept a
|
|
265
|
+
// hand-authored diagram at one of them would have lost it on the first sync
|
|
266
|
+
// after the upgrade that reserved the name, silently, breaking the promise the
|
|
267
|
+
// rest of the tree keeps (archive's verbatim splice: authored bytes stay
|
|
268
|
+
// authored). So the file is asked who wrote it. An EDIT of a generated file
|
|
269
|
+
// still carries the mark and is still loam's to repair — that is the whole
|
|
270
|
+
// point of `subsystem.views-stale` — and only a file that never carried it is
|
|
271
|
+
// somebody else's. Absent is not refused: there is nothing to lose.
|
|
272
|
+
if (authored)
|
|
273
|
+
return { action: "refused", write: null };
|
|
274
|
+
if (!known && actual !== null)
|
|
275
|
+
return { action: "blocked", write: null };
|
|
276
|
+
return planGenerated(file, actual);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* What the project half of this run owes, from ONE predicate. `surveyProjects`
|
|
280
|
+
* is the shared spelling of "has a model, a legal id, and no file", and it
|
|
281
|
+
* hands back the sizes of the two domains it counted against — every model it
|
|
282
|
+
* walked, and the standalone models a file is owed for — so `current` is a
|
|
283
|
+
* subtraction rather than a second reading of the same two paths. A loop here
|
|
284
|
+
* re-spelling "has a model" was the drift the shared predicate exists to
|
|
285
|
+
* prevent, and it would have kept counting a directory the survey had
|
|
286
|
+
* skipped. Without the root file the gaps are real but stay unfilled —
|
|
287
|
+
* writing them would promise a renderer nobody can open — and `current` is 0
|
|
288
|
+
* because "already had a file" is only a fact about a workspace that exists.
|
|
289
|
+
*/
|
|
290
|
+
function planProjects(docsDir, services, shapes) {
|
|
291
|
+
const root = existsSync(rootProjectPath(docsDir));
|
|
292
|
+
// A model that EXTENDS the map is owed no file, and the survey is where that
|
|
293
|
+
// is decided — one predicate for the writer and the grader, so `validate --all`
|
|
294
|
+
// can never name a gap this run would not fill. An unreadable model classifies
|
|
295
|
+
// as standalone (`readModelShapes`), which is the conservative arm: it gets a
|
|
296
|
+
// project file, which is what every model got before shapes existed.
|
|
297
|
+
const survey = surveyProjects(services, serviceRenderPaths, (model) => shapes.get(resolve(model)) !== "extending");
|
|
298
|
+
if (!root)
|
|
299
|
+
return { root, gaps: [], strays: [], current: 0, modelled: survey.modelled, models: survey.models };
|
|
300
|
+
return {
|
|
301
|
+
root,
|
|
302
|
+
gaps: survey.gaps,
|
|
303
|
+
// The removal is gated on the root file for the same reason the create is:
|
|
304
|
+
// without it the docs root is not a LikeC4 workspace, so nothing there is
|
|
305
|
+
// claiming anything out of a root project that does not exist, and deleting
|
|
306
|
+
// a team's file over a renderer nobody can open is a writer acting on a
|
|
307
|
+
// hazard it cannot see.
|
|
308
|
+
strays: survey.strays,
|
|
309
|
+
// Over the STANDALONE domain, which is the one `gaps` was drawn from: a
|
|
310
|
+
// model that extends the map is owed no file, so it can neither be a gap nor
|
|
311
|
+
// "already have one", and subtracting from `modelled` counted every
|
|
312
|
+
// extending model as the latter.
|
|
313
|
+
current: survey.standalone - survey.gaps.length,
|
|
314
|
+
modelled: survey.modelled,
|
|
315
|
+
models: survey.models,
|
|
100
316
|
};
|
|
101
|
-
console.log(sentence[out.action]);
|
|
102
317
|
}
|
|
103
318
|
//# sourceMappingURL=sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/commands/subsystem/sync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam subsystem sync` — the ONE repair for `subsystem.views-stale`:\n * recompute the generated views file from the tree, write it when it\n * differs, remove it when the tree has no subsystems, and say which.\n *\n * The write goes through the same lock + journaled transaction every docs\n * writer uses (`commitStaged`), so a predecessor's interrupted commit is\n * recovered first and a sync killed mid-swap is rolled forward by the next\n * writer. Idempotent by construction: the expected bytes are a pure function\n * of the tree and the committed landscape, so a second run reports `current`\n * and writes nothing.\n *\n * The expected bytes come from `./txn/views.ts` — the same module every\n * writer verb renders from, so sync and the transactions cannot disagree\n * about what the file should say.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { emitJson, fail } from \"../../core/envelope/json.js\";\nimport { subsystemViewsPath } from \"../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../core/repo/repo.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { viewsAgree } from \"../../core/repo/tree/views.js\";\nimport { expectedViews } from \"./txn/views.js\";\n\n/** What one sync did to the file. */\ntype SyncAction = \"current\" | \"created\" | \"updated\" | \"removed\";\n\nexport async function runSync(docsDir: DocsDir, json: boolean): Promise<void> {\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLockWaiting(docsDir, LOCK_WAIT_MS);\n } catch (err) {\n if (!(err instanceof DocsBusyError)) throw err;\n fail(json, \"docs-busy\", err.message);\n return;\n }\n try {\n // A predecessor's journal is recovered (or refused) before the tree is\n // read: the views file itself may be the half-written one.\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n fail(json, \"commit-interrupted\", err.message);\n return;\n }\n\n const tree = await listFleetTree(docsDir);\n const expected = await expectedViews(docsDir, tree);\n const path = subsystemViewsPath(docsDir);\n const expectedBytes = expected === null ? null : Buffer.from(expected, \"utf8\");\n // Content, not bytes: `viewsAgree` records why, beside the generator. A\n // Windows clone hands this file back CRLF with not one fact changed, and a\n // byte compare made `sync` answer `updated` and rewrite it LF — after which\n // git shows the file permanently modified and the next checkout puts the\n // CRLF back. `validate` stopped demanding that rewrite; `sync` must stop\n // offering it, or the two disagree about the same file.\n const actual = existsSync(path) ? await readFile(path, \"utf8\") : null;\n if (viewsAgree(actual, expected)) {\n report(json, { action: \"current\", subsystems: tree.subsystems.length, recovered });\n return;\n }\n const action: SyncAction = actual === null ? \"created\" : expectedBytes === null ? \"removed\" : \"updated\";\n const staged = await stageWrites([{ path, content: expectedBytes }]);\n const committed = await commitStaged(\n { root: docsDir, command: \"subsystem\", rerun: \"loam subsystem sync\", target: \"subsystems\" },\n staged,\n \"synced\",\n );\n if (!committed.ok) {\n fail(json, committed.code, committed.message);\n return;\n }\n report(json, { action, subsystems: tree.subsystems.length, recovered });\n } finally {\n await releaseLock();\n }\n}\n\nfunction report(\n json: boolean,\n out: { action: SyncAction; subsystems: number; recovered: CommitRecovery | null },\n): void {\n if (json) {\n emitJson({\n command: \"subsystem\",\n path: \"architecture/subsystems.likec4\",\n action: out.action,\n subsystems: out.subsystems,\n ...(out.recovered === null ? {} : { recovered: out.recovered }),\n });\n return;\n }\n if (out.recovered !== null && out.recovered.outcome !== \"consistent\") {\n console.log(\n `note: recovered an interrupted \\`loam ${out.recovered.command}\\` commit first (${out.recovered.outcome}).`,\n );\n }\n const sentence: Record<SyncAction, string> = {\n current: `architecture/subsystems.likec4 is current (${out.subsystems} subsystem(s)) — nothing to write.`,\n created: `wrote architecture/subsystems.likec4 — ${out.subsystems} subsystem view(s).`,\n updated: `updated architecture/subsystems.likec4 — ${out.subsystems} subsystem view(s).`,\n removed: `removed architecture/subsystems.likec4 — the tree has no subsystems, so the generated file must be absent.`,\n };\n console.log(sentence[out.action]);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/commands/subsystem/sync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam subsystem sync` — the writer of everything under the tree that exists\n * only to be RENDERED, which is the renderer's reading of the tree twice over:\n *\n * - `architecture/subsystems.likec4`, the GENERATED views file. Recomputed from\n * the tree on every run, compared, written when it differs, removed when the\n * tree has no subsystems. One of the two repairs for `subsystem.views-stale`.\n * Left exactly as it is when the `architecture/` project does not parse and\n * the file already exists — `action: \"blocked\"`, below.\n * - `architecture/services.likec4`, the OTHER generated views file: one\n * overview view per service the map draws, so a reader can open a service and\n * see what it touches. Same rules, same commit, same code when it drifts —\n * which is what makes ONE `subsystem.views-stale` honest about two files, and\n * why this command is named as the repair for both. Removed when NO service\n * resolves to a map element, exactly as the subsystem file is removed when\n * the tree has no subsystems. `serviceViews` is its `action`; `action` still\n * means the subsystem file alone.\n * - `services/<…>/<id>/likec4.config.json`, one LikeC4 project file beside\n * every model that STANDS ALONE. Never compared, never rewritten. Created\n * where a standalone model has none, and DELETED where an extending model has\n * one: measured at the 1.59.2 pin, that nested project claims the model out of\n * the root project and the fleet loses the service's interior\n * (`core/repo/tree/render/projects.ts` carries the measurement). This is the\n * repair `service.likec4-config-stray` has always named and, until now, did\n * not perform.\n * - the ROOT `likec4.config.json`'s `exclude` list, and ONLY that key. Derived\n * from the shapes on disk on every run and rewritten when it differs, because\n * the two shapes need opposite answers from it: a standalone model must be\n * excluded (inside the root project every kind it declares is a duplicate)\n * and an extending model must not be (the root project is the only place it\n * parses). `planExclude` below states the gate that keeps an old repository\n * untouched, and `core/c4/service-model/renderer.ts` states which half of the\n * list is loam's and which is the team's. One entry loam did NOT write is\n * taken anyway, and only this one: an entry naming a directory that HIDES an\n * extending model — a subsystem-wide `services/platform/**` survived every\n * sync while `service.model-excluded` named this command as the repair.\n *\n * NEITHER views file is written over a document loam did not write. Both are\n * filenames loam RESERVES inside `architecture/`, a directory the team owns, and\n * both are rewritten whole — so a repository that already kept a hand-authored\n * diagram at one of those paths would have lost it, silently, on the first sync\n * after the release that reserved the name. One predicate answers it for both\n * (`readGeneratedFile`, off the file's own first line), the run reports\n * `refused` for that file, writes nothing for it, and exits non-zero. A hand\n * EDIT of a generated file is a different thing and is still repaired: it keeps\n * the mark, so it is loam's file with wrong bytes in it, which is exactly what\n * `subsystem.views-stale` says.\n *\n * THIS COMMAND IS NOT THE ONLY WRITER, and for a while it was the only one\n * holding that line — reproduced, with a hand-authored\n * `architecture/services.likec4` in the tree: `loam subsystem new platform`\n * printed \"architecture/services.likec4 updated\" and destroyed it. The predicate\n * and the refusal sentence therefore live below every writer\n * (`core/repo/tree/render/service/header.ts`), and `./txn/txn.ts` and\n * `../seed/plan.ts` ask the same question of the same two paths.\n *\n * WHAT DIFFERS IS THE POLICY, not the question, and the difference is the shape\n * of the operation rather than a second opinion about ownership. Sync REPORTS\n * `refused` for the one file and does the rest: nothing it writes depends on\n * that file, every part of it is idempotent, and a run that also created a\n * project file really did create it. A tree-changing verb and `seed` REFUSE\n * WHOLE, because their generated file has to land in the same commit as the\n * change to the tree — completing half of that is the very state the commit\n * window exists never to leave between two commits, and `subsystem move` would\n * additionally leave git-staged renames behind it.\n *\n * The rules differ because the files answer to different owners. The\n * views files are a pure function of the tree and the committed landscape, so\n * every byte of them is loam's and a hand edit is by definition stale. The\n * project file's bytes are a function of the service id ALONE — the identity\n * no verb ever changes — so nothing can make it stale, and the keys LikeC4\n * lets a team add to it (`title`, styles, a contact) are the team's; a compare\n * would fight them, and a rewrite would take them. `core/repo/tree/render/projects.ts`\n * records the measurement behind the file and the naming rule; here, presence\n * is the one question asked of it, through the SAME predicate `validate --all`\n * grades with (`missingProjects`), so the grader can never name a gap this\n * writer would not fill.\n *\n * Nothing under `services/` is written, and no `exclude` is recomputed, unless\n * the ROOT `likec4.config.json` exists. Without it the tree is not a loadable\n * workspace at all — every model would merge into one project and report every\n * declaration as a duplicate — and a nested project beside a missing root is a\n * promise about a renderer that cannot be opened. `loam doctor` prints the root\n * file; sync says so and writes nothing for the services until it is there.\n *\n * Every write goes through the same lock + journaled transaction every docs\n * writer uses (`commitStaged`), and both kinds land in ONE commit: a\n * predecessor's interrupted commit is recovered first and a sync killed\n * mid-swap is rolled forward by the next writer. Idempotent by construction:\n * the expected views bytes are a pure function of the tree, and a project\n * file, once present, is never a gap again — so a second run reports\n * `current` and writes nothing.\n *\n * The expected views bytes come from `./txn/views.ts` — the same module every\n * writer verb renders from, so sync and the transactions cannot disagree\n * about what the file should say. The subsystem verbs' own commit window\n * (`./txn/txn.ts`) does NOT write project files, and records why. What this run\n * SAYS it did is `./txn/report.ts`: the third answer about the same three files,\n * and the phase that is not part of the commit.\n */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { readModelShapes, type ModelShape } from \"../../core/c4/service-model/shape.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { fail, repoPath } from \"../../core/envelope/json.js\";\nimport { LIKEC4_ROOT_PROJECT, rootProjectPath, serviceRenderPaths } from \"../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../core/repo/repo.js\";\nimport {\n type ProjectGap,\n renderServiceProject,\n surveyProjects,\n type SurveyedModel,\n} from \"../../core/repo/tree/render/projects.js\";\nimport type { WalkedService } from \"../../core/repo/tree/walk.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { openCommitWindow } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { planWrite, type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { readGeneratedFile } from \"../../core/repo/tree/render/service/header.js\";\nimport { NO_EXCLUDE, planExclude } from \"./txn/exclude.js\";\nimport { type ProjectsReport, reportSync, type SyncAction } from \"./txn/report.js\";\nimport { type ExpectedFile, expectedViews, planGenerated } from \"./txn/views.js\";\n\n/**\n * The per-service project half of one sync, before the write: what the\n * predicate found, and the two counts the report needs beside it.\n */\ninterface ProjectPlan {\n /** Whether the root project file exists — the gate on every service write. */\n root: boolean;\n /** The files this run will create. Empty whenever `root` is false. */\n gaps: ProjectGap<WalkedService>[];\n /** The files this run will DELETE — a project file beside an extending model. Empty whenever `root` is false. */\n strays: ProjectGap<WalkedService>[];\n /**\n * Services owed a project file that ALREADY had one — so STANDALONE models\n * only; 0 whenever `root` is false. An extending model is owed none, and\n * counting it here said a file was in place beside a model that must not have\n * one (`service.likec4-config-stray` grades exactly that state).\n */\n current: number;\n /** Model-bearing services, root or no root — what the root-missing note is owed to. */\n modelled: number;\n /** Every model this run considered, with its shape — the exclude list is derived from the same walk. */\n models: SurveyedModel<WalkedService>[];\n}\n\nexport async function runSync(docsDir: DocsDir, json: boolean): Promise<void> {\n // A predecessor's journal is recovered (or refused) before the tree is\n // read: the views file itself may be the half-written one.\n const opened = await openCommitWindow(docsDir, LOCK_WAIT_MS);\n if (!opened.ok) {\n fail(json, opened.code, opened.message);\n return;\n }\n const { recovered } = opened;\n try {\n const tree = await listFleetTree(docsDir);\n // BOTH generated files, from ONE read of the map — `./txn/views.ts` says why\n // the pair is one answer and why the parsed document, never a literal, is\n // what the per-service peer sets are computed from.\n const expected = await expectedViews(docsDir, tree);\n const views = await planFile(expected.subsystems, expected.known);\n const services = await planFile(expected.services, expected.known);\n // ONE read of every model's bytes for the whole run, and the same scan the\n // grader uses: it decides which directories are owed a project file AND\n // which the root project must exclude, and those are opposite answers for\n // the two shapes (`core/repo/tree/render/projects.ts`).\n const shapes = await readModelShapes(tree.services.map((svc) => serviceRenderPaths(svc.dir).model));\n const plan = planProjects(docsDir, tree.services, shapes);\n const exclude = plan.root ? await planExclude(docsDir, tree.services, plan.models) : NO_EXCLUDE;\n // The plan IS the report: every gap is created and every stray removed in\n // the one commit below, and the early return is the branch where both lists\n // are empty.\n const projects = (): ProjectsReport => ({\n root: plan.root,\n // SORTED BY PATH, which is what the contract says these keys hold and what\n // the survey's own order is not: `surveyProjects` sorts by service id, so\n // `services/svc-a/…` came out before `services/platform/svc-d/…` and a\n // consumer told the list was \"sorted\" got an order no path sort produces\n // (re-verification 2026-09-04, area C item 6). The text view prints the\n // same array, so the two orders cannot drift apart.\n created: plan.gaps.map((gap) => repoPath(docsDir, gap.path)).sort(),\n removed: plan.strays.map((stray) => repoPath(docsDir, stray.path)).sort(),\n current: plan.current,\n modelled: plan.modelled,\n exclude,\n });\n // `action` still describes `architecture/subsystems.likec4` ALONE — a run\n // that only created project files, or only rewrote the per-service\n // overviews, answers `current` for it, exactly as before either existed, so\n // nothing reading the key sees a new meaning. The second generated file\n // arrives as a key of its own (`serviceViews`), because a payload key's\n // meaning is frozen and folding two files into one answer would lose which\n // one moved — the only thing either sentence has to say.\n const say = (): void => {\n reportSync(json, {\n action: views.action,\n serviceViews: services.action,\n subsystems: tree.subsystems.length,\n recovered,\n projects: projects(),\n mapUnreadable: !expected.known,\n });\n // A REFUSAL MUST NOT READ AS A CLEAN RUN. The rest of the sync really did\n // happen — the other generated file, the project files, the root\n // `exclude` — so this is `loam doctor`'s and `loam validate`'s shape and\n // not `fail()`'s: the whole report is emitted, and the exit code says the\n // tree was not left in the state the command was asked to leave it in. A\n // caller that runs `loam subsystem sync` in a hook or a CI step would\n // otherwise be told nothing, forever, about a file it can never repair —\n // and `loam validate --all` would go on reporting `subsystem.views-stale`\n // for that path with this very command named as the fix.\n if (views.action === \"refused\" || services.action === \"refused\") process.exitCode = 1;\n };\n const generated = [views.write, services.write].filter((write) => write !== null);\n if (generated.length === 0 && plan.gaps.length === 0 && plan.strays.length === 0 && exclude.write === null) {\n say();\n return;\n }\n const writes: PlannedWrite[] = [...generated];\n // `planWrite` marks a non-existent target exclusive — a no-clobber create\n // that fails EEXIST rather than replacing a file another writer landed in\n // the same instant. That IS the create-only rule, enforced by the write\n // path rather than by the `existsSync` the predicate already ran: a file\n // that appears between the check and the swap is somebody's, and stays.\n for (const gap of plan.gaps) {\n writes.push(planWrite(gap.path, renderServiceProject(gap.service.id, LIKEC4_ROOT_PROJECT)));\n }\n // The one file this verb DELETES, and it rides the same journal as the rest,\n // so a run killed mid-swap is rolled forward rather than half-done.\n // `content: null` is the delete the views file already uses.\n for (const stray of plan.strays) writes.push({ path: stray.path, content: null });\n // The root project's `exclude`, in the SAME transaction as the files it\n // decides the fate of: a run that wrote the per-service projects and died\n // before the exclude would leave a tree where the renderer loads a\n // standalone model twice — once in the root project, where every kind it\n // declares is a duplicate — which is worse than either end state.\n if (exclude.write !== null) writes.push(exclude.write);\n // One lock, one journal, one commit for both kinds of file — a sync killed\n // between the views swap and the project creates would otherwise leave a\n // half-state no later run could tell from a team's deliberate deletion.\n const staged = await stageWrites(writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"subsystem\", rerun: \"loam subsystem sync\", target: \"subsystems\" },\n staged,\n \"synced\",\n );\n if (!committed.ok) {\n fail(json, committed.code, committed.message);\n return;\n }\n say();\n } finally {\n await opened.release();\n }\n}\n\n/**\n * What this run will do to ONE generated file: the action to report, and the\n * write to stage (or null for \"leave it alone\").\n *\n * ONE function for both generated files, because the rule is one rule and a\n * second spelling of it would be a second chance for `subsystems.likec4` and\n * `services.likec4` to answer differently about the same map. Past the two\n * vetoes below, the compare is `planGenerated` (`./txn/views.ts`), the one the\n * tree-changing verbs make too, which says why it is over CONTENT, not bytes.\n *\n * `blocked` is the map's veto and it is the whole reason `known` is threaded\n * this far. The render cannot be trusted against a file that already exists when\n * `architecture/` does not parse as one project: without the element join and\n * the style census, `subsystems.likec4` is a title and a description — writing\n * it would take the `include` lines, the boundary comment and the palette\n * reference off a file whose only fault is a sibling document that does not\n * parse — and `services.likec4` is NOTHING AT ALL, because every one of its\n * views is gated on a service resolving to a map element, so the tolerant render\n * would delete the whole file. An absent file is not protected: there is nothing\n * to lose and a fleet being scaffolded must still get its first one.\n *\n * `refused` is the OTHER veto and it comes from the file rather than from the\n * map: a file at one of these reserved paths that does not open with loam's mark\n * was written by somebody, and this command writes both files whole. It is\n * checked first, applies to both files through one predicate, and is the one\n * answer here that makes the run exit non-zero — see `runSync`.\n */\nasync function planFile(file: ExpectedFile, known: boolean): Promise<{ action: SyncAction; write: PlannedWrite | null }> {\n // ONE read for both questions — what the file says, and whose it is. The\n // predicate and the reader live beside the mark itself\n // (`core/repo/tree/render/service/header.ts`), below every writer of these two\n // paths, because sync is one of FIVE and was for a while the only one holding\n // the line.\n const { text: actual, authored } = await readGeneratedFile(file.path);\n // OWNERSHIP FIRST, before the map's veto and before the compare, because it is\n // the one question whose wrong answer destroys work. Both of these are\n // filenames loam RESERVES inside a directory the team owns, and every arm\n // below writes the whole file or deletes it: a docs repo that already kept a\n // hand-authored diagram at one of them would have lost it on the first sync\n // after the upgrade that reserved the name, silently, breaking the promise the\n // rest of the tree keeps (archive's verbatim splice: authored bytes stay\n // authored). So the file is asked who wrote it. An EDIT of a generated file\n // still carries the mark and is still loam's to repair — that is the whole\n // point of `subsystem.views-stale` — and only a file that never carried it is\n // somebody else's. Absent is not refused: there is nothing to lose.\n if (authored) return { action: \"refused\", write: null };\n if (!known && actual !== null) return { action: \"blocked\", write: null };\n return planGenerated(file, actual);\n}\n\n/**\n * What the project half of this run owes, from ONE predicate. `surveyProjects`\n * is the shared spelling of \"has a model, a legal id, and no file\", and it\n * hands back the sizes of the two domains it counted against — every model it\n * walked, and the standalone models a file is owed for — so `current` is a\n * subtraction rather than a second reading of the same two paths. A loop here\n * re-spelling \"has a model\" was the drift the shared predicate exists to\n * prevent, and it would have kept counting a directory the survey had\n * skipped. Without the root file the gaps are real but stay unfilled —\n * writing them would promise a renderer nobody can open — and `current` is 0\n * because \"already had a file\" is only a fact about a workspace that exists.\n */\nfunction planProjects(\n docsDir: DocsDir,\n services: readonly WalkedService[],\n shapes: ReadonlyMap<string, ModelShape>,\n): ProjectPlan {\n const root = existsSync(rootProjectPath(docsDir));\n // A model that EXTENDS the map is owed no file, and the survey is where that\n // is decided — one predicate for the writer and the grader, so `validate --all`\n // can never name a gap this run would not fill. An unreadable model classifies\n // as standalone (`readModelShapes`), which is the conservative arm: it gets a\n // project file, which is what every model got before shapes existed.\n const survey = surveyProjects(services, serviceRenderPaths, (model) => shapes.get(resolve(model)) !== \"extending\");\n if (!root) return { root, gaps: [], strays: [], current: 0, modelled: survey.modelled, models: survey.models };\n return {\n root,\n gaps: survey.gaps,\n // The removal is gated on the root file for the same reason the create is:\n // without it the docs root is not a LikeC4 workspace, so nothing there is\n // claiming anything out of a root project that does not exist, and deleting\n // a team's file over a renderer nobody can open is a writer acting on a\n // hazard it cannot see.\n strays: survey.strays,\n // Over the STANDALONE domain, which is the one `gaps` was drawn from: a\n // model that extends the map is owed no file, so it can neither be a gap nor\n // \"already have one\", and subtracting from `modelled` counted every\n // extending model as the latter.\n current: survey.standalone - survey.gaps.length,\n modelled: survey.modelled,\n models: survey.models,\n };\n}\n"]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ROOT `likec4.config.json`'s `exclude` list, as `loam subsystem sync`
|
|
3
|
+
* derives it from the shapes on disk.
|
|
4
|
+
*
|
|
5
|
+
* It sits beside `./views.ts` for that module's reason and by the same seam:
|
|
6
|
+
* both answer "what should this renderer-only file say, given the tree?", both
|
|
7
|
+
* are pure derivations one command writes, and keeping them together is what
|
|
8
|
+
* stops the writer and the grader spelling one rule twice. The difference is
|
|
9
|
+
* ownership, and it is stated in `core/c4/service-model/renderer.ts`: every byte
|
|
10
|
+
* of the generated views file is loam's, while this file is the team's and loam
|
|
11
|
+
* maintains exactly the `services/` entries in it. What an entry COVERS is
|
|
12
|
+
* `core/c4/root-project/exclude.ts`'s question, asked of the directory the entry
|
|
13
|
+
* names rather than of its spelling.
|
|
14
|
+
*
|
|
15
|
+
* It is a module rather than three functions in `../sync.ts` because that file
|
|
16
|
+
* reached its line limit carrying them, and this is the phase boundary inside
|
|
17
|
+
* it: `sync.ts` is the lock, the transaction and the report; this is one of the
|
|
18
|
+
* three things it decides to write.
|
|
19
|
+
*/
|
|
20
|
+
import { readFile } from "node:fs/promises";
|
|
21
|
+
import { excludingEntry, parseRootProject, readRootExclude } from "../../../core/c4/root-project/exclude.js";
|
|
22
|
+
import { standaloneExclude } from "../../../core/c4/service-model/renderer.js";
|
|
23
|
+
import { serviceTreePath } from "../../../core/kernel/ids/dirs.js";
|
|
24
|
+
import { rootProjectPath } from "../../../core/repo/paths.js";
|
|
25
|
+
import { planWrite } from "../../../core/staging/writes.js";
|
|
26
|
+
/** The answer for a run that has no root file to read, and the shape every other arm fills. */
|
|
27
|
+
export const NO_EXCLUDE = {
|
|
28
|
+
updated: false,
|
|
29
|
+
entries: [],
|
|
30
|
+
added: [],
|
|
31
|
+
removed: [],
|
|
32
|
+
write: null,
|
|
33
|
+
unreadable: false,
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The root project's `exclude` list, recomputed from the shapes on disk.
|
|
37
|
+
*
|
|
38
|
+
* WHAT MAKES THIS SAFE TO RUN ON EVERY SYNC is the split `standaloneExclude`
|
|
39
|
+
* enforces: the `services/` half of the list is loam's and every other entry is
|
|
40
|
+
* the team's, kept in order. The scaffold's node_modules glob, `features/**`, a
|
|
41
|
+
* team's own `drafts/**` all survive; only the entries that decide whether a
|
|
42
|
+
* model is renderable are derived. A standalone model MUST be excluded (inside
|
|
43
|
+
* the root project every kind it declares is a duplicate blamed on the map as
|
|
44
|
+
* well) and an extending model must NOT be (the root project is the only place
|
|
45
|
+
* it parses), so a service that migrates from one shape to the other has its
|
|
46
|
+
* entry added or dropped on the next sync instead of quietly blanking a project.
|
|
47
|
+
*
|
|
48
|
+
* THE GATE keeps an old repository untouched. A fleet whose models all stand
|
|
49
|
+
* alone and whose root already says `services/**` is already correct, and
|
|
50
|
+
* rewriting it into one entry per service would be a diff with no fact behind
|
|
51
|
+
* it — so the rewrite happens only when at least one model extends the map (the
|
|
52
|
+
* blanket exclusion is then wrong) or some standalone model is not covered at
|
|
53
|
+
* all (the renderer is merging it into the map right now). Comparing the parsed
|
|
54
|
+
* ARRAYS rather than the bytes is the second half of the same rule: a file whose
|
|
55
|
+
* `exclude` already says what this run would write is left exactly as the team
|
|
56
|
+
* formatted it.
|
|
57
|
+
*
|
|
58
|
+
* A root config loam cannot read an `exclude` list out of is REPORTED and never
|
|
59
|
+
* rewritten. Guessing at the JSON a team hand-wrote is how a writer destroys a
|
|
60
|
+
* file it did not understand.
|
|
61
|
+
*/
|
|
62
|
+
export async function planExclude(docsDir, services, models) {
|
|
63
|
+
const current = await readRootExclude(docsDir);
|
|
64
|
+
if (current === null)
|
|
65
|
+
return { ...NO_EXCLUDE, unreadable: true };
|
|
66
|
+
const standalone = models.filter((m) => m.standalone).map((m) => serviceTreePath(m.service));
|
|
67
|
+
// Both halves of the shape scan travel, not just the standalone one: an entry
|
|
68
|
+
// is loam's to take back either because loam wrote it (a tree the enumeration
|
|
69
|
+
// returns) or because it HIDES a model that only parses in the root project,
|
|
70
|
+
// and only the extending list can answer the second question. Without it a
|
|
71
|
+
// subsystem-wide `services/platform/**` survived every sync while
|
|
72
|
+
// `service.model-excluded` named that same sync as the repair.
|
|
73
|
+
const extending = models.filter((m) => !m.standalone).map((m) => serviceTreePath(m.service));
|
|
74
|
+
const wanted = standaloneExclude(current, {
|
|
75
|
+
standalone,
|
|
76
|
+
extending,
|
|
77
|
+
enumerated: services.map((svc) => serviceTreePath(svc)),
|
|
78
|
+
});
|
|
79
|
+
const uncovered = standalone.some((tree) => excludingEntry(current, tree) === null);
|
|
80
|
+
if (extending.length === 0 && !uncovered)
|
|
81
|
+
return { ...NO_EXCLUDE, entries: [...current] };
|
|
82
|
+
if (current.length === wanted.length && current.every((entry, i) => entry === wanted[i])) {
|
|
83
|
+
return { ...NO_EXCLUDE, entries: wanted };
|
|
84
|
+
}
|
|
85
|
+
const bytes = await rewrittenRootProject(docsDir, wanted);
|
|
86
|
+
if (bytes === null)
|
|
87
|
+
return { ...NO_EXCLUDE, entries: [...current], unreadable: true };
|
|
88
|
+
return {
|
|
89
|
+
updated: true,
|
|
90
|
+
entries: wanted,
|
|
91
|
+
added: wanted.filter((entry) => !current.includes(entry)),
|
|
92
|
+
removed: current.filter((entry) => !wanted.includes(entry)),
|
|
93
|
+
// `planWrite` decides create-vs-overwrite from the filesystem, so the root
|
|
94
|
+
// file — which by definition already exists here — is staged as an
|
|
95
|
+
// OVERWRITE, and `swapStaged` compares it against its pre-image immediately
|
|
96
|
+
// before the rename: a team member editing the same file during the sync
|
|
97
|
+
// stops the commit instead of having their bytes buried.
|
|
98
|
+
write: planWrite(rootProjectPath(docsDir), bytes),
|
|
99
|
+
unreadable: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The root project file with its `exclude` replaced and NOTHING else touched —
|
|
104
|
+
* every other key kept, in the order the team wrote them, re-serialised as
|
|
105
|
+
* 2-space JSON with a trailing newline (`core/docs.ts`'s own form, so a
|
|
106
|
+
* scaffolded file round-trips byte for byte).
|
|
107
|
+
*
|
|
108
|
+
* Null when the file stopped being a JSON object between `readRootExclude` and
|
|
109
|
+
* here, which is a concurrent edit rather than a state loam may write over.
|
|
110
|
+
*/
|
|
111
|
+
async function rewrittenRootProject(docsDir, exclude) {
|
|
112
|
+
let text;
|
|
113
|
+
try {
|
|
114
|
+
text = await readFile(rootProjectPath(docsDir), "utf8");
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Unreadable is not "excludes nothing": the caller reports it and writes
|
|
118
|
+
// nothing at all.
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// The SAME parse the grader read the list with — `parseRootProject`, which
|
|
122
|
+
// tolerates the byte-order mark PowerShell's `Out-File` writes. Two parses
|
|
123
|
+
// would mean a run that read an `exclude` here and then declared the file
|
|
124
|
+
// unreadable at the write, which is exactly what a BOM produced before
|
|
125
|
+
// (re-verification 2026-09-04, area C item 3). Re-read rather than carried:
|
|
126
|
+
// the point of this read is that it happens immediately before the swap.
|
|
127
|
+
const parsed = parseRootProject(text);
|
|
128
|
+
if (parsed === null)
|
|
129
|
+
return null;
|
|
130
|
+
return `${JSON.stringify({ ...parsed, exclude: [...exclude] }, null, 2)}\n`;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=exclude.js.map
|