@ybotok/loam 0.1.0-beta.2 → 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 +2570 -61
- 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 +2362 -245
- 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 +82 -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/agent/agents-stamp.js +155 -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/read.js +239 -0
- 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 +59 -55
- 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 +132 -77
- 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 -291
- 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 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- 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 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- 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/brief.js +0 -733
- 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 -559
- 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 -479
- 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 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- 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 -1104
- 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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The emission's commit window: lock the owned root, recover a predecessor,
|
|
3
|
+
* stage every write and delete, prove the tree still matches what reconcile
|
|
4
|
+
* graded, and land it through the journaled transaction.
|
|
5
|
+
*
|
|
6
|
+
* Split from gherkin.ts so the lock's extent is visible in the code shape,
|
|
7
|
+
* exactly as vouch's commitStamp is — and because this is the writer the
|
|
8
|
+
* roadmap named: a kill inside the old write/delete loop left a half-old,
|
|
9
|
+
* half-new suite with no marker, in the one repo (the service's own) where
|
|
10
|
+
* nothing else of loam's ever looked.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { mkdir } from "node:fs/promises";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { quietRmdir, rollbackStaged, stageWrites } from "../../core/staging/commit.js";
|
|
16
|
+
import { COMMIT_INTENT, InterruptedCommitError } from "../../core/staging/interrupted.js";
|
|
17
|
+
import { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from "../../core/staging/lock.js";
|
|
18
|
+
import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
|
|
19
|
+
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
20
|
+
import {} from "../../core/staging/writes.js";
|
|
21
|
+
import {} from "./reconcile.js";
|
|
22
|
+
/**
|
|
23
|
+
* Roll a predecessor's interrupted emission forward before ANYTHING reads the
|
|
24
|
+
* suite — reconcile grades the very bytes a half-commit corrupts. Cheap when
|
|
25
|
+
* there is nothing to do: no journal, no lock, no I/O beyond one existsSync.
|
|
26
|
+
*/
|
|
27
|
+
export async function recoverEmissionRoot(root) {
|
|
28
|
+
if (!existsSync(join(root, COMMIT_INTENT)))
|
|
29
|
+
return { ok: true, recovered: null };
|
|
30
|
+
let release;
|
|
31
|
+
try {
|
|
32
|
+
release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (err instanceof DocsBusyError)
|
|
36
|
+
return { ok: false, code: "docs-busy", message: err.message };
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return { ok: true, recovered: await recoverInterruptedCommit(root) };
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err instanceof InterruptedCommitError)
|
|
44
|
+
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await release();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stage, compare, journal, swap. The compare is against the bytes RECONCILE
|
|
53
|
+
* read: a `.feature` that changed (or appeared) between the grading and this
|
|
54
|
+
* commit is refused by name, never buried — the suite is code somebody may be
|
|
55
|
+
* editing, and "refused, not buried" is the no-clobber rule the roadmap
|
|
56
|
+
* spells for exactly this writer.
|
|
57
|
+
*/
|
|
58
|
+
export async function commitEmission(spec, plan) {
|
|
59
|
+
const { root, service, scope } = spec;
|
|
60
|
+
// The lock file lives in the root, so the root must exist first. If the
|
|
61
|
+
// commit then fails having created it, the empty directory is removed
|
|
62
|
+
// again — an empty loam/ flips the repo into "opted in" and tells
|
|
63
|
+
// `loam validate` the whole living suite is missing.
|
|
64
|
+
const rootExisted = existsSync(root);
|
|
65
|
+
await mkdir(root, { recursive: true });
|
|
66
|
+
let release;
|
|
67
|
+
try {
|
|
68
|
+
release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (!rootExisted)
|
|
72
|
+
await quietRmdir(root);
|
|
73
|
+
if (err instanceof DocsBusyError)
|
|
74
|
+
return { ok: false, code: "docs-busy", message: err.message };
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// A writer can have crashed between the command-start recovery and this
|
|
79
|
+
// window; the re-check is one existsSync when nothing happened.
|
|
80
|
+
let recovered;
|
|
81
|
+
try {
|
|
82
|
+
recovered = await recoverInterruptedCommit(root);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof InterruptedCommitError)
|
|
86
|
+
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
const planned = [
|
|
90
|
+
// A "written" row was graded against an ABSENT file, so its create is
|
|
91
|
+
// exclusive: a file appearing after planning loses the link(2) race and
|
|
92
|
+
// the run refuses rather than overwriting it.
|
|
93
|
+
...plan.writes.map((a) => a.action === "written" ? { path: a.path, content: a.content, exclusive: true } : { path: a.path, content: a.content }),
|
|
94
|
+
...plan.orphans.map((o) => ({ path: o.path, content: null })),
|
|
95
|
+
];
|
|
96
|
+
const staged = await stageWrites(planned);
|
|
97
|
+
// The bytes reconcile graded must be the bytes this commit replaces or
|
|
98
|
+
// deletes. `swapStaged` re-checks against ITS read just before each
|
|
99
|
+
// rename; this compare closes the other half of the window, from the
|
|
100
|
+
// grading to the staging.
|
|
101
|
+
const graded = new Map();
|
|
102
|
+
for (const a of plan.writes)
|
|
103
|
+
if (a.action === "replaced")
|
|
104
|
+
graded.set(a.path, a.raw);
|
|
105
|
+
for (const o of plan.orphans)
|
|
106
|
+
graded.set(o.path, o.raw);
|
|
107
|
+
const moved = staged
|
|
108
|
+
.filter((s) => {
|
|
109
|
+
const raw = graded.get(s.write.path);
|
|
110
|
+
return raw !== undefined && (s.before === null || !s.before.equals(raw));
|
|
111
|
+
})
|
|
112
|
+
.map((s) => s.write.path);
|
|
113
|
+
if (moved.length > 0) {
|
|
114
|
+
await rollbackStaged(staged);
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
code: "merge-failed",
|
|
118
|
+
message: `the suite changed while this emission was being planned — ${moved.join(", ")} ` +
|
|
119
|
+
`no longer holds the bytes the plan was graded against. Nothing was written: re-run.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const committed = await commitStaged({
|
|
123
|
+
root,
|
|
124
|
+
command: "gherkin",
|
|
125
|
+
rerun: scope.mode === "feature" ? `loam gherkin ${scope.featureId}` : "loam gherkin",
|
|
126
|
+
target: service,
|
|
127
|
+
}, staged, "emitted");
|
|
128
|
+
if (!committed.ok) {
|
|
129
|
+
return committed.raced
|
|
130
|
+
? {
|
|
131
|
+
ok: false,
|
|
132
|
+
code: "merge-failed",
|
|
133
|
+
message: "a .feature file appeared after planning and the emission refused to overwrite it — " +
|
|
134
|
+
`${committed.message}. Nothing else was written: re-run to grade the new file.`,
|
|
135
|
+
}
|
|
136
|
+
: { ok: false, code: committed.code, message: committed.message };
|
|
137
|
+
}
|
|
138
|
+
return { ok: true, recovered };
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
await release();
|
|
142
|
+
if (!rootExisted && !existsSync(join(root, COMMIT_INTENT)))
|
|
143
|
+
await quietRmdir(root);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/gherkin/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The emission's commit window: lock the owned root, recover a predecessor,\n * stage every write and delete, prove the tree still matches what reconcile\n * graded, and land it through the journaled transaction.\n *\n * Split from gherkin.ts so the lock's extent is visible in the code shape,\n * exactly as vouch's commitStamp is — and because this is the writer the\n * roadmap named: a kill inside the old write/delete loop left a half-old,\n * half-new suite with no marker, in the one repo (the service's own) where\n * nothing else of loam's ever looked.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { quietRmdir, rollbackStaged, stageWrites } from \"../../core/staging/commit.js\";\nimport { COMMIT_INTENT, type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { type ActionRow, type Scope } from \"./reconcile.js\";\n\nexport type EmissionCommit =\n | { ok: true; recovered: CommitRecovery | null }\n | { ok: false; code: \"docs-busy\" | \"commit-interrupted\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * Roll a predecessor's interrupted emission forward before ANYTHING reads the\n * suite — reconcile grades the very bytes a half-commit corrupts. Cheap when\n * there is nothing to do: no journal, no lock, no I/O beyond one existsSync.\n */\nexport async function recoverEmissionRoot(root: string): Promise<EmissionCommit> {\n if (!existsSync(join(root, COMMIT_INTENT))) return { ok: true, recovered: null };\n let release: () => Promise<void>;\n try {\n release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n return { ok: true, recovered: await recoverInterruptedCommit(root) };\n } catch (err) {\n if (err instanceof InterruptedCommitError) return { ok: false, code: \"commit-interrupted\", message: err.message };\n throw err;\n } finally {\n await release();\n }\n}\n\n/**\n * Stage, compare, journal, swap. The compare is against the bytes RECONCILE\n * read: a `.feature` that changed (or appeared) between the grading and this\n * commit is refused by name, never buried — the suite is code somebody may be\n * editing, and \"refused, not buried\" is the no-clobber rule the roadmap\n * spells for exactly this writer.\n */\nexport async function commitEmission(\n spec: { root: string; service: string; scope: Scope },\n plan: { writes: ActionRow[]; orphans: { path: string; raw: Buffer }[] },\n): Promise<EmissionCommit> {\n const { root, service, scope } = spec;\n // The lock file lives in the root, so the root must exist first. If the\n // commit then fails having created it, the empty directory is removed\n // again — an empty loam/ flips the repo into \"opted in\" and tells\n // `loam validate` the whole living suite is missing.\n const rootExisted = existsSync(root);\n await mkdir(root, { recursive: true });\n let release: () => Promise<void>;\n try {\n release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);\n } catch (err) {\n if (!rootExisted) await quietRmdir(root);\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n // A writer can have crashed between the command-start recovery and this\n // window; the re-check is one existsSync when nothing happened.\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(root);\n } catch (err) {\n if (err instanceof InterruptedCommitError) return { ok: false, code: \"commit-interrupted\", message: err.message };\n throw err;\n }\n\n const planned: PlannedWrite[] = [\n // A \"written\" row was graded against an ABSENT file, so its create is\n // exclusive: a file appearing after planning loses the link(2) race and\n // the run refuses rather than overwriting it.\n ...plan.writes.map((a): PlannedWrite =>\n a.action === \"written\" ? { path: a.path, content: a.content, exclusive: true } : { path: a.path, content: a.content },\n ),\n ...plan.orphans.map((o): PlannedWrite => ({ path: o.path, content: null })),\n ];\n const staged = await stageWrites(planned);\n\n // The bytes reconcile graded must be the bytes this commit replaces or\n // deletes. `swapStaged` re-checks against ITS read just before each\n // rename; this compare closes the other half of the window, from the\n // grading to the staging.\n const graded = new Map<string, Buffer>();\n for (const a of plan.writes) if (a.action === \"replaced\") graded.set(a.path, a.raw);\n for (const o of plan.orphans) graded.set(o.path, o.raw);\n const moved = staged\n .filter((s) => {\n const raw = graded.get(s.write.path);\n return raw !== undefined && (s.before === null || !s.before.equals(raw));\n })\n .map((s) => s.write.path);\n if (moved.length > 0) {\n await rollbackStaged(staged);\n return {\n ok: false,\n code: \"merge-failed\",\n message:\n `the suite changed while this emission was being planned — ${moved.join(\", \")} ` +\n `no longer holds the bytes the plan was graded against. Nothing was written: re-run.`,\n };\n }\n\n const committed = await commitStaged(\n {\n root,\n command: \"gherkin\",\n rerun: scope.mode === \"feature\" ? `loam gherkin ${scope.featureId}` : \"loam gherkin\",\n target: service,\n },\n staged,\n \"emitted\",\n );\n if (!committed.ok) {\n return committed.raced\n ? {\n ok: false,\n code: \"merge-failed\",\n message:\n \"a .feature file appeared after planning and the emission refused to overwrite it — \" +\n `${committed.message}. Nothing else was written: re-run to grade the new file.`,\n }\n : { ok: false, code: committed.code, message: committed.message };\n }\n return { ok: true, recovered };\n } finally {\n await release();\n if (!rootExisted && !existsSync(join(root, COMMIT_INTENT))) await quietRmdir(root);\n }\n}\n\n"]}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { relative } from "node:path";
|
|
4
|
+
import { loadConfig } from "../../core/envelope/config.js";
|
|
5
|
+
import { decodeDocument, NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
|
|
6
|
+
import { emitJson, emitJsonError, fail, NO_SERVICE_MESSAGE, reportNoConfig, } from "../../core/envelope/json.js";
|
|
7
|
+
import { featureSpecPaths, SPEC_AXES } from "../../core/repo/paths.js";
|
|
8
|
+
import { locateServicePaths } from "../../core/repo/service-target.js";
|
|
9
|
+
import { listFeatures, missingFeatureMessage, resolveFeature } from "../../core/repo/repo.js";
|
|
10
|
+
import { parseRequirements } from "../../core/document/parse.js";
|
|
11
|
+
import {} from "../../core/document/spec.js";
|
|
12
|
+
import { axisLabel, planEmission } from "../../core/gherkin/emit.js";
|
|
13
|
+
import { gherkinRoot } from "../../core/gherkin/stamp.js";
|
|
14
|
+
import { LOAM_VERSION } from "../../core/envelope/version.js";
|
|
15
|
+
import { UnsafePathError } from "../../core/kernel/path-safety.js";
|
|
16
|
+
import { commitEmission, recoverEmissionRoot } from "./commit.js";
|
|
17
|
+
import { reconcile } from "./reconcile.js";
|
|
18
|
+
import { render } from "./render.js";
|
|
19
|
+
import { sayRecovered } from "../policy/format.js";
|
|
20
|
+
export function registerGherkin(program) {
|
|
21
|
+
program
|
|
22
|
+
.command("gherkin")
|
|
23
|
+
.argument("[featureId]", "feature id — emit its ADDED/MODIFIED requirements; omit for the full living suite")
|
|
24
|
+
.description("Emit Gherkin .feature files from spec scenarios into this service repo's <gherkinDir>/loam/")
|
|
25
|
+
.option("--service <id>", "service to emit for (defaults to the configured service)")
|
|
26
|
+
.option("--dry-run", "print the plan (writes, replacements, deletions) and write nothing — beyond first finishing a predecessor's interrupted commit, exactly as a real run would")
|
|
27
|
+
.option("--json", "emit the machine contract instead of the human view")
|
|
28
|
+
.action(async (featureArg, opts) => {
|
|
29
|
+
const json = opts.json === true;
|
|
30
|
+
const dryRun = opts.dryRun === true;
|
|
31
|
+
const loaded = await loadConfig();
|
|
32
|
+
if (loaded.kind !== "loaded") {
|
|
33
|
+
reportNoConfig(json, loaded);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const config = loaded.config;
|
|
37
|
+
const service = opts.service ?? config.service;
|
|
38
|
+
if (service === undefined) {
|
|
39
|
+
return fail(json, "invalid-option", NO_SERVICE_MESSAGE);
|
|
40
|
+
}
|
|
41
|
+
// The emission writes into the repo loam is standing in, so it can only
|
|
42
|
+
// run where that repo is this service's — vouch's refusal, verbatim logic.
|
|
43
|
+
//
|
|
44
|
+
// TWO conditions, two codes, and the split is the whole point. Both used
|
|
45
|
+
// to answer `invalid-option`, whose `loam explain` row says the
|
|
46
|
+
// invocation itself is wrong — contradictory flags, a mistyped one, a
|
|
47
|
+
// value that cannot be right. Here the invocation is perfectly correct
|
|
48
|
+
// and the REPOSITORY is the wrong one, so that code sent a user whose
|
|
49
|
+
// flags were fine off to re-read their flags, and left CI and agents
|
|
50
|
+
// unable to tell "run this in the service repo" from "you typed the flag
|
|
51
|
+
// wrong". `loam verify --record --service` already draws exactly this
|
|
52
|
+
// line for exactly this reason (see commands/verify/verify.ts); gherkin
|
|
53
|
+
// and vouch now answer the same pair, so a caller branching on
|
|
54
|
+
// `error.code` gets one answer from all three. The messages are
|
|
55
|
+
// unchanged — only the code moved.
|
|
56
|
+
if (config.service === undefined) {
|
|
57
|
+
return fail(json, "repository-unavailable", `Cannot emit gherkin for '${service}' from here: this is not a service repo. Generated .feature files land in the service's own repo — run it there.`);
|
|
58
|
+
}
|
|
59
|
+
if (config.service !== service) {
|
|
60
|
+
return fail(json, "service-mismatch", `Cannot emit gherkin for '${service}' from here: this repo is '${config.service}'. Generated .feature files land in the service's own repo — run it there.`);
|
|
61
|
+
}
|
|
62
|
+
// The repo root, not the cwd: `config.root` is the directory loam.json
|
|
63
|
+
// was actually found in, and config discovery walks upward. Taken from
|
|
64
|
+
// `process.cwd()` this emitted the whole suite into whatever directory
|
|
65
|
+
// the caller happened to stand in — `src/api/features/loam/…` inside the
|
|
66
|
+
// source tree, which the next run from the root neither finds, replaces
|
|
67
|
+
// nor deletes, and which `loam validate` grades as a suite that was never
|
|
68
|
+
// generated.
|
|
69
|
+
const repoDir = config.root ?? process.cwd();
|
|
70
|
+
let root;
|
|
71
|
+
try {
|
|
72
|
+
root = gherkinRoot(repoDir, config.gherkinDir);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (!(err instanceof UnsafePathError))
|
|
76
|
+
throw err;
|
|
77
|
+
return fail(json, "invalid-option", `Cannot emit Gherkin: ${err.message}. The output directory must stay inside the service repo.`);
|
|
78
|
+
}
|
|
79
|
+
const rel = (abs) => relative(repoDir, abs).split(/[\\/]/).join("/");
|
|
80
|
+
// A predecessor's interrupted emission is rolled forward before ANY
|
|
81
|
+
// read — and before the feature argument resolves: reconcile grades the
|
|
82
|
+
// very bytes a half-commit corrupts, and the journal's stored rerun may
|
|
83
|
+
// name a feature that has since ARCHIVED, whose recovery must still
|
|
84
|
+
// work. `new` fixed the same ordering and documented it; refusing
|
|
85
|
+
// unknown-target over a wedged root left doctor printing a repair that
|
|
86
|
+
// could never run.
|
|
87
|
+
const early = await recoverEmissionRoot(root);
|
|
88
|
+
if (!early.ok)
|
|
89
|
+
return fail(json, early.code, early.message);
|
|
90
|
+
let recovered = early.recovered;
|
|
91
|
+
const feature = featureArg === undefined ? null : await resolveFeature(config.docsDir, featureArg, "exclude");
|
|
92
|
+
if (featureArg !== undefined && feature === null) {
|
|
93
|
+
return fail(json, "unknown-target", await missingFeatureMessage(config.docsDir, featureArg));
|
|
94
|
+
}
|
|
95
|
+
const scope = feature === null ? { mode: "living" } : { mode: "feature", featureId: feature.id };
|
|
96
|
+
// The requirement set, by scope. Feature mode takes ADDED and MODIFIED —
|
|
97
|
+
// a BASE requirement is the living state quoted, a REMOVED one is being
|
|
98
|
+
// retired, and neither is behaviour anybody is about to test. Living mode
|
|
99
|
+
// takes everything the living files hold.
|
|
100
|
+
//
|
|
101
|
+
// Read through `decodeDocument`, not `readFile(…, "utf8")`, and refused
|
|
102
|
+
// rather than degraded: a spec.md saved as UTF-16 decodes to a document
|
|
103
|
+
// with no requirements in it, which here is not merely a wrong answer but
|
|
104
|
+
// a destructive one — an empty plan makes every existing .feature under
|
|
105
|
+
// loam/ an orphan, and a living run DELETES its orphans. The whole
|
|
106
|
+
// generated suite would go because a docs file was written by PowerShell.
|
|
107
|
+
const byAxis = [];
|
|
108
|
+
const readSpec = async (path) => parseRequirements(decodeDocument(await readFile(path), path));
|
|
109
|
+
try {
|
|
110
|
+
if (feature !== null) {
|
|
111
|
+
// `config.service`, not `service`: the guard above proved they are
|
|
112
|
+
// the same id, and only the config's copy carries the load-time
|
|
113
|
+
// parse, so it is the pathable spelling — same as the else arm.
|
|
114
|
+
const paths = featureSpecPaths(feature.dir, config.service);
|
|
115
|
+
for (const axis of SPEC_AXES) {
|
|
116
|
+
const path = paths[axis.key];
|
|
117
|
+
const reqs = existsSync(path)
|
|
118
|
+
? (await readSpec(path)).filter((r) => r.kind === "ADDED" || r.kind === "MODIFIED")
|
|
119
|
+
: [];
|
|
120
|
+
byAxis.push({ axis, reqs });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// The guard above proved `service` IS `config.service`; only the
|
|
125
|
+
// latter carries the load-time parse, so it is the pathable spelling.
|
|
126
|
+
const paths = await locateServicePaths(config.docsDir, config.service);
|
|
127
|
+
if (!existsSync(paths.spec)) {
|
|
128
|
+
return fail(json, "unknown-target", `No living spec at ${paths.spec}. Run \`loam adopt\` for '${service}' first.`);
|
|
129
|
+
}
|
|
130
|
+
for (const axis of SPEC_AXES) {
|
|
131
|
+
const path = paths[axis.key];
|
|
132
|
+
const reqs = existsSync(path) ? (await readSpec(path)).filter((r) => r.kind !== "REMOVED") : [];
|
|
133
|
+
byAxis.push({ axis, reqs });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (!(err instanceof NotUtf8DocumentError))
|
|
139
|
+
throw err;
|
|
140
|
+
return fail(json, "repository-unavailable", `Cannot emit gherkin: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
// `service` is not decoration: it salts every `@loam-digest-…` stamp, and
|
|
143
|
+
// it is the same string `loam verify` files the matching claims under.
|
|
144
|
+
const plan = planEmission(byAxis, {
|
|
145
|
+
service,
|
|
146
|
+
featureTag: scope.mode === "feature" ? scope.featureId : undefined,
|
|
147
|
+
version: LOAM_VERSION,
|
|
148
|
+
});
|
|
149
|
+
// Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns
|
|
150
|
+
// only its own emissions: stamped files carrying this feature's tag whose
|
|
151
|
+
// file is no longer in the plan (a requirement renamed or dropped).
|
|
152
|
+
// `Set<string>`, not the ids' own brand: this set answers membership
|
|
153
|
+
// questions for tags read out of .feature files — document text.
|
|
154
|
+
const activeIds = new Set((await listFeatures(config.docsDir)).map((f) => f.id));
|
|
155
|
+
let reconciled;
|
|
156
|
+
try {
|
|
157
|
+
reconciled = await reconcile(plan, { root, repoDir }, scope, activeIds);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (!(err instanceof UnsafePathError))
|
|
161
|
+
throw err;
|
|
162
|
+
return fail(json, "invalid-option", `Cannot emit Gherkin: ${err.message}.`);
|
|
163
|
+
}
|
|
164
|
+
const { actions, orphans, conflicts } = reconciled;
|
|
165
|
+
// All or nothing: one conflicting file refuses the whole emission, so a
|
|
166
|
+
// half-written suite can never be the state an agent has to reason about.
|
|
167
|
+
// Only a feature run can conflict — living mode keeps the in-flight file
|
|
168
|
+
// rather than refusing — and standing inside that scope is also how the
|
|
169
|
+
// message names the feature without asserting there is one.
|
|
170
|
+
if (scope.mode === "feature" && conflicts.length > 0) {
|
|
171
|
+
const detail = conflicts
|
|
172
|
+
.map((c) => `${rel(c.path)} is @${c.owners.join(" @")} (requirement '${c.requirement.name}')`)
|
|
173
|
+
.join("; ");
|
|
174
|
+
const message = `Cannot emit gherkin for ${scope.featureId}: ${conflicts.length} file(s) belong to another feature still in flight — ${detail}. ` +
|
|
175
|
+
`A .feature file carries one feature's delta; overwriting would revert that feature's wording and destroy the digest stamps \`loam verify --results\` matches on. ` +
|
|
176
|
+
`Archive (or abandon) the owning feature first, or rename the requirement in ${scope.featureId}'s delta so the two stop sharing a file name.`;
|
|
177
|
+
if (json) {
|
|
178
|
+
emitJsonError("gherkin-conflict", message, {
|
|
179
|
+
conflicts: conflicts.map((c) => ({
|
|
180
|
+
path: rel(c.path),
|
|
181
|
+
action: "conflict",
|
|
182
|
+
requirement: c.requirement.name,
|
|
183
|
+
inFlight: c.owners,
|
|
184
|
+
})),
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
return fail(json, "gherkin-conflict", message);
|
|
189
|
+
}
|
|
190
|
+
const writes = actions.filter((a) => a.action !== "kept");
|
|
191
|
+
if (!dryRun && (writes.length > 0 || orphans.length > 0)) {
|
|
192
|
+
// The commit window: locked, journaled, compared against the bytes
|
|
193
|
+
// reconcile graded. A zero-op run takes no lock and creates no root —
|
|
194
|
+
// an emission with nothing to emit must not flip the repo into
|
|
195
|
+
// "opted in", where an empty loam/ tells `loam validate` the whole
|
|
196
|
+
// living suite is missing.
|
|
197
|
+
const committed = await commitEmission({ root, service, scope }, { writes, orphans });
|
|
198
|
+
if (!committed.ok)
|
|
199
|
+
return fail(json, committed.code, committed.message);
|
|
200
|
+
recovered = committed.recovered ?? recovered;
|
|
201
|
+
}
|
|
202
|
+
if (json) {
|
|
203
|
+
emitJson({
|
|
204
|
+
command: "gherkin",
|
|
205
|
+
mode: scope.mode,
|
|
206
|
+
...(scope.mode === "feature" ? { feature: scope.featureId } : {}),
|
|
207
|
+
service,
|
|
208
|
+
root: rel(root),
|
|
209
|
+
written: !dryRun,
|
|
210
|
+
files: actions.map((a) => ({
|
|
211
|
+
path: rel(a.path),
|
|
212
|
+
action: a.action,
|
|
213
|
+
axis: axisLabel(a.axis),
|
|
214
|
+
requirement: a.requirement.name,
|
|
215
|
+
// A kept file's numbers describe what STAYS on disk (the in-flight
|
|
216
|
+
// emission), not the emission that was withheld.
|
|
217
|
+
scenarios: a.action === "kept" ? a.kept.scenarios.length : a.digests.length,
|
|
218
|
+
digests: a.action === "kept" ? a.kept.scenarios.map((s) => s.digest) : a.digests,
|
|
219
|
+
...(a.action === "kept"
|
|
220
|
+
? { inFlight: a.kept.tags.filter((t) => activeIds.has(t)) }
|
|
221
|
+
: {
|
|
222
|
+
stepless: a.stepless,
|
|
223
|
+
malformedExamples: a.malformedExamples,
|
|
224
|
+
strandedBlocks: a.strandedBlocks,
|
|
225
|
+
}),
|
|
226
|
+
})),
|
|
227
|
+
deleted: orphans.map((o) => rel(o.path)),
|
|
228
|
+
...(recovered === null ? {} : { recovered }),
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (recovered !== null)
|
|
233
|
+
console.log(`${sayRecovered(recovered)}\n`);
|
|
234
|
+
render(actions, {
|
|
235
|
+
service,
|
|
236
|
+
root: rel(root),
|
|
237
|
+
absRoot: root,
|
|
238
|
+
featureId: scope.mode === "feature" ? scope.featureId : null,
|
|
239
|
+
dryRun,
|
|
240
|
+
activeIds,
|
|
241
|
+
writes: writes.length,
|
|
242
|
+
orphans: orphans.map((o) => o.path),
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=gherkin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../../../src/commands/gherkin/gherkin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gherkin` — the third writer in the CLI (after archive and vouch), and\n * the only one that writes into the SERVICE repo, never the docs repo: the\n * docs state what must be true, the tests live with the code that makes it so.\n *\n * Two scopes, one emitter. `loam gherkin <FEAT>` emits the feature's ADDED and\n * MODIFIED requirements for this service — both spec axes — as the acceptance\n * skeleton to implement against, each file tagged with the feature id.\n * `loam gherkin --service <id>` (no feature) emits the full living suite from\n * spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n * adoption. Either way the output is `<gherkinDir>/loam/` inside this repo,\n * a directory loam owns outright: the scope's files are rewritten, the scope's\n * orphans are deleted (and reported), and nothing outside `loam/` is touched —\n * except that a living run neither deletes NOR overwrites a file tagged with a\n * feature still in flight: it answers to that feature's delta until it\n * archives, and is reported as kept. A FEATURE run cannot keep — the file it\n * wants holds one feature's delta — so when the file it would write belongs to\n * a different feature still in flight, the whole run refuses (`gherkin-conflict`)\n * and names the owner rather than reverting it.\n *\n * It refuses to run anywhere but the service's own repo — vouch's discipline,\n * for vouch's reason: the output lands in the repo loam is standing in, and\n * from anywhere else that repo is somebody else's.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport {\n emitJson,\n emitJsonError,\n fail,\n NO_SERVICE_MESSAGE,\n reportNoConfig,\n} from \"../../core/envelope/json.js\";\nimport { featureSpecPaths, SPEC_AXES, type SpecAxis } from \"../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { listFeatures, missingFeatureMessage, resolveFeature } from \"../../core/repo/repo.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { type Requirement } from \"../../core/document/spec.js\";\nimport { axisLabel, planEmission } from \"../../core/gherkin/emit.js\";\nimport { gherkinRoot } from \"../../core/gherkin/stamp.js\";\nimport { LOAM_VERSION } from \"../../core/envelope/version.js\";\nimport { UnsafePathError } from \"../../core/kernel/path-safety.js\";\nimport { commitEmission, recoverEmissionRoot } from \"./commit.js\";\nimport { reconcile, type Scope } from \"./reconcile.js\";\nimport { render } from \"./render.js\";\nimport { sayRecovered } from \"../policy/format.js\";\n\ninterface GherkinOptions {\n service?: string;\n dryRun?: boolean;\n json?: boolean;\n}\n\nexport function registerGherkin(program: Command): void {\n program\n .command(\"gherkin\")\n .argument(\"[featureId]\", \"feature id — emit its ADDED/MODIFIED requirements; omit for the full living suite\")\n .description(\n \"Emit Gherkin .feature files from spec scenarios into this service repo's <gherkinDir>/loam/\",\n )\n .option(\"--service <id>\", \"service to emit for (defaults to the configured service)\")\n .option(\n \"--dry-run\",\n \"print the plan (writes, replacements, deletions) and write nothing — beyond first finishing a predecessor's interrupted commit, exactly as a real run would\",\n )\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureArg: string | undefined, opts: GherkinOptions) => {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const service = opts.service ?? config.service;\n if (service === undefined) {\n return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n }\n // The emission writes into the repo loam is standing in, so it can only\n // run where that repo is this service's — vouch's refusal, verbatim logic.\n //\n // TWO conditions, two codes, and the split is the whole point. Both used\n // to answer `invalid-option`, whose `loam explain` row says the\n // invocation itself is wrong — contradictory flags, a mistyped one, a\n // value that cannot be right. Here the invocation is perfectly correct\n // and the REPOSITORY is the wrong one, so that code sent a user whose\n // flags were fine off to re-read their flags, and left CI and agents\n // unable to tell \"run this in the service repo\" from \"you typed the flag\n // wrong\". `loam verify --record --service` already draws exactly this\n // line for exactly this reason (see commands/verify/verify.ts); gherkin\n // and vouch now answer the same pair, so a caller branching on\n // `error.code` gets one answer from all three. The messages are\n // unchanged — only the code moved.\n if (config.service === undefined) {\n return fail(\n json,\n \"repository-unavailable\",\n `Cannot emit gherkin for '${service}' from here: this is not a service repo. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n if (config.service !== service) {\n return fail(\n json,\n \"service-mismatch\",\n `Cannot emit gherkin for '${service}' from here: this repo is '${config.service}'. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n\n // The repo root, not the cwd: `config.root` is the directory loam.json\n // was actually found in, and config discovery walks upward. Taken from\n // `process.cwd()` this emitted the whole suite into whatever directory\n // the caller happened to stand in — `src/api/features/loam/…` inside the\n // source tree, which the next run from the root neither finds, replaces\n // nor deletes, and which `loam validate` grades as a suite that was never\n // generated.\n const repoDir = config.root ?? process.cwd();\n let root: string;\n try {\n root = gherkinRoot(repoDir, config.gherkinDir);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit Gherkin: ${err.message}. The output directory must stay inside the service repo.`,\n );\n }\n const rel = (abs: string): string => relative(repoDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // A predecessor's interrupted emission is rolled forward before ANY\n // read — and before the feature argument resolves: reconcile grades the\n // very bytes a half-commit corrupts, and the journal's stored rerun may\n // name a feature that has since ARCHIVED, whose recovery must still\n // work. `new` fixed the same ordering and documented it; refusing\n // unknown-target over a wedged root left doctor printing a repair that\n // could never run.\n const early = await recoverEmissionRoot(root);\n if (!early.ok) return fail(json, early.code, early.message);\n let recovered = early.recovered;\n\n const feature =\n featureArg === undefined ? null : await resolveFeature(config.docsDir, featureArg, \"exclude\");\n if (featureArg !== undefined && feature === null) {\n return fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureArg));\n }\n const scope: Scope =\n feature === null ? { mode: \"living\" } : { mode: \"feature\", featureId: feature.id };\n\n // The requirement set, by scope. Feature mode takes ADDED and MODIFIED —\n // a BASE requirement is the living state quoted, a REMOVED one is being\n // retired, and neither is behaviour anybody is about to test. Living mode\n // takes everything the living files hold.\n //\n // Read through `decodeDocument`, not `readFile(…, \"utf8\")`, and refused\n // rather than degraded: a spec.md saved as UTF-16 decodes to a document\n // with no requirements in it, which here is not merely a wrong answer but\n // a destructive one — an empty plan makes every existing .feature under\n // loam/ an orphan, and a living run DELETES its orphans. The whole\n // generated suite would go because a docs file was written by PowerShell.\n const byAxis: Array<{ axis: SpecAxis; reqs: Requirement[] }> = [];\n const readSpec = async (path: string): Promise<Requirement[]> =>\n parseRequirements(decodeDocument(await readFile(path), path));\n try {\n if (feature !== null) {\n // `config.service`, not `service`: the guard above proved they are\n // the same id, and only the config's copy carries the load-time\n // parse, so it is the pathable spelling — same as the else arm.\n const paths = featureSpecPaths(feature.dir, config.service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path)\n ? (await readSpec(path)).filter((r) => r.kind === \"ADDED\" || r.kind === \"MODIFIED\")\n : [];\n byAxis.push({ axis, reqs });\n }\n } else {\n // The guard above proved `service` IS `config.service`; only the\n // latter carries the load-time parse, so it is the pathable spelling.\n const paths = await locateServicePaths(config.docsDir, config.service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path) ? (await readSpec(path)).filter((r) => r.kind !== \"REMOVED\") : [];\n byAxis.push({ axis, reqs });\n }\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot emit gherkin: ${err.message}`);\n }\n\n // `service` is not decoration: it salts every `@loam-digest-…` stamp, and\n // it is the same string `loam verify` files the matching claims under.\n const plan = planEmission(byAxis, {\n service,\n featureTag: scope.mode === \"feature\" ? scope.featureId : undefined,\n version: LOAM_VERSION,\n });\n\n // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns\n // only its own emissions: stamped files carrying this feature's tag whose\n // file is no longer in the plan (a requirement renamed or dropped).\n\n // `Set<string>`, not the ids' own brand: this set answers membership\n // questions for tags read out of .feature files — document text.\n const activeIds = new Set<string>((await listFeatures(config.docsDir)).map((f) => f.id));\n let reconciled;\n try {\n reconciled = await reconcile(plan, { root, repoDir }, scope, activeIds);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", `Cannot emit Gherkin: ${err.message}.`);\n }\n const { actions, orphans, conflicts } = reconciled;\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n if (scope.mode === \"feature\" && conflicts.length > 0) {\n const detail = conflicts\n .map((c) => `${rel(c.path)} is @${c.owners.join(\" @\")} (requirement '${c.requirement.name}')`)\n .join(\"; \");\n const message =\n `Cannot emit gherkin for ${scope.featureId}: ${conflicts.length} file(s) belong to another feature still in flight — ${detail}. ` +\n `A .feature file carries one feature's delta; overwriting would revert that feature's wording and destroy the digest stamps \\`loam verify --results\\` matches on. ` +\n `Archive (or abandon) the owning feature first, or rename the requirement in ${scope.featureId}'s delta so the two stop sharing a file name.`;\n if (json) {\n emitJsonError(\"gherkin-conflict\", message, {\n conflicts: conflicts.map((c) => ({\n path: rel(c.path),\n action: \"conflict\",\n requirement: c.requirement.name,\n inFlight: c.owners,\n })),\n });\n return;\n }\n return fail(json, \"gherkin-conflict\", message);\n }\n\n const writes = actions.filter((a) => a.action !== \"kept\");\n\n if (!dryRun && (writes.length > 0 || orphans.length > 0)) {\n // The commit window: locked, journaled, compared against the bytes\n // reconcile graded. A zero-op run takes no lock and creates no root —\n // an emission with nothing to emit must not flip the repo into\n // \"opted in\", where an empty loam/ tells `loam validate` the whole\n // living suite is missing.\n const committed = await commitEmission({ root, service, scope }, { writes, orphans });\n if (!committed.ok) return fail(json, committed.code, committed.message);\n recovered = committed.recovered ?? recovered;\n }\n\n if (json) {\n emitJson({\n command: \"gherkin\",\n mode: scope.mode,\n ...(scope.mode === \"feature\" ? { feature: scope.featureId } : {}),\n service,\n root: rel(root),\n written: !dryRun,\n files: actions.map((a) => ({\n path: rel(a.path),\n action: a.action,\n axis: axisLabel(a.axis),\n requirement: a.requirement.name,\n // A kept file's numbers describe what STAYS on disk (the in-flight\n // emission), not the emission that was withheld.\n scenarios: a.action === \"kept\" ? a.kept.scenarios.length : a.digests.length,\n digests: a.action === \"kept\" ? a.kept.scenarios.map((s) => s.digest) : a.digests,\n ...(a.action === \"kept\"\n ? { inFlight: a.kept.tags.filter((t) => activeIds.has(t)) }\n : {\n stepless: a.stepless,\n malformedExamples: a.malformedExamples,\n strandedBlocks: a.strandedBlocks,\n }),\n })),\n deleted: orphans.map((o) => rel(o.path)),\n ...(recovered === null ? {} : { recovered }),\n });\n return;\n }\n\n if (recovered !== null) console.log(`${sayRecovered(recovered)}\\n`);\n render(actions, {\n service,\n root: rel(root),\n absRoot: root,\n featureId: scope.mode === \"feature\" ? scope.featureId : null,\n dryRun,\n activeIds,\n writes: writes.length,\n orphans: orphans.map((o) => o.path),\n });\n });\n}\n"]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plan against what is already on disk: which files are orphans, which are
|
|
3
|
+
* written, replaced, kept, or owned by another feature.
|
|
4
|
+
*
|
|
5
|
+
* A module of its own because it is the only part of `loam gherkin` that
|
|
6
|
+
* DELETES, and the reasoning that makes deletion safe is all here: a candidate
|
|
7
|
+
* is an orphan only when its containing directory really resolves inside the
|
|
8
|
+
* owned root, and a root that does not resolve proves nothing about what lives
|
|
9
|
+
* beneath it, so nothing is collected. Leaving an orphan behind is
|
|
10
|
+
* self-repairing — `loam validate` grades it and the next run removes it — and a
|
|
11
|
+
* deletion taken on an unproven path is not.
|
|
12
|
+
*
|
|
13
|
+
* The in-flight exemption is here for the same reason: files are named by
|
|
14
|
+
* requirement slug in both modes, so a MODIFIED requirement's living emission
|
|
15
|
+
* always collides with the active feature's file, and replacing it reverted the
|
|
16
|
+
* delta's wording mid-flight with its digest stamps destroyed.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
19
|
+
import { readFile } from "node:fs/promises";
|
|
20
|
+
import { dirname, join, relative } from "node:path";
|
|
21
|
+
import {} from "../../core/gherkin/emit.js";
|
|
22
|
+
import { parseStampedFeature } from "../../core/gherkin/read.js";
|
|
23
|
+
import { featureFilesUnder } from "../../core/gherkin/stale.js";
|
|
24
|
+
import { isPathInside, resolveInside, UnsafePathError } from "../../core/kernel/path-safety.js";
|
|
25
|
+
export async function reconcile(plan, where, scope, activeIds) {
|
|
26
|
+
const { root, repoDir } = where;
|
|
27
|
+
// Living mode owns the whole suite — any .feature not in the plan goes —
|
|
28
|
+
// EXCEPT files tagged with a feature still in flight: those answer to
|
|
29
|
+
// their feature's delta until it archives, and `loam gherkin <FEAT>` is
|
|
30
|
+
// their regeneration.
|
|
31
|
+
const planned = new Set(plan.map((f) => f.fileName));
|
|
32
|
+
// "Inside loam/" has to hold after the links are resolved, not merely in
|
|
33
|
+
// the spelling of the path. `featureFilesUnder` FOLLOWS symlinks and
|
|
34
|
+
// recurses with the LINK's path, so a planted `<gherkinDir>/loam/sub ->
|
|
35
|
+
// /outside` hands back candidates that read as ours while `unlink`
|
|
36
|
+
// resolves them and destroys files that were never in this repo. Planned
|
|
37
|
+
// names are flat, so nothing nested is ever spared by `planned.has(...)`,
|
|
38
|
+
// and deletion is the one irreversible thing this command does: a
|
|
39
|
+
// candidate is an orphan only when its CONTAINING directory really
|
|
40
|
+
// resolves inside the owned root. The write path below refuses the same
|
|
41
|
+
// attack for the same reason.
|
|
42
|
+
//
|
|
43
|
+
// The candidate FILE is deliberately NOT resolved. A symlinked .feature
|
|
44
|
+
// sitting directly in loam/ is loam's to remove — unlinking it takes the
|
|
45
|
+
// link and leaves its target alone — so only the directory chain has to
|
|
46
|
+
// be ours.
|
|
47
|
+
const realOrNull = (p) => {
|
|
48
|
+
try {
|
|
49
|
+
return realpathSync(p);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// A root that does not resolve — absent, dangling, or vanished mid-run —
|
|
56
|
+
// proves nothing about what lives beneath it, so nothing is collected.
|
|
57
|
+
// Leaving an orphan behind is self-repairing (`loam validate` grades it
|
|
58
|
+
// `gherkin.orphaned` and the next run removes it); a deletion taken on an
|
|
59
|
+
// unproven path is not.
|
|
60
|
+
const rootReal = realOrNull(root);
|
|
61
|
+
const orphans = [];
|
|
62
|
+
if (rootReal !== null) {
|
|
63
|
+
for (const abs of await featureFilesUnder(root)) {
|
|
64
|
+
if (planned.has(relative(root, abs).split(/[\\/]/).join("/")))
|
|
65
|
+
continue;
|
|
66
|
+
const holder = realOrNull(dirname(abs));
|
|
67
|
+
if (holder === null || !isPathInside(rootReal, holder))
|
|
68
|
+
continue;
|
|
69
|
+
const raw = await readFile(abs);
|
|
70
|
+
const stamped = parseStampedFeature(raw.toString("utf8"));
|
|
71
|
+
if (scope.mode === "feature") {
|
|
72
|
+
if (stamped !== null && stamped.tags.includes(scope.featureId))
|
|
73
|
+
orphans.push({ path: abs, raw });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (stamped !== null && stamped.tags.some((t) => activeIds.has(t)))
|
|
77
|
+
continue;
|
|
78
|
+
orphans.push({ path: abs, raw });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// The in-flight exemption guards the OVERWRITE path too, not only the
|
|
83
|
+
// orphan scan above: files are named by requirement slug in both modes,
|
|
84
|
+
// so a MODIFIED requirement's living emission always collides with the
|
|
85
|
+
// active feature's file — and replacing it reverted the delta's wording
|
|
86
|
+
// mid-flight, feature tag and new digest stamps destroyed, invisibly
|
|
87
|
+
// (the reverted file grades current against the living spec). A planned
|
|
88
|
+
// path whose existing content is stamped and tagged with a feature still
|
|
89
|
+
// in flight is KEPT and reported as such; it answers to its feature's
|
|
90
|
+
// delta until the feature archives, and then living regeneration
|
|
91
|
+
// replaces it normally.
|
|
92
|
+
//
|
|
93
|
+
// FEATURE mode has the same collision and cannot solve it by keeping:
|
|
94
|
+
// two features in flight against one requirement slug want the same
|
|
95
|
+
// file, and whichever runs second used to `replace` — silently reverting
|
|
96
|
+
// the other feature's wording, feature tag and digest stamps, so its
|
|
97
|
+
// `verify --results` could never confirm a scenario again. Nothing loam
|
|
98
|
+
// can write is right here (the file holds ONE feature's delta), so the
|
|
99
|
+
// run refuses and names the owner: the two features have to be sequenced,
|
|
100
|
+
// or the requirement renamed.
|
|
101
|
+
const actions = [];
|
|
102
|
+
for (const f of plan) {
|
|
103
|
+
let path;
|
|
104
|
+
try {
|
|
105
|
+
// Check the final file as well as the owned root: a pre-planted
|
|
106
|
+
// `<slug>.feature` symlink must not turn writeFile into an overwrite
|
|
107
|
+
// outside the repository.
|
|
108
|
+
path = resolveInside(repoDir, relative(repoDir, join(root, f.fileName)), `gherkin file '${f.fileName}'`);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (!(err instanceof UnsafePathError))
|
|
112
|
+
throw err;
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
if (!existsSync(path)) {
|
|
116
|
+
actions.push({ ...f, path, action: "written" });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const raw = await readFile(path);
|
|
120
|
+
const existing = parseStampedFeature(raw.toString("utf8"));
|
|
121
|
+
if (scope.mode === "living") {
|
|
122
|
+
if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {
|
|
123
|
+
actions.push({ ...f, path, action: "kept", kept: existing });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (existing !== null) {
|
|
128
|
+
// An unstamped file is nobody's delta — it owns nothing, so it is
|
|
129
|
+
// replaced like any other, which is what the empty owner list used
|
|
130
|
+
// to say the long way round.
|
|
131
|
+
const owners = existing.tags.filter((t) => t !== scope.featureId && activeIds.has(t));
|
|
132
|
+
if (owners.length > 0) {
|
|
133
|
+
actions.push({ ...f, path, action: "conflict", kept: existing, owners });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
actions.push({ ...f, path, action: "replaced", raw });
|
|
138
|
+
}
|
|
139
|
+
// All or nothing: one conflicting file refuses the whole emission, so a
|
|
140
|
+
// half-written suite can never be the state an agent has to reason about.
|
|
141
|
+
// Only a feature run can conflict — living mode keeps the in-flight file
|
|
142
|
+
// rather than refusing — and standing inside that scope is also how the
|
|
143
|
+
// message names the feature without asserting there is one.
|
|
144
|
+
const conflicts = scope.mode === "feature"
|
|
145
|
+
? actions.filter((a) => a.action === "conflict")
|
|
146
|
+
: [];
|
|
147
|
+
return { orphans, actions, conflicts };
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=reconcile.js.map
|