@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2635 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +599 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +252 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +204 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +270 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +337 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +139 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +174 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +287 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +137 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +210 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +277 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +197 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +72 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +58 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which targets a base git ref puts in scope — the narrowing behind
|
|
3
|
+
* `loam validate --all --base <ref>`.
|
|
4
|
+
*
|
|
5
|
+
* The defect this exists for: a partly-adopted fleet has no CI setting that is
|
|
6
|
+
* both passing today and tightening over time. `--all` is green over eleven
|
|
7
|
+
* undocumented services (they warn, and warnings do not gate) and `--strict` is
|
|
8
|
+
* red from the first minute of adoption until the last boundary is written, so
|
|
9
|
+
* a team installs either a gate that never notices adoption stalling at 8% or
|
|
10
|
+
* one it cannot turn on. `--base` is the third setting: grade what this branch
|
|
11
|
+
* TOUCHED, at full `--strict` severity if the caller wants it, and let the
|
|
12
|
+
* untouched majority stay untouched.
|
|
13
|
+
*
|
|
14
|
+
* A SUB-PACKAGE of `core/diff/` rather than a sixth file in it, because that
|
|
15
|
+
* directory is at the five-file limit and because this is a different subject
|
|
16
|
+
* from the semantic diff beside it: `diff` compares two states of the fleet and
|
|
17
|
+
* reports what moved, this one only decides WHICH SUBJECTS a run should look
|
|
18
|
+
* at. Nothing here reads a document.
|
|
19
|
+
*
|
|
20
|
+
* What it does NOT do, and the line is not negotiable: a diff-scoped validate
|
|
21
|
+
* cannot see a victim boundary the change did not touch. Removing an operation
|
|
22
|
+
* from `payment-service` puts `payment-service` in scope and leaves
|
|
23
|
+
* `checkout-web` — whose living requirement still names the operation — out of
|
|
24
|
+
* it. That join is `loam diff --base`'s question and it stays `loam diff`'s;
|
|
25
|
+
* the two gates answer "did you document what you touched" and "did you break a
|
|
26
|
+
* consumer", and neither substitutes for the other. Selling a green here that
|
|
27
|
+
* hides a broken join would be worse than shipping no flag at all.
|
|
28
|
+
*/
|
|
29
|
+
import { declineDetail, gitText } from "../../provenance/git.js";
|
|
30
|
+
import { repoPath } from "../../envelope/json.js";
|
|
31
|
+
import { resolveBase } from "../base-git.js";
|
|
32
|
+
/**
|
|
33
|
+
* Every path that differs between `ref` and the WORKING TREE, docs-relative.
|
|
34
|
+
*
|
|
35
|
+
* Two questions, not one, and the second is not optional: `git diff` compares
|
|
36
|
+
* the tree git tracks, so a service adopted five minutes ago — a brand new
|
|
37
|
+
* `services/<id>/spec.md` nobody has committed — is invisible to it. `validate`
|
|
38
|
+
* grades the working tree, so the scope has to be the working tree's, and an
|
|
39
|
+
* untracked new boundary that the run then skipped would be the flag's very
|
|
40
|
+
* first user reporting that it graded nothing.
|
|
41
|
+
*
|
|
42
|
+
* `--no-renames` on purpose: with rename detection on (git's default since
|
|
43
|
+
* 2.9) a service moved into a subsystem reports only its DESTINATION, and the
|
|
44
|
+
* checks that grade the fleet from the old placement would be out of scope.
|
|
45
|
+
* Two paths for a move is the honest answer.
|
|
46
|
+
*
|
|
47
|
+
* `-z` because git quotes non-ASCII and space-bearing paths in line output —
|
|
48
|
+
* the same reason `gitTrackedFiles` asks that way, and the same phantom
|
|
49
|
+
* directory named `"src` if it did not.
|
|
50
|
+
*/
|
|
51
|
+
async function changedPaths(docsDir, commit, prefix) {
|
|
52
|
+
// Both run from docsDir. `git diff` ignores cwd and answers for the whole
|
|
53
|
+
// repository with root-relative paths; `git ls-files` restricts itself to
|
|
54
|
+
// cwd, and `--full-name` makes ITS paths root-relative too — so one prefix
|
|
55
|
+
// strip below is correct for both, exactly as `listBaseTree` needs one.
|
|
56
|
+
const tracked = await gitText(docsDir, ["diff", "--name-only", "--no-renames", "-z", commit, "--"]);
|
|
57
|
+
if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {
|
|
58
|
+
return { detail: tracked.overflowed ? "the changed-path listing exceeded the output cap." : declineDetail(tracked) };
|
|
59
|
+
}
|
|
60
|
+
const untracked = await gitText(docsDir, ["ls-files", "--others", "--exclude-standard", "--full-name", "-z"]);
|
|
61
|
+
if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {
|
|
62
|
+
return { detail: untracked.overflowed ? "the untracked-path listing exceeded the output cap." : declineDetail(untracked) };
|
|
63
|
+
}
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
for (const raw of [...tracked.out.split("\0"), ...untracked.out.split("\0")]) {
|
|
66
|
+
// A docs repo checked out as a subdirectory of a larger repository answers
|
|
67
|
+
// in the whole repository's paths; anything outside the docs dir is
|
|
68
|
+
// somebody else's change and is not a target here.
|
|
69
|
+
if (raw === "" || !raw.startsWith(prefix))
|
|
70
|
+
continue;
|
|
71
|
+
const rel = raw.slice(prefix.length);
|
|
72
|
+
if (rel !== "")
|
|
73
|
+
seen.add(rel);
|
|
74
|
+
}
|
|
75
|
+
return [...seen].sort();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Is `path` inside `dir`? Both docs-relative, forward-slashed.
|
|
79
|
+
*
|
|
80
|
+
* A prefix test on the directory plus a separator, never a split on "/" and a
|
|
81
|
+
* read of the second segment: a service filed under a subsystem lives at
|
|
82
|
+
* `services/platform/payment-service/`, not at `services/<id>/`, and a
|
|
83
|
+
* path-splitting implementation drops every filed service out of scope
|
|
84
|
+
* SILENTLY — the run stays green because it graded nothing, which is the exact
|
|
85
|
+
* failure this flag is not allowed to have. The separator is what stops
|
|
86
|
+
* `services/pay` from claiming `services/payments/spec.md`.
|
|
87
|
+
*/
|
|
88
|
+
function inside(path, dir) {
|
|
89
|
+
return dir !== "" && (path === dir || path.startsWith(`${dir}/`));
|
|
90
|
+
}
|
|
91
|
+
/** The fleet's own documents — the landscape target's subject, which owns no service directory. */
|
|
92
|
+
const ARCHITECTURE_DIR = "architecture";
|
|
93
|
+
/**
|
|
94
|
+
* Narrow a `--all` run to what changed since `ref`.
|
|
95
|
+
*
|
|
96
|
+
* Targets are matched through the ENUMERATIONS' own directories
|
|
97
|
+
* (`ServiceEntry.dir`, `FeatureEntry.dir`), turned docs-relative by the same
|
|
98
|
+
* `repoPath` the JSON contract uses. That is the whole reason the enumerations
|
|
99
|
+
* are passed in rather than re-derived: they already know where each service
|
|
100
|
+
* sits, at whatever depth, and asking them is what makes a filed fleet work.
|
|
101
|
+
*/
|
|
102
|
+
export async function scopeSince(request) {
|
|
103
|
+
const { docsDir, ref } = request;
|
|
104
|
+
const base = await resolveBase(docsDir, ref);
|
|
105
|
+
if (base.kind === "no-git") {
|
|
106
|
+
return {
|
|
107
|
+
kind: "refused",
|
|
108
|
+
code: "repository-unavailable",
|
|
109
|
+
message: `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +
|
|
110
|
+
`--base narrows the run to what changed since a ref, which it reads with read-only git questions ` +
|
|
111
|
+
`(rev-parse/diff/ls-files). Clone the docs repo with its history, then re-run — or drop --base to grade every target.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (base.kind === "no-ref") {
|
|
115
|
+
return {
|
|
116
|
+
kind: "refused",
|
|
117
|
+
code: "unknown-target",
|
|
118
|
+
message: `--base ${ref} does not resolve to a commit in the docs repo at ${docsDir}` +
|
|
119
|
+
`${base.detail === "" ? "" : ` (${base.detail})`}. ` +
|
|
120
|
+
`Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const changed = await changedPaths(docsDir, base.commit, base.prefix);
|
|
124
|
+
if (!Array.isArray(changed)) {
|
|
125
|
+
// Git resolved the ref and then would not say what moved. There is no
|
|
126
|
+
// partial answer to give: a scope built from half an answer grades a
|
|
127
|
+
// subset nobody chose and reports it as the branch's whole footprint.
|
|
128
|
+
return {
|
|
129
|
+
kind: "refused",
|
|
130
|
+
code: "repository-unavailable",
|
|
131
|
+
message: `git could not list what changed since ${ref} in the docs repo at ${docsDir} (${changed.detail}) — ` +
|
|
132
|
+
`without that list there is no honest scope, and a run over a guessed subset would report a green ` +
|
|
133
|
+
`for targets nobody graded. Re-run once git can answer, or drop --base to grade every target.`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const dirOf = (abs) => repoPath(docsDir, abs);
|
|
137
|
+
return {
|
|
138
|
+
kind: "scope",
|
|
139
|
+
ref,
|
|
140
|
+
commit: base.commit,
|
|
141
|
+
// `architecture/` is the landscape target's whole directory: the fleet map,
|
|
142
|
+
// the permission/capability/obligation vocabularies and the fleet ADRs all
|
|
143
|
+
// live there, and every one of them is graded by the landscape target
|
|
144
|
+
// rather than by any service.
|
|
145
|
+
landscape: changed.some((path) => inside(path, ARCHITECTURE_DIR)),
|
|
146
|
+
services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),
|
|
147
|
+
features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),
|
|
148
|
+
totals: { services: request.services.length, features: request.features.length },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The `scope` object the `--json` payload carries — the run saying out loud
|
|
153
|
+
* what it looked at.
|
|
154
|
+
*
|
|
155
|
+
* Additive, and emitted only under `--base`, so no consumer of today's payload
|
|
156
|
+
* moves. It exists because the alternative is a payload that is byte-shaped
|
|
157
|
+
* like a whole-fleet run and is not one: `summary.services` would report two
|
|
158
|
+
* where the fleet has twelve, `valid: true` would read as a claim about the
|
|
159
|
+
* fleet, and nothing in the document would say otherwise.
|
|
160
|
+
*/
|
|
161
|
+
export function scopeJson(scope) {
|
|
162
|
+
return {
|
|
163
|
+
base: scope.ref,
|
|
164
|
+
commit: scope.commit,
|
|
165
|
+
landscape: scope.landscape,
|
|
166
|
+
services: scope.services.map((svc) => svc.id),
|
|
167
|
+
features: scope.features.map((feat) => feat.id),
|
|
168
|
+
totals: scope.totals,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The one line both the text view and a reader of a CI log need: how much of
|
|
173
|
+
* the fleet this run actually graded, and against what.
|
|
174
|
+
*
|
|
175
|
+
* The "nothing was graded" clause is the load-bearing half. A `--base` run with
|
|
176
|
+
* no targets in scope has nothing to report and exits 0 — correctly, the branch
|
|
177
|
+
* touched no governed boundary — and a 0 that reads like a green over the
|
|
178
|
+
* system is the exact failure the docs-repo readiness gate exists to prevent.
|
|
179
|
+
* So the sentence says it, rather than letting an empty report say nothing.
|
|
180
|
+
*/
|
|
181
|
+
export function scopeLine(scope) {
|
|
182
|
+
const graded = scope.services.length + scope.features.length + (scope.landscape ? 1 : 0);
|
|
183
|
+
return (`${scope.services.length} of ${scope.totals.services} services, ` +
|
|
184
|
+
`${scope.features.length} of ${scope.totals.features} features in scope since ${scope.ref}` +
|
|
185
|
+
(scope.landscape ? ", plus the landscape" : "") +
|
|
186
|
+
(graded === 0 ? " — nothing was graded" : ""));
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=changed-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changed-paths.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/changed-paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which targets a base git ref puts in scope — the narrowing behind\n * `loam validate --all --base <ref>`.\n *\n * The defect this exists for: a partly-adopted fleet has no CI setting that is\n * both passing today and tightening over time. `--all` is green over eleven\n * undocumented services (they warn, and warnings do not gate) and `--strict` is\n * red from the first minute of adoption until the last boundary is written, so\n * a team installs either a gate that never notices adoption stalling at 8% or\n * one it cannot turn on. `--base` is the third setting: grade what this branch\n * TOUCHED, at full `--strict` severity if the caller wants it, and let the\n * untouched majority stay untouched.\n *\n * A SUB-PACKAGE of `core/diff/` rather than a sixth file in it, because that\n * directory is at the five-file limit and because this is a different subject\n * from the semantic diff beside it: `diff` compares two states of the fleet and\n * reports what moved, this one only decides WHICH SUBJECTS a run should look\n * at. Nothing here reads a document.\n *\n * What it does NOT do, and the line is not negotiable: a diff-scoped validate\n * cannot see a victim boundary the change did not touch. Removing an operation\n * from `payment-service` puts `payment-service` in scope and leaves\n * `checkout-web` — whose living requirement still names the operation — out of\n * it. That join is `loam diff --base`'s question and it stays `loam diff`'s;\n * the two gates answer \"did you document what you touched\" and \"did you break a\n * consumer\", and neither substitutes for the other. Selling a green here that\n * hides a broken join would be worse than shipping no flag at all.\n */\nimport { declineDetail, gitText } from \"../../provenance/git.js\";\nimport { repoPath, type ErrorCode } from \"../../envelope/json.js\";\nimport { resolveBase } from \"../base-git.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { FeatureEntry, ServiceEntry } from \"../../repo/entries.js\";\n\n/** Everything the narrowing needs: where to ask, what to ask about, and what there is to narrow. */\nexport interface ScopeRequest {\n docsDir: DocsDir;\n /** The ref as the caller spelled it — echoed in every message and in the payload. */\n ref: string;\n /** The run's full service enumeration, at whatever depth the tree walk found each one. */\n services: readonly ServiceEntry[];\n /** The run's full ACTIVE feature enumeration. */\n features: readonly FeatureEntry[];\n}\n\n/** The narrowed run: which targets the branch touched, and what it narrowed from. */\nexport interface ValidateScope {\n kind: \"scope\";\n ref: string;\n /** The 40-hex commit `ref` resolved to — what a rerun would have to pass to reproduce this scope. */\n commit: string;\n /** Did `architecture/` change? The landscape target is the fleet's own subject and has no directory of its own. */\n landscape: boolean;\n services: ServiceEntry[];\n features: FeatureEntry[];\n /** The denominators — what the run would have graded without `--base`. */\n totals: { services: number; features: number };\n}\n\n/**\n * A refusal, already spelled. The code is one `loam diff` already emits for the\n * same two conditions and the message is in the same register, deliberately: a\n * second stable code for \"that ref does not resolve\" would be a second thing to\n * document, branch on and keep in step, for a condition that is identical.\n *\n * The prose is built here rather than in the command because it is the same\n * sentence for every caller and because `base-git.ts`'s `detail` — git's own\n * words, bounded — is what makes it diagnosable; a command that only received a\n * `kind` would have to reinvent the sentence to say why.\n */\nexport interface ScopeRefusal {\n kind: \"refused\";\n code: ErrorCode;\n message: string;\n}\n\nexport type ScopeOutcome = ValidateScope | ScopeRefusal;\n\n/**\n * Every path that differs between `ref` and the WORKING TREE, docs-relative.\n *\n * Two questions, not one, and the second is not optional: `git diff` compares\n * the tree git tracks, so a service adopted five minutes ago — a brand new\n * `services/<id>/spec.md` nobody has committed — is invisible to it. `validate`\n * grades the working tree, so the scope has to be the working tree's, and an\n * untracked new boundary that the run then skipped would be the flag's very\n * first user reporting that it graded nothing.\n *\n * `--no-renames` on purpose: with rename detection on (git's default since\n * 2.9) a service moved into a subsystem reports only its DESTINATION, and the\n * checks that grade the fleet from the old placement would be out of scope.\n * Two paths for a move is the honest answer.\n *\n * `-z` because git quotes non-ASCII and space-bearing paths in line output —\n * the same reason `gitTrackedFiles` asks that way, and the same phantom\n * directory named `\"src` if it did not.\n */\nasync function changedPaths(docsDir: DocsDir, commit: string, prefix: string): Promise<string[] | { detail: string }> {\n // Both run from docsDir. `git diff` ignores cwd and answers for the whole\n // repository with root-relative paths; `git ls-files` restricts itself to\n // cwd, and `--full-name` makes ITS paths root-relative too — so one prefix\n // strip below is correct for both, exactly as `listBaseTree` needs one.\n const tracked = await gitText(docsDir, [\"diff\", \"--name-only\", \"--no-renames\", \"-z\", commit, \"--\"]);\n if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {\n return { detail: tracked.overflowed ? \"the changed-path listing exceeded the output cap.\" : declineDetail(tracked) };\n }\n const untracked = await gitText(docsDir, [\"ls-files\", \"--others\", \"--exclude-standard\", \"--full-name\", \"-z\"]);\n if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {\n return { detail: untracked.overflowed ? \"the untracked-path listing exceeded the output cap.\" : declineDetail(untracked) };\n }\n const seen = new Set<string>();\n for (const raw of [...tracked.out.split(\"\\0\"), ...untracked.out.split(\"\\0\")]) {\n // A docs repo checked out as a subdirectory of a larger repository answers\n // in the whole repository's paths; anything outside the docs dir is\n // somebody else's change and is not a target here.\n if (raw === \"\" || !raw.startsWith(prefix)) continue;\n const rel = raw.slice(prefix.length);\n if (rel !== \"\") seen.add(rel);\n }\n return [...seen].sort();\n}\n\n/**\n * Is `path` inside `dir`? Both docs-relative, forward-slashed.\n *\n * A prefix test on the directory plus a separator, never a split on \"/\" and a\n * read of the second segment: a service filed under a subsystem lives at\n * `services/platform/payment-service/`, not at `services/<id>/`, and a\n * path-splitting implementation drops every filed service out of scope\n * SILENTLY — the run stays green because it graded nothing, which is the exact\n * failure this flag is not allowed to have. The separator is what stops\n * `services/pay` from claiming `services/payments/spec.md`.\n */\nfunction inside(path: string, dir: string): boolean {\n return dir !== \"\" && (path === dir || path.startsWith(`${dir}/`));\n}\n\n/** The fleet's own documents — the landscape target's subject, which owns no service directory. */\nconst ARCHITECTURE_DIR = \"architecture\";\n\n/**\n * Narrow a `--all` run to what changed since `ref`.\n *\n * Targets are matched through the ENUMERATIONS' own directories\n * (`ServiceEntry.dir`, `FeatureEntry.dir`), turned docs-relative by the same\n * `repoPath` the JSON contract uses. That is the whole reason the enumerations\n * are passed in rather than re-derived: they already know where each service\n * sits, at whatever depth, and asking them is what makes a filed fleet work.\n */\nexport async function scopeSince(request: ScopeRequest): Promise<ScopeOutcome> {\n const { docsDir, ref } = request;\n const base = await resolveBase(docsDir, ref);\n if (base.kind === \"no-git\") {\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +\n `--base narrows the run to what changed since a ref, which it reads with read-only git questions ` +\n `(rev-parse/diff/ls-files). Clone the docs repo with its history, then re-run — or drop --base to grade every target.`,\n };\n }\n if (base.kind === \"no-ref\") {\n return {\n kind: \"refused\",\n code: \"unknown-target\",\n message:\n `--base ${ref} does not resolve to a commit in the docs repo at ${docsDir}` +\n `${base.detail === \"\" ? \"\" : ` (${base.detail})`}. ` +\n `Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,\n };\n }\n\n const changed = await changedPaths(docsDir, base.commit, base.prefix);\n if (!Array.isArray(changed)) {\n // Git resolved the ref and then would not say what moved. There is no\n // partial answer to give: a scope built from half an answer grades a\n // subset nobody chose and reports it as the branch's whole footprint.\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `git could not list what changed since ${ref} in the docs repo at ${docsDir} (${changed.detail}) — ` +\n `without that list there is no honest scope, and a run over a guessed subset would report a green ` +\n `for targets nobody graded. Re-run once git can answer, or drop --base to grade every target.`,\n };\n }\n\n const dirOf = (abs: string): string => repoPath(docsDir, abs);\n return {\n kind: \"scope\",\n ref,\n commit: base.commit,\n // `architecture/` is the landscape target's whole directory: the fleet map,\n // the permission/capability/obligation vocabularies and the fleet ADRs all\n // live there, and every one of them is graded by the landscape target\n // rather than by any service.\n landscape: changed.some((path) => inside(path, ARCHITECTURE_DIR)),\n services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),\n features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),\n totals: { services: request.services.length, features: request.features.length },\n };\n}\n\n/**\n * The `scope` object the `--json` payload carries — the run saying out loud\n * what it looked at.\n *\n * Additive, and emitted only under `--base`, so no consumer of today's payload\n * moves. It exists because the alternative is a payload that is byte-shaped\n * like a whole-fleet run and is not one: `summary.services` would report two\n * where the fleet has twelve, `valid: true` would read as a claim about the\n * fleet, and nothing in the document would say otherwise.\n */\nexport function scopeJson(scope: ValidateScope): Record<string, unknown> {\n return {\n base: scope.ref,\n commit: scope.commit,\n landscape: scope.landscape,\n services: scope.services.map((svc) => svc.id),\n features: scope.features.map((feat) => feat.id),\n totals: scope.totals,\n };\n}\n\n/**\n * The one line both the text view and a reader of a CI log need: how much of\n * the fleet this run actually graded, and against what.\n *\n * The \"nothing was graded\" clause is the load-bearing half. A `--base` run with\n * no targets in scope has nothing to report and exits 0 — correctly, the branch\n * touched no governed boundary — and a 0 that reads like a green over the\n * system is the exact failure the docs-repo readiness gate exists to prevent.\n * So the sentence says it, rather than letting an empty report say nothing.\n */\nexport function scopeLine(scope: ValidateScope): string {\n const graded = scope.services.length + scope.features.length + (scope.landscape ? 1 : 0);\n return (\n `${scope.services.length} of ${scope.totals.services} services, ` +\n `${scope.features.length} of ${scope.totals.features} features in scope since ${scope.ref}` +\n (scope.landscape ? \", plus the landscape\" : \"\") +\n (graded === 0 ? \" — nothing was graded\" : \"\")\n );\n}\n"]}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The diff computation: join the base fleet (`./base-state.ts`) against the
|
|
3
|
+
* working tree (`./current-state.ts`) and say, per service, what changed in
|
|
4
|
+
* FLEET-MEANINGFUL terms — requirements by identity and digest, operations
|
|
5
|
+
* and messages by their join tokens, cross-service joins by tuple — with the
|
|
6
|
+
* current consumers of every removal named (`./victims.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Two disciplines rule every branch here. Removal is graded against who still
|
|
9
|
+
* depends on it: a removal nobody names is a warning, a removal the fleet
|
|
10
|
+
* still joins to is an error and sets `breaking`. And "nobody could look" is
|
|
11
|
+
* never "nothing changed": an unreadable artifact on EITHER side suspends
|
|
12
|
+
* that subject's axis — recorded in `unreadable[]`, counted in the summary,
|
|
13
|
+
* carried into the exit code by the command — instead of letting an
|
|
14
|
+
* unreadable base read as everything-added or an unreadable working file
|
|
15
|
+
* read as everything-removed.
|
|
16
|
+
*/
|
|
17
|
+
import { requirementDigest } from "../document/spec.js";
|
|
18
|
+
import { consumerJoinChanges } from "./victims.js";
|
|
19
|
+
/** Severity is a fact about the code, not a call-site choice — one table, no drift. */
|
|
20
|
+
const DIFF_SEVERITY = {
|
|
21
|
+
"diff.service-added": "ok",
|
|
22
|
+
"diff.service-removed": "warn",
|
|
23
|
+
"diff.requirement-added": "ok",
|
|
24
|
+
"diff.requirement-removed": "warn",
|
|
25
|
+
"diff.requirement-modified": "ok",
|
|
26
|
+
"diff.op-added": "ok",
|
|
27
|
+
"diff.op-removed": "warn",
|
|
28
|
+
"diff.op-removed-consumed": "error",
|
|
29
|
+
"diff.op-deprecated": "warn",
|
|
30
|
+
"diff.message-added": "ok",
|
|
31
|
+
"diff.message-removed": "warn",
|
|
32
|
+
"diff.message-removed-consumed": "error",
|
|
33
|
+
"diff.consumer-added": "ok",
|
|
34
|
+
"diff.consumer-removed": "ok",
|
|
35
|
+
};
|
|
36
|
+
function found(code, message, details) {
|
|
37
|
+
const carry = details !== undefined && details.length > 0;
|
|
38
|
+
return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };
|
|
39
|
+
}
|
|
40
|
+
/** A side the service does not exist on: absence everywhere, honestly. */
|
|
41
|
+
const ABSENT = { kind: "absent" };
|
|
42
|
+
const emptyState = (id) => ({ id, dir: "", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });
|
|
43
|
+
/**
|
|
44
|
+
* Guard one axis pair: record unreadable sides on the sink and say whether
|
|
45
|
+
* the axis may be compared at all. Reading one good side against a suspended
|
|
46
|
+
* one would grade absence it never proved.
|
|
47
|
+
*/
|
|
48
|
+
function comparable(sink, axis, pair) {
|
|
49
|
+
for (const side of ["base", "current"]) {
|
|
50
|
+
const state = pair[side];
|
|
51
|
+
if (state.kind === "unreadable")
|
|
52
|
+
sink.unreadable.push({ side, axis, path: state.path, error: state.error });
|
|
53
|
+
}
|
|
54
|
+
return pair.base.kind !== "unreadable" && pair.current.kind !== "unreadable";
|
|
55
|
+
}
|
|
56
|
+
const valueOr = (axis) => (axis.kind === "read" ? axis.value : []);
|
|
57
|
+
/** Identity: `Requirement-ID:` when authored, the heading otherwise — the same join `loam rebase` pins against. */
|
|
58
|
+
const reqKey = (r) => (r.id !== undefined ? `id:${r.id}` : `name:${r.name}`);
|
|
59
|
+
const reqLabel = (r) => (r.id !== undefined ? `${r.name} [${r.id}]` : r.name);
|
|
60
|
+
/** First declaration wins a duplicated key — the same arbitrary-but-stated pick every join on these axes makes. */
|
|
61
|
+
function firstWins(items, key) {
|
|
62
|
+
const map = new Map();
|
|
63
|
+
for (const item of items)
|
|
64
|
+
if (!map.has(key(item)))
|
|
65
|
+
map.set(key(item), item);
|
|
66
|
+
return map;
|
|
67
|
+
}
|
|
68
|
+
/** Entry comparator by map key — explicit, because the default sort stringifies whole entries. */
|
|
69
|
+
const byKey = (a, b) => (a[0] < b[0] ? -1 : 1);
|
|
70
|
+
function requirementsDiff(sink, pair) {
|
|
71
|
+
// v1 diffs spec.md's requirements only; arch.spec.md is read for the
|
|
72
|
+
// Consumes: join but its requirement inventory is deferred (the two axes
|
|
73
|
+
// are separate namespaces, and diffing both doubles the surface). Both
|
|
74
|
+
// pairs are guarded BEFORE the early return, so a service whose two spec
|
|
75
|
+
// files are both unreadable reports both suspensions, not just the first.
|
|
76
|
+
const specComparable = comparable(sink, "spec", { base: pair.base.spec, current: pair.current.spec });
|
|
77
|
+
comparable(sink, "archSpec", { base: pair.base.archSpec, current: pair.current.archSpec });
|
|
78
|
+
if (!specComparable)
|
|
79
|
+
return;
|
|
80
|
+
const base = firstWins(valueOr(pair.base.spec), reqKey);
|
|
81
|
+
const current = firstWins(valueOr(pair.current.spec), reqKey);
|
|
82
|
+
for (const [key, r] of [...current].sort(byKey)) {
|
|
83
|
+
const before = base.get(key);
|
|
84
|
+
if (before === undefined)
|
|
85
|
+
sink.findings.push(found("diff.requirement-added", `living requirement added since base: '${reqLabel(r)}'`));
|
|
86
|
+
else if (requirementDigest(before) !== requirementDigest(r)) {
|
|
87
|
+
sink.findings.push(found("diff.requirement-modified", `living requirement modified since base: '${reqLabel(r)}'`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const [key, r] of [...base].sort(byKey)) {
|
|
91
|
+
if (!current.has(key))
|
|
92
|
+
sink.findings.push(found("diff.requirement-removed", `living requirement removed since base: '${reqLabel(r)}'`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function opsDiff(sink, pair, victims) {
|
|
96
|
+
if (!comparable(sink, "openapi", { base: pair.base.openapi, current: pair.current.openapi }))
|
|
97
|
+
return;
|
|
98
|
+
const base = firstWins(valueOr(pair.base.openapi), (op) => op.id);
|
|
99
|
+
const current = firstWins(valueOr(pair.current.openapi), (op) => op.id);
|
|
100
|
+
for (const [id, op] of [...current].sort(byKey)) {
|
|
101
|
+
const before = base.get(id);
|
|
102
|
+
if (before === undefined) {
|
|
103
|
+
sink.findings.push(found("diff.op-added", `operation '${id}' added since base (${op.method.toUpperCase()} ${op.path})`));
|
|
104
|
+
}
|
|
105
|
+
else if (!before.deprecated && op.deprecated) {
|
|
106
|
+
const scan = await victims.opConsumers(sink.id, id);
|
|
107
|
+
// Three honest phrasings — never a confident negative over a scan that skipped an unreadable consumer document.
|
|
108
|
+
const stance = scan.victims.length > 0 ? `${scan.victims.length} current consumer(s) still name it`
|
|
109
|
+
: scan.suspended.length > 0 ? "whether current consumers name it could not be fully answered"
|
|
110
|
+
: "no current consumer names it";
|
|
111
|
+
sink.findings.push(found("diff.op-deprecated", `operation '${id}' is deprecated since base — ${stance}`, [...scan.victims, ...scan.suspended]));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const [id] of [...base].sort(byKey)) {
|
|
115
|
+
if (current.has(id))
|
|
116
|
+
continue;
|
|
117
|
+
const scan = await victims.opConsumers(sink.id, id);
|
|
118
|
+
if (scan.victims.length > 0) {
|
|
119
|
+
sink.findings.push(found("diff.op-removed-consumed", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, [...scan.victims, ...scan.suspended]));
|
|
120
|
+
}
|
|
121
|
+
else if (scan.suspended.length > 0) {
|
|
122
|
+
// "Nobody could be scanned" is not "nobody names it" — the consumer's own
|
|
123
|
+
// unreadable[] entry already carries the run to exit 1; this message must
|
|
124
|
+
// not assert a negative the scan never proved.
|
|
125
|
+
sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; no current landscape edge or living requirement names it`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function messagesDiff(sink, pair, victims) {
|
|
133
|
+
if (!comparable(sink, "asyncapi", { base: pair.base.asyncapi, current: pair.current.asyncapi }))
|
|
134
|
+
return;
|
|
135
|
+
const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);
|
|
136
|
+
const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);
|
|
137
|
+
for (const [name] of [...current].sort(byKey)) {
|
|
138
|
+
if (!base.has(name))
|
|
139
|
+
sink.findings.push(found("diff.message-added", `message '${name}' added since base`));
|
|
140
|
+
}
|
|
141
|
+
for (const [name] of [...base].sort(byKey)) {
|
|
142
|
+
if (current.has(name))
|
|
143
|
+
continue;
|
|
144
|
+
// Fleet-global join: a name another current service still declares is not
|
|
145
|
+
// orphaned (positive evidence only — an unreadable contract never vouches).
|
|
146
|
+
const elsewhere = victims.declarers(name).filter((d) => d !== sink.id);
|
|
147
|
+
if (elsewhere.length > 0) {
|
|
148
|
+
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; still declared by ${elsewhere.join(", ")}`));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const scan = await victims.messageConsumers(sink.id, name);
|
|
152
|
+
if (scan.victims.length > 0) {
|
|
153
|
+
sink.findings.push(found("diff.message-removed-consumed", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, [...scan.victims, ...scan.suspended]));
|
|
154
|
+
}
|
|
155
|
+
else if (scan.suspended.length > 0) {
|
|
156
|
+
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; nothing currently consumes it`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** The graded join changes (`victims.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */
|
|
164
|
+
function consumerFindings(input) {
|
|
165
|
+
const changes = consumerJoinChanges({
|
|
166
|
+
base: input.base.services,
|
|
167
|
+
baseAmbiguous: new Set(input.base.ambiguous.keys()),
|
|
168
|
+
current: input.current.services,
|
|
169
|
+
currentAmbiguous: new Set(input.current.ambiguous.keys()),
|
|
170
|
+
});
|
|
171
|
+
const byProvider = new Map();
|
|
172
|
+
for (const { t, change } of changes) {
|
|
173
|
+
const what = t.kind === "op" ? `operation '${t.token}'` : `message '${t.token}'`;
|
|
174
|
+
const message = change === "added"
|
|
175
|
+
? `${t.consumer}'s living requirements now name this service's ${what} — a cross-service join appeared since base`
|
|
176
|
+
: `${t.consumer}'s living requirements no longer name this service's ${what} — a cross-service join went away`;
|
|
177
|
+
// The call sits directly in the push — assigned to a `const x = found(…)`
|
|
178
|
+
// it reads as an arrow-function DECLARATION to the stable-code collector,
|
|
179
|
+
// and both consumer codes silently leave the guarded set.
|
|
180
|
+
byProvider.set(t.provider, [
|
|
181
|
+
...(byProvider.get(t.provider) ?? []),
|
|
182
|
+
found(change === "added" ? "diff.consumer-added" : "diff.consumer-removed", message),
|
|
183
|
+
]);
|
|
184
|
+
}
|
|
185
|
+
return byProvider;
|
|
186
|
+
}
|
|
187
|
+
async function diffService(unit) {
|
|
188
|
+
const sink = { id: unit.id, change: "unchanged", findings: [], unreadable: [] };
|
|
189
|
+
if (unit.base === undefined)
|
|
190
|
+
sink.findings.push(found("diff.service-added", `service '${unit.id}' added since base`));
|
|
191
|
+
if (unit.current === undefined && unit.base !== undefined) {
|
|
192
|
+
sink.findings.push(found("diff.service-removed", `service '${unit.id}' (${unit.base.dir}) is gone from the working tree`));
|
|
193
|
+
}
|
|
194
|
+
const pair = { base: unit.base ?? emptyState(unit.id), current: unit.current ?? emptyState(unit.id) };
|
|
195
|
+
requirementsDiff(sink, pair);
|
|
196
|
+
await opsDiff(sink, pair, unit.victims);
|
|
197
|
+
await messagesDiff(sink, pair, unit.victims);
|
|
198
|
+
return sink;
|
|
199
|
+
}
|
|
200
|
+
export async function computeDiff(input) {
|
|
201
|
+
const sides = [input.base.services, input.base.ambiguous, input.current.services, input.current.ambiguous];
|
|
202
|
+
const ids = [...new Set(sides.flatMap((side) => [...side.keys()]))].sort();
|
|
203
|
+
const joins = consumerFindings({ base: input.base, current: input.current });
|
|
204
|
+
const services = [];
|
|
205
|
+
// Sequential on purpose: the report's order IS the contract (identical
|
|
206
|
+
// states must emit identical bytes), the work per service is in-memory
|
|
207
|
+
// joins, and the one expensive read — the victim index's landscape — is
|
|
208
|
+
// memoised behind its first ask, so a fan-out would buy nothing.
|
|
209
|
+
for (const id of ids) {
|
|
210
|
+
const claimants = [...(input.base.ambiguous.get(id) ?? []), ...(input.current.ambiguous.get(id) ?? [])];
|
|
211
|
+
if (claimants.length > 0) {
|
|
212
|
+
// An id two directories claim — either side — has no single state to
|
|
213
|
+
// diff; the whole subject suspends (containment), and the live walk's
|
|
214
|
+
// `subsystem.name-collision` names the same defect for validate.
|
|
215
|
+
services.push({ id, change: "changed", findings: [], unreadable: [], ambiguous: claimants });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const sink = await diffService({ id, base: input.base.services.get(id), current: input.current.services.get(id), victims: input.victims });
|
|
219
|
+
sink.findings.push(...(joins.get(id) ?? []));
|
|
220
|
+
if (input.base.services.has(id) && !input.current.services.has(id))
|
|
221
|
+
sink.change = "removed";
|
|
222
|
+
else if (!input.base.services.has(id))
|
|
223
|
+
sink.change = "added";
|
|
224
|
+
else
|
|
225
|
+
sink.change = sink.findings.length > 0 || sink.unreadable.length > 0 ? "changed" : "unchanged";
|
|
226
|
+
services.push(sink);
|
|
227
|
+
}
|
|
228
|
+
const all = services.flatMap((s) => s.findings);
|
|
229
|
+
const count = (codes) => all.filter(codes).length;
|
|
230
|
+
return {
|
|
231
|
+
services,
|
|
232
|
+
breaking: all.some((f) => f.severity === "error"),
|
|
233
|
+
summary: {
|
|
234
|
+
added: count((f) => f.code.endsWith("-added")),
|
|
235
|
+
removed: count((f) => f.code.endsWith("-removed") || f.code.endsWith("-removed-consumed")),
|
|
236
|
+
modified: count((f) => f.code === "diff.requirement-modified"),
|
|
237
|
+
deprecated: count((f) => f.code === "diff.op-deprecated"),
|
|
238
|
+
unreadable: services.reduce((n, s) => n + s.unreadable.length, 0) +
|
|
239
|
+
services.filter((s) => s.ambiguous !== undefined).length,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=semantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../../src/core/diff/semantic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,iBAAiB,EAAoB,MAAM,qBAAqB,CAAC;AAI1E,OAAO,EAAE,mBAAmB,EAAoB,MAAM,cAAc,CAAC;AAqCrE,uFAAuF;AACvF,MAAM,aAAa,GAA+B;IAChD,oBAAoB,EAAE,IAAI;IAC1B,sBAAsB,EAAE,MAAM;IAC9B,wBAAwB,EAAE,IAAI;IAC9B,0BAA0B,EAAE,MAAM;IAClC,2BAA2B,EAAE,IAAI;IACjC,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,MAAM;IACzB,0BAA0B,EAAE,OAAO;IACnC,oBAAoB,EAAE,MAAM;IAC5B,oBAAoB,EAAE,IAAI;IAC1B,sBAAsB,EAAE,MAAM;IAC9B,+BAA+B,EAAE,OAAO;IACxC,qBAAqB,EAAE,IAAI;IAC3B,uBAAuB,EAAE,IAAI;CAC9B,CAAC;AAEF,SAAS,KAAK,CAAC,IAAc,EAAE,OAAe,EAAE,OAAkB;IAChE,MAAM,KAAK,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1D,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC;AA2BD,0EAA0E;AAC1E,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAW,CAAC;AAC3C,MAAM,UAAU,GAAG,CAAC,EAAU,EAAgB,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAEtI;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAiB,EAAE,IAA6B,EAAE,IAA+D;IACnI,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAU,EAAE,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9G,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC;AAC/E,CAAC;AAED,MAAM,OAAO,GAAG,CAAI,IAAoB,EAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAE3F,mHAAmH;AACnH,MAAM,MAAM,GAAG,CAAC,CAAc,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAClG,MAAM,QAAQ,GAAG,CAAC,CAAc,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAEnG,mHAAmH;AACnH,SAAS,SAAS,CAAI,KAAU,EAAE,GAAwB;IACxD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAa,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kGAAkG;AAClG,MAAM,KAAK,GAAG,CAAI,CAAc,EAAE,CAAc,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpF,SAAS,gBAAgB,CAAC,IAAiB,EAAE,IAAmD;IAC9F,qEAAqE;IACrE,yEAAyE;IACzE,uEAAuE;IACvE,yEAAyE;IACzE,0EAA0E;IAC1E,MAAM,cAAc,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACtG,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3F,IAAI,CAAC,cAAc;QAAE,OAAO;IAC5B,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9D,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,wBAAwB,EAAE,yCAAyC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;aAClI,IAAI,iBAAiB,CAAC,MAAM,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,2BAA2B,EAAE,4CAA4C,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,0BAA0B,EAAE,2CAA2C,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1I,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAiB,EAAE,IAAmD,EAAE,OAAoB;IACjH,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO;IACrG,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACxE,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,cAAc,EAAE,uBAAuB,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC3H,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;YAC/C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACpD,gHAAgH;YAChH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,oCAAoC;gBACjG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,+DAA+D;oBAC7F,CAAC,CAAC,8BAA8B,CAAC;YACnC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,cAAc,EAAE,gCAAgC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClJ,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,SAAS;QAC9B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,0BAA0B,EAAE,cAAc,EAAE,mFAAmF,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACnM,CAAC;aAAM,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,0EAA0E;YAC1E,0EAA0E;YAC1E,+CAA+C;YAC/C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,cAAc,EAAE,oGAAoG,IAAI,CAAC,SAAS,CAAC,MAAM,0CAA0C,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACpP,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,cAAc,EAAE,oFAAoF,CAAC,CAAC,CAAC;QACrJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,IAAiB,EAAE,IAAmD,EAAE,OAAoB;IACtH,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QAAE,OAAO;IACxG,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzE,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,YAAY,IAAI,oBAAoB,CAAC,CAAC,CAAC;IAC7G,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAChC,0EAA0E;QAC1E,4EAA4E;QAC5E,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE,YAAY,IAAI,+CAA+C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACzI,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,+BAA+B,EAAE,YAAY,IAAI,sFAAsF,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC3M,CAAC;aAAM,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE,YAAY,IAAI,8FAA8F,IAAI,CAAC,SAAS,CAAC,MAAM,0CAA0C,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACnP,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE,YAAY,IAAI,yDAAyD,CAAC,CAAC,CAAC;QAC/H,CAAC;IACH,CAAC;AACH,CAAC;AAED,yHAAyH;AACzH,SAAS,gBAAgB,CAAC,KAAiD;IACzE,MAAM,OAAO,GAAG,mBAAmB,CAAC;QAClC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ;QACzB,aAAa,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACnD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ;QAC/B,gBAAgB,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;KAC1D,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAChD,KAAK,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,KAAK,GAAG,CAAC;QACjF,MAAM,OAAO,GAAG,MAAM,KAAK,OAAO;YAChC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,kDAAkD,IAAI,6CAA6C;YAClH,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,wDAAwD,IAAI,mCAAmC,CAAC;QACjH,0EAA0E;QAC1E,0EAA0E;QAC1E,0DAA0D;QAC1D,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE;YACzB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACrC,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACrF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAuF;IAChH,MAAM,IAAI,GAAgB,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC7F,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,YAAY,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACtH,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC1D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE,YAAY,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,iCAAiC,CAAC,CAAC,CAAC;IAC7H,CAAC;IACD,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACtG,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAuE;IACvG,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3G,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3E,MAAM,KAAK,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,uEAAuE;IACvE,uEAAuE;IACvE,wEAAwE;IACxE,iEAAiE;IACjE,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxG,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,qEAAqE;YACrE,sEAAsE;YACtE,iEAAiE;YACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;YAC7F,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3I,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7C,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;aACvF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;;YACxD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;QACpG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,CAAC,KAA8B,EAAU,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IACnF,OAAO;QACL,QAAQ;QACR,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;QACjD,OAAO,EAAE;YACP,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;YAC1F,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,2BAA2B,CAAC;YAC9D,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,oBAAoB,CAAC;YACzD,UAAU,EACR,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;gBACrD,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,MAAM;SAC3D;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The diff computation: join the base fleet (`./base-state.ts`) against the\n * working tree (`./current-state.ts`) and say, per service, what changed in\n * FLEET-MEANINGFUL terms — requirements by identity and digest, operations\n * and messages by their join tokens, cross-service joins by tuple — with the\n * current consumers of every removal named (`./victims.ts`).\n *\n * Two disciplines rule every branch here. Removal is graded against who still\n * depends on it: a removal nobody names is a warning, a removal the fleet\n * still joins to is an error and sets `breaking`. And \"nobody could look\" is\n * never \"nothing changed\": an unreadable artifact on EITHER side suspends\n * that subject's axis — recorded in `unreadable[]`, counted in the summary,\n * carried into the exit code by the command — instead of letting an\n * unreadable base read as everything-added or an unreadable working file\n * read as everything-removed.\n */\nimport { requirementDigest, type Requirement } from \"../document/spec.js\";\nimport type { Severity, Finding } from \"../vocabulary/report.js\";\nimport type { AxisState, BaseFleet, ServiceState } from \"./base-state.js\";\nimport type { CurrentFleet } from \"./current-state.js\";\nimport { consumerJoinChanges, type VictimIndex } from \"./victims.js\";\n\n/**\n * The stable finding codes `loam diff` emits. Codes are the machine contract;\n * prose may be reworded, these may not. Documented (backticked) in the\n * generated AGENTS.md's diff section — codes-drift enforces that.\n */\nexport type DiffCode =\n /** A `services/<id>/` directory exists now that did not at the base ref. */\n | \"diff.service-added\"\n /** A service present at the base ref is gone from the working tree. */\n | \"diff.service-removed\"\n /** A living requirement (by `Requirement-ID:`, else heading) not present at base. */\n | \"diff.requirement-added\"\n /** A base requirement no longer in the living spec. */\n | \"diff.requirement-removed\"\n /** Same requirement identity on both sides, `requirementDigest` moved — rebase pins never move it. */\n | \"diff.requirement-modified\"\n /** An operationId in the living contract absent at base. */\n | \"diff.op-added\"\n /** A base operationId gone from the living contract, and no current consumer names it. */\n | \"diff.op-removed\"\n /** Removed since base AND the current fleet still names it — landscape edge or foreign living requirement; details list the victims. Sets `breaking`. */\n | \"diff.op-removed-consumed\"\n /** `deprecated: true` introduced since base; details name the current consumers. */\n | \"diff.op-deprecated\"\n /** An AsyncAPI message name absent at base. */\n | \"diff.message-added\"\n /** A base message gone, nothing currently consumes it (or another service still declares it — the details say). */\n | \"diff.message-removed\"\n /** Removed since base AND still consumed — a `Consumes:` line or a consumes-edge; details list the victims. Sets `breaking`. */\n | \"diff.message-removed-consumed\"\n /** A cross-service join appeared since base: another service's living requirement now names this service's operation or message. */\n | \"diff.consumer-added\"\n /** A cross-service join present at base is gone. */\n | \"diff.consumer-removed\";\n\n/** Severity is a fact about the code, not a call-site choice — one table, no drift. */\nconst DIFF_SEVERITY: Record<DiffCode, Severity> = {\n \"diff.service-added\": \"ok\",\n \"diff.service-removed\": \"warn\",\n \"diff.requirement-added\": \"ok\",\n \"diff.requirement-removed\": \"warn\",\n \"diff.requirement-modified\": \"ok\",\n \"diff.op-added\": \"ok\",\n \"diff.op-removed\": \"warn\",\n \"diff.op-removed-consumed\": \"error\",\n \"diff.op-deprecated\": \"warn\",\n \"diff.message-added\": \"ok\",\n \"diff.message-removed\": \"warn\",\n \"diff.message-removed-consumed\": \"error\",\n \"diff.consumer-added\": \"ok\",\n \"diff.consumer-removed\": \"ok\",\n};\n\nfunction found(code: DiffCode, message: string, details?: string[]): Finding {\n const carry = details !== undefined && details.length > 0;\n return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };\n}\n\n/** One suspended axis: which side could not be read, and why. */\nexport interface UnreadableEntry {\n side: \"base\" | \"current\";\n axis: \"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\";\n path: string;\n error: string;\n}\n\nexport interface ServiceDiff {\n id: string;\n change: \"added\" | \"removed\" | \"changed\" | \"unchanged\";\n findings: Finding[];\n unreadable: UnreadableEntry[];\n /** Directories claiming this id when more than one does, on either side — every finding suspended. */\n ambiguous?: string[];\n}\n\nexport interface FleetDiff {\n services: ServiceDiff[];\n /** True iff any error-severity finding — a removal the fleet still consumes. */\n breaking: boolean;\n /** `unreadable` counts suspended axes plus ambiguous base identities: places nobody could look. */\n summary: { added: number; removed: number; modified: number; deprecated: number; unreadable: number };\n}\n\n/** A side the service does not exist on: absence everywhere, honestly. */\nconst ABSENT = { kind: \"absent\" } as const;\nconst emptyState = (id: string): ServiceState => ({ id, dir: \"\", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });\n\n/**\n * Guard one axis pair: record unreadable sides on the sink and say whether\n * the axis may be compared at all. Reading one good side against a suspended\n * one would grade absence it never proved.\n */\nfunction comparable(sink: ServiceDiff, axis: UnreadableEntry[\"axis\"], pair: { base: AxisState<unknown>; current: AxisState<unknown> }): boolean {\n for (const side of [\"base\", \"current\"] as const) {\n const state = pair[side];\n if (state.kind === \"unreadable\") sink.unreadable.push({ side, axis, path: state.path, error: state.error });\n }\n return pair.base.kind !== \"unreadable\" && pair.current.kind !== \"unreadable\";\n}\n\nconst valueOr = <T>(axis: AxisState<T[]>): T[] => (axis.kind === \"read\" ? axis.value : []);\n\n/** Identity: `Requirement-ID:` when authored, the heading otherwise — the same join `loam rebase` pins against. */\nconst reqKey = (r: Requirement): string => (r.id !== undefined ? `id:${r.id}` : `name:${r.name}`);\nconst reqLabel = (r: Requirement): string => (r.id !== undefined ? `${r.name} [${r.id}]` : r.name);\n\n/** First declaration wins a duplicated key — the same arbitrary-but-stated pick every join on these axes makes. */\nfunction firstWins<T>(items: T[], key: (item: T) => string): Map<string, T> {\n const map = new Map<string, T>();\n for (const item of items) if (!map.has(key(item))) map.set(key(item), item);\n return map;\n}\n\n/** Entry comparator by map key — explicit, because the default sort stringifies whole entries. */\nconst byKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : 1);\n\nfunction requirementsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }): void {\n // v1 diffs spec.md's requirements only; arch.spec.md is read for the\n // Consumes: join but its requirement inventory is deferred (the two axes\n // are separate namespaces, and diffing both doubles the surface). Both\n // pairs are guarded BEFORE the early return, so a service whose two spec\n // files are both unreadable reports both suspensions, not just the first.\n const specComparable = comparable(sink, \"spec\", { base: pair.base.spec, current: pair.current.spec });\n comparable(sink, \"archSpec\", { base: pair.base.archSpec, current: pair.current.archSpec });\n if (!specComparable) return;\n const base = firstWins(valueOr(pair.base.spec), reqKey);\n const current = firstWins(valueOr(pair.current.spec), reqKey);\n for (const [key, r] of [...current].sort(byKey)) {\n const before = base.get(key);\n if (before === undefined) sink.findings.push(found(\"diff.requirement-added\", `living requirement added since base: '${reqLabel(r)}'`));\n else if (requirementDigest(before) !== requirementDigest(r)) {\n sink.findings.push(found(\"diff.requirement-modified\", `living requirement modified since base: '${reqLabel(r)}'`));\n }\n }\n for (const [key, r] of [...base].sort(byKey)) {\n if (!current.has(key)) sink.findings.push(found(\"diff.requirement-removed\", `living requirement removed since base: '${reqLabel(r)}'`));\n }\n}\n\nasync function opsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: VictimIndex): Promise<void> {\n if (!comparable(sink, \"openapi\", { base: pair.base.openapi, current: pair.current.openapi })) return;\n const base = firstWins(valueOr(pair.base.openapi), (op) => op.id);\n const current = firstWins(valueOr(pair.current.openapi), (op) => op.id);\n for (const [id, op] of [...current].sort(byKey)) {\n const before = base.get(id);\n if (before === undefined) {\n sink.findings.push(found(\"diff.op-added\", `operation '${id}' added since base (${op.method.toUpperCase()} ${op.path})`));\n } else if (!before.deprecated && op.deprecated) {\n const scan = await victims.opConsumers(sink.id, id);\n // Three honest phrasings — never a confident negative over a scan that skipped an unreadable consumer document.\n const stance = scan.victims.length > 0 ? `${scan.victims.length} current consumer(s) still name it`\n : scan.suspended.length > 0 ? \"whether current consumers name it could not be fully answered\"\n : \"no current consumer names it\";\n sink.findings.push(found(\"diff.op-deprecated\", `operation '${id}' is deprecated since base — ${stance}`, [...scan.victims, ...scan.suspended]));\n }\n }\n for (const [id] of [...base].sort(byKey)) {\n if (current.has(id)) continue;\n const scan = await victims.opConsumers(sink.id, id);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.op-removed-consumed\", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, [...scan.victims, ...scan.suspended]));\n } else if (scan.suspended.length > 0) {\n // \"Nobody could be scanned\" is not \"nobody names it\" — the consumer's own\n // unreadable[] entry already carries the run to exit 1; this message must\n // not assert a negative the scan never proved.\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));\n } else {\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; no current landscape edge or living requirement names it`));\n }\n }\n}\n\nasync function messagesDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: VictimIndex): Promise<void> {\n if (!comparable(sink, \"asyncapi\", { base: pair.base.asyncapi, current: pair.current.asyncapi })) return;\n const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);\n const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);\n for (const [name] of [...current].sort(byKey)) {\n if (!base.has(name)) sink.findings.push(found(\"diff.message-added\", `message '${name}' added since base`));\n }\n for (const [name] of [...base].sort(byKey)) {\n if (current.has(name)) continue;\n // Fleet-global join: a name another current service still declares is not\n // orphaned (positive evidence only — an unreadable contract never vouches).\n const elsewhere = victims.declarers(name).filter((d) => d !== sink.id);\n if (elsewhere.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; still declared by ${elsewhere.join(\", \")}`));\n continue;\n }\n const scan = await victims.messageConsumers(sink.id, name);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.message-removed-consumed\", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, [...scan.victims, ...scan.suspended]));\n } else if (scan.suspended.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));\n } else {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; nothing currently consumes it`));\n }\n }\n}\n\n/** The graded join changes (`victims.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */\nfunction consumerFindings(input: { base: BaseFleet; current: CurrentFleet }): Map<string, Finding[]> {\n const changes = consumerJoinChanges({\n base: input.base.services,\n baseAmbiguous: new Set(input.base.ambiguous.keys()),\n current: input.current.services,\n currentAmbiguous: new Set(input.current.ambiguous.keys()),\n });\n const byProvider = new Map<string, Finding[]>();\n for (const { t, change } of changes) {\n const what = t.kind === \"op\" ? `operation '${t.token}'` : `message '${t.token}'`;\n const message = change === \"added\"\n ? `${t.consumer}'s living requirements now name this service's ${what} — a cross-service join appeared since base`\n : `${t.consumer}'s living requirements no longer name this service's ${what} — a cross-service join went away`;\n // The call sits directly in the push — assigned to a `const x = found(…)`\n // it reads as an arrow-function DECLARATION to the stable-code collector,\n // and both consumer codes silently leave the guarded set.\n byProvider.set(t.provider, [\n ...(byProvider.get(t.provider) ?? []),\n found(change === \"added\" ? \"diff.consumer-added\" : \"diff.consumer-removed\", message),\n ]);\n }\n return byProvider;\n}\n\nasync function diffService(unit: { id: string; base?: ServiceState; current?: ServiceState; victims: VictimIndex }): Promise<ServiceDiff> {\n const sink: ServiceDiff = { id: unit.id, change: \"unchanged\", findings: [], unreadable: [] };\n if (unit.base === undefined) sink.findings.push(found(\"diff.service-added\", `service '${unit.id}' added since base`));\n if (unit.current === undefined && unit.base !== undefined) {\n sink.findings.push(found(\"diff.service-removed\", `service '${unit.id}' (${unit.base.dir}) is gone from the working tree`));\n }\n const pair = { base: unit.base ?? emptyState(unit.id), current: unit.current ?? emptyState(unit.id) };\n requirementsDiff(sink, pair);\n await opsDiff(sink, pair, unit.victims);\n await messagesDiff(sink, pair, unit.victims);\n return sink;\n}\n\nexport async function computeDiff(input: { base: BaseFleet; current: CurrentFleet; victims: VictimIndex }): Promise<FleetDiff> {\n const sides = [input.base.services, input.base.ambiguous, input.current.services, input.current.ambiguous];\n const ids = [...new Set(sides.flatMap((side) => [...side.keys()]))].sort();\n const joins = consumerFindings({ base: input.base, current: input.current });\n const services: ServiceDiff[] = [];\n // Sequential on purpose: the report's order IS the contract (identical\n // states must emit identical bytes), the work per service is in-memory\n // joins, and the one expensive read — the victim index's landscape — is\n // memoised behind its first ask, so a fan-out would buy nothing.\n for (const id of ids) {\n const claimants = [...(input.base.ambiguous.get(id) ?? []), ...(input.current.ambiguous.get(id) ?? [])];\n if (claimants.length > 0) {\n // An id two directories claim — either side — has no single state to\n // diff; the whole subject suspends (containment), and the live walk's\n // `subsystem.name-collision` names the same defect for validate.\n services.push({ id, change: \"changed\", findings: [], unreadable: [], ambiguous: claimants });\n continue;\n }\n const sink = await diffService({ id, base: input.base.services.get(id), current: input.current.services.get(id), victims: input.victims });\n sink.findings.push(...(joins.get(id) ?? []));\n if (input.base.services.has(id) && !input.current.services.has(id)) sink.change = \"removed\";\n else if (!input.base.services.has(id)) sink.change = \"added\";\n else sink.change = sink.findings.length > 0 || sink.unreadable.length > 0 ? \"changed\" : \"unchanged\";\n services.push(sink);\n }\n const all = services.flatMap((s) => s.findings);\n const count = (codes: (f: Finding) => boolean): number => all.filter(codes).length;\n return {\n services,\n breaking: all.some((f) => f.severity === \"error\"),\n summary: {\n added: count((f) => f.code.endsWith(\"-added\")),\n removed: count((f) => f.code.endsWith(\"-removed\") || f.code.endsWith(\"-removed-consumed\")),\n modified: count((f) => f.code === \"diff.requirement-modified\"),\n deprecated: count((f) => f.code === \"diff.op-deprecated\"),\n unreadable:\n services.reduce((n, s) => n + s.unreadable.length, 0) +\n services.filter((s) => s.ambiguous !== undefined).length,\n },\n };\n}\n"]}
|