@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
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 +2635 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +599 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +252 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +204 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +270 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +337 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +139 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +174 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +287 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +188 -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 +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +137 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +210 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +277 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +197 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +72 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +58 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a delta may declare that the landscape merge cannot carry.
|
|
3
|
+
*
|
|
4
|
+
* `planLandscapeMerge` splices out of the delta's `model { }` block and nothing
|
|
5
|
+
* else. That is correct — the living landscape is a model plus the views its own
|
|
6
|
+
* authors chose — but it used to be SILENT: a delta declaring `deployment { }`
|
|
7
|
+
* or a `dynamic view` archived at exit 0, reported `+N element(s), +M
|
|
8
|
+
* relationship(s)` exactly as usual, and the block was simply gone. The merge's
|
|
9
|
+
* own parse net cannot catch it either, because a landscape missing a deployment
|
|
10
|
+
* model is perfectly legal — there is nothing to fail on. Nobody finds out until
|
|
11
|
+
* somebody goes looking for a diagram that was archived months ago.
|
|
12
|
+
*
|
|
13
|
+
* So the loss is made a refusal, which is the same doctrine as the merge's other
|
|
14
|
+
* mechanical refusals (an unlocatable declaration, two statements on one line):
|
|
15
|
+
* the input cannot be spliced as authored, `--approve` deliberately does not
|
|
16
|
+
* override it — that flag overrides loam's JUDGMENT about coherence, never its
|
|
17
|
+
* ability to carry an axis — and nothing is written.
|
|
18
|
+
*
|
|
19
|
+
* ## Why views are graded and not simply banned
|
|
20
|
+
*
|
|
21
|
+
* `loam new` scaffolds a `views { view <feat> { include * } }` block into every
|
|
22
|
+
* delta.likec4 (commands/new/templates.ts), so refusing the block outright would
|
|
23
|
+
* refuse loam's own scaffold on its first archive. It is there to be RENDERED
|
|
24
|
+
* while the feature is in flight, and it is meant not to travel.
|
|
25
|
+
*
|
|
26
|
+
* The line drawn here is recoverability, and it is the only line that survives
|
|
27
|
+
* the scaffold:
|
|
28
|
+
*
|
|
29
|
+
* - A static `view` is a rendering OF the model. Once the merge lands, the
|
|
30
|
+
* whole model is in landscape.likec4, so the author can restate the view
|
|
31
|
+
* there at any time from information that still exists. Dropping it loses a
|
|
32
|
+
* convenience.
|
|
33
|
+
* - A `dynamic view`'s ordered steps, and a `deployment { }` block's topology,
|
|
34
|
+
* exist in NO other document the fleet keeps. When the merge drops those,
|
|
35
|
+
* the information is gone. That is what gets refused. Rule 26 sharpens the
|
|
36
|
+
* stakes rather than the rule: a `dynamic view`'s steps are now something
|
|
37
|
+
* loam READS, so dropping one silently would discard checked content, not
|
|
38
|
+
* merely a convenience.
|
|
39
|
+
*
|
|
40
|
+
* A residual is accepted knowingly: a delta carrying a SECOND static view still
|
|
41
|
+
* loses it without a word. Refusing that would mean telling an author with two
|
|
42
|
+
* preview views to delete one, which is a false refusal on a harmless file, and
|
|
43
|
+
* the loss there is recoverable by the argument above.
|
|
44
|
+
*
|
|
45
|
+
* ## The dynamic-view refusal is unchanged; its ADVICE moved
|
|
46
|
+
*
|
|
47
|
+
* A `dynamic view` still cannot ride inside `delta.likec4`, and the reason is
|
|
48
|
+
* mechanical rather than a policy about where flows belong: this document
|
|
49
|
+
* re-declares the landscape's own identifiers to anchor its new edges and
|
|
50
|
+
* carries its own `specification` block because LikeC4 parses it standalone, so
|
|
51
|
+
* it cannot be staged beside the map in one project — a duplicate specification
|
|
52
|
+
* is an error blamed on both files, and a re-declared element is a duplicate id.
|
|
53
|
+
* What changed is where the refusal sends the author. It used to say
|
|
54
|
+
* `architecture/landscape.likec4`, which was the only honest answer while the
|
|
55
|
+
* axis had no feature-delta path: a flow could not ship with the change that
|
|
56
|
+
* made it true. `features/<FEAT>/usecases/<name>.likec4` is that path
|
|
57
|
+
* (`core/usecases/delta/flows.ts`), and it is a views-only document — which is
|
|
58
|
+
* exactly why it stages beside the map when this one cannot.
|
|
59
|
+
*/
|
|
60
|
+
import { maskSource, matchBrace } from "../source-mask.js";
|
|
61
|
+
import { LandscapeSpliceError } from "./contract.js";
|
|
62
|
+
/**
|
|
63
|
+
* Top-level blocks a delta can declare. `specification` and `model` are absent
|
|
64
|
+
* on purpose: the specification is re-declared in every delta because LikeC4
|
|
65
|
+
* parses each one standalone, and the model is the thing being spliced.
|
|
66
|
+
*/
|
|
67
|
+
const TOP_LEVEL_BLOCK = /\b(deployment|global|views)\s*\{/g;
|
|
68
|
+
/** A `dynamic view` / `deployment view` inside a views block — the irrecoverable kinds. */
|
|
69
|
+
const AUTHORED_VIEW = /\b(dynamic|deployment)\s+view\b/;
|
|
70
|
+
function refuse(block, detail, home) {
|
|
71
|
+
throw new LandscapeSpliceError(`delta.likec4 declares ${detail}, and the landscape merge splices only what is inside \`model { }\` — ` +
|
|
72
|
+
`archiving would drop it silently, so nothing was written. Move the ${block} into ` +
|
|
73
|
+
`${home} and re-run the archive.`);
|
|
74
|
+
}
|
|
75
|
+
/** Where the living documents keep what this delta cannot carry. */
|
|
76
|
+
const LANDSCAPE_HOME = "architecture/landscape.likec4, where the living documents keep it,";
|
|
77
|
+
/** Where a FEATURE keeps TOPOLOGY — the third whole-file slot, added with the deployment axis. */
|
|
78
|
+
const DEPLOYMENT_HOME = "features/<FEAT>/deployment/<name>.likec4 — a document of its own, which `loam archive` copies into " +
|
|
79
|
+
"architecture/ and `loam unarchive` takes back; say `extend` in it to add to a region the living map " +
|
|
80
|
+
"already declares —";
|
|
81
|
+
/** Where a FEATURE keeps a flow — a slot of its own, not the living tree. See the header. */
|
|
82
|
+
const FLOW_HOME = "features/<FEAT>/usecases/<name>.likec4 — a views-only document of its own, which `loam archive` copies into " +
|
|
83
|
+
"architecture/usecases/ and `loam unarchive` takes back —";
|
|
84
|
+
/**
|
|
85
|
+
* Refuse a delta whose non-model blocks the merge would discard.
|
|
86
|
+
*
|
|
87
|
+
* Runs over MASKED source, so a `deployment {` written inside a comment or a
|
|
88
|
+
* description string cannot trigger it — the same masking discipline
|
|
89
|
+
* `scanModel` splices under, and for the same reason: this refuses an archive,
|
|
90
|
+
* and a refusal a comment can provoke is worse than the silence it replaced.
|
|
91
|
+
*
|
|
92
|
+
* Brace depth is tracked in one forward pass rather than recounted per match:
|
|
93
|
+
* only a block at depth 0 is a top-level declaration, and `deployment` at depth
|
|
94
|
+
* 1 is an ordinary `specification { deploymentNode … }` or a view predicate.
|
|
95
|
+
*/
|
|
96
|
+
export function assertMergeableDelta(deltaText) {
|
|
97
|
+
const { code } = maskSource(deltaText);
|
|
98
|
+
let depth = 0;
|
|
99
|
+
let cursor = 0;
|
|
100
|
+
for (const m of code.matchAll(TOP_LEVEL_BLOCK)) {
|
|
101
|
+
for (; cursor < m.index; cursor += 1) {
|
|
102
|
+
if (code[cursor] === "{")
|
|
103
|
+
depth += 1;
|
|
104
|
+
else if (code[cursor] === "}")
|
|
105
|
+
depth -= 1;
|
|
106
|
+
}
|
|
107
|
+
if (depth !== 0)
|
|
108
|
+
continue;
|
|
109
|
+
const block = m[1];
|
|
110
|
+
if (block !== "views") {
|
|
111
|
+
refuse(`\`${block} { }\` block`, `a top-level \`${block} { }\` block`,
|
|
112
|
+
// The refusal is unchanged and always will be — this document
|
|
113
|
+
// re-declares the landscape's identifiers and carries its own
|
|
114
|
+
// `specification`, so it cannot be staged beside the map in one project.
|
|
115
|
+
// What changed is where it sends the author: a feature introducing
|
|
116
|
+
// topology WITH the change that makes it true now has a slot of its
|
|
117
|
+
// own, and pointing at the living landscape was the advice that made
|
|
118
|
+
// the axis's headline case a two-pull-request job. `global { }` has no
|
|
119
|
+
// such slot and keeps the living home.
|
|
120
|
+
block === "deployment" ? DEPLOYMENT_HOME : LANDSCAPE_HOME);
|
|
121
|
+
}
|
|
122
|
+
const open = m.index + m[0].length - 1;
|
|
123
|
+
const close = matchBrace(code, open);
|
|
124
|
+
const inner = code.slice(open + 1, close === -1 ? code.length : close);
|
|
125
|
+
const authored = AUTHORED_VIEW.exec(inner);
|
|
126
|
+
if (authored !== null) {
|
|
127
|
+
const kind = authored[1];
|
|
128
|
+
refuse(`\`${kind} view\``, `a \`${kind} view\` in its \`views { }\` block — a view of that kind describes ordering or topology ` +
|
|
129
|
+
`recorded in no other document, unlike the static preview view \`loam new\` scaffolds`,
|
|
130
|
+
// A deployment view is topology and belongs in the living map; a dynamic
|
|
131
|
+
// view is a flow, and a flow now has a feature slot of its own. Sending
|
|
132
|
+
// an author to the living landscape for one they are introducing WITH
|
|
133
|
+
// this change was the advice that made the axis's headline case a
|
|
134
|
+
// two-pull-request job.
|
|
135
|
+
kind === "dynamic" ? FLOW_HOME : DEPLOYMENT_HOME);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=delta-blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delta-blocks.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/delta-blocks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a delta may declare that the landscape merge cannot carry.\n *\n * `planLandscapeMerge` splices out of the delta's `model { }` block and nothing\n * else. That is correct — the living landscape is a model plus the views its own\n * authors chose — but it used to be SILENT: a delta declaring `deployment { }`\n * or a `dynamic view` archived at exit 0, reported `+N element(s), +M\n * relationship(s)` exactly as usual, and the block was simply gone. The merge's\n * own parse net cannot catch it either, because a landscape missing a deployment\n * model is perfectly legal — there is nothing to fail on. Nobody finds out until\n * somebody goes looking for a diagram that was archived months ago.\n *\n * So the loss is made a refusal, which is the same doctrine as the merge's other\n * mechanical refusals (an unlocatable declaration, two statements on one line):\n * the input cannot be spliced as authored, `--approve` deliberately does not\n * override it — that flag overrides loam's JUDGMENT about coherence, never its\n * ability to carry an axis — and nothing is written.\n *\n * ## Why views are graded and not simply banned\n *\n * `loam new` scaffolds a `views { view <feat> { include * } }` block into every\n * delta.likec4 (commands/new/templates.ts), so refusing the block outright would\n * refuse loam's own scaffold on its first archive. It is there to be RENDERED\n * while the feature is in flight, and it is meant not to travel.\n *\n * The line drawn here is recoverability, and it is the only line that survives\n * the scaffold:\n *\n * - A static `view` is a rendering OF the model. Once the merge lands, the\n * whole model is in landscape.likec4, so the author can restate the view\n * there at any time from information that still exists. Dropping it loses a\n * convenience.\n * - A `dynamic view`'s ordered steps, and a `deployment { }` block's topology,\n * exist in NO other document the fleet keeps. When the merge drops those,\n * the information is gone. That is what gets refused. Rule 26 sharpens the\n * stakes rather than the rule: a `dynamic view`'s steps are now something\n * loam READS, so dropping one silently would discard checked content, not\n * merely a convenience.\n *\n * A residual is accepted knowingly: a delta carrying a SECOND static view still\n * loses it without a word. Refusing that would mean telling an author with two\n * preview views to delete one, which is a false refusal on a harmless file, and\n * the loss there is recoverable by the argument above.\n *\n * ## The dynamic-view refusal is unchanged; its ADVICE moved\n *\n * A `dynamic view` still cannot ride inside `delta.likec4`, and the reason is\n * mechanical rather than a policy about where flows belong: this document\n * re-declares the landscape's own identifiers to anchor its new edges and\n * carries its own `specification` block because LikeC4 parses it standalone, so\n * it cannot be staged beside the map in one project — a duplicate specification\n * is an error blamed on both files, and a re-declared element is a duplicate id.\n * What changed is where the refusal sends the author. It used to say\n * `architecture/landscape.likec4`, which was the only honest answer while the\n * axis had no feature-delta path: a flow could not ship with the change that\n * made it true. `features/<FEAT>/usecases/<name>.likec4` is that path\n * (`core/usecases/delta/flows.ts`), and it is a views-only document — which is\n * exactly why it stages beside the map when this one cannot.\n */\nimport { maskSource, matchBrace } from \"../source-mask.js\";\nimport { LandscapeSpliceError } from \"./contract.js\";\n\n/**\n * Top-level blocks a delta can declare. `specification` and `model` are absent\n * on purpose: the specification is re-declared in every delta because LikeC4\n * parses each one standalone, and the model is the thing being spliced.\n */\nconst TOP_LEVEL_BLOCK = /\\b(deployment|global|views)\\s*\\{/g;\n\n/** A `dynamic view` / `deployment view` inside a views block — the irrecoverable kinds. */\nconst AUTHORED_VIEW = /\\b(dynamic|deployment)\\s+view\\b/;\n\nfunction refuse(block: string, detail: string, home: string): never {\n throw new LandscapeSpliceError(\n `delta.likec4 declares ${detail}, and the landscape merge splices only what is inside \\`model { }\\` — ` +\n `archiving would drop it silently, so nothing was written. Move the ${block} into ` +\n `${home} and re-run the archive.`,\n );\n}\n\n/** Where the living documents keep what this delta cannot carry. */\nconst LANDSCAPE_HOME = \"architecture/landscape.likec4, where the living documents keep it,\";\n\n/** Where a FEATURE keeps TOPOLOGY — the third whole-file slot, added with the deployment axis. */\nconst DEPLOYMENT_HOME =\n \"features/<FEAT>/deployment/<name>.likec4 — a document of its own, which `loam archive` copies into \" +\n \"architecture/ and `loam unarchive` takes back; say `extend` in it to add to a region the living map \" +\n \"already declares —\";\n\n/** Where a FEATURE keeps a flow — a slot of its own, not the living tree. See the header. */\nconst FLOW_HOME =\n \"features/<FEAT>/usecases/<name>.likec4 — a views-only document of its own, which `loam archive` copies into \" +\n \"architecture/usecases/ and `loam unarchive` takes back —\";\n\n/**\n * Refuse a delta whose non-model blocks the merge would discard.\n *\n * Runs over MASKED source, so a `deployment {` written inside a comment or a\n * description string cannot trigger it — the same masking discipline\n * `scanModel` splices under, and for the same reason: this refuses an archive,\n * and a refusal a comment can provoke is worse than the silence it replaced.\n *\n * Brace depth is tracked in one forward pass rather than recounted per match:\n * only a block at depth 0 is a top-level declaration, and `deployment` at depth\n * 1 is an ordinary `specification { deploymentNode … }` or a view predicate.\n */\nexport function assertMergeableDelta(deltaText: string): void {\n const { code } = maskSource(deltaText);\n let depth = 0;\n let cursor = 0;\n for (const m of code.matchAll(TOP_LEVEL_BLOCK)) {\n for (; cursor < m.index; cursor += 1) {\n if (code[cursor] === \"{\") depth += 1;\n else if (code[cursor] === \"}\") depth -= 1;\n }\n if (depth !== 0) continue;\n const block = m[1]!;\n if (block !== \"views\") {\n refuse(\n `\\`${block} { }\\` block`,\n `a top-level \\`${block} { }\\` block`,\n // The refusal is unchanged and always will be — this document\n // re-declares the landscape's identifiers and carries its own\n // `specification`, so it cannot be staged beside the map in one project.\n // What changed is where it sends the author: a feature introducing\n // topology WITH the change that makes it true now has a slot of its\n // own, and pointing at the living landscape was the advice that made\n // the axis's headline case a two-pull-request job. `global { }` has no\n // such slot and keeps the living home.\n block === \"deployment\" ? DEPLOYMENT_HOME : LANDSCAPE_HOME,\n );\n }\n const open = m.index + m[0].length - 1;\n const close = matchBrace(code, open);\n const inner = code.slice(open + 1, close === -1 ? code.length : close);\n const authored = AUTHORED_VIEW.exec(inner);\n if (authored !== null) {\n const kind = authored[1]!;\n refuse(\n `\\`${kind} view\\``,\n `a \\`${kind} view\\` in its \\`views { }\\` block — a view of that kind describes ordering or topology ` +\n `recorded in no other document, unlike the static preview view \\`loam new\\` scaffolds`,\n // A deployment view is topology and belongs in the living map; a dynamic\n // view is a flow, and a flow now has a feature slot of its own. Sending\n // an author to the living landscape for one they are introducing WITH\n // this change was the advice that made the axis's headline case a\n // two-pull-request job.\n kind === \"dynamic\" ? FLOW_HOME : DEPLOYMENT_HOME,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The order of loam-inserted relationships that share a landing region:
|
|
3
|
+
* (source title, target title, the three spine keys, title) as one comparable
|
|
4
|
+
* string. Titles, not ids — the namespaces differ. The same edge must sort
|
|
5
|
+
* identically as today's addition and as a statement in the next archive's
|
|
6
|
+
* scan, so every field is one BOTH `Rel` and `ScannedRel` carry.
|
|
7
|
+
*/
|
|
8
|
+
export function relSortKey(els, r) {
|
|
9
|
+
const spine = [r.op ?? "", r.publishes ?? "", r.consumes ?? ""];
|
|
10
|
+
return JSON.stringify([titleOf(els, r.source), titleOf(els, r.target), ...spine, r.title ?? ""]);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* What makes two edges the same edge. Endpoints are compared by TITLE, which is
|
|
14
|
+
* stable across the delta's and the landscape's id namespaces.
|
|
15
|
+
*
|
|
16
|
+
* An edge carrying a SPINE KEY — `op`, `publishes` or `consumes` — IS that call
|
|
17
|
+
* or that message, whatever it is titled: retitling it must not merge a second
|
|
18
|
+
* copy. An edge with none has only its title. Separate namespaces, because they
|
|
19
|
+
* are separate things: an op-less edge titled `authorizePayment` is not the edge
|
|
20
|
+
* whose operationId is authorizePayment, and keying on `op ?? title` quietly
|
|
21
|
+
* merged only one of them.
|
|
22
|
+
*
|
|
23
|
+
* ALL THREE keys, not just `op`. While the other two were out, a delta edge differing from a
|
|
24
|
+
* living one only by `metadata { publishes 'x' }` hashed the same, counted as already present,
|
|
25
|
+
* and was dropped — `+0 relationship(s)` at exit 0 over a binding that never reached the map.
|
|
26
|
+
*/
|
|
27
|
+
export function relKey(els, r) {
|
|
28
|
+
const src = titleOf(els, r.source);
|
|
29
|
+
const tgt = titleOf(els, r.target);
|
|
30
|
+
const spine = [r.op ?? "", r.publishes ?? "", r.consumes ?? ""];
|
|
31
|
+
return JSON.stringify(spine.some((v) => v !== "") ? ["spine", src, tgt, ...spine] : ["title", src, tgt, r.title ?? ""]);
|
|
32
|
+
}
|
|
33
|
+
export function titleOf(elements, id) {
|
|
34
|
+
return elements.find((e) => e.id === id)?.title ?? id;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=edges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edges.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/edges.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,GAAW,EACX,CAAyG;IAEzG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,CAAM;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1H,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,EAAU;IAClD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;AACxD,CAAC","sourcesContent":["/**\n * How the landscape merge decides that two relationships are the SAME EDGE,\n * and in what order equal ones are written.\n *\n * A subject of its own, and not a corner of placement: everything in\n * `../placement.ts` answers *where in the text* a statement goes, in byte\n * offsets, while everything here answers *which statement this is*, as a\n * comparable string. The merge asks the identity question first and the\n * placement question second, and getting the first one wrong is the failure\n * mode that is silent — a delta edge that hashes the same as a living one is\n * counted as already present and dropped at exit 0, with `+0 relationship(s)`\n * printed over a binding that never reached the map.\n *\n * Both keys are built from fields BOTH `Rel` and `ScannedRel` carry, because\n * the same edge must key identically as today's addition and as a statement in\n * the next archive's scan of the file it was written into. A field only one of\n * them has would make the merge disagree with itself one archive later.\n */\nimport type { Elem, Rel } from \"../../likec4.js\";\n\n/**\n * The order of loam-inserted relationships that share a landing region:\n * (source title, target title, the three spine keys, title) as one comparable\n * string. Titles, not ids — the namespaces differ. The same edge must sort\n * identically as today's addition and as a statement in the next archive's\n * scan, so every field is one BOTH `Rel` and `ScannedRel` carry.\n */\nexport function relSortKey(\n els: Elem[],\n r: { source: string; target: string; title?: string; op?: string; publishes?: string; consumes?: string },\n): string {\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify([titleOf(els, r.source), titleOf(els, r.target), ...spine, r.title ?? \"\"]);\n}\n\n/**\n * What makes two edges the same edge. Endpoints are compared by TITLE, which is\n * stable across the delta's and the landscape's id namespaces.\n *\n * An edge carrying a SPINE KEY — `op`, `publishes` or `consumes` — IS that call\n * or that message, whatever it is titled: retitling it must not merge a second\n * copy. An edge with none has only its title. Separate namespaces, because they\n * are separate things: an op-less edge titled `authorizePayment` is not the edge\n * whose operationId is authorizePayment, and keying on `op ?? title` quietly\n * merged only one of them.\n *\n * ALL THREE keys, not just `op`. While the other two were out, a delta edge differing from a\n * living one only by `metadata { publishes 'x' }` hashed the same, counted as already present,\n * and was dropped — `+0 relationship(s)` at exit 0 over a binding that never reached the map.\n */\nexport function relKey(els: Elem[], r: Rel): string {\n const src = titleOf(els, r.source);\n const tgt = titleOf(els, r.target);\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify(spine.some((v) => v !== \"\") ? [\"spine\", src, tgt, ...spine] : [\"title\", src, tgt, r.title ?? \"\"]);\n}\n\nexport function titleOf(elements: Elem[], id: string): string {\n return elements.find((e) => e.id === id)?.title ?? id;\n}\n"]}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { loadSource } from "../likec4.js";
|
|
2
|
+
import { elementService, serviceOf } from "../resolve/service.js";
|
|
3
|
+
import { scanModel } from "../source-scan.js";
|
|
4
|
+
import { spliceSource } from "./authored-source.js";
|
|
5
|
+
import { LandscapeSpliceError } from "./contract.js";
|
|
6
|
+
import { assertMergeableDelta } from "./delta-blocks.js";
|
|
7
|
+
import { relKey, relSortKey } from "./identity/edges.js";
|
|
8
|
+
import { elementSpot, nestedInsert, relSpot, topStatements, } from "./placement.js";
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/* Merging */
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
/**
|
|
13
|
+
* Plan the merge of the feature's new elements + relationships into the living
|
|
14
|
+
* landscape's `model { ... }` block (preserving the rest of the file).
|
|
15
|
+
*
|
|
16
|
+
* The additions are SPLICED from delta.likec4 as authored — byte for byte,
|
|
17
|
+
* technology, style, icons, links, metadata, nested children and all — never
|
|
18
|
+
* re-serialized from the parsed model: a rebuild keeps only the fields loam
|
|
19
|
+
* models, and every field it forgets is authored detail destroyed. The one
|
|
20
|
+
* edit on the way over is dropping the feature's own tag — the additions are
|
|
21
|
+
* baseline now (SCHEMA.md documents the drop as why unarchive restores bytes
|
|
22
|
+
* instead of recomputing) — and a construct the strip empties goes with it.
|
|
23
|
+
*
|
|
24
|
+
* Placement is deterministic and SERVICE-GROUPED (the spot* helpers in
|
|
25
|
+
* placement.ts):
|
|
26
|
+
* a top-level addition lands beside what already belongs to its service, so
|
|
27
|
+
* two concurrent archives touching different services usually splice on
|
|
28
|
+
* different lines and git merges their PRs — the single shared append point
|
|
29
|
+
* every archive used to hit made two concurrent archives conflict BY
|
|
30
|
+
* CONSTRUCTION. A NESTED element lands inside its parent's block — inside the
|
|
31
|
+
* living parent when the landscape already has it, riding verbatim inside the
|
|
32
|
+
* spliced parent when the parent is new from the same delta — never as a flat
|
|
33
|
+
* dotted id at top level, which LikeC4 rejects. The additions carry no marker
|
|
34
|
+
* comment: which archive added what is the git history's to say.
|
|
35
|
+
*
|
|
36
|
+
* Existence is checked semantically against the parsed landscape (by element
|
|
37
|
+
* id/title and by edge identity), so re-archiving is idempotent and title
|
|
38
|
+
* strings appearing elsewhere in the source cause no false skips. Assumes the
|
|
39
|
+
* delta reuses the landscape's element identifiers for existing services. A
|
|
40
|
+
* title match whose two sides are BOTH bound to different services is not an
|
|
41
|
+
* existence hit but a collision, and refuses the archive (see the guard).
|
|
42
|
+
*
|
|
43
|
+
* Splicing is text surgery, so the computed result is PARSED before it is
|
|
44
|
+
* returned: a merged landscape LikeC4 rejects refuses the archive at plan time
|
|
45
|
+
* (merge-failed, nothing written — the unparseable-delta discipline) instead
|
|
46
|
+
* of landing in the living docs.
|
|
47
|
+
*/
|
|
48
|
+
export async function planLandscapeMerge(merge) {
|
|
49
|
+
const { landscapeText: text, deltaText, deltaElements, newEls, newRels, featureId } = merge;
|
|
50
|
+
// First, and before the early return for "nothing to add": a block this merge
|
|
51
|
+
// cannot carry is lost whether or not the delta also has tagged elements to
|
|
52
|
+
// splice. See delta-blocks.ts.
|
|
53
|
+
assertMergeableDelta(deltaText);
|
|
54
|
+
const land = await loadSource(text);
|
|
55
|
+
if (land.errors.length > 0) {
|
|
56
|
+
throw new LandscapeSpliceError(`landscape.likec4 has ${land.errors.length} error(s) — fix it before archiving`);
|
|
57
|
+
}
|
|
58
|
+
const haveIds = new Set(land.elements.map((e) => e.id));
|
|
59
|
+
// The title join needs the matched element back, not just membership: the
|
|
60
|
+
// cross-service guard below compares service BINDINGS across the join.
|
|
61
|
+
const byTitle = new Map();
|
|
62
|
+
const seeTitle = (el) => {
|
|
63
|
+
byTitle.set(el.title, [...(byTitle.get(el.title) ?? []), el]);
|
|
64
|
+
};
|
|
65
|
+
for (const el of land.elements)
|
|
66
|
+
seeTitle(el);
|
|
67
|
+
const addedEls = [];
|
|
68
|
+
for (const e of newEls) {
|
|
69
|
+
if (haveIds.has(e.id))
|
|
70
|
+
continue;
|
|
71
|
+
const sameTitle = byTitle.get(e.title);
|
|
72
|
+
if (sameTitle !== undefined) {
|
|
73
|
+
// A title match is the id-less fallback join, and skipping on it is only
|
|
74
|
+
// safe when the two sides could be the same box. When both sides carry an
|
|
75
|
+
// explicit `metadata { service }` binding and every binding disagrees,
|
|
76
|
+
// they are provably DIFFERENT services' boxes sharing a title ('API',
|
|
77
|
+
// 'Database') — the skip would silently drop the addition, and any delta
|
|
78
|
+
// edge into it would then refuse the whole archive at the parse net
|
|
79
|
+
// below with a message about nothing. Refuse here instead, at plan time,
|
|
80
|
+
// naming both sides.
|
|
81
|
+
if (e.service !== undefined && sameTitle.every((m) => m.service !== undefined && m.service !== e.service)) {
|
|
82
|
+
const m = sameTitle[0];
|
|
83
|
+
throw new LandscapeSpliceError(`the delta's '${e.id}' (bound to service '${e.service}') shares the title '${e.title}' with '${m.id}' (bound to service '${m.service}') — a title join across services would silently drop the addition; ` +
|
|
84
|
+
`retitle one of them, or reuse the id '${m.id}' if they really are the same element`);
|
|
85
|
+
}
|
|
86
|
+
// KNOWN (narrowed by the guard above): with EITHER side unbound the title
|
|
87
|
+
// join stays trusting — the unbound title-fallback is the legal legacy
|
|
88
|
+
// pattern — so a cross-service collision hiding behind an unbound element
|
|
89
|
+
// is still silently skipped here. Scoping titles per service is backlog.
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
haveIds.add(e.id);
|
|
93
|
+
seeTitle(e);
|
|
94
|
+
addedEls.push(e);
|
|
95
|
+
}
|
|
96
|
+
// Edges are matched by COUNT, not by membership: two edges the model cannot tell
|
|
97
|
+
// apart are still two edges, and dropping the second one silently loses a call
|
|
98
|
+
// the author drew. An edge already in the landscape consumes one delta edge of
|
|
99
|
+
// the same identity, which is what keeps re-archiving idempotent.
|
|
100
|
+
const have = new Map();
|
|
101
|
+
for (const r of land.relationships) {
|
|
102
|
+
const k = relKey(land.elements, r);
|
|
103
|
+
have.set(k, (have.get(k) ?? 0) + 1);
|
|
104
|
+
}
|
|
105
|
+
const addedRels = [];
|
|
106
|
+
for (const r of newRels) {
|
|
107
|
+
const k = relKey(deltaElements, r);
|
|
108
|
+
const n = have.get(k) ?? 0;
|
|
109
|
+
if (n > 0) {
|
|
110
|
+
have.set(k, n - 1);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
addedRels.push(r);
|
|
114
|
+
}
|
|
115
|
+
if (addedEls.length === 0 && addedRels.length === 0)
|
|
116
|
+
return { content: null, addedEls, addedRels };
|
|
117
|
+
const scan = scanModel(deltaText);
|
|
118
|
+
if (scan === null) {
|
|
119
|
+
throw new LandscapeSpliceError("delta.likec4 has no model block — nothing to splice the additions from");
|
|
120
|
+
}
|
|
121
|
+
// Everything below either locates a declaration's authored bytes or refuses.
|
|
122
|
+
// `spliced` remembers the delta ranges already carried over, so a child whose
|
|
123
|
+
// parent is itself new is recognised as riding inside the parent's text and
|
|
124
|
+
// is never inserted twice.
|
|
125
|
+
const byId = new Map(scan.elements.map((e) => [e.id, e]));
|
|
126
|
+
const spliced = [];
|
|
127
|
+
const rides = (start, end) => spliced.some((r) => start >= r.start && end <= r.end);
|
|
128
|
+
// The living model, scanned on MASKED source (scanModel) and RE-SCANNED
|
|
129
|
+
// after every splice: each addition lands in the text exactly as the
|
|
130
|
+
// previous one left it, so an archive of N additions produces the same
|
|
131
|
+
// bytes as N single-addition archives run back to back. Placement then
|
|
132
|
+
// composes per statement — the unit the order-independence argument below
|
|
133
|
+
// is stated in — instead of per archive, where a batch computed against a
|
|
134
|
+
// stale layout could interleave with itself. Masking matters — a `model {`
|
|
135
|
+
// spelled inside a comment or string above the real block must not capture
|
|
136
|
+
// the match, or every top-level addition lands inside the comment (legal
|
|
137
|
+
// LikeC4, so the parse net below would pass a landscape containing none of
|
|
138
|
+
// the architecture).
|
|
139
|
+
let content = text;
|
|
140
|
+
// Service binding for placement: living elements first (they win a shared
|
|
141
|
+
// id), the delta's after — a spliced statement's id resolves to its service
|
|
142
|
+
// the moment its bytes land in the scan.
|
|
143
|
+
const bindEls = [...land.elements, ...deltaElements.filter((d) => !land.elements.some((l) => l.id === d.id))];
|
|
144
|
+
let livingScan = scanModel(content);
|
|
145
|
+
let stmts = livingScan === null ? [] : topStatements(livingScan, bindEls);
|
|
146
|
+
const rescan = () => {
|
|
147
|
+
livingScan = scanModel(content);
|
|
148
|
+
stmts = livingScan === null ? [] : topStatements(livingScan, bindEls);
|
|
149
|
+
};
|
|
150
|
+
const requireModel = () => {
|
|
151
|
+
if (livingScan === null)
|
|
152
|
+
throw new LandscapeSpliceError("landscape.likec4 has no model block");
|
|
153
|
+
return livingScan;
|
|
154
|
+
};
|
|
155
|
+
// Built fresh at every use, never captured: `content` and `stmts` are rebound
|
|
156
|
+
// by each rescan, and a region assembled before a splice describes a document
|
|
157
|
+
// that no longer exists.
|
|
158
|
+
const modelRegion = () => ({ text: content, stmts, close: requireModel().close });
|
|
159
|
+
const applyAt = (at, insert) => {
|
|
160
|
+
content = content.slice(0, at) + insert + content.slice(at);
|
|
161
|
+
rescan();
|
|
162
|
+
};
|
|
163
|
+
const applyTop = (spot, block) => {
|
|
164
|
+
// A bare spot shares its line with other text (the closing brace, or a
|
|
165
|
+
// statement the block displaces) — it brings its own newlines; a
|
|
166
|
+
// line-start spot slots between lines.
|
|
167
|
+
applyAt(spot.at, spot.bare ? `\n${block}\n` : `${block}\n`);
|
|
168
|
+
};
|
|
169
|
+
// Placement walks the scanned statement layout, so a declaration the scan
|
|
170
|
+
// cannot see is one placement would blindly splice around — LikeC4 accepts
|
|
171
|
+
// two declarations on one line, but scanModel's statement head runs to the
|
|
172
|
+
// end of the line, so the second rides invisibly inside the first: an
|
|
173
|
+
// element bound to its service would miss its neighborhood, and a bodyless
|
|
174
|
+
// parent gaining a body could wrap the wrong declaration. Refuse
|
|
175
|
+
// mechanically (the parse-net discipline) instead of splicing blind.
|
|
176
|
+
if (livingScan !== null) {
|
|
177
|
+
const seen = new Set(livingScan.elements.map((e) => e.id));
|
|
178
|
+
const invisible = land.elements.find((e) => !seen.has(e.id));
|
|
179
|
+
if (invisible !== undefined) {
|
|
180
|
+
throw new LandscapeSpliceError(`landscape.likec4 declares '${invisible.id}' in a form placement cannot locate — most often two declarations sharing one line; give each its own line, then re-run`);
|
|
181
|
+
}
|
|
182
|
+
if (livingScan.rels.length < land.relationships.length) {
|
|
183
|
+
throw new LandscapeSpliceError(`landscape.likec4 declares ${land.relationships.length} relationship(s) but placement can locate only ${livingScan.rels.length} — most often two statements sharing one line; give each its own line, then re-run`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const livingParentOf = (parentId) => {
|
|
187
|
+
if (livingScan === null)
|
|
188
|
+
return null;
|
|
189
|
+
const direct = livingScan.elements.find((e) => e.id === parentId);
|
|
190
|
+
if (direct !== undefined)
|
|
191
|
+
return direct;
|
|
192
|
+
// The delta may spell an existing element under its own id; the title is
|
|
193
|
+
// the stable cross-namespace name (the same rule the existence check uses).
|
|
194
|
+
const title = deltaElements.find((e) => e.id === parentId)?.title;
|
|
195
|
+
const livingId = title === undefined ? undefined : land.elements.find((e) => e.title === title)?.id;
|
|
196
|
+
if (livingId === undefined)
|
|
197
|
+
return null;
|
|
198
|
+
return livingScan.elements.find((e) => e.id === livingId) ?? null;
|
|
199
|
+
};
|
|
200
|
+
// Ancestors first: a spliced parent covers its children before they are seen.
|
|
201
|
+
const depth = (id) => id.split(".").length;
|
|
202
|
+
const sortedEls = [...addedEls].sort((a, b) => depth(a.id) - depth(b.id) || (byId.get(a.id)?.start ?? 0) - (byId.get(b.id)?.start ?? 0));
|
|
203
|
+
for (const e of sortedEls) {
|
|
204
|
+
const src = byId.get(e.id);
|
|
205
|
+
if (src === undefined) {
|
|
206
|
+
throw new LandscapeSpliceError(`cannot locate '${e.id}' in delta.likec4 — the landscape merge splices authored source, and this declaration was not found`);
|
|
207
|
+
}
|
|
208
|
+
if (rides(src.start, src.end))
|
|
209
|
+
continue;
|
|
210
|
+
const dot = e.id.lastIndexOf(".");
|
|
211
|
+
if (dot === -1) {
|
|
212
|
+
const spot = elementSpot(modelRegion(), e.id, elementService(e));
|
|
213
|
+
applyTop(spot, spliceSource(deltaText, src, featureId, " "));
|
|
214
|
+
spliced.push({ start: src.start, end: src.end });
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const parentId = e.id.slice(0, dot);
|
|
218
|
+
const parent = livingParentOf(parentId);
|
|
219
|
+
if (parent === null) {
|
|
220
|
+
throw new LandscapeSpliceError(`'${e.id}' nests under '${parentId}', which is neither in the living landscape nor added by this delta — there is nowhere to insert it`);
|
|
221
|
+
}
|
|
222
|
+
const nested = nestedInsert(content, parent, spliceSource(deltaText, src, featureId, parent.indent + " "));
|
|
223
|
+
applyAt(nested.at, nested.insert);
|
|
224
|
+
spliced.push({ start: src.start, end: src.end });
|
|
225
|
+
}
|
|
226
|
+
// Relationships: match each parsed addition back to its statement in the
|
|
227
|
+
// delta source — full identity (endpoints, title, all three spine keys,
|
|
228
|
+
// tags), consumed one statement per addition so duplicates stay duplicates.
|
|
229
|
+
// The spine keys earn their place here the same way they do in `relKey`:
|
|
230
|
+
// without them two additions differing only by `publishes` share one pool
|
|
231
|
+
// entry, and `shift()` hands the second one the first one's authored bytes.
|
|
232
|
+
const relKeyOf = (r) => JSON.stringify([r.source, r.target, r.title ?? "", r.op ?? "", r.publishes ?? "", r.consumes ?? "", [...r.tags].sort()]);
|
|
233
|
+
const pool = new Map();
|
|
234
|
+
for (const s of scan.rels) {
|
|
235
|
+
const k = relKeyOf(s);
|
|
236
|
+
pool.set(k, [...(pool.get(k) ?? []), s]);
|
|
237
|
+
}
|
|
238
|
+
for (const r of addedRels) {
|
|
239
|
+
const s = pool.get(relKeyOf(r))?.shift();
|
|
240
|
+
if (s === undefined) {
|
|
241
|
+
throw new LandscapeSpliceError(`cannot locate the '${r.source} -> ${r.target}' relationship in delta.likec4 — the landscape merge splices authored source, and no matching declaration was found`);
|
|
242
|
+
}
|
|
243
|
+
if (rides(s.start, s.end))
|
|
244
|
+
continue;
|
|
245
|
+
const key = relSortKey(deltaElements, r);
|
|
246
|
+
// Deliberately no `known` fleet set: this resolves only the anchor a new
|
|
247
|
+
// relationship lands after (placement.ts's reason — cosmetic grouping,
|
|
248
|
+
// safety-netted by the re-parse below), never which service a check
|
|
249
|
+
// grades.
|
|
250
|
+
const spot = relSpot(modelRegion(), serviceOf(deltaElements, r.source), key);
|
|
251
|
+
applyTop(spot, spliceSource(deltaText, s, featureId, " "));
|
|
252
|
+
}
|
|
253
|
+
// The safety net: prove the computed landscape parses before anything is
|
|
254
|
+
// written. Splice bugs — and legal inputs the living document cannot absorb,
|
|
255
|
+
// like a kind its specification never declares — refuse here, at plan time,
|
|
256
|
+
// instead of corrupting the one file the whole fleet reads.
|
|
257
|
+
const check = await loadSource(content);
|
|
258
|
+
if (check.errors.length > 0) {
|
|
259
|
+
const detail = check.errors
|
|
260
|
+
.slice(0, 3)
|
|
261
|
+
.map((e) => (typeof e.line === "number" ? `L${e.line}: ${e.message}` : e.message))
|
|
262
|
+
.join("; ");
|
|
263
|
+
throw new LandscapeSpliceError(`the merged landscape would not parse (${check.errors.length} error(s): ${detail}) — nothing was written. ` +
|
|
264
|
+
`The delta's additions do not fit the living landscape as authored — most often an element kind or tag ` +
|
|
265
|
+
`its specification block does not declare; fix the landscape's specification or the delta, then re-run`);
|
|
266
|
+
}
|
|
267
|
+
return { content, addedEls, addedRels };
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=landscape-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"landscape-merge.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/landscape-merge.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { loadSource, type Elem, type Rel } from \"../likec4.js\";\nimport { elementService, serviceOf } from \"../resolve/service.js\";\nimport { scanModel, type ScannedElement, type ScannedModel, type ScannedRel } from \"../source-scan.js\";\nimport { spliceSource } from \"./authored-source.js\";\nimport { LandscapeSpliceError, type LandscapeMergeRequest, type LandscapePlan } from \"./contract.js\";\nimport { assertMergeableDelta } from \"./delta-blocks.js\";\nimport { relKey, relSortKey } from \"./identity/edges.js\";\nimport {\n elementSpot,\n nestedInsert,\n relSpot,\n topStatements,\n type ModelRegion,\n type Spot,\n} from \"./placement.js\";\n\n/* ------------------------------------------------------------------ */\n/* Merging */\n/* ------------------------------------------------------------------ */\n\n/**\n * Plan the merge of the feature's new elements + relationships into the living\n * landscape's `model { ... }` block (preserving the rest of the file).\n *\n * The additions are SPLICED from delta.likec4 as authored — byte for byte,\n * technology, style, icons, links, metadata, nested children and all — never\n * re-serialized from the parsed model: a rebuild keeps only the fields loam\n * models, and every field it forgets is authored detail destroyed. The one\n * edit on the way over is dropping the feature's own tag — the additions are\n * baseline now (SCHEMA.md documents the drop as why unarchive restores bytes\n * instead of recomputing) — and a construct the strip empties goes with it.\n *\n * Placement is deterministic and SERVICE-GROUPED (the spot* helpers in\n * placement.ts):\n * a top-level addition lands beside what already belongs to its service, so\n * two concurrent archives touching different services usually splice on\n * different lines and git merges their PRs — the single shared append point\n * every archive used to hit made two concurrent archives conflict BY\n * CONSTRUCTION. A NESTED element lands inside its parent's block — inside the\n * living parent when the landscape already has it, riding verbatim inside the\n * spliced parent when the parent is new from the same delta — never as a flat\n * dotted id at top level, which LikeC4 rejects. The additions carry no marker\n * comment: which archive added what is the git history's to say.\n *\n * Existence is checked semantically against the parsed landscape (by element\n * id/title and by edge identity), so re-archiving is idempotent and title\n * strings appearing elsewhere in the source cause no false skips. Assumes the\n * delta reuses the landscape's element identifiers for existing services. A\n * title match whose two sides are BOTH bound to different services is not an\n * existence hit but a collision, and refuses the archive (see the guard).\n *\n * Splicing is text surgery, so the computed result is PARSED before it is\n * returned: a merged landscape LikeC4 rejects refuses the archive at plan time\n * (merge-failed, nothing written — the unparseable-delta discipline) instead\n * of landing in the living docs.\n */\nexport async function planLandscapeMerge(merge: LandscapeMergeRequest): Promise<LandscapePlan> {\n const { landscapeText: text, deltaText, deltaElements, newEls, newRels, featureId } = merge;\n // First, and before the early return for \"nothing to add\": a block this merge\n // cannot carry is lost whether or not the delta also has tagged elements to\n // splice. See delta-blocks.ts.\n assertMergeableDelta(deltaText);\n const land = await loadSource(text);\n if (land.errors.length > 0) {\n throw new LandscapeSpliceError(`landscape.likec4 has ${land.errors.length} error(s) — fix it before archiving`);\n }\n const haveIds = new Set(land.elements.map((e) => e.id));\n // The title join needs the matched element back, not just membership: the\n // cross-service guard below compares service BINDINGS across the join.\n const byTitle = new Map<string, Elem[]>();\n const seeTitle = (el: Elem): void => {\n byTitle.set(el.title, [...(byTitle.get(el.title) ?? []), el]);\n };\n for (const el of land.elements) seeTitle(el);\n const addedEls: Elem[] = [];\n for (const e of newEls) {\n if (haveIds.has(e.id)) continue;\n const sameTitle = byTitle.get(e.title);\n if (sameTitle !== undefined) {\n // A title match is the id-less fallback join, and skipping on it is only\n // safe when the two sides could be the same box. When both sides carry an\n // explicit `metadata { service }` binding and every binding disagrees,\n // they are provably DIFFERENT services' boxes sharing a title ('API',\n // 'Database') — the skip would silently drop the addition, and any delta\n // edge into it would then refuse the whole archive at the parse net\n // below with a message about nothing. Refuse here instead, at plan time,\n // naming both sides.\n if (e.service !== undefined && sameTitle.every((m) => m.service !== undefined && m.service !== e.service)) {\n const m = sameTitle[0]!;\n throw new LandscapeSpliceError(\n `the delta's '${e.id}' (bound to service '${e.service}') shares the title '${e.title}' with '${m.id}' (bound to service '${m.service}') — a title join across services would silently drop the addition; ` +\n `retitle one of them, or reuse the id '${m.id}' if they really are the same element`,\n );\n }\n // KNOWN (narrowed by the guard above): with EITHER side unbound the title\n // join stays trusting — the unbound title-fallback is the legal legacy\n // pattern — so a cross-service collision hiding behind an unbound element\n // is still silently skipped here. Scoping titles per service is backlog.\n continue;\n }\n haveIds.add(e.id);\n seeTitle(e);\n addedEls.push(e);\n }\n\n // Edges are matched by COUNT, not by membership: two edges the model cannot tell\n // apart are still two edges, and dropping the second one silently loses a call\n // the author drew. An edge already in the landscape consumes one delta edge of\n // the same identity, which is what keeps re-archiving idempotent.\n const have = new Map<string, number>();\n for (const r of land.relationships) {\n const k = relKey(land.elements, r);\n have.set(k, (have.get(k) ?? 0) + 1);\n }\n const addedRels: Rel[] = [];\n for (const r of newRels) {\n const k = relKey(deltaElements, r);\n const n = have.get(k) ?? 0;\n if (n > 0) {\n have.set(k, n - 1);\n continue;\n }\n addedRels.push(r);\n }\n\n if (addedEls.length === 0 && addedRels.length === 0) return { content: null, addedEls, addedRels };\n\n const scan = scanModel(deltaText);\n if (scan === null) {\n throw new LandscapeSpliceError(\"delta.likec4 has no model block — nothing to splice the additions from\");\n }\n\n // Everything below either locates a declaration's authored bytes or refuses.\n // `spliced` remembers the delta ranges already carried over, so a child whose\n // parent is itself new is recognised as riding inside the parent's text and\n // is never inserted twice.\n const byId = new Map(scan.elements.map((e) => [e.id, e]));\n const spliced: Array<{ start: number; end: number }> = [];\n const rides = (start: number, end: number): boolean =>\n spliced.some((r) => start >= r.start && end <= r.end);\n\n // The living model, scanned on MASKED source (scanModel) and RE-SCANNED\n // after every splice: each addition lands in the text exactly as the\n // previous one left it, so an archive of N additions produces the same\n // bytes as N single-addition archives run back to back. Placement then\n // composes per statement — the unit the order-independence argument below\n // is stated in — instead of per archive, where a batch computed against a\n // stale layout could interleave with itself. Masking matters — a `model {`\n // spelled inside a comment or string above the real block must not capture\n // the match, or every top-level addition lands inside the comment (legal\n // LikeC4, so the parse net below would pass a landscape containing none of\n // the architecture).\n let content = text;\n // Service binding for placement: living elements first (they win a shared\n // id), the delta's after — a spliced statement's id resolves to its service\n // the moment its bytes land in the scan.\n const bindEls = [...land.elements, ...deltaElements.filter((d) => !land.elements.some((l) => l.id === d.id))];\n let livingScan = scanModel(content);\n let stmts = livingScan === null ? [] : topStatements(livingScan, bindEls);\n const rescan = (): void => {\n livingScan = scanModel(content);\n stmts = livingScan === null ? [] : topStatements(livingScan, bindEls);\n };\n const requireModel = (): ScannedModel => {\n if (livingScan === null) throw new LandscapeSpliceError(\"landscape.likec4 has no model block\");\n return livingScan;\n };\n // Built fresh at every use, never captured: `content` and `stmts` are rebound\n // by each rescan, and a region assembled before a splice describes a document\n // that no longer exists.\n const modelRegion = (): ModelRegion => ({ text: content, stmts, close: requireModel().close });\n const applyAt = (at: number, insert: string): void => {\n content = content.slice(0, at) + insert + content.slice(at);\n rescan();\n };\n const applyTop = (spot: Spot, block: string): void => {\n // A bare spot shares its line with other text (the closing brace, or a\n // statement the block displaces) — it brings its own newlines; a\n // line-start spot slots between lines.\n applyAt(spot.at, spot.bare ? `\\n${block}\\n` : `${block}\\n`);\n };\n\n // Placement walks the scanned statement layout, so a declaration the scan\n // cannot see is one placement would blindly splice around — LikeC4 accepts\n // two declarations on one line, but scanModel's statement head runs to the\n // end of the line, so the second rides invisibly inside the first: an\n // element bound to its service would miss its neighborhood, and a bodyless\n // parent gaining a body could wrap the wrong declaration. Refuse\n // mechanically (the parse-net discipline) instead of splicing blind.\n if (livingScan !== null) {\n const seen = new Set(livingScan.elements.map((e) => e.id));\n const invisible = land.elements.find((e) => !seen.has(e.id));\n if (invisible !== undefined) {\n throw new LandscapeSpliceError(\n `landscape.likec4 declares '${invisible.id}' in a form placement cannot locate — most often two declarations sharing one line; give each its own line, then re-run`,\n );\n }\n if (livingScan.rels.length < land.relationships.length) {\n throw new LandscapeSpliceError(\n `landscape.likec4 declares ${land.relationships.length} relationship(s) but placement can locate only ${livingScan.rels.length} — most often two statements sharing one line; give each its own line, then re-run`,\n );\n }\n }\n\n const livingParentOf = (parentId: string): ScannedElement | null => {\n if (livingScan === null) return null;\n const direct = livingScan.elements.find((e) => e.id === parentId);\n if (direct !== undefined) return direct;\n // The delta may spell an existing element under its own id; the title is\n // the stable cross-namespace name (the same rule the existence check uses).\n const title = deltaElements.find((e) => e.id === parentId)?.title;\n const livingId = title === undefined ? undefined : land.elements.find((e) => e.title === title)?.id;\n if (livingId === undefined) return null;\n return livingScan.elements.find((e) => e.id === livingId) ?? null;\n };\n\n // Ancestors first: a spliced parent covers its children before they are seen.\n const depth = (id: string): number => id.split(\".\").length;\n const sortedEls = [...addedEls].sort(\n (a, b) => depth(a.id) - depth(b.id) || (byId.get(a.id)?.start ?? 0) - (byId.get(b.id)?.start ?? 0),\n );\n for (const e of sortedEls) {\n const src = byId.get(e.id);\n if (src === undefined) {\n throw new LandscapeSpliceError(\n `cannot locate '${e.id}' in delta.likec4 — the landscape merge splices authored source, and this declaration was not found`,\n );\n }\n if (rides(src.start, src.end)) continue;\n const dot = e.id.lastIndexOf(\".\");\n if (dot === -1) {\n const spot = elementSpot(modelRegion(), e.id, elementService(e));\n applyTop(spot, spliceSource(deltaText, src, featureId, \" \"));\n spliced.push({ start: src.start, end: src.end });\n continue;\n }\n const parentId = e.id.slice(0, dot);\n const parent = livingParentOf(parentId);\n if (parent === null) {\n throw new LandscapeSpliceError(\n `'${e.id}' nests under '${parentId}', which is neither in the living landscape nor added by this delta — there is nowhere to insert it`,\n );\n }\n const nested = nestedInsert(content, parent, spliceSource(deltaText, src, featureId, parent.indent + \" \"));\n applyAt(nested.at, nested.insert);\n spliced.push({ start: src.start, end: src.end });\n }\n\n // Relationships: match each parsed addition back to its statement in the\n // delta source — full identity (endpoints, title, all three spine keys,\n // tags), consumed one statement per addition so duplicates stay duplicates.\n // The spine keys earn their place here the same way they do in `relKey`:\n // without them two additions differing only by `publishes` share one pool\n // entry, and `shift()` hands the second one the first one's authored bytes.\n const relKeyOf = (r: Rel | ScannedRel): string =>\n JSON.stringify([r.source, r.target, r.title ?? \"\", r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\", [...r.tags].sort()]);\n const pool = new Map<string, ScannedRel[]>();\n for (const s of scan.rels) {\n const k = relKeyOf(s);\n pool.set(k, [...(pool.get(k) ?? []), s]);\n }\n for (const r of addedRels) {\n const s = pool.get(relKeyOf(r))?.shift();\n if (s === undefined) {\n throw new LandscapeSpliceError(\n `cannot locate the '${r.source} -> ${r.target}' relationship in delta.likec4 — the landscape merge splices authored source, and no matching declaration was found`,\n );\n }\n if (rides(s.start, s.end)) continue;\n const key = relSortKey(deltaElements, r);\n // Deliberately no `known` fleet set: this resolves only the anchor a new\n // relationship lands after (placement.ts's reason — cosmetic grouping,\n // safety-netted by the re-parse below), never which service a check\n // grades.\n const spot = relSpot(modelRegion(), serviceOf(deltaElements, r.source), key);\n applyTop(spot, spliceSource(deltaText, s, featureId, \" \"));\n }\n\n // The safety net: prove the computed landscape parses before anything is\n // written. Splice bugs — and legal inputs the living document cannot absorb,\n // like a kind its specification never declares — refuse here, at plan time,\n // instead of corrupting the one file the whole fleet reads.\n const check = await loadSource(content);\n if (check.errors.length > 0) {\n const detail = check.errors\n .slice(0, 3)\n .map((e) => (typeof e.line === \"number\" ? `L${e.line}: ${e.message}` : e.message))\n .join(\"; \");\n throw new LandscapeSpliceError(\n `the merged landscape would not parse (${check.errors.length} error(s): ${detail}) — nothing was written. ` +\n `The delta's additions do not fit the living landscape as authored — most often an element kind or tag ` +\n `its specification block does not declare; fix the landscape's specification or the delta, then re-run`,\n );\n }\n\n return { content, addedEls, addedRels };\n}\n"]}
|