@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.1
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 +2548 -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 +494 -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 +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -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 +251 -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 +194 -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 +236 -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 +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -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 +334 -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 +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.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 +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.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 +126 -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 +172 -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 +251 -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/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 +118 -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 +192 -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 +236 -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 +171 -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 +187 -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 +235 -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 +55 -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,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `$ref` as a pair of plain questions — which ones does this tree contain, and
|
|
3
|
+
* what does one point at — and the component closure the merge answers them
|
|
4
|
+
* for.
|
|
5
|
+
*
|
|
6
|
+
* The closure is verdict-driven, not reachability-driven: every component the
|
|
7
|
+
* feature DECLARES is classified against its `x-loam-baselines` entry exactly
|
|
8
|
+
* as an operation is classified against its pin, and reachability decides only
|
|
9
|
+
* one thing — whether a genuinely NEW component rides along — whenever the
|
|
10
|
+
* delta merges paths at all. The old rule, "copy everything reachable from any
|
|
11
|
+
* feature path item", let a QUOTED operation drag its authoring-time component
|
|
12
|
+
* copy over someone else's landed change; the walk now starts from what this
|
|
13
|
+
* merge actually WROTE, so a quote pulls nothing.
|
|
14
|
+
*
|
|
15
|
+
* Where the delta merges no path, that walk starts from nothing, and "is a new
|
|
16
|
+
* component reachable?" has no meaningful answer to give: a components-only
|
|
17
|
+
* delta declared every one of them on purpose and there is no operation for
|
|
18
|
+
* them to hide behind. Parking them forever is what made such a delta merge
|
|
19
|
+
* nothing at all, at exit 0 — `componentsAreTheDelta` is that case, and only
|
|
20
|
+
* that case.
|
|
21
|
+
*/
|
|
22
|
+
import { isDeepStrictEqual } from "node:util";
|
|
23
|
+
import { isMap, isScalar } from "yaml";
|
|
24
|
+
import { classifyBaselineDigests, valueDigest, withoutFeatureKeysDeep } from "../digest.js";
|
|
25
|
+
import { entryFor, restatedSurfaces, surfaceIn } from "../baseline/record.js";
|
|
26
|
+
export function collectRefs(node) {
|
|
27
|
+
const out = [];
|
|
28
|
+
const walk = (current) => {
|
|
29
|
+
if (Array.isArray(current)) {
|
|
30
|
+
for (const value of current)
|
|
31
|
+
walk(value);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (current !== null && typeof current === "object") {
|
|
35
|
+
for (const [key, value] of Object.entries(current)) {
|
|
36
|
+
if (key === "$ref" && typeof value === "string")
|
|
37
|
+
out.push(value);
|
|
38
|
+
else
|
|
39
|
+
walk(value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walk(node);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** Resolve a local JSON pointer (`#/a/b~1c`) against a plain JS tree. */
|
|
47
|
+
export function resolvePointer(root, ref) {
|
|
48
|
+
let current = root;
|
|
49
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
50
|
+
const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
51
|
+
if (Array.isArray(current)) {
|
|
52
|
+
const index = Number(segment);
|
|
53
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length) {
|
|
54
|
+
return { found: false, value: undefined };
|
|
55
|
+
}
|
|
56
|
+
current = current[index];
|
|
57
|
+
}
|
|
58
|
+
else if (current !== null && typeof current === "object" && segment in current) {
|
|
59
|
+
current = current[segment];
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return { found: false, value: undefined };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { found: true, value: current };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Merge the feature's components into the living document by verdict:
|
|
69
|
+
*
|
|
70
|
+
* - quote → skipped; the living copy stays, whatever landed on it since.
|
|
71
|
+
* - edit, stale (under --approve), or unpinned-and-differing → copied; the
|
|
72
|
+
* unpinned copy is the old upsert, kept for never-rebased deltas.
|
|
73
|
+
* - unpinned and living-equal → skipped, as it always was: nothing to write.
|
|
74
|
+
* - unfounded (pinned, living gone — the gate refused; --approve) → copied,
|
|
75
|
+
* because a written operation may reference it and the merge must never
|
|
76
|
+
* leave a dangling ref behind.
|
|
77
|
+
* - NEW (unpinned, living-absent) → copied only when reachable from written
|
|
78
|
+
* content or from another copied component's own refs. The visited-set
|
|
79
|
+
* fixpoint is what keeps A↔B cycles terminating and unrelated namespaces
|
|
80
|
+
* uncopied. When the components ARE the delta there is no written content
|
|
81
|
+
* to be reachable from, so every one of them is copied outright.
|
|
82
|
+
*
|
|
83
|
+
* The unresolved sweep is unchanged in meaning: every local ref reachable from
|
|
84
|
+
* written content and copied components must resolve in the copy set or the
|
|
85
|
+
* living document.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Write one component's value, matching the existing key AS A STRING: a
|
|
89
|
+
* living `404:` response is the YAML number 404, and a plain `setIn` with the
|
|
90
|
+
* string "404" misses it and APPENDS a second pair — the contract then
|
|
91
|
+
* declares the component twice, forever, with the pre-merge copy first in
|
|
92
|
+
* reading order. Exported for the strip's twin write on the feature side.
|
|
93
|
+
*/
|
|
94
|
+
export function setComponentValue(doc, kind, name, value) {
|
|
95
|
+
const kindNode = doc.getIn(["components", kind]);
|
|
96
|
+
const existing = isMap(kindNode)
|
|
97
|
+
? kindNode.items.find((it) => isScalar(it.key) && String(it.key.value) === name)
|
|
98
|
+
: undefined;
|
|
99
|
+
if (existing !== undefined)
|
|
100
|
+
existing.value = doc.createNode(value);
|
|
101
|
+
else
|
|
102
|
+
doc.setIn(["components", kind, name], value);
|
|
103
|
+
}
|
|
104
|
+
export function mergeComponentClosure(input) {
|
|
105
|
+
const { living, featPlain, livingPlain, record, written, componentsAreTheDelta } = input;
|
|
106
|
+
const componentsModified = [];
|
|
107
|
+
const componentsAdded = [];
|
|
108
|
+
const componentsQuoted = [];
|
|
109
|
+
const componentsStale = [];
|
|
110
|
+
const unresolved = [];
|
|
111
|
+
const copies = [];
|
|
112
|
+
const copied = new Set();
|
|
113
|
+
/** Genuinely new components, parked until something written reaches them. */
|
|
114
|
+
const parked = new Map();
|
|
115
|
+
/**
|
|
116
|
+
* Which declared surfaces the living contract does not have — recorded here,
|
|
117
|
+
* during classification, because this is the one place that asks. What was
|
|
118
|
+
* ADDED is decided at the write below instead: a parked component the
|
|
119
|
+
* fixpoint never reaches is absent from living and stays that way, and
|
|
120
|
+
* calling it added would name a component the merge did not write.
|
|
121
|
+
*/
|
|
122
|
+
const absentFromLiving = new Set();
|
|
123
|
+
// ONE enumeration (baseline/record.ts) with the rebase plan and the gate —
|
|
124
|
+
// and ONE living lookup — so "which components does this delta declare, and
|
|
125
|
+
// do they exist" cannot drift between the command that pins, the gate that
|
|
126
|
+
// grades and this closure that writes.
|
|
127
|
+
for (const surface of restatedSurfaces(featPlain)) {
|
|
128
|
+
if (surface.kind !== "component")
|
|
129
|
+
continue;
|
|
130
|
+
const cut = surface.id.indexOf("/");
|
|
131
|
+
const entry = { kind: surface.id.slice(0, cut), name: surface.id.slice(cut + 1), value: surface.value };
|
|
132
|
+
const inLiving = surfaceIn(livingPlain, surface);
|
|
133
|
+
if (!inLiving.found)
|
|
134
|
+
absentFromLiving.add(surface.id);
|
|
135
|
+
const verdict = classifyBaselineDigests(entryFor(record, surface), valueDigest(surface.value), inLiving.found ? valueDigest(inLiving.value) : undefined);
|
|
136
|
+
if (verdict === "quote") {
|
|
137
|
+
componentsQuoted.push(surface.id);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (verdict === "unpinned") {
|
|
141
|
+
if (!inLiving.found) {
|
|
142
|
+
parked.set(surface.id, entry);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (isDeepStrictEqual(inLiving.value, surface.value))
|
|
146
|
+
continue;
|
|
147
|
+
componentsModified.push(surface.id);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
if (verdict === "stale")
|
|
151
|
+
componentsStale.push(surface.id);
|
|
152
|
+
if (inLiving.found && !isDeepStrictEqual(inLiving.value, surface.value))
|
|
153
|
+
componentsModified.push(surface.id);
|
|
154
|
+
}
|
|
155
|
+
copies.push(entry);
|
|
156
|
+
copied.add(surface.id);
|
|
157
|
+
}
|
|
158
|
+
// A components-only delta wrote no path, so the fixpoint below would start
|
|
159
|
+
// from an empty queue and every parked component would stay parked forever —
|
|
160
|
+
// the merge published a document byte-identical to the living one and archive
|
|
161
|
+
// said `ok`. The promotion happens HERE, before the queue is built, and that
|
|
162
|
+
// order is the whole point: a component promoted afterwards would carry its
|
|
163
|
+
// own `$ref`s past the sweep, and `openapi.ref-unresolved` would stop firing
|
|
164
|
+
// for exactly the deltas that most need it — a new schema pointing at a
|
|
165
|
+
// second one the author forgot to bring. Iterated as a SNAPSHOT: this loop
|
|
166
|
+
// empties `parked` as it goes and `visitRef` deletes from the same Map below,
|
|
167
|
+
// and neither may read it live while writing it.
|
|
168
|
+
if (componentsAreTheDelta) {
|
|
169
|
+
const promotions = [...parked.entries()];
|
|
170
|
+
for (const [id, entry] of promotions) {
|
|
171
|
+
parked.delete(id);
|
|
172
|
+
copies.push(entry);
|
|
173
|
+
copied.add(id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// The reachability fixpoint: refs out of written content and out of every
|
|
177
|
+
// copy — classification copies and any promotions now, parked promotions as
|
|
178
|
+
// they join.
|
|
179
|
+
const queue = [
|
|
180
|
+
...written.map((w) => ({ node: w.value, from: w.from })),
|
|
181
|
+
...copies.map((c) => ({ node: c.value, from: `components/${c.kind}/${c.name}` })),
|
|
182
|
+
];
|
|
183
|
+
const visited = new Set();
|
|
184
|
+
const visitRef = (ref, from) => {
|
|
185
|
+
if (!ref.startsWith("#/"))
|
|
186
|
+
return;
|
|
187
|
+
const match = /^#\/components\/([^/]+)\/([^/]+)(?:\/|$)/.exec(ref);
|
|
188
|
+
if (!match) {
|
|
189
|
+
if (!resolvePointer(featPlain, ref).found && !resolvePointer(livingPlain, ref).found) {
|
|
190
|
+
unresolved.push({ ref, from });
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const key = `${match[1]}/${match[2]}`;
|
|
195
|
+
if (visited.has(key))
|
|
196
|
+
return;
|
|
197
|
+
visited.add(key);
|
|
198
|
+
// Already copied by verdict: its own refs are in the queue from the start.
|
|
199
|
+
if (copied.has(key))
|
|
200
|
+
return;
|
|
201
|
+
const parkedEntry = parked.get(key);
|
|
202
|
+
if (parkedEntry !== undefined) {
|
|
203
|
+
parked.delete(key);
|
|
204
|
+
copies.push(parkedEntry);
|
|
205
|
+
copied.add(key);
|
|
206
|
+
queue.push({ node: parkedEntry.value, from: `components/${key}` });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Not copied and not new: a quoted or unchanged declaration, or something
|
|
210
|
+
// the feature never spells. Either way the LIVING document must have it.
|
|
211
|
+
if (!resolvePointer(livingPlain, `#/components/${key}`).found) {
|
|
212
|
+
unresolved.push({ ref, from });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
while (queue.length > 0) {
|
|
216
|
+
const { node, from } = queue.pop();
|
|
217
|
+
for (const ref of collectRefs(node))
|
|
218
|
+
visitRef(ref, from);
|
|
219
|
+
}
|
|
220
|
+
for (const { kind, name, value } of copies) {
|
|
221
|
+
// Decided at the write, over the copies that actually happened: a promoted
|
|
222
|
+
// component is an addition, a parked one that nothing reached is not.
|
|
223
|
+
if (absentFromLiving.has(`${kind}/${name}`))
|
|
224
|
+
componentsAdded.push(`${kind}/${name}`);
|
|
225
|
+
// The value is deep-stripped of loam's bookkeeping keys — a component
|
|
226
|
+
// holding an operation shape nests them where the path-level strip never
|
|
227
|
+
// walks, and `--approve` once published `x-loam-remove: true` into a
|
|
228
|
+
// living component. Promoted copies go through this same write, so a
|
|
229
|
+
// components-only delta cannot be the one shape that publishes a marker.
|
|
230
|
+
setComponentValue(living, kind, name, withoutFeatureKeysDeep(value));
|
|
231
|
+
}
|
|
232
|
+
return { componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved };
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `$ref` as a pair of plain questions — which ones does this tree contain, and\n * what does one point at — and the component closure the merge answers them\n * for.\n *\n * The closure is verdict-driven, not reachability-driven: every component the\n * feature DECLARES is classified against its `x-loam-baselines` entry exactly\n * as an operation is classified against its pin, and reachability decides only\n * one thing — whether a genuinely NEW component rides along — whenever the\n * delta merges paths at all. The old rule, \"copy everything reachable from any\n * feature path item\", let a QUOTED operation drag its authoring-time component\n * copy over someone else's landed change; the walk now starts from what this\n * merge actually WROTE, so a quote pulls nothing.\n *\n * Where the delta merges no path, that walk starts from nothing, and \"is a new\n * component reachable?\" has no meaningful answer to give: a components-only\n * delta declared every one of them on purpose and there is no operation for\n * them to hide behind. Parking them forever is what made such a delta merge\n * nothing at all, at exit 0 — `componentsAreTheDelta` is that case, and only\n * that case.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { isMap, isScalar, type Document } from \"yaml\";\nimport { classifyBaselineDigests, valueDigest, withoutFeatureKeysDeep } from \"../digest.js\";\nimport { entryFor, restatedSurfaces, surfaceIn, type BaselineRecord } from \"../baseline/record.js\";\n\nexport function collectRefs(node: unknown): string[] {\n const out: string[] = [];\n const walk = (current: unknown): void => {\n if (Array.isArray(current)) {\n for (const value of current) walk(value);\n return;\n }\n if (current !== null && typeof current === \"object\") {\n for (const [key, value] of Object.entries(current)) {\n if (key === \"$ref\" && typeof value === \"string\") out.push(value);\n else walk(value);\n }\n }\n };\n walk(node);\n return out;\n}\n\n/** Resolve a local JSON pointer (`#/a/b~1c`) against a plain JS tree. */\nexport function resolvePointer(root: unknown, ref: string): { found: boolean; value: unknown } {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) {\n return { found: false, value: undefined };\n }\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return { found: false, value: undefined };\n }\n }\n return { found: true, value: current };\n}\n\n/** Everything the closure needs; one object because the merge is its only caller. */\nexport interface ComponentClosureInput {\n /** The living document the copies are written into. */\n living: Document;\n featPlain: unknown;\n livingPlain: unknown;\n /** The feature's `x-loam-baselines` record, read once by the merge. */\n record: BaselineRecord;\n /** The plain values the path merge actually wrote, labelled for the ref sweep. */\n written: Array<{ from: string; value: unknown }>;\n /**\n * True only when the feature's `paths` holds no entry at all, so `written` is\n * empty and nothing this merge wrote can reach a new component. The merge\n * computes it from the path ENTRIES, not from what the path loop happened to\n * write: one path entry, even an entirely quoted one, means the reachability\n * question is real and this stays false. Widening it past that would copy the\n * authoring-time component a quoted operation carries — the exact revert the\n * verdict-driven closure exists to stop.\n */\n componentsAreTheDelta: boolean;\n}\n\n/** What the closure computed: components overwritten, added, quoted, stale, and refs nothing resolves. */\nexport interface ComponentClosureOutcome {\n componentsModified: string[];\n /** `<kind>/<name>` of components written where the living contract had none. */\n componentsAdded: string[];\n componentsQuoted: string[];\n componentsStale: string[];\n unresolved: Array<{ ref: string; from: string }>;\n}\n\n/**\n * Merge the feature's components into the living document by verdict:\n *\n * - quote → skipped; the living copy stays, whatever landed on it since.\n * - edit, stale (under --approve), or unpinned-and-differing → copied; the\n * unpinned copy is the old upsert, kept for never-rebased deltas.\n * - unpinned and living-equal → skipped, as it always was: nothing to write.\n * - unfounded (pinned, living gone — the gate refused; --approve) → copied,\n * because a written operation may reference it and the merge must never\n * leave a dangling ref behind.\n * - NEW (unpinned, living-absent) → copied only when reachable from written\n * content or from another copied component's own refs. The visited-set\n * fixpoint is what keeps A↔B cycles terminating and unrelated namespaces\n * uncopied. When the components ARE the delta there is no written content\n * to be reachable from, so every one of them is copied outright.\n *\n * The unresolved sweep is unchanged in meaning: every local ref reachable from\n * written content and copied components must resolve in the copy set or the\n * living document.\n */\n/**\n * Write one component's value, matching the existing key AS A STRING: a\n * living `404:` response is the YAML number 404, and a plain `setIn` with the\n * string \"404\" misses it and APPENDS a second pair — the contract then\n * declares the component twice, forever, with the pre-merge copy first in\n * reading order. Exported for the strip's twin write on the feature side.\n */\nexport function setComponentValue(doc: Document, kind: string, name: string, value: unknown): void {\n const kindNode = doc.getIn([\"components\", kind]);\n const existing = isMap(kindNode)\n ? kindNode.items.find((it) => isScalar(it.key) && String(it.key.value) === name)\n : undefined;\n if (existing !== undefined) existing.value = doc.createNode(value);\n else doc.setIn([\"components\", kind, name], value);\n}\n\nexport function mergeComponentClosure(input: ComponentClosureInput): ComponentClosureOutcome {\n const { living, featPlain, livingPlain, record, written, componentsAreTheDelta } = input;\n const componentsModified: string[] = [];\n const componentsAdded: string[] = [];\n const componentsQuoted: string[] = [];\n const componentsStale: string[] = [];\n const unresolved: ComponentClosureOutcome[\"unresolved\"] = [];\n const copies: Array<{ kind: string; name: string; value: unknown }> = [];\n const copied = new Set<string>();\n /** Genuinely new components, parked until something written reaches them. */\n const parked = new Map<string, { kind: string; name: string; value: unknown }>();\n /**\n * Which declared surfaces the living contract does not have — recorded here,\n * during classification, because this is the one place that asks. What was\n * ADDED is decided at the write below instead: a parked component the\n * fixpoint never reaches is absent from living and stays that way, and\n * calling it added would name a component the merge did not write.\n */\n const absentFromLiving = new Set<string>();\n\n // ONE enumeration (baseline/record.ts) with the rebase plan and the gate —\n // and ONE living lookup — so \"which components does this delta declare, and\n // do they exist\" cannot drift between the command that pins, the gate that\n // grades and this closure that writes.\n for (const surface of restatedSurfaces(featPlain)) {\n if (surface.kind !== \"component\") continue;\n const cut = surface.id.indexOf(\"/\");\n const entry = { kind: surface.id.slice(0, cut), name: surface.id.slice(cut + 1), value: surface.value };\n const inLiving = surfaceIn(livingPlain, surface);\n if (!inLiving.found) absentFromLiving.add(surface.id);\n const verdict = classifyBaselineDigests(\n entryFor(record, surface),\n valueDigest(surface.value),\n inLiving.found ? valueDigest(inLiving.value) : undefined,\n );\n if (verdict === \"quote\") {\n componentsQuoted.push(surface.id);\n continue;\n }\n if (verdict === \"unpinned\") {\n if (!inLiving.found) {\n parked.set(surface.id, entry);\n continue;\n }\n if (isDeepStrictEqual(inLiving.value, surface.value)) continue;\n componentsModified.push(surface.id);\n } else {\n if (verdict === \"stale\") componentsStale.push(surface.id);\n if (inLiving.found && !isDeepStrictEqual(inLiving.value, surface.value)) componentsModified.push(surface.id);\n }\n copies.push(entry);\n copied.add(surface.id);\n }\n\n // A components-only delta wrote no path, so the fixpoint below would start\n // from an empty queue and every parked component would stay parked forever —\n // the merge published a document byte-identical to the living one and archive\n // said `ok`. The promotion happens HERE, before the queue is built, and that\n // order is the whole point: a component promoted afterwards would carry its\n // own `$ref`s past the sweep, and `openapi.ref-unresolved` would stop firing\n // for exactly the deltas that most need it — a new schema pointing at a\n // second one the author forgot to bring. Iterated as a SNAPSHOT: this loop\n // empties `parked` as it goes and `visitRef` deletes from the same Map below,\n // and neither may read it live while writing it.\n if (componentsAreTheDelta) {\n const promotions = [...parked.entries()];\n for (const [id, entry] of promotions) {\n parked.delete(id);\n copies.push(entry);\n copied.add(id);\n }\n }\n\n // The reachability fixpoint: refs out of written content and out of every\n // copy — classification copies and any promotions now, parked promotions as\n // they join.\n const queue: Array<{ node: unknown; from: string }> = [\n ...written.map((w) => ({ node: w.value, from: w.from })),\n ...copies.map((c) => ({ node: c.value, from: `components/${c.kind}/${c.name}` })),\n ];\n const visited = new Set<string>();\n const visitRef = (ref: string, from: string): void => {\n if (!ref.startsWith(\"#/\")) return;\n const match = /^#\\/components\\/([^/]+)\\/([^/]+)(?:\\/|$)/.exec(ref);\n if (!match) {\n if (!resolvePointer(featPlain, ref).found && !resolvePointer(livingPlain, ref).found) {\n unresolved.push({ ref, from });\n }\n return;\n }\n const key = `${match[1]!}/${match[2]!}`;\n if (visited.has(key)) return;\n visited.add(key);\n // Already copied by verdict: its own refs are in the queue from the start.\n if (copied.has(key)) return;\n const parkedEntry = parked.get(key);\n if (parkedEntry !== undefined) {\n parked.delete(key);\n copies.push(parkedEntry);\n copied.add(key);\n queue.push({ node: parkedEntry.value, from: `components/${key}` });\n return;\n }\n // Not copied and not new: a quoted or unchanged declaration, or something\n // the feature never spells. Either way the LIVING document must have it.\n if (!resolvePointer(livingPlain, `#/components/${key}`).found) {\n unresolved.push({ ref, from });\n }\n };\n while (queue.length > 0) {\n const { node, from } = queue.pop()!;\n for (const ref of collectRefs(node)) visitRef(ref, from);\n }\n\n for (const { kind, name, value } of copies) {\n // Decided at the write, over the copies that actually happened: a promoted\n // component is an addition, a parked one that nothing reached is not.\n if (absentFromLiving.has(`${kind}/${name}`)) componentsAdded.push(`${kind}/${name}`);\n // The value is deep-stripped of loam's bookkeeping keys — a component\n // holding an operation shape nests them where the path-level strip never\n // walks, and `--approve` once published `x-loam-remove: true` into a\n // living component. Promoted copies go through this same write, so a\n // components-only delta cannot be the one shape that publishes a marker.\n setComponentValue(living, kind, name, withoutFeatureKeysDeep(value));\n }\n\n return { componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved };\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one refusal every stage of the contract delta path raises: a document the
|
|
3
|
+
* planner cannot read, named by which side it came from.
|
|
4
|
+
*
|
|
5
|
+
* It is the package's leaf for the same reason the pin, the markers and the
|
|
6
|
+
* merge each throw it: whichever of them fails, the command above has to map
|
|
7
|
+
* one domain error onto its own envelope. A module that owned the error would
|
|
8
|
+
* be imported by its two siblings for that reason alone.
|
|
9
|
+
*/
|
|
10
|
+
// Every `isRecord` below asks one question of a resolved plain tree: is this a
|
|
11
|
+
// node that can hold OpenAPI keys — an object, not an array?
|
|
12
|
+
/** A document the merge planner cannot read. Commands map this domain error to their own envelope. */
|
|
13
|
+
export class OpenapiMergeError extends Error {
|
|
14
|
+
source;
|
|
15
|
+
service;
|
|
16
|
+
name = "OpenapiMergeError";
|
|
17
|
+
constructor(source, service, detail) {
|
|
18
|
+
super(`${source} openapi for ${service} is not valid YAML: ${detail}`);
|
|
19
|
+
this.source = source;
|
|
20
|
+
this.service = service;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function errorMessage(error) {
|
|
24
|
+
return error instanceof Error ? error.message : String(error);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/error.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,+EAA+E;AAC/E,6DAA6D;AAI7D,sGAAsG;AACtG,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAI/B,MAAM;IACN,OAAO;IAJA,IAAI,GAAG,mBAAmB,CAAC;IAE7C,YACW,MAA0B,EAC1B,OAAe,EACxB,MAAc;QAEd,KAAK,CAAC,GAAG,MAAM,gBAAgB,OAAO,uBAAuB,MAAM,EAAE,CAAC,CAAC;sBAJ9D,MAAM;uBACN,OAAO;IAIlB,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC","sourcesContent":["/**\n * The one refusal every stage of the contract delta path raises: a document the\n * planner cannot read, named by which side it came from.\n *\n * It is the package's leaf for the same reason the pin, the markers and the\n * merge each throw it: whichever of them fails, the command above has to map\n * one domain error onto its own envelope. A module that owned the error would\n * be imported by its two siblings for that reason alone.\n */\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\n\nexport type OpenapiMergeSource = \"feature\" | \"living\";\n\n/** A document the merge planner cannot read. Commands map this domain error to their own envelope. */\nexport class OpenapiMergeError extends Error {\n override readonly name = \"OpenapiMergeError\";\n\n constructor(\n readonly source: OpenapiMergeSource,\n readonly service: string,\n detail: string,\n ) {\n super(`${source} openapi for ${service} is not valid YAML: ${detail}`);\n }\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keys loam adds to a feature's contract that must never reach the living
|
|
3
|
+
* one: `x-loam-remove`, `x-loam-based-on`, and the root `x-loam-baselines`
|
|
4
|
+
* record.
|
|
5
|
+
*
|
|
6
|
+
* They are gathered here rather than spread through the merge because they are
|
|
7
|
+
* the vocabulary both ends of the delta path share — `stripOpenapiRemovalMarkers`
|
|
8
|
+
* takes them out for the projection a service repository sees, and the merge
|
|
9
|
+
* takes them out again on the way into the living document. Two spellings of
|
|
10
|
+
* "which keys are feature-only" is how one of the two starts leaking a marker.
|
|
11
|
+
*/
|
|
12
|
+
import { isDeepStrictEqual } from "node:util";
|
|
13
|
+
import { isMap, isScalar, parseDocument } from "yaml";
|
|
14
|
+
// Every `isRecord` below asks one question of a resolved plain tree: is this a
|
|
15
|
+
// node that can hold OpenAPI keys — an object, not an array?
|
|
16
|
+
import { isRecord } from "../../kernel/records.js";
|
|
17
|
+
import { HTTP_METHODS } from "../doc.js";
|
|
18
|
+
import { FEATURE_ONLY_KEYS, isRemoval, operationBaselineOf, withoutFeatureKeysDeep, withoutOperationBaseline, OPENAPI_BASELINE_KEY, OPENAPI_BASELINES_KEY, } from "../digest.js";
|
|
19
|
+
import { setComponentValue } from "./components.js";
|
|
20
|
+
import { errorMessage, OpenapiMergeError } from "./error.js";
|
|
21
|
+
/**
|
|
22
|
+
* Remove feature-only operation-removal markers — and the root
|
|
23
|
+
* `x-loam-baselines` record — before a feature contract is used to create a
|
|
24
|
+
* brand-new living document. Normally coherence refuses the removal shape
|
|
25
|
+
* because there cannot be a removal target; this guard also keeps `--approve`
|
|
26
|
+
* from ever persisting a marker into living docs. The record needs the strip
|
|
27
|
+
* on THIS path even more than the pins do: archive's create branch publishes
|
|
28
|
+
* this function's output verbatim, and a components-only delta has nothing
|
|
29
|
+
* strippable inside `paths` at all — the root key must not ride out on the
|
|
30
|
+
* early returns below.
|
|
31
|
+
*/
|
|
32
|
+
export function stripOpenapiRemovalMarkers(featureText, service) {
|
|
33
|
+
const feature = parseDocument(featureText);
|
|
34
|
+
if (feature.errors.length > 0) {
|
|
35
|
+
throw new OpenapiMergeError("feature", service, feature.errors[0].message);
|
|
36
|
+
}
|
|
37
|
+
let plain;
|
|
38
|
+
try {
|
|
39
|
+
plain = feature.toJS() ?? {};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new OpenapiMergeError("feature", service, errorMessage(error));
|
|
43
|
+
}
|
|
44
|
+
const hasRecord = isRecord(plain) && OPENAPI_BASELINES_KEY in plain;
|
|
45
|
+
// Shape from the RESOLVED tree, for the same reason the merge reads it there:
|
|
46
|
+
// an aliased `paths` or path item is not a map node, and treating it as
|
|
47
|
+
// "nothing to strip" is how a feature-only marker reached a living contract.
|
|
48
|
+
const paths = plainChild(plain, "paths");
|
|
49
|
+
if (!isRecord(paths)) {
|
|
50
|
+
if (!hasRecord)
|
|
51
|
+
return featureText;
|
|
52
|
+
feature.deleteIn([OPENAPI_BASELINES_KEY]);
|
|
53
|
+
return serialize(feature, service);
|
|
54
|
+
}
|
|
55
|
+
const cleaned = {};
|
|
56
|
+
let stripped = false;
|
|
57
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
58
|
+
const kept = withoutFeatureMarkers(item);
|
|
59
|
+
if (!isDeepStrictEqual(kept, item))
|
|
60
|
+
stripped = true;
|
|
61
|
+
// A path the strip emptied goes with its last operation: `\/x: {}` is a
|
|
62
|
+
// path the contract advertises and nothing answers.
|
|
63
|
+
if (kept !== undefined)
|
|
64
|
+
cleaned[path] = kept;
|
|
65
|
+
}
|
|
66
|
+
// Component values get the same discipline as path items: a component
|
|
67
|
+
// holding an operation shape (a `pathItems` component, a callback) nests
|
|
68
|
+
// the feature-only keys where the paths walk never looks, and the create
|
|
69
|
+
// branch publishes this function's output verbatim. The write is
|
|
70
|
+
// string-key-matched (setComponentValue) so a numeric component name is
|
|
71
|
+
// replaced, never duplicated.
|
|
72
|
+
const comps = plainChild(plain, "components");
|
|
73
|
+
if (isRecord(comps)) {
|
|
74
|
+
for (const [kind, names] of Object.entries(comps)) {
|
|
75
|
+
if (!isRecord(names))
|
|
76
|
+
continue;
|
|
77
|
+
for (const [name, value] of Object.entries(names)) {
|
|
78
|
+
const kept = withoutFeatureKeysDeep(value);
|
|
79
|
+
if (kept !== value) {
|
|
80
|
+
setComponentValue(feature, kind, name, kept);
|
|
81
|
+
stripped = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!stripped) {
|
|
87
|
+
if (!hasRecord)
|
|
88
|
+
return featureText;
|
|
89
|
+
feature.deleteIn([OPENAPI_BASELINES_KEY]);
|
|
90
|
+
return serialize(feature, service);
|
|
91
|
+
}
|
|
92
|
+
// Edit the AST in place when every node the strip touches IS a node — the
|
|
93
|
+
// author's comments, key order and formatting are theirs to keep. An alias
|
|
94
|
+
// cannot be edited in place (there is one shared value behind it, and
|
|
95
|
+
// deleting a key through it would change every use), so those documents get
|
|
96
|
+
// their `paths` rewritten from the resolved tree instead: losing formatting
|
|
97
|
+
// is a cost, shipping the marker is a corruption.
|
|
98
|
+
//
|
|
99
|
+
// The test has to reach the OPERATION nodes too, not just the path items. The
|
|
100
|
+
// in-place branch strips a surviving operation's pin through
|
|
101
|
+
// `["paths", p, m, x-loam-based-on]`, and an aliased operation is exactly one
|
|
102
|
+
// level below where the check used to look — so `deleteIn` walked into a node
|
|
103
|
+
// that is not a collection and threw out of archive as `internal`, on a
|
|
104
|
+
// document whose only fault was writing one operation twice.
|
|
105
|
+
//
|
|
106
|
+
// What disqualifies a method is "there is something here to strip and I cannot
|
|
107
|
+
// reach it", not "this is not a map node". Only a resolved MAPPING can be a
|
|
108
|
+
// removal marker or carry a pin, so a method spelled as a scalar — `get: ~`,
|
|
109
|
+
// or a string — is nothing the in-place branch would have touched; demanding a
|
|
110
|
+
// map node of it too demoted whole documents to the rewrite branch and cost
|
|
111
|
+
// their author every comment, anchor and hand-chosen key order inside `paths`,
|
|
112
|
+
// in the bytes archive installs as the living contract. What is left is the
|
|
113
|
+
// real hazard: a mapping the AST does not hand back as a map. An alias is the
|
|
114
|
+
// one node kind that resolves to a mapping without being one, and a method
|
|
115
|
+
// whose KEY is an alias is not findable by name at all, so `deleteIn` reports
|
|
116
|
+
// nothing deleted and the marker ships in silence. Both answer `undefined` or
|
|
117
|
+
// a non-map here, and both belong on the safe branch.
|
|
118
|
+
const editable = isMap(feature.get("paths")) &&
|
|
119
|
+
Object.entries(paths).every(([p, item]) => isMap(feature.getIn(["paths", p])) &&
|
|
120
|
+
(!isRecord(item) ||
|
|
121
|
+
Object.entries(item).every(([m, op]) => !HTTP_METHODS.has(m) || !isRecord(op) || isMap(feature.getIn(["paths", p, m])))));
|
|
122
|
+
if (!editable) {
|
|
123
|
+
feature.setIn(["paths"], cleaned);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
127
|
+
if (!isRecord(item))
|
|
128
|
+
continue;
|
|
129
|
+
for (const [m, value] of Object.entries(item)) {
|
|
130
|
+
// A loam marker at PATH level, deleted here for the same reason the
|
|
131
|
+
// resolved-tree branch drops it: the two branches must agree exactly
|
|
132
|
+
// about what a living contract may carry.
|
|
133
|
+
if (FEATURE_ONLY_KEYS.has(m))
|
|
134
|
+
feature.deleteIn(["paths", path, m]);
|
|
135
|
+
if (!HTTP_METHODS.has(m))
|
|
136
|
+
continue;
|
|
137
|
+
if (isRemoval(value))
|
|
138
|
+
feature.deleteIn(["paths", path, m]);
|
|
139
|
+
// The surviving operations keep their formatting but lose their pin:
|
|
140
|
+
// the in-place branch must strip everything the resolved-tree branch
|
|
141
|
+
// does, or the two disagree about what a living contract may carry.
|
|
142
|
+
else if (operationBaselineOf(value) !== undefined) {
|
|
143
|
+
feature.deleteIn(["paths", path, m, OPENAPI_BASELINE_KEY]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const remaining = feature.getIn(["paths", path]);
|
|
147
|
+
// The merge's twin: no OPERATIONS left means the path goes, surviving
|
|
148
|
+
// path-level keys included — `{}` was only the easy shape of dead.
|
|
149
|
+
if (isMap(remaining) && !remaining.items.some((it) => isScalar(it.key) && HTTP_METHODS.has(String(it.key.value)))) {
|
|
150
|
+
feature.deleteIn(["paths", path]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// The root record goes on BOTH serialization branches: it is feature
|
|
155
|
+
// bookkeeping exactly like the pins, and the rewrite branch above replaced
|
|
156
|
+
// only `paths`, so the root key survives either way without this.
|
|
157
|
+
if (hasRecord)
|
|
158
|
+
feature.deleteIn([OPENAPI_BASELINES_KEY]);
|
|
159
|
+
return serialize(feature, service);
|
|
160
|
+
}
|
|
161
|
+
/** One spelling of "hand the document back", so every strip branch fails the same way. */
|
|
162
|
+
function serialize(feature, service) {
|
|
163
|
+
try {
|
|
164
|
+
return feature.toString();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
throw new OpenapiMergeError("feature", service, errorMessage(error));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function opLabel(before, after, method, path) {
|
|
171
|
+
const operation = operationIdOf(after) ?? operationIdOf(before);
|
|
172
|
+
return operation !== undefined ? `'${operation}' (${method} ${path})` : `${method} ${path}`;
|
|
173
|
+
}
|
|
174
|
+
/** Read an operationId from a YAML or plain operation node. */
|
|
175
|
+
export function operationIdOf(node) {
|
|
176
|
+
const value = isMap(node)
|
|
177
|
+
? node.get("operationId")
|
|
178
|
+
: node !== null && typeof node === "object"
|
|
179
|
+
? node["operationId"]
|
|
180
|
+
: undefined;
|
|
181
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Copy a path item without anything that belongs to the FEATURE rather than to
|
|
185
|
+
* the contract: removal-marker operations, the `x-loam-based-on` pins on the
|
|
186
|
+
* operations that survive, and a loam marker written at PATH level. All three
|
|
187
|
+
* are instructions to loam, and a living contract that carries any of them is
|
|
188
|
+
* lying to the next reader — and, for the pin, to the next feature's baseline.
|
|
189
|
+
*
|
|
190
|
+
* The path-level case used to fall through: the filter guarded only HTTP
|
|
191
|
+
* methods, so `x-loam-remove: true` beside the methods was copied verbatim into
|
|
192
|
+
* the fleet's living contract, where it also kept the empty-path cleanup from
|
|
193
|
+
* ever seeing an empty path.
|
|
194
|
+
*/
|
|
195
|
+
export function withoutFeatureMarkers(node) {
|
|
196
|
+
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
197
|
+
return node;
|
|
198
|
+
const entries = Object.entries(node)
|
|
199
|
+
.filter(([key, value]) => !FEATURE_ONLY_KEYS.has(key) && !(HTTP_METHODS.has(key) && isRemoval(value)))
|
|
200
|
+
.map(([key, value]) => [key, HTTP_METHODS.has(key) ? withoutOperationBaseline(value) : value]);
|
|
201
|
+
return entries.length === 0 ? undefined : Object.fromEntries(entries);
|
|
202
|
+
}
|
|
203
|
+
export function plainChild(parent, key) {
|
|
204
|
+
return parent !== null && typeof parent === "object"
|
|
205
|
+
? parent[key]
|
|
206
|
+
: undefined;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=markers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markers.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/markers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The keys loam adds to a feature's contract that must never reach the living\n * one: `x-loam-remove`, `x-loam-based-on`, and the root `x-loam-baselines`\n * record.\n *\n * They are gathered here rather than spread through the merge because they are\n * the vocabulary both ends of the delta path share — `stripOpenapiRemovalMarkers`\n * takes them out for the projection a service repository sees, and the merge\n * takes them out again on the way into the living document. Two spellings of\n * \"which keys are feature-only\" is how one of the two starts leaking a marker.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { isMap, isScalar, parseDocument } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport {\n FEATURE_ONLY_KEYS,\n isRemoval,\n operationBaselineOf,\n withoutFeatureKeysDeep,\n withoutOperationBaseline,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n} from \"../digest.js\";\nimport { setComponentValue } from \"./components.js\";\nimport { errorMessage, OpenapiMergeError } from \"./error.js\";\n\n/**\n * Remove feature-only operation-removal markers — and the root\n * `x-loam-baselines` record — before a feature contract is used to create a\n * brand-new living document. Normally coherence refuses the removal shape\n * because there cannot be a removal target; this guard also keeps `--approve`\n * from ever persisting a marker into living docs. The record needs the strip\n * on THIS path even more than the pins do: archive's create branch publishes\n * this function's output verbatim, and a components-only delta has nothing\n * strippable inside `paths` at all — the root key must not ride out on the\n * early returns below.\n */\nexport function stripOpenapiRemovalMarkers(featureText: string, service: string): string {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let plain: unknown;\n try {\n plain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n const hasRecord = isRecord(plain) && OPENAPI_BASELINES_KEY in plain;\n // Shape from the RESOLVED tree, for the same reason the merge reads it there:\n // an aliased `paths` or path item is not a map node, and treating it as\n // \"nothing to strip\" is how a feature-only marker reached a living contract.\n const paths = plainChild(plain, \"paths\");\n if (!isRecord(paths)) {\n if (!hasRecord) return featureText;\n feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return serialize(feature, service);\n }\n\n const cleaned: Record<string, unknown> = {};\n let stripped = false;\n for (const [path, item] of Object.entries(paths)) {\n const kept = withoutFeatureMarkers(item);\n if (!isDeepStrictEqual(kept, item)) stripped = true;\n // A path the strip emptied goes with its last operation: `\\/x: {}` is a\n // path the contract advertises and nothing answers.\n if (kept !== undefined) cleaned[path] = kept;\n }\n // Component values get the same discipline as path items: a component\n // holding an operation shape (a `pathItems` component, a callback) nests\n // the feature-only keys where the paths walk never looks, and the create\n // branch publishes this function's output verbatim. The write is\n // string-key-matched (setComponentValue) so a numeric component name is\n // replaced, never duplicated.\n const comps = plainChild(plain, \"components\");\n if (isRecord(comps)) {\n for (const [kind, names] of Object.entries(comps)) {\n if (!isRecord(names)) continue;\n for (const [name, value] of Object.entries(names)) {\n const kept = withoutFeatureKeysDeep(value);\n if (kept !== value) {\n setComponentValue(feature, kind, name, kept);\n stripped = true;\n }\n }\n }\n }\n\n if (!stripped) {\n if (!hasRecord) return featureText;\n feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return serialize(feature, service);\n }\n\n // Edit the AST in place when every node the strip touches IS a node — the\n // author's comments, key order and formatting are theirs to keep. An alias\n // cannot be edited in place (there is one shared value behind it, and\n // deleting a key through it would change every use), so those documents get\n // their `paths` rewritten from the resolved tree instead: losing formatting\n // is a cost, shipping the marker is a corruption.\n //\n // The test has to reach the OPERATION nodes too, not just the path items. The\n // in-place branch strips a surviving operation's pin through\n // `[\"paths\", p, m, x-loam-based-on]`, and an aliased operation is exactly one\n // level below where the check used to look — so `deleteIn` walked into a node\n // that is not a collection and threw out of archive as `internal`, on a\n // document whose only fault was writing one operation twice.\n //\n // What disqualifies a method is \"there is something here to strip and I cannot\n // reach it\", not \"this is not a map node\". Only a resolved MAPPING can be a\n // removal marker or carry a pin, so a method spelled as a scalar — `get: ~`,\n // or a string — is nothing the in-place branch would have touched; demanding a\n // map node of it too demoted whole documents to the rewrite branch and cost\n // their author every comment, anchor and hand-chosen key order inside `paths`,\n // in the bytes archive installs as the living contract. What is left is the\n // real hazard: a mapping the AST does not hand back as a map. An alias is the\n // one node kind that resolves to a mapping without being one, and a method\n // whose KEY is an alias is not findable by name at all, so `deleteIn` reports\n // nothing deleted and the marker ships in silence. Both answer `undefined` or\n // a non-map here, and both belong on the safe branch.\n const editable =\n isMap(feature.get(\"paths\")) &&\n Object.entries(paths).every(\n ([p, item]) =>\n isMap(feature.getIn([\"paths\", p])) &&\n (!isRecord(item) ||\n Object.entries(item).every(\n ([m, op]) => !HTTP_METHODS.has(m) || !isRecord(op) || isMap(feature.getIn([\"paths\", p, m])),\n )),\n );\n if (!editable) {\n feature.setIn([\"paths\"], cleaned);\n } else {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [m, value] of Object.entries(item)) {\n // A loam marker at PATH level, deleted here for the same reason the\n // resolved-tree branch drops it: the two branches must agree exactly\n // about what a living contract may carry.\n if (FEATURE_ONLY_KEYS.has(m)) feature.deleteIn([\"paths\", path, m]);\n if (!HTTP_METHODS.has(m)) continue;\n if (isRemoval(value)) feature.deleteIn([\"paths\", path, m]);\n // The surviving operations keep their formatting but lose their pin:\n // the in-place branch must strip everything the resolved-tree branch\n // does, or the two disagree about what a living contract may carry.\n else if (operationBaselineOf(value) !== undefined) {\n feature.deleteIn([\"paths\", path, m, OPENAPI_BASELINE_KEY]);\n }\n }\n const remaining = feature.getIn([\"paths\", path]);\n // The merge's twin: no OPERATIONS left means the path goes, surviving\n // path-level keys included — `{}` was only the easy shape of dead.\n if (isMap(remaining) && !remaining.items.some((it) => isScalar(it.key) && HTTP_METHODS.has(String(it.key.value)))) {\n feature.deleteIn([\"paths\", path]);\n }\n }\n }\n // The root record goes on BOTH serialization branches: it is feature\n // bookkeeping exactly like the pins, and the rewrite branch above replaced\n // only `paths`, so the root key survives either way without this.\n if (hasRecord) feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return serialize(feature, service);\n}\n\n/** One spelling of \"hand the document back\", so every strip branch fails the same way. */\nfunction serialize(feature: ReturnType<typeof parseDocument>, service: string): string {\n try {\n return feature.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\nexport function opLabel(before: unknown, after: unknown, method: string, path: string): string {\n const operation = operationIdOf(after) ?? operationIdOf(before);\n return operation !== undefined ? `'${operation}' (${method} ${path})` : `${method} ${path}`;\n}\n\n/** Read an operationId from a YAML or plain operation node. */\nexport function operationIdOf(node: unknown): string | undefined {\n const value = isMap(node)\n ? node.get(\"operationId\")\n : node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)[\"operationId\"]\n : undefined;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n\n\n/**\n * Copy a path item without anything that belongs to the FEATURE rather than to\n * the contract: removal-marker operations, the `x-loam-based-on` pins on the\n * operations that survive, and a loam marker written at PATH level. All three\n * are instructions to loam, and a living contract that carries any of them is\n * lying to the next reader — and, for the pin, to the next feature's baseline.\n *\n * The path-level case used to fall through: the filter guarded only HTTP\n * methods, so `x-loam-remove: true` beside the methods was copied verbatim into\n * the fleet's living contract, where it also kept the empty-path cleanup from\n * ever seeing an empty path.\n */\nexport function withoutFeatureMarkers(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([key, value]) => !FEATURE_ONLY_KEYS.has(key) && !(HTTP_METHODS.has(key) && isRemoval(value)))\n .map(([key, value]) => [key, HTTP_METHODS.has(key) ? withoutOperationBaseline(value) : value] as const);\n return entries.length === 0 ? undefined : Object.fromEntries(entries);\n}\n\nexport function plainChild(parent: unknown, key: string): unknown {\n return parent !== null && typeof parent === \"object\"\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n}\n"]}
|