@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2548 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each delta requirement SELECTS in the living document, and whether the
|
|
3
|
+
* merge would do what its author meant.
|
|
4
|
+
*
|
|
5
|
+
* This is the half that needs both documents at once, which is why it is not in
|
|
6
|
+
* `./document.ts`. Every refusal here is a shape the merge would apply cleanly
|
|
7
|
+
* and silently: MODIFIED of a requirement that does not exist is merged as a
|
|
8
|
+
* creation, ADDED of one that does exist REPLACES it scenarios and all, and a
|
|
9
|
+
* stale `Based-On:` means someone landed a change in between that this delta
|
|
10
|
+
* would overwrite with text written against a document that no longer exists.
|
|
11
|
+
*/
|
|
12
|
+
import {} from "../vocabulary/issue.js";
|
|
13
|
+
import { isRequirementsHeading } from "../document/parse.js";
|
|
14
|
+
import { basedOnDeclarations, REQUIREMENT_DIGEST_RE, requirementDigest, } from "../document/spec.js";
|
|
15
|
+
import {} from "./claims.js";
|
|
16
|
+
import {} from "./scope.js";
|
|
17
|
+
export async function selectionIssues(scope, reqs, living, claims) {
|
|
18
|
+
const { featureId, subject, where, livingDoc } = scope;
|
|
19
|
+
const issues = [];
|
|
20
|
+
/**
|
|
21
|
+
* The pin, against the living requirement this delta actually selects.
|
|
22
|
+
*
|
|
23
|
+
* This is the check `delta.modified-conflict` cannot be: that one names
|
|
24
|
+
* the other feature while BOTH are in flight, and the collision that
|
|
25
|
+
* happens on a fleet outlives the window. A archives, leaves `features/`,
|
|
26
|
+
* and stops being an active claim; B revalidates GREEN, and its MODIFIED —
|
|
27
|
+
* carrying the full text it wrote against the pre-A document — replaces
|
|
28
|
+
* A's requirement wholesale, scenarios and all, with `+0 ~1 -0` and exit
|
|
29
|
+
* 0. Nothing downstream can even detect it afterwards: `unarchive A`
|
|
30
|
+
* refuses `snapshot-stale`, and `--force` takes B out with it.
|
|
31
|
+
*
|
|
32
|
+
* A digest of what the author read closes that by not depending on
|
|
33
|
+
* timing at all. Stale is an ERROR and gates, because there is no reading
|
|
34
|
+
* of a stale delta under which the merge is what its author meant.
|
|
35
|
+
* Missing is a warning that GATES: the document is legal — a delta
|
|
36
|
+
* adopted from OpenSpec never had the line — but an unpinned MODIFIED or
|
|
37
|
+
* REMOVED is exactly the timing hole the pin exists to close, and letting
|
|
38
|
+
* it through archived the silent rollback at exit 0. The migrated corpus
|
|
39
|
+
* is why this stays a warning rather than an error, and why the refusal
|
|
40
|
+
* is not an outage: `loam rebase <feat>` pins every delta in one command,
|
|
41
|
+
* and `--approve` remains the way to say the unpinned merge is meant.
|
|
42
|
+
*/
|
|
43
|
+
const checkBaseline = (r, selected) => {
|
|
44
|
+
// A pin the document pass already refused is not also stale: that would
|
|
45
|
+
// send its author to `loam rebase` for a problem rebase does not fix.
|
|
46
|
+
const declared = basedOnDeclarations(r);
|
|
47
|
+
if (declared.length > 1)
|
|
48
|
+
return;
|
|
49
|
+
if (r.basedOn !== undefined && !REQUIREMENT_DIGEST_RE.test(r.basedOn))
|
|
50
|
+
return;
|
|
51
|
+
const current = requirementDigest(selected);
|
|
52
|
+
if (r.basedOn === undefined) {
|
|
53
|
+
issues.push({
|
|
54
|
+
severity: "warn",
|
|
55
|
+
gates: true,
|
|
56
|
+
code: "delta.baseline-missing",
|
|
57
|
+
subject,
|
|
58
|
+
message: `${where}: ${r.kind} requirement '${r.name}' carries no Based-On, so nothing can say whether the living text moved since this delta was written — merging it would replace whatever landed in between. Run \`loam rebase ${featureId}\` to pin it (Based-On: ${current}), or archive with --approve to merge unpinned deliberately.`,
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (r.basedOn === current)
|
|
63
|
+
return;
|
|
64
|
+
issues.push({
|
|
65
|
+
severity: "error",
|
|
66
|
+
code: "delta.baseline-stale",
|
|
67
|
+
subject,
|
|
68
|
+
message: `${where}: ${r.kind} requirement '${r.name}' was written against living version ${r.basedOn}, but the ${livingDoc} now holds ${current} — someone landed a change to it in between. ` +
|
|
69
|
+
(r.kind === "MODIFIED"
|
|
70
|
+
? `This MODIFIED carries its FULL new text, so merging it would replace theirs outright.`
|
|
71
|
+
: `Merging this REMOVED would delete what they landed.`) +
|
|
72
|
+
` Re-read the living requirement, fold in what you still mean, then run \`loam rebase ${featureId}\`.`,
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
for (const r of reqs) {
|
|
76
|
+
// BASE in a delta file means the requirement is under no delta section, so
|
|
77
|
+
// the merge skips it. Reported rather than merged: deciding that `## Behavior`
|
|
78
|
+
// means ADDED would be archive guessing at intent, and it would guess wrong
|
|
79
|
+
// for every prose section that quotes a requirement as documentation. A
|
|
80
|
+
// requirement above every heading has no heading to name, and no claim to be
|
|
81
|
+
// written in the delta grammar at all — same judgement as `## Requirements`.
|
|
82
|
+
//
|
|
83
|
+
// A warning that GATES archive — the one check where the two axes
|
|
84
|
+
// diverge. Severity says whether the document is valid: this shape is
|
|
85
|
+
// legal OpenSpec, so an error would fail `loam validate` on every
|
|
86
|
+
// adopted repo whose active deltas still use it. Gating says whether
|
|
87
|
+
// the merge is safe: it is not — the merge would silently drop authored
|
|
88
|
+
// content, the exact loss this module exists to prevent — so archive
|
|
89
|
+
// refuses, and `--approve` remains the way to say the loss is meant.
|
|
90
|
+
if (r.kind === "BASE") {
|
|
91
|
+
if (r.section !== undefined && !isRequirementsHeading(r.section)) {
|
|
92
|
+
issues.push({
|
|
93
|
+
severity: "warn",
|
|
94
|
+
gates: true,
|
|
95
|
+
code: "delta.requirement-not-merged",
|
|
96
|
+
subject,
|
|
97
|
+
message: `${where}: requirement '${r.name}' sits under '${r.section}', which is not a delta section — archive will NOT merge it. Move it under '## ADDED Requirements' (or MODIFIED/REMOVED), or drop it if it is documentation.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (r.kind === "ADDED") {
|
|
103
|
+
const idMatches = r.id === undefined ? [] : (living.byId.get(r.id) ?? []);
|
|
104
|
+
const nameMatches = living.byName.get(r.name) ?? [];
|
|
105
|
+
const nameSelectsOther = r.id !== undefined && nameMatches.some((candidate) => candidate.id !== r.id);
|
|
106
|
+
if (nameSelectsOther) {
|
|
107
|
+
issues.push({
|
|
108
|
+
severity: "error",
|
|
109
|
+
code: "delta.requirement-identity-collision",
|
|
110
|
+
subject,
|
|
111
|
+
message: `${where}: ADDED requirement '${r.name}' carries Requirement-ID '${r.id}', but that heading already identifies a different living requirement — ID and name cannot select different identities`,
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if ((r.id === undefined && living.names.has(r.name)) || idMatches.length > 0) {
|
|
116
|
+
issues.push({
|
|
117
|
+
severity: "error",
|
|
118
|
+
code: "delta.added-duplicate",
|
|
119
|
+
subject,
|
|
120
|
+
message: r.id === undefined
|
|
121
|
+
? `${where}: ADDED requirement '${r.name}' already exists in the ${livingDoc} — the merge would REPLACE it, scenarios and all. Use MODIFIED, or rename.`
|
|
122
|
+
: `${where}: ADDED requirement '${r.name}' uses Requirement-ID '${r.id}', which already exists in the ${livingDoc} — use MODIFIED to change that identity`,
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Merge identity is exact string equality (applyRequirementDelta), so a
|
|
127
|
+
// name that differs only in case slips past the duplicate check above and
|
|
128
|
+
// lands as a SECOND requirement next to the living one. A warning, not an
|
|
129
|
+
// error: the author may genuinely mean a distinct requirement, but with
|
|
130
|
+
// LLM authors the case drift is the likely story. Never fires together
|
|
131
|
+
// with added-duplicate — an exact match continues before this runs.
|
|
132
|
+
const near = living.folded.get(r.name.toLowerCase());
|
|
133
|
+
if (near !== undefined) {
|
|
134
|
+
issues.push({
|
|
135
|
+
severity: "warn",
|
|
136
|
+
code: "delta.added-near-duplicate",
|
|
137
|
+
subject,
|
|
138
|
+
message: `${where}: ADDED requirement '${r.name}' differs only in case from living requirement '${near}' — the merge matches names exactly, so both would coexist. Match the living spelling and use MODIFIED, or pick a distinct name.`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const other = await claims.added(scope, r);
|
|
142
|
+
if (other !== undefined) {
|
|
143
|
+
issues.push({
|
|
144
|
+
severity: "warn",
|
|
145
|
+
code: "delta.added-conflict",
|
|
146
|
+
subject,
|
|
147
|
+
message: `${where}: requirement '${r.name}' is also added by ${other} — whichever archives first lands it, and the second archive is refused (delta.added-duplicate) unless --approve`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// MODIFIED vs MODIFIED on ONE living requirement. The ADDED case has
|
|
153
|
+
// been caught since delta.added-conflict: two features adding the same
|
|
154
|
+
// name collide loudly, because the second archive is refused. Two
|
|
155
|
+
// features CHANGING the same requirement collide in total silence —
|
|
156
|
+
// both deltas apply cleanly, and whichever archives second replaces
|
|
157
|
+
// the first's text, scenarios and all, with a version written against
|
|
158
|
+
// a document that no longer exists. A warning, not a gate: the second
|
|
159
|
+
// author may well be rewriting on purpose, and only they can say.
|
|
160
|
+
const alsoChanged = await claims.changed(scope, r);
|
|
161
|
+
if (alsoChanged !== undefined) {
|
|
162
|
+
issues.push({
|
|
163
|
+
severity: "warn",
|
|
164
|
+
code: "delta.modified-conflict",
|
|
165
|
+
subject,
|
|
166
|
+
message: `${where}: ${r.kind} requirement '${r.name}' is also changed by ${alsoChanged} — both deltas apply cleanly, so whichever archives second REPLACES the other's text wholesale. Agree on one owner, or fold the two changes together.`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (r.id !== undefined) {
|
|
170
|
+
const idMatches = living.byId.get(r.id) ?? [];
|
|
171
|
+
const nameMatches = living.byName.get(r.name) ?? [];
|
|
172
|
+
const nameSelectsOther = nameMatches.some((candidate) => candidate.id !== r.id);
|
|
173
|
+
// The second disjunct is defensive rather than reachable: a living
|
|
174
|
+
// requirement under this heading that the ID did NOT select must carry
|
|
175
|
+
// some other ID, which is what `nameSelectsOther` already says. It is
|
|
176
|
+
// kept because it states the rule the reader is being told — ID and
|
|
177
|
+
// heading must select the same identity — without depending on that
|
|
178
|
+
// derivation holding after the next edit.
|
|
179
|
+
if (nameSelectsOther || (idMatches.length === 0 && nameMatches.length > 0)) {
|
|
180
|
+
issues.push({
|
|
181
|
+
severity: "error",
|
|
182
|
+
code: "delta.requirement-identity-collision",
|
|
183
|
+
subject,
|
|
184
|
+
message: `${where}: ${r.kind} requirement '${r.name}' carries Requirement-ID '${r.id}', but its ID and heading select different living requirements — fix the ID or heading; archive will not guess`,
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
// A differing heading is the explicit loam rename mechanism: the
|
|
189
|
+
// stable ID selects the old requirement, MODIFIED supplies its new name.
|
|
190
|
+
//
|
|
191
|
+
// Total in the ID, deliberately: an ID that matches SEVERAL living
|
|
192
|
+
// requirements has already been refused as
|
|
193
|
+
// `delta.living-requirement-id-invalid` (severity error, so archive is
|
|
194
|
+
// gated either way), and the living document is where it gets fixed.
|
|
195
|
+
// Falling through instead added `delta.modified-unknown` — "does not
|
|
196
|
+
// exist … Did you mean ADDED?" — beside it, which is both false and
|
|
197
|
+
// the more actionable-sounding of the two, and it sends the author to
|
|
198
|
+
// edit the delta over a problem that is not in the delta. There is no
|
|
199
|
+
// baseline to check against an ambiguous selection, so the pin is
|
|
200
|
+
// simply not graded until the ambiguity is gone.
|
|
201
|
+
if (idMatches.length > 0) {
|
|
202
|
+
const selected = idMatches.length === 1 ? idMatches[0] : undefined;
|
|
203
|
+
if (selected !== undefined)
|
|
204
|
+
checkBaseline(r, selected);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (living.names.has(r.name)) {
|
|
209
|
+
// Twins under one heading are already refused
|
|
210
|
+
// (`delta.living-duplicate-requirement`), and applyRequirementDelta
|
|
211
|
+
// takes the first either way — so the pin is compared against the
|
|
212
|
+
// same requirement the merge would rewrite.
|
|
213
|
+
checkBaseline(r, living.byName.get(r.name)[0]);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const other = await claims.added(scope, r);
|
|
217
|
+
if (other !== undefined) {
|
|
218
|
+
issues.push({
|
|
219
|
+
severity: "warn",
|
|
220
|
+
code: r.kind === "MODIFIED" ? "delta.modified-pending" : "delta.removed-pending",
|
|
221
|
+
subject,
|
|
222
|
+
message: `${where}: ${r.kind} requirement '${r.name}' is not in the ${livingDoc} yet — ${other} introduces it. Archive ${other} first.`,
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
issues.push({
|
|
227
|
+
severity: "error",
|
|
228
|
+
code: r.kind === "MODIFIED" ? "delta.modified-unknown" : "delta.removed-unknown",
|
|
229
|
+
subject,
|
|
230
|
+
message: r.kind === "MODIFIED"
|
|
231
|
+
? `${where}: MODIFIED requirement '${r.name}' does not exist in the ${livingDoc} — the merge would create it. Did you mean ADDED?`
|
|
232
|
+
: `${where}: REMOVED requirement '${r.name}' does not exist in the ${livingDoc} — there is nothing to remove`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return issues;
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/core/delta/select.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What each delta requirement SELECTS in the living document, and whether the\n * merge would do what its author meant.\n *\n * This is the half that needs both documents at once, which is why it is not in\n * `./document.ts`. Every refusal here is a shape the merge would apply cleanly\n * and silently: MODIFIED of a requirement that does not exist is merged as a\n * creation, ADDED of one that does exist REPLACES it scenarios and all, and a\n * stale `Based-On:` means someone landed a change in between that this delta\n * would overwrite with text written against a document that no longer exists.\n */\nimport { type Issue } from \"../vocabulary/issue.js\";\nimport { isRequirementsHeading } from \"../document/parse.js\";\nimport {\n basedOnDeclarations,\n REQUIREMENT_DIGEST_RE,\n requirementDigest,\n type Requirement,\n} from \"../document/spec.js\";\nimport { type ClaimLookup } from \"./claims.js\";\nimport { type DeltaScope, type LivingIndex } from \"./scope.js\";\n\nexport async function selectionIssues(\n scope: DeltaScope,\n reqs: Requirement[],\n living: LivingIndex,\n claims: ClaimLookup,\n): Promise<Issue[]> {\n const { featureId, subject, where, livingDoc } = scope;\n const issues: Issue[] = [];\n /**\n * The pin, against the living requirement this delta actually selects.\n *\n * This is the check `delta.modified-conflict` cannot be: that one names\n * the other feature while BOTH are in flight, and the collision that\n * happens on a fleet outlives the window. A archives, leaves `features/`,\n * and stops being an active claim; B revalidates GREEN, and its MODIFIED —\n * carrying the full text it wrote against the pre-A document — replaces\n * A's requirement wholesale, scenarios and all, with `+0 ~1 -0` and exit\n * 0. Nothing downstream can even detect it afterwards: `unarchive A`\n * refuses `snapshot-stale`, and `--force` takes B out with it.\n *\n * A digest of what the author read closes that by not depending on\n * timing at all. Stale is an ERROR and gates, because there is no reading\n * of a stale delta under which the merge is what its author meant.\n * Missing is a warning that GATES: the document is legal — a delta\n * adopted from OpenSpec never had the line — but an unpinned MODIFIED or\n * REMOVED is exactly the timing hole the pin exists to close, and letting\n * it through archived the silent rollback at exit 0. The migrated corpus\n * is why this stays a warning rather than an error, and why the refusal\n * is not an outage: `loam rebase <feat>` pins every delta in one command,\n * and `--approve` remains the way to say the unpinned merge is meant.\n */\n const checkBaseline = (r: Requirement, selected: Requirement): void => {\n // A pin the document pass already refused is not also stale: that would\n // send its author to `loam rebase` for a problem rebase does not fix.\n const declared = basedOnDeclarations(r);\n if (declared.length > 1) return;\n if (r.basedOn !== undefined && !REQUIREMENT_DIGEST_RE.test(r.basedOn)) return;\n const current = requirementDigest(selected);\n if (r.basedOn === undefined) {\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"delta.baseline-missing\",\n subject,\n message: `${where}: ${r.kind} requirement '${r.name}' carries no Based-On, so nothing can say whether the living text moved since this delta was written — merging it would replace whatever landed in between. Run \\`loam rebase ${featureId}\\` to pin it (Based-On: ${current}), or archive with --approve to merge unpinned deliberately.`,\n });\n return;\n }\n if (r.basedOn === current) return;\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-stale\",\n subject,\n message:\n `${where}: ${r.kind} requirement '${r.name}' was written against living version ${r.basedOn}, but the ${livingDoc} now holds ${current} — someone landed a change to it in between. ` +\n (r.kind === \"MODIFIED\"\n ? `This MODIFIED carries its FULL new text, so merging it would replace theirs outright.`\n : `Merging this REMOVED would delete what they landed.`) +\n ` Re-read the living requirement, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n };\n\n for (const r of reqs) {\n // BASE in a delta file means the requirement is under no delta section, so\n // the merge skips it. Reported rather than merged: deciding that `## Behavior`\n // means ADDED would be archive guessing at intent, and it would guess wrong\n // for every prose section that quotes a requirement as documentation. A\n // requirement above every heading has no heading to name, and no claim to be\n // written in the delta grammar at all — same judgement as `## Requirements`.\n //\n // A warning that GATES archive — the one check where the two axes\n // diverge. Severity says whether the document is valid: this shape is\n // legal OpenSpec, so an error would fail `loam validate` on every\n // adopted repo whose active deltas still use it. Gating says whether\n // the merge is safe: it is not — the merge would silently drop authored\n // content, the exact loss this module exists to prevent — so archive\n // refuses, and `--approve` remains the way to say the loss is meant.\n if (r.kind === \"BASE\") {\n if (r.section !== undefined && !isRequirementsHeading(r.section)) {\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"delta.requirement-not-merged\",\n subject,\n message: `${where}: requirement '${r.name}' sits under '${r.section}', which is not a delta section — archive will NOT merge it. Move it under '## ADDED Requirements' (or MODIFIED/REMOVED), or drop it if it is documentation.`,\n });\n }\n continue;\n }\n\n if (r.kind === \"ADDED\") {\n const idMatches = r.id === undefined ? [] : (living.byId.get(r.id) ?? []);\n const nameMatches = living.byName.get(r.name) ?? [];\n const nameSelectsOther = r.id !== undefined && nameMatches.some((candidate) => candidate.id !== r.id);\n if (nameSelectsOther) {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-identity-collision\",\n subject,\n message: `${where}: ADDED requirement '${r.name}' carries Requirement-ID '${r.id}', but that heading already identifies a different living requirement — ID and name cannot select different identities`,\n });\n continue;\n }\n if ((r.id === undefined && living.names.has(r.name)) || idMatches.length > 0) {\n issues.push({\n severity: \"error\",\n code: \"delta.added-duplicate\",\n subject,\n message: r.id === undefined\n ? `${where}: ADDED requirement '${r.name}' already exists in the ${livingDoc} — the merge would REPLACE it, scenarios and all. Use MODIFIED, or rename.`\n : `${where}: ADDED requirement '${r.name}' uses Requirement-ID '${r.id}', which already exists in the ${livingDoc} — use MODIFIED to change that identity`,\n });\n continue;\n }\n // Merge identity is exact string equality (applyRequirementDelta), so a\n // name that differs only in case slips past the duplicate check above and\n // lands as a SECOND requirement next to the living one. A warning, not an\n // error: the author may genuinely mean a distinct requirement, but with\n // LLM authors the case drift is the likely story. Never fires together\n // with added-duplicate — an exact match continues before this runs.\n const near = living.folded.get(r.name.toLowerCase());\n if (near !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.added-near-duplicate\",\n subject,\n message: `${where}: ADDED requirement '${r.name}' differs only in case from living requirement '${near}' — the merge matches names exactly, so both would coexist. Match the living spelling and use MODIFIED, or pick a distinct name.`,\n });\n }\n const other = await claims.added(scope, r);\n if (other !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.added-conflict\",\n subject,\n message: `${where}: requirement '${r.name}' is also added by ${other} — whichever archives first lands it, and the second archive is refused (delta.added-duplicate) unless --approve`,\n });\n }\n continue;\n }\n\n // MODIFIED vs MODIFIED on ONE living requirement. The ADDED case has\n // been caught since delta.added-conflict: two features adding the same\n // name collide loudly, because the second archive is refused. Two\n // features CHANGING the same requirement collide in total silence —\n // both deltas apply cleanly, and whichever archives second replaces\n // the first's text, scenarios and all, with a version written against\n // a document that no longer exists. A warning, not a gate: the second\n // author may well be rewriting on purpose, and only they can say.\n const alsoChanged = await claims.changed(scope, r);\n if (alsoChanged !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.modified-conflict\",\n subject,\n message: `${where}: ${r.kind} requirement '${r.name}' is also changed by ${alsoChanged} — both deltas apply cleanly, so whichever archives second REPLACES the other's text wholesale. Agree on one owner, or fold the two changes together.`,\n });\n }\n\n if (r.id !== undefined) {\n const idMatches = living.byId.get(r.id) ?? [];\n const nameMatches = living.byName.get(r.name) ?? [];\n const nameSelectsOther = nameMatches.some((candidate) => candidate.id !== r.id);\n // The second disjunct is defensive rather than reachable: a living\n // requirement under this heading that the ID did NOT select must carry\n // some other ID, which is what `nameSelectsOther` already says. It is\n // kept because it states the rule the reader is being told — ID and\n // heading must select the same identity — without depending on that\n // derivation holding after the next edit.\n if (nameSelectsOther || (idMatches.length === 0 && nameMatches.length > 0)) {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-identity-collision\",\n subject,\n message: `${where}: ${r.kind} requirement '${r.name}' carries Requirement-ID '${r.id}', but its ID and heading select different living requirements — fix the ID or heading; archive will not guess`,\n });\n continue;\n }\n // A differing heading is the explicit loam rename mechanism: the\n // stable ID selects the old requirement, MODIFIED supplies its new name.\n //\n // Total in the ID, deliberately: an ID that matches SEVERAL living\n // requirements has already been refused as\n // `delta.living-requirement-id-invalid` (severity error, so archive is\n // gated either way), and the living document is where it gets fixed.\n // Falling through instead added `delta.modified-unknown` — \"does not\n // exist … Did you mean ADDED?\" — beside it, which is both false and\n // the more actionable-sounding of the two, and it sends the author to\n // edit the delta over a problem that is not in the delta. There is no\n // baseline to check against an ambiguous selection, so the pin is\n // simply not graded until the ambiguity is gone.\n if (idMatches.length > 0) {\n const selected = idMatches.length === 1 ? idMatches[0] : undefined;\n if (selected !== undefined) checkBaseline(r, selected);\n continue;\n }\n } else if (living.names.has(r.name)) {\n // Twins under one heading are already refused\n // (`delta.living-duplicate-requirement`), and applyRequirementDelta\n // takes the first either way — so the pin is compared against the\n // same requirement the merge would rewrite.\n checkBaseline(r, living.byName.get(r.name)![0]!);\n continue;\n }\n\n const other = await claims.added(scope, r);\n if (other !== undefined) {\n issues.push({\n severity: \"warn\",\n code: r.kind === \"MODIFIED\" ? \"delta.modified-pending\" : \"delta.removed-pending\",\n subject,\n message: `${where}: ${r.kind} requirement '${r.name}' is not in the ${livingDoc} yet — ${other} introduces it. Archive ${other} first.`,\n });\n continue;\n }\n issues.push({\n severity: \"error\",\n code: r.kind === \"MODIFIED\" ? \"delta.modified-unknown\" : \"delta.removed-unknown\",\n subject,\n message:\n r.kind === \"MODIFIED\"\n ? `${where}: MODIFIED requirement '${r.name}' does not exist in the ${livingDoc} — the merge would create it. Did you mean ADDED?`\n : `${where}: REMOVED requirement '${r.name}' does not exist in the ${livingDoc} — there is nothing to remove`,\n });\n }\n return issues;\n}\n"]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The active-feature dependency graph: who has to archive before whom.
|
|
3
|
+
*
|
|
4
|
+
* A feature depends on another when it MODIFIES or REMOVES a requirement the
|
|
5
|
+
* other adds, or calls an operation the other introduces — the two ways one
|
|
6
|
+
* delta can only apply after another has landed. This module is the walk that
|
|
7
|
+
* puts `./facts.ts` and `./graph.ts` together; the reasons it attaches to each
|
|
8
|
+
* edge are what makes the answer explainable rather than merely true.
|
|
9
|
+
*/
|
|
10
|
+
import { inOrder } from "../kernel/concurrency.js";
|
|
11
|
+
import { FleetContext } from "../fleet-context.js";
|
|
12
|
+
import { compareIds } from "../repo/entries.js";
|
|
13
|
+
import { listFeatures } from "../repo/repo.js";
|
|
14
|
+
import { addOwner, opIndexKey, readFacts, reqIndexKey, reqKeys, } from "./facts.js";
|
|
15
|
+
import { appendReason, compareReasons, dependencyFirstOrder, stronglyConnected } from "./graph.js";
|
|
16
|
+
/** Derive the complete active-feature graph, or one feature and its transitive prerequisites. */
|
|
17
|
+
export async function analyzeDependencies(docsDir, featureId, context = new FleetContext()) {
|
|
18
|
+
const features = await listFeatures(docsDir, {}, context);
|
|
19
|
+
// Bounded, not `Promise.all`: `readFacts` loads the feature's delta.likec4,
|
|
20
|
+
// and each of those is a whole Langium workspace held open until the read
|
|
21
|
+
// finishes — the same resource `validate --all` rations for the same measured
|
|
22
|
+
// reason (see core/kernel/concurrency.ts). This module fanned out over every active
|
|
23
|
+
// feature at once, and it is not only `loam dependencies` that pays: the fleet
|
|
24
|
+
// form of `loam status` calls in here too. `inOrder` returns results in input
|
|
25
|
+
// order, so the graph, the conflict list and their ordering are unchanged.
|
|
26
|
+
const facts = await inOrder(features, (feature) => readFacts(docsDir, feature, context));
|
|
27
|
+
const requirementOwners = new Map();
|
|
28
|
+
// The second index over the SAME identity: who MODIFIES or REMOVES it. Only
|
|
29
|
+
// the first index existed, so the graph could see "two features add the same
|
|
30
|
+
// requirement" and was blind to "two features rewrite the same requirement" —
|
|
31
|
+
// the collision that actually happens on a fleet, and the one whose loser
|
|
32
|
+
// loses their whole authored text without a word from any command.
|
|
33
|
+
const changedOwners = new Map();
|
|
34
|
+
const operationOwners = new Map();
|
|
35
|
+
for (const fact of facts) {
|
|
36
|
+
for (const added of fact.addedRequirements) {
|
|
37
|
+
for (const identity of reqKeys(added.requirement)) {
|
|
38
|
+
const at = { service: added.service, axis: added.axis, identity };
|
|
39
|
+
addOwner(requirementOwners, reqIndexKey(at), at, fact.feature.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const changed of fact.changedRequirements) {
|
|
43
|
+
for (const identity of reqKeys(changed.requirement)) {
|
|
44
|
+
const at = { service: changed.service, axis: changed.axis, identity };
|
|
45
|
+
addOwner(changedOwners, reqIndexKey(at), at, fact.feature.id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const operation of fact.introducedOperations) {
|
|
49
|
+
addOwner(operationOwners, opIndexKey(operation), operation, fact.feature.id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const edgeMap = new Map();
|
|
53
|
+
for (const fact of facts) {
|
|
54
|
+
for (const changed of fact.changedRequirements) {
|
|
55
|
+
for (const identity of reqKeys(changed.requirement)) {
|
|
56
|
+
const at = { service: changed.service, axis: changed.axis, identity };
|
|
57
|
+
for (const owner of requirementOwners.get(reqIndexKey(at))?.features ?? []) {
|
|
58
|
+
appendReason(edgeMap, fact.feature.id, owner, { kind: "requirement", ...at });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const required of fact.requiredOperations) {
|
|
63
|
+
for (const owner of operationOwners.get(opIndexKey(required))?.features ?? []) {
|
|
64
|
+
appendReason(edgeMap, fact.feature.id, owner, { kind: "operation", ...required });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let edges = [...edgeMap.values()]
|
|
69
|
+
.map((edge) => ({ ...edge, reasons: edge.reasons.sort(compareReasons) }))
|
|
70
|
+
.sort((a, b) => compareIds(a.from, b.from) || compareIds(a.to, b.to));
|
|
71
|
+
const conflicts = [];
|
|
72
|
+
for (const [index, change] of [
|
|
73
|
+
[requirementOwners, "added"],
|
|
74
|
+
[changedOwners, "changed"],
|
|
75
|
+
]) {
|
|
76
|
+
for (const { at, features } of index.values()) {
|
|
77
|
+
if (features.size < 2)
|
|
78
|
+
continue;
|
|
79
|
+
conflicts.push({
|
|
80
|
+
kind: "requirement",
|
|
81
|
+
change,
|
|
82
|
+
service: at.service,
|
|
83
|
+
axis: at.axis,
|
|
84
|
+
identity: at.identity,
|
|
85
|
+
features: [...features].sort(compareIds),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const { at, features } of operationOwners.values()) {
|
|
90
|
+
if (features.size < 2)
|
|
91
|
+
continue;
|
|
92
|
+
conflicts.push({
|
|
93
|
+
kind: "operation",
|
|
94
|
+
// Two features DEFINING the same operationId — the only way an operation
|
|
95
|
+
// collides, since removals address a living slot rather than claim one.
|
|
96
|
+
change: "added",
|
|
97
|
+
service: at.service,
|
|
98
|
+
identity: at.operationId,
|
|
99
|
+
features: [...features].sort(compareIds),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
conflicts.sort((a, b) => compareIds(a.kind, b.kind)
|
|
103
|
+
|| compareIds(a.change, b.change)
|
|
104
|
+
|| compareIds(a.service, b.service)
|
|
105
|
+
|| compareIds(a.axis ?? "", b.axis ?? "")
|
|
106
|
+
|| compareIds(a.identity, b.identity));
|
|
107
|
+
// `Set<string>`, not the ids' own brand: `DependencyEdge.from`/`to` are plain
|
|
108
|
+
// strings, and the reassignment below rebuilds the set from a caller's
|
|
109
|
+
// argument.
|
|
110
|
+
let selected = new Set(features.map((feature) => feature.id));
|
|
111
|
+
if (featureId !== undefined) {
|
|
112
|
+
selected = new Set([featureId]);
|
|
113
|
+
let changed = true;
|
|
114
|
+
while (changed) {
|
|
115
|
+
changed = false;
|
|
116
|
+
for (const edge of edges) {
|
|
117
|
+
if (selected.has(edge.from) && !selected.has(edge.to)) {
|
|
118
|
+
selected.add(edge.to);
|
|
119
|
+
changed = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
edges = edges.filter((edge) => selected.has(edge.from) && selected.has(edge.to));
|
|
124
|
+
}
|
|
125
|
+
const selectedFeatures = features.filter((feature) => selected.has(feature.id));
|
|
126
|
+
const ids = selectedFeatures.map((feature) => feature.id);
|
|
127
|
+
const nodes = selectedFeatures.map((feature) => ({
|
|
128
|
+
id: feature.id,
|
|
129
|
+
dirName: feature.dirName,
|
|
130
|
+
services: [...feature.services],
|
|
131
|
+
dependsOn: edges.filter((edge) => edge.from === feature.id).map((edge) => edge.to).sort(compareIds),
|
|
132
|
+
}));
|
|
133
|
+
return {
|
|
134
|
+
nodes,
|
|
135
|
+
edges,
|
|
136
|
+
conflicts: conflicts.filter((conflict) => conflict.features.some((id) => selected.has(id))),
|
|
137
|
+
order: dependencyFirstOrder(ids, edges),
|
|
138
|
+
cycles: stronglyConnected(ids, edges),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=dependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../../src/core/dependencies/dependencies.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The active-feature dependency graph: who has to archive before whom.\n *\n * A feature depends on another when it MODIFIES or REMOVES a requirement the\n * other adds, or calls an operation the other introduces — the two ways one\n * delta can only apply after another has landed. This module is the walk that\n * puts `./facts.ts` and `./graph.ts` together; the reasons it attaches to each\n * edge are what makes the answer explainable rather than merely true.\n */\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { compareIds } from \"../repo/entries.js\";\nimport { listFeatures } from \"../repo/repo.js\";\nimport {\n addOwner,\n opIndexKey,\n readFacts,\n reqIndexKey,\n reqKeys,\n type DependencyEdge,\n type DependencyGraph,\n type DependencyNode,\n type FeatureConflict,\n type OpAt,\n type Owners,\n type ReqAt,\n} from \"./facts.js\";\nimport { appendReason, compareReasons, dependencyFirstOrder, stronglyConnected } from \"./graph.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/** Derive the complete active-feature graph, or one feature and its transitive prerequisites. */\nexport async function analyzeDependencies(\n docsDir: DocsDir,\n featureId?: string,\n context = new FleetContext(),\n): Promise<DependencyGraph> {\n const features = await listFeatures(docsDir, {}, context);\n // Bounded, not `Promise.all`: `readFacts` loads the feature's delta.likec4,\n // and each of those is a whole Langium workspace held open until the read\n // finishes — the same resource `validate --all` rations for the same measured\n // reason (see core/kernel/concurrency.ts). This module fanned out over every active\n // feature at once, and it is not only `loam dependencies` that pays: the fleet\n // form of `loam status` calls in here too. `inOrder` returns results in input\n // order, so the graph, the conflict list and their ordering are unchanged.\n const facts = await inOrder(features, (feature) => readFacts(docsDir, feature, context));\n const requirementOwners = new Map<string, Owners<ReqAt>>();\n // The second index over the SAME identity: who MODIFIES or REMOVES it. Only\n // the first index existed, so the graph could see \"two features add the same\n // requirement\" and was blind to \"two features rewrite the same requirement\" —\n // the collision that actually happens on a fleet, and the one whose loser\n // loses their whole authored text without a word from any command.\n const changedOwners = new Map<string, Owners<ReqAt>>();\n const operationOwners = new Map<string, Owners<OpAt>>();\n\n for (const fact of facts) {\n for (const added of fact.addedRequirements) {\n for (const identity of reqKeys(added.requirement)) {\n const at: ReqAt = { service: added.service, axis: added.axis, identity };\n addOwner(requirementOwners, reqIndexKey(at), at, fact.feature.id);\n }\n }\n for (const changed of fact.changedRequirements) {\n for (const identity of reqKeys(changed.requirement)) {\n const at: ReqAt = { service: changed.service, axis: changed.axis, identity };\n addOwner(changedOwners, reqIndexKey(at), at, fact.feature.id);\n }\n }\n for (const operation of fact.introducedOperations) {\n addOwner(operationOwners, opIndexKey(operation), operation, fact.feature.id);\n }\n }\n\n const edgeMap = new Map<string, DependencyEdge>();\n for (const fact of facts) {\n for (const changed of fact.changedRequirements) {\n for (const identity of reqKeys(changed.requirement)) {\n const at: ReqAt = { service: changed.service, axis: changed.axis, identity };\n for (const owner of requirementOwners.get(reqIndexKey(at))?.features ?? []) {\n appendReason(edgeMap, fact.feature.id, owner, { kind: \"requirement\", ...at });\n }\n }\n }\n for (const required of fact.requiredOperations) {\n for (const owner of operationOwners.get(opIndexKey(required))?.features ?? []) {\n appendReason(edgeMap, fact.feature.id, owner, { kind: \"operation\", ...required });\n }\n }\n }\n\n let edges = [...edgeMap.values()]\n .map((edge) => ({ ...edge, reasons: edge.reasons.sort(compareReasons) }))\n .sort((a, b) => compareIds(a.from, b.from) || compareIds(a.to, b.to));\n\n const conflicts: FeatureConflict[] = [];\n for (const [index, change] of [\n [requirementOwners, \"added\"],\n [changedOwners, \"changed\"],\n ] as const) {\n for (const { at, features } of index.values()) {\n if (features.size < 2) continue;\n conflicts.push({\n kind: \"requirement\",\n change,\n service: at.service,\n axis: at.axis,\n identity: at.identity,\n features: [...features].sort(compareIds),\n });\n }\n }\n for (const { at, features } of operationOwners.values()) {\n if (features.size < 2) continue;\n conflicts.push({\n kind: \"operation\",\n // Two features DEFINING the same operationId — the only way an operation\n // collides, since removals address a living slot rather than claim one.\n change: \"added\",\n service: at.service,\n identity: at.operationId,\n features: [...features].sort(compareIds),\n });\n }\n conflicts.sort((a, b) =>\n compareIds(a.kind, b.kind)\n || compareIds(a.change, b.change)\n || compareIds(a.service, b.service)\n || compareIds(a.axis ?? \"\", b.axis ?? \"\")\n || compareIds(a.identity, b.identity));\n\n // `Set<string>`, not the ids' own brand: `DependencyEdge.from`/`to` are plain\n // strings, and the reassignment below rebuilds the set from a caller's\n // argument.\n let selected = new Set<string>(features.map((feature) => feature.id));\n if (featureId !== undefined) {\n selected = new Set<string>([featureId]);\n let changed = true;\n while (changed) {\n changed = false;\n for (const edge of edges) {\n if (selected.has(edge.from) && !selected.has(edge.to)) {\n selected.add(edge.to);\n changed = true;\n }\n }\n }\n edges = edges.filter((edge) => selected.has(edge.from) && selected.has(edge.to));\n }\n\n const selectedFeatures = features.filter((feature) => selected.has(feature.id));\n const ids = selectedFeatures.map((feature) => feature.id);\n const nodes = selectedFeatures.map((feature): DependencyNode => ({\n id: feature.id,\n dirName: feature.dirName,\n services: [...feature.services],\n dependsOn: edges.filter((edge) => edge.from === feature.id).map((edge) => edge.to).sort(compareIds),\n }));\n\n return {\n nodes,\n edges,\n conflicts: conflicts.filter((conflict) => conflict.features.some((id) => selected.has(id))),\n order: dependencyFirstOrder(ids, edges),\n cycles: stronglyConnected(ids, edges),\n };\n}\n"]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one feature ADDS and what it NEEDS, read out of its own delta.
|
|
3
|
+
*
|
|
4
|
+
* The whole graph is built from these five lists and nothing else, which is why
|
|
5
|
+
* they are computed once per feature and indexed rather than re-derived per
|
|
6
|
+
* edge: a feature is a node here only because some other feature's requirement
|
|
7
|
+
* or operation resolves into it, and answering that per pair would be a fleet
|
|
8
|
+
* walk per pair.
|
|
9
|
+
*
|
|
10
|
+
* `reqKeys` returns BOTH spellings of a requirement's identity — the stable
|
|
11
|
+
* `Requirement-ID` and the exact heading — because a legacy requirement has only
|
|
12
|
+
* the second, and an edge that existed under one spelling and not the other
|
|
13
|
+
* would make the graph depend on when the corpus was migrated.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { FleetContext } from "../fleet-context.js";
|
|
17
|
+
import { serviceResolver } from "../c4/resolve/service.js";
|
|
18
|
+
import { operations } from "../openapi/doc.js";
|
|
19
|
+
import {} from "../repo/entries.js";
|
|
20
|
+
import { SPEC_AXES, featurePaths, featureSpecPaths } from "../repo/paths.js";
|
|
21
|
+
import { locateServicePaths } from "../repo/service-target.js";
|
|
22
|
+
import { enumeratedServiceIds, enumeratedServiceIndex } from "../repo/service-target.js";
|
|
23
|
+
export function reqKeys(requirement) {
|
|
24
|
+
return [
|
|
25
|
+
...(requirement.id === undefined ? [] : [`id:${requirement.id}`]),
|
|
26
|
+
`name:${requirement.name}`,
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
export function reqIndexKey(at) {
|
|
30
|
+
return `${at.service}\0${at.axis}\0${at.identity}`;
|
|
31
|
+
}
|
|
32
|
+
export function opIndexKey(at) {
|
|
33
|
+
return `${at.service}\0${at.operationId}`;
|
|
34
|
+
}
|
|
35
|
+
export function addOwner(index, key, at, feature) {
|
|
36
|
+
const owners = index.get(key) ?? { at, features: new Set() };
|
|
37
|
+
owners.features.add(feature);
|
|
38
|
+
index.set(key, owners);
|
|
39
|
+
}
|
|
40
|
+
export async function readFacts(docsDir, feature, context) {
|
|
41
|
+
const addedRequirements = [];
|
|
42
|
+
const changedRequirements = [];
|
|
43
|
+
const introducedOperations = [];
|
|
44
|
+
const requiredOperations = [];
|
|
45
|
+
/**
|
|
46
|
+
* The operationIds a service ALREADY has, per service.
|
|
47
|
+
*
|
|
48
|
+
* This is the whole difference between a dependency graph and a rumour mill.
|
|
49
|
+
* A feature's `openapi.yaml` is a COMPLETE document, not a patch — authors
|
|
50
|
+
* restate the living API around the slot they are changing — so every feature
|
|
51
|
+
* that so much as mentions `authorizePayment` used to be recorded as
|
|
52
|
+
* INTRODUCING it, and every other feature governing it as REQUIRING it. Two
|
|
53
|
+
* features quoting the same living operation therefore depended on each other
|
|
54
|
+
* in both directions: an invented cycle, an invented conflict, and an ordering
|
|
55
|
+
* for work that had none. `loam validate`'s coherence pass already subtracts
|
|
56
|
+
* the living contract this way (core/coherence/coherence.ts); this is the
|
|
57
|
+
* same subtraction, so the two cannot disagree about what "new" means.
|
|
58
|
+
*
|
|
59
|
+
* Cached per service inside the call and, through the FleetContext, across
|
|
60
|
+
* features: a fleet-wide graph asks about the same ten services N times.
|
|
61
|
+
*/
|
|
62
|
+
const livingByService = new Map();
|
|
63
|
+
const living = async (service) => {
|
|
64
|
+
let ids = livingByService.get(service);
|
|
65
|
+
if (ids === undefined) {
|
|
66
|
+
ids = new Set((await operations((await locateServicePaths(docsDir, service, context)).openapi, context))
|
|
67
|
+
.filter((op) => !op.remove)
|
|
68
|
+
.map((op) => op.id));
|
|
69
|
+
livingByService.set(service, ids);
|
|
70
|
+
}
|
|
71
|
+
return ids;
|
|
72
|
+
};
|
|
73
|
+
for (const service of feature.services) {
|
|
74
|
+
const paths = featureSpecPaths(feature.dir, service);
|
|
75
|
+
const livingIds = await living(service);
|
|
76
|
+
for (const axis of SPEC_AXES) {
|
|
77
|
+
const path = paths[axis.key];
|
|
78
|
+
if (!existsSync(path))
|
|
79
|
+
continue;
|
|
80
|
+
const requirements = await context.readRequirements(path);
|
|
81
|
+
for (const requirement of requirements) {
|
|
82
|
+
const fact = { service, axis: axis.file, requirement };
|
|
83
|
+
if (requirement.kind === "ADDED")
|
|
84
|
+
addedRequirements.push(fact);
|
|
85
|
+
if (requirement.kind === "MODIFIED" || requirement.kind === "REMOVED") {
|
|
86
|
+
changedRequirements.push(fact);
|
|
87
|
+
}
|
|
88
|
+
for (const operationId of requirement.operations) {
|
|
89
|
+
// Governing an operation the service already provides is not a
|
|
90
|
+
// dependency on anybody: the contract it needs is already merged.
|
|
91
|
+
if (livingIds.has(operationId))
|
|
92
|
+
continue;
|
|
93
|
+
requiredOperations.push({ service, operationId });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (existsSync(paths.openapi)) {
|
|
98
|
+
const featureOperations = await operations(paths.openapi, context);
|
|
99
|
+
for (const operation of featureOperations) {
|
|
100
|
+
if (livingIds.has(operation.id))
|
|
101
|
+
continue;
|
|
102
|
+
if (operation.remove)
|
|
103
|
+
requiredOperations.push({ service, operationId: operation.id });
|
|
104
|
+
else
|
|
105
|
+
introducedOperations.push({ service, operationId: operation.id });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// The C4 delta is the FOURTH place a feature names an operation, and it was
|
|
110
|
+
// the one the graph could not see: an edge carrying `metadata { op 'x' }` is
|
|
111
|
+
// a call this feature builds on `x`, which is exactly the "must land first"
|
|
112
|
+
// relation this module exists to compute. Validate already says so — it
|
|
113
|
+
// reports `c4-api.op-pending` when the op is defined by another feature in
|
|
114
|
+
// flight — so the graph disagreeing with the validator about the same pair of
|
|
115
|
+
// features was the graph being wrong, not quiet.
|
|
116
|
+
//
|
|
117
|
+
// Endpoints resolve through `serviceResolver` — the element's
|
|
118
|
+
// `metadata { service }` binding, title as fallback — the same join every
|
|
119
|
+
// other check uses, so renaming a box in a diagram cannot silently unhook it
|
|
120
|
+
// here either.
|
|
121
|
+
const deltaPath = featurePaths(feature.dir).delta;
|
|
122
|
+
if (existsSync(deltaPath)) {
|
|
123
|
+
const doc = await context.loadLikeC4(deltaPath);
|
|
124
|
+
// A delta that does not parse proves nothing about who depends on whom.
|
|
125
|
+
// `loam validate` owns that diagnosis (`delta.invalid`); inventing edges out
|
|
126
|
+
// of a half-read document would be worse than the silence.
|
|
127
|
+
if (doc.errors.length === 0) {
|
|
128
|
+
// One resolver for the whole delta: `serviceOf` rebuilds its id map on
|
|
129
|
+
// every call, and this loop asks once per edge. The enumerated fleet —
|
|
130
|
+
// `services/` plus this feature's own `specs/` directories, the same
|
|
131
|
+
// union `enumeratedTarget` probes below — rides into the resolver so a
|
|
132
|
+
// delta that models CONTAINERS keeps its edges attached: without it, an
|
|
133
|
+
// edge into `payment.api` resolves to a service called "api" that has
|
|
134
|
+
// never existed, the op lookup misses the living contract, and the graph
|
|
135
|
+
// invents a dependency (or orders two features that share nothing). An
|
|
136
|
+
// unenumerable services/ falls back to the feature's own names — every
|
|
137
|
+
// living probe would have found nothing there anyway.
|
|
138
|
+
const fleetIds = await enumeratedServiceIds(docsDir, context);
|
|
139
|
+
const svcOf = serviceResolver(doc.elements, new Set([...fleetIds, ...feature.services]));
|
|
140
|
+
// The resolver answers with document text and `living` builds a path, so
|
|
141
|
+
// the name crosses through the enumeration — coherence.ts grades its
|
|
142
|
+
// edges through the same bridge. A name no enumeration vouches for has
|
|
143
|
+
// no living contract anywhere: the op is required, exactly what the
|
|
144
|
+
// probe answered for an absent service.
|
|
145
|
+
const enumeratedTarget = enumeratedServiceIndex(docsDir, feature.services, context);
|
|
146
|
+
for (const rel of doc.relationships) {
|
|
147
|
+
if (rel.op === undefined)
|
|
148
|
+
continue;
|
|
149
|
+
const service = svcOf(rel.target);
|
|
150
|
+
const pathable = await enumeratedTarget(service);
|
|
151
|
+
if (pathable !== undefined && (await living(pathable)).has(rel.op))
|
|
152
|
+
continue;
|
|
153
|
+
requiredOperations.push({ service, operationId: rel.op });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
feature,
|
|
159
|
+
addedRequirements,
|
|
160
|
+
changedRequirements,
|
|
161
|
+
introducedOperations,
|
|
162
|
+
requiredOperations,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=facts.js.map
|