@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2548 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fleet map merge, the flows drawn over it, and the services this archive
|
|
3
|
+
* brings into existence.
|
|
4
|
+
*
|
|
5
|
+
* The landscape is the one document every feature writes into, so it is spliced
|
|
6
|
+
* rather than rewritten: the authored file keeps its shape and the delta's
|
|
7
|
+
* elements are placed into it. A service arriving on the architecture axis alone
|
|
8
|
+
* has no `model.likec4` yet, and the warning for that is carried out of the plan
|
|
9
|
+
* — `validate` would report it next, and reporting it here is what stops that
|
|
10
|
+
* being a surprise.
|
|
11
|
+
*
|
|
12
|
+
* `planFlows` at the bottom is the other half of the same axis and shares this
|
|
13
|
+
* module for that reason rather than for the file cap: both write into
|
|
14
|
+
* `architecture/`, and the two merges are read together — the map gains the
|
|
15
|
+
* elements, the flow gains the ordered hops over them. It is a whole-file copy
|
|
16
|
+
* where the landscape is a splice, because a flow is a document of its own and
|
|
17
|
+
* there is nothing to merge partially.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { planWrite, readUtf8 } from "../../../core/staging/writes.js";
|
|
21
|
+
import { planLandscapeMerge } from "../../../core/c4/splice/landscape-merge.js";
|
|
22
|
+
import { titleOf } from "../../../core/c4/splice/identity/edges.js";
|
|
23
|
+
import { parseServiceId } from "../../../core/kernel/ids/service.js";
|
|
24
|
+
import { landscapePath as landscapeFile } from "../../../core/repo/paths.js";
|
|
25
|
+
import { enumeratedServiceIds } from "../../../core/repo/service-target.js";
|
|
26
|
+
import {} from "./state.js";
|
|
27
|
+
import { ArchiveFailure } from "./refusal.js";
|
|
28
|
+
import { featurePaths } from "../../../core/repo/paths.js";
|
|
29
|
+
import { featureFlows, livingFlowPath, USECASE_SUBDIR } from "../../../core/usecases/delta/flows.js";
|
|
30
|
+
export async function planLandscape(config, gated, plan, say) {
|
|
31
|
+
const { id, featureDir, deltaDoc, deltaServices } = gated;
|
|
32
|
+
const deltaLikec4 = featurePaths(featureDir).delta;
|
|
33
|
+
const { writes, planWarns, architectureServices } = plan;
|
|
34
|
+
const landscapePath = landscapeFile(config.docsDir);
|
|
35
|
+
if (deltaDoc !== undefined) {
|
|
36
|
+
const delta = deltaDoc;
|
|
37
|
+
if (delta.errors.length > 0) {
|
|
38
|
+
// --approve overrides loam's JUDGMENT about coherence, never its ability to
|
|
39
|
+
// read an axis. Skipping here would silently drop one merge axis in the one
|
|
40
|
+
// command engineered against quiet partial merges — same rule as an
|
|
41
|
+
// unparseable landscape or openapi: the plan stops before anything is written.
|
|
42
|
+
throw new ArchiveFailure("merge-failed", `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\`loam validate --feature ${id}\`) or delete the file`);
|
|
43
|
+
}
|
|
44
|
+
// The feature tag is the whole selection mechanism of this merge, and since
|
|
45
|
+
// LikeC4 1.59.0 a specification KIND can carry a tag that every element or
|
|
46
|
+
// edge of that kind then inherits. A delta declaring its own feature tag on
|
|
47
|
+
// a kind makes `newEls`/`newRels` below select the entire document —
|
|
48
|
+
// including the context declarations the scaffold ships commented out
|
|
49
|
+
// precisely so they are NOT merged — and archive would splice somebody
|
|
50
|
+
// else's services into the fleet map at exit 0. Mechanical, so `--approve`
|
|
51
|
+
// does not reach it, and refused before the merge is even planned.
|
|
52
|
+
const kindTagged = [
|
|
53
|
+
...Object.entries(delta.specification?.elementKindTags ?? {}),
|
|
54
|
+
...Object.entries(delta.specification?.relationshipKindTags ?? {}),
|
|
55
|
+
].find(([, tags]) => tags.includes(id));
|
|
56
|
+
if (kindTagged !== undefined) {
|
|
57
|
+
throw new ArchiveFailure("merge-failed", `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +
|
|
58
|
+
`'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +
|
|
59
|
+
`additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +
|
|
60
|
+
`written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`);
|
|
61
|
+
}
|
|
62
|
+
const newEls = delta.elements.filter((e) => e.tags.includes(id));
|
|
63
|
+
const newRels = delta.relationships.filter((r) => r.tags.includes(id));
|
|
64
|
+
if (existsSync(landscapePath)) {
|
|
65
|
+
const plan = await planLandscapeMerge({
|
|
66
|
+
landscapeText: await readUtf8(landscapePath),
|
|
67
|
+
deltaText: await readUtf8(deltaLikec4),
|
|
68
|
+
deltaElements: delta.elements,
|
|
69
|
+
newEls,
|
|
70
|
+
newRels,
|
|
71
|
+
featureId: id,
|
|
72
|
+
});
|
|
73
|
+
// A service can arrive on the ARCHITECTURE axis alone: an element this
|
|
74
|
+
// merge ADDS, carrying a `metadata { service }` binding, with no
|
|
75
|
+
// `specs/<svc>/` anywhere in the feature. It is a service the fleet gate
|
|
76
|
+
// will demand a directory for the moment this merge lands, so it owes the
|
|
77
|
+
// same warning as one arriving with a requirement delta — and until it
|
|
78
|
+
// did, the closing "complete + current" line printed over a landscape
|
|
79
|
+
// this very archive had just made red. Read off the ADDED elements, not
|
|
80
|
+
// the tagged ones: an element the living landscape already had is not
|
|
81
|
+
// arriving, and one that is never merged is not there to demand anything.
|
|
82
|
+
// A binding is document text, which the service path builders no longer accept.
|
|
83
|
+
// The parse cannot actually filter anything here: an illegal binding is
|
|
84
|
+
// `c4.service-binding-invalid`, a coherence ERROR `--approve` does not
|
|
85
|
+
// override, refused before any merge is planned — so the `ok` test only
|
|
86
|
+
// carries the compiler's proof that document text never reaches a path.
|
|
87
|
+
for (const e of plan.addedEls) {
|
|
88
|
+
const parsed = e.service === undefined ? undefined : parseServiceId(e.service);
|
|
89
|
+
if (parsed?.ok === true)
|
|
90
|
+
architectureServices.add(parsed.id);
|
|
91
|
+
}
|
|
92
|
+
if (plan.content !== null)
|
|
93
|
+
writes.push(planWrite(landscapePath, plan.content));
|
|
94
|
+
say(`\n architecture: merged into landscape.likec4 — +${plan.addedEls.length} element(s), +${plan.addedRels.length} relationship(s)`);
|
|
95
|
+
for (const e of plan.addedEls)
|
|
96
|
+
say(` + ${e.title} (${e.kind})`);
|
|
97
|
+
for (const r of plan.addedRels) {
|
|
98
|
+
say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} "${r.title ?? ""}"`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
say(`\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// A service this archive BRINGS INTO EXISTENCE arrives without the one file
|
|
106
|
+
// `validate` demands of every service: its own model.likec4. The merge cannot
|
|
107
|
+
// write it — the delta's tagged subtree is a landscape-level box, not a
|
|
108
|
+
// container model, and inventing a plausible one is the kind of quiet fiction
|
|
109
|
+
// the rest of loam exists to prevent — so the archive says so instead, and
|
|
110
|
+
// stops claiming the docs are complete. Non-gating: the feature is coherent
|
|
111
|
+
// and the merge is correct; what is missing is the next step, and refusing
|
|
112
|
+
// here would make onboarding a new service impossible in one command.
|
|
113
|
+
// "New" is enumeration membership, not an existsSync of services/<svc>/ at
|
|
114
|
+
// the root: a FILED service exists wherever the tree walk found it, and the
|
|
115
|
+
// root probe would grade every filed service as newly created here.
|
|
116
|
+
const enumerated = new Set(await enumeratedServiceIds(config.docsDir));
|
|
117
|
+
const newServices = [...new Set([...deltaServices, ...architectureServices])].filter((svc) => !enumerated.has(svc));
|
|
118
|
+
for (const svc of newServices) {
|
|
119
|
+
// Two shapes of the same debt: a service with a requirement delta gets its
|
|
120
|
+
// directory from this merge, one that arrives only in the landscape gets no
|
|
121
|
+
// directory at all — and `validate --all` fails the second harder
|
|
122
|
+
// (`landscape.service-unmodelled` names the binding with nothing behind it).
|
|
123
|
+
const creates = deltaServices.some((d) => d === svc)
|
|
124
|
+
? `this archive creates services/${svc}/, but nothing writes services/${svc}/model.likec4`
|
|
125
|
+
: `this archive puts '${svc}' in the landscape, but the fleet has no services/${svc}/ at all`;
|
|
126
|
+
planWarns.push({
|
|
127
|
+
severity: "warn",
|
|
128
|
+
code: "service.no-model",
|
|
129
|
+
subject: svc,
|
|
130
|
+
message: `${svc}: ${creates} — 'loam validate --all' will report the service as incomplete until it exists. Run 'loam adopt --service ${svc}' from the service repo, or write the model by hand.`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The flows this feature brings, copied into `architecture/usecases/`.
|
|
136
|
+
*
|
|
137
|
+
* A create-only whole-file copy, and the smallest merge in the command: the
|
|
138
|
+
* document is views-only, it names nothing the landscape merge has not already
|
|
139
|
+
* placed (`planFlowOverlay` in the gate is what proves that, before this runs),
|
|
140
|
+
* and a flow the living tree already holds was refused at the gate
|
|
141
|
+
* (`usecase.flow-exists`, `core/usecases/delta/flows.ts` has the reasoning). So
|
|
142
|
+
* by the time this runs every path here is a file that does not exist, and
|
|
143
|
+
* `planWrite` marks it `exclusive` — a race that created it between the gate and
|
|
144
|
+
* the commit fails the write rather than overwriting somebody.
|
|
145
|
+
*
|
|
146
|
+
* It lives beside `planLandscape` rather than beside the requirement merges
|
|
147
|
+
* because it writes into `architecture/`, and the two merges are read together:
|
|
148
|
+
* the landscape gains the elements, the flow gains the hops over them.
|
|
149
|
+
* `unarchive` needs nothing new — a create is undone by deleting, which the
|
|
150
|
+
* snapshot manifest already records for every write in the plan.
|
|
151
|
+
*/
|
|
152
|
+
export async function planFlows(config, gated, plan, say) {
|
|
153
|
+
const flows = await featureFlows(gated.featureDir);
|
|
154
|
+
if (flows.length === 0)
|
|
155
|
+
return;
|
|
156
|
+
for (const flow of flows) {
|
|
157
|
+
plan.writes.push(planWrite(livingFlowPath(config.docsDir, flow.rel), await readUtf8(flow.path)));
|
|
158
|
+
say(` use case: ${flow.rel} — created architecture/${USECASE_SUBDIR}/${flow.rel}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=landscape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet map merge, the flows drawn over it, and the services this archive\n * brings into existence.\n *\n * The landscape is the one document every feature writes into, so it is spliced\n * rather than rewritten: the authored file keeps its shape and the delta's\n * elements are placed into it. A service arriving on the architecture axis alone\n * has no `model.likec4` yet, and the warning for that is carried out of the plan\n * — `validate` would report it next, and reporting it here is what stops that\n * being a surprise.\n *\n * `planFlows` at the bottom is the other half of the same axis and shares this\n * module for that reason rather than for the file cap: both write into\n * `architecture/`, and the two merges are read together — the map gains the\n * elements, the flow gains the ordered hops over them. It is a whole-file copy\n * where the landscape is a splice, because a flow is a document of its own and\n * there is nothing to merge partially.\n */\nimport { existsSync } from \"node:fs\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { planLandscapeMerge } from \"../../../core/c4/splice/landscape-merge.js\";\nimport { titleOf } from \"../../../core/c4/splice/identity/edges.js\";\nimport { parseServiceId } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../../core/repo/service-target.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport { ArchiveFailure } from \"./refusal.js\";\nimport { featurePaths } from \"../../../core/repo/paths.js\";\nimport { featureFlows, livingFlowPath, USECASE_SUBDIR } from \"../../../core/usecases/delta/flows.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport async function planLandscape(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { id, featureDir, deltaDoc, deltaServices } = gated;\n const deltaLikec4 = featurePaths(featureDir).delta;\n const { writes, planWarns, architectureServices } = plan;\n const landscapePath = landscapeFile(config.docsDir);\n if (deltaDoc !== undefined) {\n const delta = deltaDoc;\n if (delta.errors.length > 0) {\n // --approve overrides loam's JUDGMENT about coherence, never its ability to\n // read an axis. Skipping here would silently drop one merge axis in the one\n // command engineered against quiet partial merges — same rule as an\n // unparseable landscape or openapi: the plan stops before anything is written.\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\\`loam validate --feature ${id}\\`) or delete the file`,\n );\n }\n // The feature tag is the whole selection mechanism of this merge, and since\n // LikeC4 1.59.0 a specification KIND can carry a tag that every element or\n // edge of that kind then inherits. A delta declaring its own feature tag on\n // a kind makes `newEls`/`newRels` below select the entire document —\n // including the context declarations the scaffold ships commented out\n // precisely so they are NOT merged — and archive would splice somebody\n // else's services into the fleet map at exit 0. Mechanical, so `--approve`\n // does not reach it, and refused before the merge is even planned.\n const kindTagged = [\n ...Object.entries(delta.specification?.elementKindTags ?? {}),\n ...Object.entries(delta.specification?.relationshipKindTags ?? {}),\n ].find(([, tags]) => tags.includes(id));\n if (kindTagged !== undefined) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +\n `'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +\n `additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +\n `written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`,\n );\n }\n const newEls = delta.elements.filter((e) => e.tags.includes(id));\n const newRels = delta.relationships.filter((r) => r.tags.includes(id));\n if (existsSync(landscapePath)) {\n const plan = await planLandscapeMerge({\n landscapeText: await readUtf8(landscapePath),\n deltaText: await readUtf8(deltaLikec4),\n deltaElements: delta.elements,\n newEls,\n newRels,\n featureId: id,\n });\n // A service can arrive on the ARCHITECTURE axis alone: an element this\n // merge ADDS, carrying a `metadata { service }` binding, with no\n // `specs/<svc>/` anywhere in the feature. It is a service the fleet gate\n // will demand a directory for the moment this merge lands, so it owes the\n // same warning as one arriving with a requirement delta — and until it\n // did, the closing \"complete + current\" line printed over a landscape\n // this very archive had just made red. Read off the ADDED elements, not\n // the tagged ones: an element the living landscape already had is not\n // arriving, and one that is never merged is not there to demand anything.\n // A binding is document text, which the service path builders no longer accept.\n // The parse cannot actually filter anything here: an illegal binding is\n // `c4.service-binding-invalid`, a coherence ERROR `--approve` does not\n // override, refused before any merge is planned — so the `ok` test only\n // carries the compiler's proof that document text never reaches a path.\n for (const e of plan.addedEls) {\n const parsed = e.service === undefined ? undefined : parseServiceId(e.service);\n if (parsed?.ok === true) architectureServices.add(parsed.id);\n }\n if (plan.content !== null) writes.push(planWrite(landscapePath, plan.content));\n say(`\\n architecture: merged into landscape.likec4 — +${plan.addedEls.length} element(s), +${plan.addedRels.length} relationship(s)`);\n for (const e of plan.addedEls) say(` + ${e.title} (${e.kind})`);\n for (const r of plan.addedRels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n } else {\n say(`\\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);\n }\n }\n\n // A service this archive BRINGS INTO EXISTENCE arrives without the one file\n // `validate` demands of every service: its own model.likec4. The merge cannot\n // write it — the delta's tagged subtree is a landscape-level box, not a\n // container model, and inventing a plausible one is the kind of quiet fiction\n // the rest of loam exists to prevent — so the archive says so instead, and\n // stops claiming the docs are complete. Non-gating: the feature is coherent\n // and the merge is correct; what is missing is the next step, and refusing\n // here would make onboarding a new service impossible in one command.\n // \"New\" is enumeration membership, not an existsSync of services/<svc>/ at\n // the root: a FILED service exists wherever the tree walk found it, and the\n // root probe would grade every filed service as newly created here.\n const enumerated = new Set<string>(await enumeratedServiceIds(config.docsDir));\n const newServices = [...new Set([...deltaServices, ...architectureServices])].filter(\n (svc) => !enumerated.has(svc),\n );\n for (const svc of newServices) {\n // Two shapes of the same debt: a service with a requirement delta gets its\n // directory from this merge, one that arrives only in the landscape gets no\n // directory at all — and `validate --all` fails the second harder\n // (`landscape.service-unmodelled` names the binding with nothing behind it).\n const creates = deltaServices.some((d) => d === svc)\n ? `this archive creates services/${svc}/, but nothing writes services/${svc}/model.likec4`\n : `this archive puts '${svc}' in the landscape, but the fleet has no services/${svc}/ at all`;\n planWarns.push({\n severity: \"warn\",\n code: \"service.no-model\",\n subject: svc,\n message: `${svc}: ${creates} — 'loam validate --all' will report the service as incomplete until it exists. Run 'loam adopt --service ${svc}' from the service repo, or write the model by hand.`,\n });\n }\n}\n\n/**\n * The flows this feature brings, copied into `architecture/usecases/`.\n *\n * A create-only whole-file copy, and the smallest merge in the command: the\n * document is views-only, it names nothing the landscape merge has not already\n * placed (`planFlowOverlay` in the gate is what proves that, before this runs),\n * and a flow the living tree already holds was refused at the gate\n * (`usecase.flow-exists`, `core/usecases/delta/flows.ts` has the reasoning). So\n * by the time this runs every path here is a file that does not exist, and\n * `planWrite` marks it `exclusive` — a race that created it between the gate and\n * the commit fails the write rather than overwriting somebody.\n *\n * It lives beside `planLandscape` rather than beside the requirement merges\n * because it writes into `architecture/`, and the two merges are read together:\n * the landscape gains the elements, the flow gains the hops over them.\n * `unarchive` needs nothing new — a create is undone by deleting, which the\n * snapshot manifest already records for every write in the plan.\n */\nexport async function planFlows(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const flows = await featureFlows(gated.featureDir);\n if (flows.length === 0) return;\n for (const flow of flows) {\n plan.writes.push(planWrite(livingFlowPath(config.docsDir, flow.rel), await readUtf8(flow.path)));\n say(` use case: ${flow.rel} — created architecture/${USECASE_SUBDIR}/${flow.rel}`);\n }\n}\n"]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How `loam archive` refuses, and what a refusal prints.
|
|
3
|
+
*
|
|
4
|
+
* Every arm here keeps one property: a refusal means nothing was written. The
|
|
5
|
+
* `--json` shapes carry the issues that caused it so an agent can act on the
|
|
6
|
+
* list rather than on the sentence, and `ArchiveFailure` exists so a merge that
|
|
7
|
+
* could not be computed reports as a merge failure rather than as `internal` —
|
|
8
|
+
* a read-only directory or an unreadable pre-image is not a bug in loam, and
|
|
9
|
+
* saying so sends the reader looking for one.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { emitJsonError, repoPath } from "../../../core/envelope/json.js";
|
|
13
|
+
import { approveOverrides, issueFinding, } from "../../../core/vocabulary/issue.js";
|
|
14
|
+
import {} from "../../../core/staging/interrupted.js";
|
|
15
|
+
import {} from "../../../core/staging/writes.js";
|
|
16
|
+
import { findingJson } from "../../../core/vocabulary/report.js";
|
|
17
|
+
/**
|
|
18
|
+
* A refusal or failure with its stable `--json` code attached. Thrown from the
|
|
19
|
+
* plan phase (nothing written yet) and from the commit's rollback path (whose
|
|
20
|
+
* message says whether the rollback held). Anything ELSE that escapes runArchive
|
|
21
|
+
* is a bug, and the action handler reports it as `internal`.
|
|
22
|
+
*/
|
|
23
|
+
export class ArchiveFailure extends Error {
|
|
24
|
+
code;
|
|
25
|
+
constructor(code, msg) {
|
|
26
|
+
super(msg);
|
|
27
|
+
this.code = code;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* An Issue as the `--json` envelope spells it — the Finding shape, minus details.
|
|
32
|
+
* `gates` and `overridable` are always present and already resolved: a consumer
|
|
33
|
+
* must not have to re-implement the severity default to know what blocks
|
|
34
|
+
* archive, nor keep a code list to know what `--approve` can move.
|
|
35
|
+
*/
|
|
36
|
+
export function issueJson(i, scope) {
|
|
37
|
+
return {
|
|
38
|
+
...findingJson(issueFinding(i), scope === undefined ? undefined : { path: scope, role: "scope" }),
|
|
39
|
+
overridable: approveOverrides(i),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** The failure envelope plus the issues that caused it, so a caller need not re-run validate. */
|
|
43
|
+
export function refuseJson(code, msg, issues, scope) {
|
|
44
|
+
emitJsonError(code, msg, { issues: issues.map((issue) => issueJson(issue, scope)) });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The same envelope for a refusal computed from `Finding`s rather than
|
|
48
|
+
* `Issue`s — the plan-time checks whose codes are not coherence's (they belong
|
|
49
|
+
* to the LIVING documents, not to the feature) and so are not `IssueCode`s.
|
|
50
|
+
*
|
|
51
|
+
* `gates: true` is asserted rather than derived: every finding listed in a
|
|
52
|
+
* refusal is a reason archive stopped, which is what the field means. Nothing
|
|
53
|
+
* advisory reaches here.
|
|
54
|
+
*/
|
|
55
|
+
export function refuseFindings(code, msg, findings, scope) {
|
|
56
|
+
emitJsonError(code, msg, {
|
|
57
|
+
issues: findings.map((finding) => ({
|
|
58
|
+
...findingJson(finding, scope === undefined ? undefined : { path: scope, role: "scope" }),
|
|
59
|
+
gates: true,
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Take the docs repo's advisory lock for the WHOLE plan+commit window, then
|
|
65
|
+
* archive.
|
|
66
|
+
*
|
|
67
|
+
* The window is the point. Two archives that overlap do not fight over a
|
|
68
|
+
* rename: they each read the living landscape, each splice their additions into
|
|
69
|
+
* the bytes they read, and the second write replaces the first — both exit 0,
|
|
70
|
+
* and `validate` stays green over a landscape that is missing one feature's
|
|
71
|
+
* architecture, because a document with fewer elements is not an invalid one.
|
|
72
|
+
* That is the only silent-loss path in loam that a later command cannot even
|
|
73
|
+
* detect, so it is closed at the coarsest possible granularity: one writer per
|
|
74
|
+
* docs repo, refusing rather than queueing, because a CLI that blocks for an
|
|
75
|
+
* unknown time is worse for an agent than one that says `docs-busy`.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* What the recovery did, before this command's own output — a docs repo that
|
|
79
|
+
* changed under the caller is the first thing they have to be told, not a
|
|
80
|
+
* footnote. Shared by archive and unarchive through the same CommitRecovery.
|
|
81
|
+
*/
|
|
82
|
+
export function sayRecovery(r) {
|
|
83
|
+
const what = `an interrupted \`loam ${r.command} ${r.feature}\``;
|
|
84
|
+
if (r.outcome === "completed") {
|
|
85
|
+
console.log(`⚠ ${what} had in fact finished — cleared its commit record.\n`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (r.outcome === "consistent") {
|
|
89
|
+
console.log(`⚠ ${what} was rolled back before it wrote anything — cleared its commit record.\n`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// An interrupted archive is UNDONE and an interrupted unarchive is FINISHED —
|
|
93
|
+
// the merged text a restore was replacing is written down nowhere, so there is
|
|
94
|
+
// nothing to go back to. Say which happened; the paths are the same either way.
|
|
95
|
+
const what_ = r.command === "archive" ? "put them back from" : "finished them from";
|
|
96
|
+
console.log(`⚠ ${what} left ${r.repaired.length} file(s) half-written; ${what_} its snapshot:`);
|
|
97
|
+
for (const p of r.repaired)
|
|
98
|
+
console.log(` ↩ ${p}`);
|
|
99
|
+
console.log("");
|
|
100
|
+
}
|
|
101
|
+
/** The full plan, as files: what a dry run shows instead of doing. */
|
|
102
|
+
export function printPlan(docsDir, writes, dirName) {
|
|
103
|
+
console.log(`\n plan — ${writes.length} file(s):`);
|
|
104
|
+
for (const w of writes) {
|
|
105
|
+
const verb = existsSync(w.path) ? "update" : "create";
|
|
106
|
+
console.log(` ${verb} ${repoPath(docsDir, w.path)}`);
|
|
107
|
+
}
|
|
108
|
+
console.log(` move features/${dirName} → features/archive/${dirName}`);
|
|
109
|
+
console.log("\n dry run — nothing was written.");
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/refusal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * How `loam archive` refuses, and what a refusal prints.\n *\n * Every arm here keeps one property: a refusal means nothing was written. The\n * `--json` shapes carry the issues that caused it so an agent can act on the\n * list rather than on the sentence, and `ArchiveFailure` exists so a merge that\n * could not be computed reports as a merge failure rather than as `internal` —\n * a read-only directory or an unreadable pre-image is not a bug in loam, and\n * saying so sends the reader looking for one.\n */\nimport { existsSync } from \"node:fs\";\nimport { emitJsonError, repoPath, type ErrorCode } from \"../../../core/envelope/json.js\";\nimport {\n approveOverrides,\n issueFinding,\n type Issue,\n} from \"../../../core/vocabulary/issue.js\";\nimport { type CommitRecovery } from \"../../../core/staging/interrupted.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { findingJson, type Finding } from \"../../../core/vocabulary/report.js\";\n\nexport interface ArchiveOptions {\n approve?: boolean;\n dryRun?: boolean;\n json?: boolean;\n}\n\n\n/**\n * A refusal or failure with its stable `--json` code attached. Thrown from the\n * plan phase (nothing written yet) and from the commit's rollback path (whose\n * message says whether the rollback held). Anything ELSE that escapes runArchive\n * is a bug, and the action handler reports it as `internal`.\n */\nexport class ArchiveFailure extends Error {\n constructor(\n readonly code: ErrorCode,\n msg: string,\n ) {\n super(msg);\n }\n}\n\n/**\n * An Issue as the `--json` envelope spells it — the Finding shape, minus details.\n * `gates` and `overridable` are always present and already resolved: a consumer\n * must not have to re-implement the severity default to know what blocks\n * archive, nor keep a code list to know what `--approve` can move.\n */\nexport function issueJson(i: Issue, scope?: string): Record<string, unknown> {\n return {\n ...findingJson(\n issueFinding(i),\n scope === undefined ? undefined : { path: scope, role: \"scope\" },\n ),\n overridable: approveOverrides(i),\n };\n}\n\n/** The failure envelope plus the issues that caused it, so a caller need not re-run validate. */\nexport function refuseJson(code: ErrorCode, msg: string, issues: Issue[], scope?: string): void {\n emitJsonError(code, msg, { issues: issues.map((issue) => issueJson(issue, scope)) });\n}\n\n/**\n * The same envelope for a refusal computed from `Finding`s rather than\n * `Issue`s — the plan-time checks whose codes are not coherence's (they belong\n * to the LIVING documents, not to the feature) and so are not `IssueCode`s.\n *\n * `gates: true` is asserted rather than derived: every finding listed in a\n * refusal is a reason archive stopped, which is what the field means. Nothing\n * advisory reaches here.\n */\nexport function refuseFindings(\n code: ErrorCode,\n msg: string,\n findings: Finding[],\n scope?: string,\n): void {\n emitJsonError(code, msg, {\n issues: findings.map((finding) => ({\n ...findingJson(\n finding,\n scope === undefined ? undefined : { path: scope, role: \"scope\" },\n ),\n gates: true,\n })),\n });\n}\n\n/**\n * Take the docs repo's advisory lock for the WHOLE plan+commit window, then\n * archive.\n *\n * The window is the point. Two archives that overlap do not fight over a\n * rename: they each read the living landscape, each splice their additions into\n * the bytes they read, and the second write replaces the first — both exit 0,\n * and `validate` stays green over a landscape that is missing one feature's\n * architecture, because a document with fewer elements is not an invalid one.\n * That is the only silent-loss path in loam that a later command cannot even\n * detect, so it is closed at the coarsest possible granularity: one writer per\n * docs repo, refusing rather than queueing, because a CLI that blocks for an\n * unknown time is worse for an agent than one that says `docs-busy`.\n */\n\n/**\n * What the recovery did, before this command's own output — a docs repo that\n * changed under the caller is the first thing they have to be told, not a\n * footnote. Shared by archive and unarchive through the same CommitRecovery.\n */\nexport function sayRecovery(r: CommitRecovery): void {\n const what = `an interrupted \\`loam ${r.command} ${r.feature}\\``;\n if (r.outcome === \"completed\") {\n console.log(`⚠ ${what} had in fact finished — cleared its commit record.\\n`);\n return;\n }\n if (r.outcome === \"consistent\") {\n console.log(`⚠ ${what} was rolled back before it wrote anything — cleared its commit record.\\n`);\n return;\n }\n // An interrupted archive is UNDONE and an interrupted unarchive is FINISHED —\n // the merged text a restore was replacing is written down nowhere, so there is\n // nothing to go back to. Say which happened; the paths are the same either way.\n const what_ = r.command === \"archive\" ? \"put them back from\" : \"finished them from\";\n console.log(`⚠ ${what} left ${r.repaired.length} file(s) half-written; ${what_} its snapshot:`);\n for (const p of r.repaired) console.log(` ↩ ${p}`);\n console.log(\"\");\n}\n\n/** The full plan, as files: what a dry run shows instead of doing. */\nexport function printPlan(docsDir: string, writes: PlannedWrite[], dirName: string): void {\n console.log(`\\n plan — ${writes.length} file(s):`);\n for (const w of writes) {\n const verb = existsSync(w.path) ? \"update\" : \"create\";\n console.log(` ${verb} ${repoPath(docsDir, w.path)}`);\n }\n console.log(` move features/${dirName} → features/archive/${dirName}`);\n console.log(\"\\n dry run — nothing was written.\");\n}\n"]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The requirements merge: the feature's ADDED/MODIFIED/REMOVED requirements
|
|
3
|
+
* applied into each living service spec. The contract merges live beside it in
|
|
4
|
+
* `./contracts/` — one file per axis, split out when the OpenAPI half made
|
|
5
|
+
* this one carry two merges.
|
|
6
|
+
*
|
|
7
|
+
* ONE code path for the pair of requirement-carrying files — the business
|
|
8
|
+
* spec.md and the architecture arch.spec.md ride the same delta algebra, the
|
|
9
|
+
* same prose-preserving rewrite and the same guards, parameterized by filename.
|
|
10
|
+
* A fork here would be two places the merge could disagree.
|
|
11
|
+
*
|
|
12
|
+
* Nothing is written. Everything lands in the `Plan`, so a failure on any axis
|
|
13
|
+
* leaves the living docs exactly as they were.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
|
+
import { rebaseLinks } from "../../../core/links/parse.js";
|
|
18
|
+
import { planWrite, readUtf8 } from "../../../core/staging/writes.js";
|
|
19
|
+
import { featureSpecPaths, SPEC_AXES } from "../../../core/repo/paths.js";
|
|
20
|
+
import { capabilityDocsDir, glossaryDir, livingCapabilityPaths } from "../../../core/repo/authored/paths.js";
|
|
21
|
+
import { featureGlossary, livingTermPath } from "../../../core/glossary/delta.js";
|
|
22
|
+
import { locateServicePaths } from "../../../core/repo/service-target.js";
|
|
23
|
+
import { isRequirementsHeading, parseRequirements, sectionHeadings, splitRequirementsSection, } from "../../../core/document/parse.js";
|
|
24
|
+
import { applyRequirementDelta } from "../../../core/document/apply.js";
|
|
25
|
+
import { serializeRequirements } from "../../../core/document/spec.js";
|
|
26
|
+
import {} from "../../../core/document/spec.js";
|
|
27
|
+
import { ArchiveFailure } from "./refusal.js";
|
|
28
|
+
import {} from "./state.js";
|
|
29
|
+
export async function planSpecs(config, gated, plan, say) {
|
|
30
|
+
const { featureDir, deltaServices } = gated;
|
|
31
|
+
const { writes } = plan;
|
|
32
|
+
// Requirements merge — apply ADDED/MODIFIED/REMOVED into each living service
|
|
33
|
+
// spec. ONE code path for the pair of requirement-carrying files: the business
|
|
34
|
+
// spec.md and the architecture arch.spec.md ride the same delta algebra, the
|
|
35
|
+
// same prose-preserving rewrite and the same guards, parameterized by filename
|
|
36
|
+
// (SPEC_AXES) — a fork here would be two places the merge could disagree.
|
|
37
|
+
for (const svc of deltaServices) {
|
|
38
|
+
for (const axis of SPEC_AXES) {
|
|
39
|
+
const deltaPath = featureSpecPaths(featureDir, svc)[axis.key];
|
|
40
|
+
if (!existsSync(deltaPath))
|
|
41
|
+
continue;
|
|
42
|
+
// The shared context: this is a deltaServices × SPEC_AXES loop, and a
|
|
43
|
+
// context-less locate is a full fleet walk per iteration.
|
|
44
|
+
const livingPath = (await locateServicePaths(config.docsDir, svc, config.fleet))[axis.key];
|
|
45
|
+
// Resolved BEFORE the delta is parsed, because the parse now depends on
|
|
46
|
+
// it: a requirement's markdown links are relative to the file they were
|
|
47
|
+
// written in, and this merge moves the text two directories up the tree.
|
|
48
|
+
// `core/links/parse.ts`'s `rebaseLinks` states the defect; the ordering
|
|
49
|
+
// here is what lets the fix see both ends of the move, including a
|
|
50
|
+
// service filed under a subsystem, whose living directory is deeper still.
|
|
51
|
+
const deltaReqs = parseRequirements(rebaseLinks(await readUtf8(deltaPath), { from: dirname(deltaPath), to: dirname(livingPath) }));
|
|
52
|
+
if (!existsSync(livingPath)) {
|
|
53
|
+
// New service (or first arch spec) — create the living file from the
|
|
54
|
+
// ADDED/MODIFIED requirements.
|
|
55
|
+
const created = applyRequirementDelta([], deltaReqs);
|
|
56
|
+
if (created.length === 0) {
|
|
57
|
+
say(` ${axis.label}: ${svc} — nothing to merge (delta leaves no requirements), no living ${axis.file} created`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const heading = axis.key === "spec" ? svc : `${svc} — architecture`;
|
|
61
|
+
const frontmatter = `---\nservice: ${svc}\nstatus: draft\n---\n\n# ${heading}\n\n`;
|
|
62
|
+
writes.push(planWrite(livingPath, `${frontmatter}## Requirements\n\n${serializeRequirements(created)}`));
|
|
63
|
+
say(` ${axis.label}: ${svc} — created living ${axis.file} (${created.length} requirement(s))`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const livingText = await readUtf8(livingPath);
|
|
67
|
+
// TWO `## Requirements` headings would put the rewrite's one-section
|
|
68
|
+
// invariant to a choice it must not make: the run of the first would be
|
|
69
|
+
// rewritten while the second survived verbatim in the tail — and its
|
|
70
|
+
// requirements, collected by parseRequirements, would land in the run TOO.
|
|
71
|
+
// Mechanical, like a model-less landscape, so merge-failed, not --approve.
|
|
72
|
+
const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));
|
|
73
|
+
if (reqHeadings.length > 1) {
|
|
74
|
+
throw new ArchiveFailure("merge-failed", `living ${axis.file} for ${svc} has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(", ")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`);
|
|
75
|
+
}
|
|
76
|
+
const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);
|
|
77
|
+
writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));
|
|
78
|
+
const c = summarize(deltaReqs);
|
|
79
|
+
say(` ${axis.label}: ${svc} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The BUSINESS corpus's merge: a feature's `capabilities/<id>/spec.md` deltas
|
|
85
|
+
* applied into the living `capabilities/<id>/spec.md`.
|
|
86
|
+
*
|
|
87
|
+
* Here rather than in a sixth file in this package because this module already
|
|
88
|
+
* owns "requirements merged into a living document" — it owns
|
|
89
|
+
* `rewriteRequirementsRun`, the two-headings refusal and the `applyRequirementDelta`
|
|
90
|
+
* call, and the business corpus needs all three unchanged. What differs from
|
|
91
|
+
* `planSpecs` above is only where the living document lives and what a CREATED
|
|
92
|
+
* one says at the top.
|
|
93
|
+
*
|
|
94
|
+
* ARCHIVE CREATES THE LIVING DOCUMENT, by exact analogy with the new-service
|
|
95
|
+
* branch above. The directory IS the list on this axis (`core/capabilities/tree.ts`),
|
|
96
|
+
* so an archive that merged into a file it refused to create would make the
|
|
97
|
+
* whole delta merge nothing — which is the `delta.no-delta-sections` failure
|
|
98
|
+
* class, arriving through the merge instead of through the grammar.
|
|
99
|
+
*
|
|
100
|
+
* NO FRONTMATTER on a created capability document, and that is a decision
|
|
101
|
+
* rather than an omission. Nothing reads a capability document's frontmatter:
|
|
102
|
+
* `readCapabilityTree` classifies by the file's PRESENCE and
|
|
103
|
+
* `readCapabilityVocabulary` takes `description`/`owner` from the YAML side. A
|
|
104
|
+
* `status:` field invented here would be the "second list nothing keeps
|
|
105
|
+
* current" this axis was designed against.
|
|
106
|
+
*
|
|
107
|
+
* AND IT SAYS SO OUT LOUD. Creating the first `capabilities/<id>/spec.md`
|
|
108
|
+
* creates `capabilities/`, which is one of the two ways a fleet opts INTO the
|
|
109
|
+
* capability axis — from the next command on, `loam validate --all` grades a
|
|
110
|
+
* corpus that did not exist before. A merge that changes what the whole fleet
|
|
111
|
+
* is graded on must not do it silently.
|
|
112
|
+
*/
|
|
113
|
+
export async function planCapabilities(config, gated, plan, say) {
|
|
114
|
+
const { writes } = plan;
|
|
115
|
+
// Whether `capabilities/` exists is asked BEFORE the loop, because the loop
|
|
116
|
+
// is what would create it; `creating` records whether this run actually
|
|
117
|
+
// materialises a document, so the adoption line below is not printed by a
|
|
118
|
+
// run whose every capability delta merged into a document already there.
|
|
119
|
+
const adopting = !existsSync(capabilityDocsDir(config.docsDir));
|
|
120
|
+
let creating = false;
|
|
121
|
+
for (const doc of gated.capabilityDeltas) {
|
|
122
|
+
const livingPath = livingCapabilityPaths(config.docsDir, doc.id).spec;
|
|
123
|
+
// Same move, same rewrite: `features/<FEAT>/capabilities/<id>/` is two
|
|
124
|
+
// directories deeper than `capabilities/<id>/`, so a citation of a glossary
|
|
125
|
+
// term or an ADR would land pointing above the repository.
|
|
126
|
+
const deltaReqs = parseRequirements(rebaseLinks(await readUtf8(doc.spec), { from: dirname(doc.spec), to: dirname(livingPath) }));
|
|
127
|
+
if (!existsSync(livingPath)) {
|
|
128
|
+
const created = applyRequirementDelta([], deltaReqs);
|
|
129
|
+
if (created.length === 0) {
|
|
130
|
+
say(` capability: ${doc.id} — nothing to merge (delta leaves no requirements), no living spec.md created`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// The heading is the id and nothing else. loam merges requirements and
|
|
134
|
+
// never prose, so it must not fabricate the narrative that is the whole
|
|
135
|
+
// point of the document — the line below tells the author it is theirs.
|
|
136
|
+
writes.push(planWrite(livingPath, `# ${doc.id}\n\n## Requirements\n\n${serializeRequirements(created)}`));
|
|
137
|
+
creating = true;
|
|
138
|
+
say(` capability: ${doc.id} — created capabilities/${doc.id}/spec.md (${created.length} requirement(s))`);
|
|
139
|
+
say(` write the narrative above '## Requirements' — loam merges requirements, not prose`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const livingText = await readUtf8(livingPath);
|
|
143
|
+
// The same one-section invariant as the service axis above, and the same
|
|
144
|
+
// reason it cannot be guessed at.
|
|
145
|
+
const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));
|
|
146
|
+
if (reqHeadings.length > 1) {
|
|
147
|
+
throw new ArchiveFailure("merge-failed", `living capabilities/${doc.id}/spec.md has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(", ")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`);
|
|
148
|
+
}
|
|
149
|
+
const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);
|
|
150
|
+
writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));
|
|
151
|
+
const c = summarize(deltaReqs);
|
|
152
|
+
say(` capability: ${doc.id} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);
|
|
153
|
+
}
|
|
154
|
+
if (adopting && creating) {
|
|
155
|
+
say(` capabilities/ did not exist — this archive opts the fleet into the business axis, and \`loam validate --all\` grades it from now on`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Rewrite ONLY the requirements run of a living spec. Byte-for-byte preserved:
|
|
160
|
+
* everything before the first requirement inside `## Requirements` (the intro,
|
|
161
|
+
* the heading line, prose under the heading) and everything from the section's
|
|
162
|
+
* end onward (the next `## ` heading to EOF) — the old cut was a substring
|
|
163
|
+
* `indexOf("\n## Requirements")`, a prefix match that also hit
|
|
164
|
+
* `## Requirements Extra` and silently destroyed every section after the
|
|
165
|
+
* requirements. Prose BETWEEN requirements is body text of whatever is open
|
|
166
|
+
* above it (parseRequirements attributes it to the previous requirement's last
|
|
167
|
+
* scenario, or its text) and survives inside the re-serialized run, framing
|
|
168
|
+
* normalized. `merged` must contain every living requirement — runArchive's
|
|
169
|
+
* stray guard refuses any document whose requirements sit outside the section,
|
|
170
|
+
* using the same heading definition, before this output is ever written.
|
|
171
|
+
*/
|
|
172
|
+
function rewriteRequirementsRun(text, merged) {
|
|
173
|
+
const s = splitRequirementsSection(text);
|
|
174
|
+
// No `## Requirements` at all: the stray guard has already refused any doc
|
|
175
|
+
// whose requirements live elsewhere, so this one has none — open the section.
|
|
176
|
+
if (s === null)
|
|
177
|
+
return `${text.trimEnd()}\n\n## Requirements\n\n${serializeRequirements(merged)}`;
|
|
178
|
+
const body = serializeRequirements(merged);
|
|
179
|
+
// head/tail are raw slices; only the run's own framing is normalized. When
|
|
180
|
+
// the section held no requirements yet, the glue supplies the blank line the
|
|
181
|
+
// author never had reason to write.
|
|
182
|
+
const headGlue = s.run !== "" || s.head.endsWith("\n\n") ? "" : s.head.endsWith("\n") ? "\n" : "\n\n";
|
|
183
|
+
const tailGlue = s.tail === "" ? "" : "\n";
|
|
184
|
+
return s.head + headGlue + body + tailGlue + s.tail;
|
|
185
|
+
}
|
|
186
|
+
function summarize(reqs) {
|
|
187
|
+
const c = { ADDED: 0, MODIFIED: 0, REMOVED: 0 };
|
|
188
|
+
for (const r of reqs) {
|
|
189
|
+
if (r.kind === "ADDED")
|
|
190
|
+
c.ADDED += 1;
|
|
191
|
+
else if (r.kind === "MODIFIED")
|
|
192
|
+
c.MODIFIED += 1;
|
|
193
|
+
else if (r.kind === "REMOVED")
|
|
194
|
+
c.REMOVED += 1;
|
|
195
|
+
}
|
|
196
|
+
return c;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The vocabulary merge: each `features/<FEAT>/glossary/<term>.md` copied to
|
|
200
|
+
* `glossary/<term>.md`.
|
|
201
|
+
*
|
|
202
|
+
* A WHOLE-FILE COPY, and there is nothing else it could be. A capability delta
|
|
203
|
+
* is folded into a living document by the requirement algebra because the two
|
|
204
|
+
* documents share identified parts; a term document has one part, and merging
|
|
205
|
+
* two definitions of one word is not something a machine may attempt. So the
|
|
206
|
+
* definition the author wrote in the feature IS the definition that lands,
|
|
207
|
+
* verbatim — loam re-encodes it as UTF-8 through `readUtf8` and changes nothing
|
|
208
|
+
* else, not even the trailing newline.
|
|
209
|
+
*
|
|
210
|
+
* CREATE-ONLY, which is why this loop has no other branch. A term the living
|
|
211
|
+
* glossary already defines was refused at the gate (`glossary.term-exists`,
|
|
212
|
+
* `core/glossary/delta.ts` has the reasoning), so by the time the plan runs
|
|
213
|
+
* every path here is a file that does not exist. `planWrite` marks it
|
|
214
|
+
* `exclusive`, so a race that created it between the gate and the commit fails
|
|
215
|
+
* the write rather than overwriting somebody.
|
|
216
|
+
*
|
|
217
|
+
* AND IT SAYS SO OUT LOUD when it creates `glossary/`, exactly as
|
|
218
|
+
* `planCapabilities` does for `capabilities/`: the directory's existence is the
|
|
219
|
+
* axis's opt-in, so this merge changes what `loam validate --all` grades from
|
|
220
|
+
* the next command on, and a merge that widens the fleet's own gate must not do
|
|
221
|
+
* it silently.
|
|
222
|
+
*/
|
|
223
|
+
export async function planGlossary(config, gated, plan, say) {
|
|
224
|
+
const glossary = await featureGlossary(gated.featureDir);
|
|
225
|
+
if (!glossary.present || glossary.terms.length === 0)
|
|
226
|
+
return;
|
|
227
|
+
// Asked BEFORE the loop, because the loop is what would create it.
|
|
228
|
+
const adopting = !existsSync(glossaryDir(config.docsDir));
|
|
229
|
+
for (const term of glossary.terms) {
|
|
230
|
+
plan.writes.push(planWrite(livingTermPath(config.docsDir, term.id), await readUtf8(term.path)));
|
|
231
|
+
say(` glossary: ${term.id} — created glossary/${term.id}.md`);
|
|
232
|
+
}
|
|
233
|
+
if (adopting) {
|
|
234
|
+
say(` glossary/ did not exist — this archive opts the fleet into the domain vocabulary, and \`loam validate --all\` grades it from now on`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=specs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/specs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The requirements merge: the feature's ADDED/MODIFIED/REMOVED requirements\n * applied into each living service spec. The contract merges live beside it in\n * `./contracts/` — one file per axis, split out when the OpenAPI half made\n * this one carry two merges.\n *\n * ONE code path for the pair of requirement-carrying files — the business\n * spec.md and the architecture arch.spec.md ride the same delta algebra, the\n * same prose-preserving rewrite and the same guards, parameterized by filename.\n * A fork here would be two places the merge could disagree.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { rebaseLinks } from \"../../../core/links/parse.js\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { featureSpecPaths, SPEC_AXES } from \"../../../core/repo/paths.js\";\nimport { capabilityDocsDir, glossaryDir, livingCapabilityPaths } from \"../../../core/repo/authored/paths.js\";\nimport { featureGlossary, livingTermPath } from \"../../../core/glossary/delta.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport {\n isRequirementsHeading,\n parseRequirements,\n sectionHeadings,\n splitRequirementsSection,\n} from \"../../../core/document/parse.js\";\nimport { applyRequirementDelta } from \"../../../core/document/apply.js\";\nimport { serializeRequirements } from \"../../../core/document/spec.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { ArchiveFailure } from \"./refusal.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function planSpecs(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes } = plan;\n // Requirements merge — apply ADDED/MODIFIED/REMOVED into each living service\n // spec. ONE code path for the pair of requirement-carrying files: the business\n // spec.md and the architecture arch.spec.md ride the same delta algebra, the\n // same prose-preserving rewrite and the same guards, parameterized by filename\n // (SPEC_AXES) — a fork here would be two places the merge could disagree.\n for (const svc of deltaServices) {\n for (const axis of SPEC_AXES) {\n const deltaPath = featureSpecPaths(featureDir, svc)[axis.key];\n if (!existsSync(deltaPath)) continue;\n\n // The shared context: this is a deltaServices × SPEC_AXES loop, and a\n // context-less locate is a full fleet walk per iteration.\n const livingPath = (await locateServicePaths(config.docsDir, svc, config.fleet))[axis.key];\n // Resolved BEFORE the delta is parsed, because the parse now depends on\n // it: a requirement's markdown links are relative to the file they were\n // written in, and this merge moves the text two directories up the tree.\n // `core/links/parse.ts`'s `rebaseLinks` states the defect; the ordering\n // here is what lets the fix see both ends of the move, including a\n // service filed under a subsystem, whose living directory is deeper still.\n const deltaReqs = parseRequirements(\n rebaseLinks(await readUtf8(deltaPath), { from: dirname(deltaPath), to: dirname(livingPath) }),\n );\n if (!existsSync(livingPath)) {\n // New service (or first arch spec) — create the living file from the\n // ADDED/MODIFIED requirements.\n const created = applyRequirementDelta([], deltaReqs);\n if (created.length === 0) {\n say(` ${axis.label}: ${svc} — nothing to merge (delta leaves no requirements), no living ${axis.file} created`);\n continue;\n }\n const heading = axis.key === \"spec\" ? svc : `${svc} — architecture`;\n const frontmatter = `---\\nservice: ${svc}\\nstatus: draft\\n---\\n\\n# ${heading}\\n\\n`;\n writes.push(planWrite(livingPath, `${frontmatter}## Requirements\\n\\n${serializeRequirements(created)}`));\n say(` ${axis.label}: ${svc} — created living ${axis.file} (${created.length} requirement(s))`);\n continue;\n }\n const livingText = await readUtf8(livingPath);\n // TWO `## Requirements` headings would put the rewrite's one-section\n // invariant to a choice it must not make: the run of the first would be\n // rewritten while the second survived verbatim in the tail — and its\n // requirements, collected by parseRequirements, would land in the run TOO.\n // Mechanical, like a model-less landscape, so merge-failed, not --approve.\n const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));\n if (reqHeadings.length > 1) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `living ${axis.file} for ${svc} has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(\", \")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`,\n );\n }\n const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);\n writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));\n\n const c = summarize(deltaReqs);\n say(` ${axis.label}: ${svc} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);\n }\n }\n}\n\n/**\n * The BUSINESS corpus's merge: a feature's `capabilities/<id>/spec.md` deltas\n * applied into the living `capabilities/<id>/spec.md`.\n *\n * Here rather than in a sixth file in this package because this module already\n * owns \"requirements merged into a living document\" — it owns\n * `rewriteRequirementsRun`, the two-headings refusal and the `applyRequirementDelta`\n * call, and the business corpus needs all three unchanged. What differs from\n * `planSpecs` above is only where the living document lives and what a CREATED\n * one says at the top.\n *\n * ARCHIVE CREATES THE LIVING DOCUMENT, by exact analogy with the new-service\n * branch above. The directory IS the list on this axis (`core/capabilities/tree.ts`),\n * so an archive that merged into a file it refused to create would make the\n * whole delta merge nothing — which is the `delta.no-delta-sections` failure\n * class, arriving through the merge instead of through the grammar.\n *\n * NO FRONTMATTER on a created capability document, and that is a decision\n * rather than an omission. Nothing reads a capability document's frontmatter:\n * `readCapabilityTree` classifies by the file's PRESENCE and\n * `readCapabilityVocabulary` takes `description`/`owner` from the YAML side. A\n * `status:` field invented here would be the \"second list nothing keeps\n * current\" this axis was designed against.\n *\n * AND IT SAYS SO OUT LOUD. Creating the first `capabilities/<id>/spec.md`\n * creates `capabilities/`, which is one of the two ways a fleet opts INTO the\n * capability axis — from the next command on, `loam validate --all` grades a\n * corpus that did not exist before. A merge that changes what the whole fleet\n * is graded on must not do it silently.\n */\nexport async function planCapabilities(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { writes } = plan;\n // Whether `capabilities/` exists is asked BEFORE the loop, because the loop\n // is what would create it; `creating` records whether this run actually\n // materialises a document, so the adoption line below is not printed by a\n // run whose every capability delta merged into a document already there.\n const adopting = !existsSync(capabilityDocsDir(config.docsDir));\n let creating = false;\n for (const doc of gated.capabilityDeltas) {\n const livingPath = livingCapabilityPaths(config.docsDir, doc.id).spec;\n // Same move, same rewrite: `features/<FEAT>/capabilities/<id>/` is two\n // directories deeper than `capabilities/<id>/`, so a citation of a glossary\n // term or an ADR would land pointing above the repository.\n const deltaReqs = parseRequirements(\n rebaseLinks(await readUtf8(doc.spec), { from: dirname(doc.spec), to: dirname(livingPath) }),\n );\n if (!existsSync(livingPath)) {\n const created = applyRequirementDelta([], deltaReqs);\n if (created.length === 0) {\n say(` capability: ${doc.id} — nothing to merge (delta leaves no requirements), no living spec.md created`);\n continue;\n }\n // The heading is the id and nothing else. loam merges requirements and\n // never prose, so it must not fabricate the narrative that is the whole\n // point of the document — the line below tells the author it is theirs.\n writes.push(planWrite(livingPath, `# ${doc.id}\\n\\n## Requirements\\n\\n${serializeRequirements(created)}`));\n creating = true;\n say(` capability: ${doc.id} — created capabilities/${doc.id}/spec.md (${created.length} requirement(s))`);\n say(` write the narrative above '## Requirements' — loam merges requirements, not prose`);\n continue;\n }\n const livingText = await readUtf8(livingPath);\n // The same one-section invariant as the service axis above, and the same\n // reason it cannot be guessed at.\n const reqHeadings = sectionHeadings(livingText).filter((h) => isRequirementsHeading(h.text));\n if (reqHeadings.length > 1) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `living capabilities/${doc.id}/spec.md has ${reqHeadings.length} '## Requirements' headings (lines ${reqHeadings.map((h) => h.line).join(\", \")}) — the merge rewrites ONE requirements section and cannot choose; merge them into one, then re-run`,\n );\n }\n const merged = applyRequirementDelta(parseRequirements(livingText), deltaReqs);\n writes.push(planWrite(livingPath, rewriteRequirementsRun(livingText, merged)));\n const c = summarize(deltaReqs);\n say(` capability: ${doc.id} ← +${c.ADDED} ~${c.MODIFIED} -${c.REMOVED} (now ${merged.length} total)`);\n }\n if (adopting && creating) {\n say(` capabilities/ did not exist — this archive opts the fleet into the business axis, and \\`loam validate --all\\` grades it from now on`);\n }\n}\n\n/**\n * Rewrite ONLY the requirements run of a living spec. Byte-for-byte preserved:\n * everything before the first requirement inside `## Requirements` (the intro,\n * the heading line, prose under the heading) and everything from the section's\n * end onward (the next `## ` heading to EOF) — the old cut was a substring\n * `indexOf(\"\\n## Requirements\")`, a prefix match that also hit\n * `## Requirements Extra` and silently destroyed every section after the\n * requirements. Prose BETWEEN requirements is body text of whatever is open\n * above it (parseRequirements attributes it to the previous requirement's last\n * scenario, or its text) and survives inside the re-serialized run, framing\n * normalized. `merged` must contain every living requirement — runArchive's\n * stray guard refuses any document whose requirements sit outside the section,\n * using the same heading definition, before this output is ever written.\n */\nfunction rewriteRequirementsRun(text: string, merged: Requirement[]): string {\n const s = splitRequirementsSection(text);\n // No `## Requirements` at all: the stray guard has already refused any doc\n // whose requirements live elsewhere, so this one has none — open the section.\n if (s === null) return `${text.trimEnd()}\\n\\n## Requirements\\n\\n${serializeRequirements(merged)}`;\n const body = serializeRequirements(merged);\n // head/tail are raw slices; only the run's own framing is normalized. When\n // the section held no requirements yet, the glue supplies the blank line the\n // author never had reason to write.\n const headGlue = s.run !== \"\" || s.head.endsWith(\"\\n\\n\") ? \"\" : s.head.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const tailGlue = s.tail === \"\" ? \"\" : \"\\n\";\n return s.head + headGlue + body + tailGlue + s.tail;\n}\n\nfunction summarize(reqs: Requirement[]): { ADDED: number; MODIFIED: number; REMOVED: number } {\n const c = { ADDED: 0, MODIFIED: 0, REMOVED: 0 };\n for (const r of reqs) {\n if (r.kind === \"ADDED\") c.ADDED += 1;\n else if (r.kind === \"MODIFIED\") c.MODIFIED += 1;\n else if (r.kind === \"REMOVED\") c.REMOVED += 1;\n }\n return c;\n}\n\n/**\n * The vocabulary merge: each `features/<FEAT>/glossary/<term>.md` copied to\n * `glossary/<term>.md`.\n *\n * A WHOLE-FILE COPY, and there is nothing else it could be. A capability delta\n * is folded into a living document by the requirement algebra because the two\n * documents share identified parts; a term document has one part, and merging\n * two definitions of one word is not something a machine may attempt. So the\n * definition the author wrote in the feature IS the definition that lands,\n * verbatim — loam re-encodes it as UTF-8 through `readUtf8` and changes nothing\n * else, not even the trailing newline.\n *\n * CREATE-ONLY, which is why this loop has no other branch. A term the living\n * glossary already defines was refused at the gate (`glossary.term-exists`,\n * `core/glossary/delta.ts` has the reasoning), so by the time the plan runs\n * every path here is a file that does not exist. `planWrite` marks it\n * `exclusive`, so a race that created it between the gate and the commit fails\n * the write rather than overwriting somebody.\n *\n * AND IT SAYS SO OUT LOUD when it creates `glossary/`, exactly as\n * `planCapabilities` does for `capabilities/`: the directory's existence is the\n * axis's opt-in, so this merge changes what `loam validate --all` grades from\n * the next command on, and a merge that widens the fleet's own gate must not do\n * it silently.\n */\nexport async function planGlossary(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const glossary = await featureGlossary(gated.featureDir);\n if (!glossary.present || glossary.terms.length === 0) return;\n // Asked BEFORE the loop, because the loop is what would create it.\n const adopting = !existsSync(glossaryDir(config.docsDir));\n for (const term of glossary.terms) {\n plan.writes.push(planWrite(livingTermPath(config.docsDir, term.id), await readUtf8(term.path)));\n say(` glossary: ${term.id} — created glossary/${term.id}.md`);\n }\n if (adopting) {\n say(` glossary/ did not exist — this archive opts the fleet into the domain vocabulary, and \\`loam validate --all\\` grades it from now on`);\n }\n}\n"]}
|