@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2635 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +599 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +252 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +204 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +270 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +337 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +139 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +174 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +287 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +137 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +210 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +277 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +197 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +72 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +58 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed's commit window: lock, recover, the under-lock provenance and
|
|
3
|
+
* fleet-consistency checks, one journaled transaction. Modeled line for line
|
|
4
|
+
* on commands/new/commit.ts and on the same seam: everything in here holds
|
|
5
|
+
* the docs lock, nothing outside it does, and the module prints nothing —
|
|
6
|
+
* the caller renders every arm.
|
|
7
|
+
*
|
|
8
|
+
* The checks live UNDER the lock on purpose: the never-overwrite promise is a
|
|
9
|
+
* statement about the landscape as it is at the moment of the write, and an
|
|
10
|
+
* unlocked classification would be a TOCTOU against every other docs writer.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
14
|
+
import { landscapeProvenance } from "../../core/c4/seed/stamp.js";
|
|
15
|
+
import { isLandscapeStub } from "../../core/scaffold/landscape.js";
|
|
16
|
+
import { parseServiceId } from "../../core/kernel/ids/service.js";
|
|
17
|
+
import { landscapePath } from "../../core/repo/paths.js";
|
|
18
|
+
import { listFleetTree } from "../../core/repo/repo.js";
|
|
19
|
+
import { DocsRepoUnavailableError } from "../../core/repo/state.js";
|
|
20
|
+
import { stageWrites } from "../../core/staging/commit.js";
|
|
21
|
+
import { InterruptedCommitError } from "../../core/staging/interrupted.js";
|
|
22
|
+
import { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from "../../core/staging/lock.js";
|
|
23
|
+
import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
|
|
24
|
+
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
25
|
+
import { planSeedWrites } from "./plan.js";
|
|
26
|
+
export async function commitSeed(req) {
|
|
27
|
+
const { docsDir } = req;
|
|
28
|
+
let releaseLock;
|
|
29
|
+
try {
|
|
30
|
+
releaseLock = await acquireDocsLockWaiting(docsDir, LOCK_WAIT_MS);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err instanceof DocsBusyError)
|
|
34
|
+
return { ok: false, code: "docs-busy", message: err.message };
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
let recovered;
|
|
39
|
+
try {
|
|
40
|
+
recovered = await recoverInterruptedCommit(docsDir);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (!(err instanceof InterruptedCommitError))
|
|
44
|
+
throw err;
|
|
45
|
+
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
46
|
+
}
|
|
47
|
+
// Provenance first, then the tree: both read the repo as this run will
|
|
48
|
+
// leave it, and both refusals must prove nothing was written.
|
|
49
|
+
const landscape = await classifyLandscape(docsDir);
|
|
50
|
+
if ("refusal" in landscape) {
|
|
51
|
+
return { ok: false, code: "seed-landscape-edited", message: landscape.refusal };
|
|
52
|
+
}
|
|
53
|
+
let tree;
|
|
54
|
+
try {
|
|
55
|
+
tree = await listFleetTree(docsDir);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (!(err instanceof DocsRepoUnavailableError))
|
|
59
|
+
throw err;
|
|
60
|
+
return { ok: false, repoGone: err };
|
|
61
|
+
}
|
|
62
|
+
const inconsistency = preflight(tree, req.seed);
|
|
63
|
+
if (inconsistency !== null)
|
|
64
|
+
return inconsistency;
|
|
65
|
+
const plan = await planSeedWrites(req, tree);
|
|
66
|
+
const staged = await stageWrites(plan.writes);
|
|
67
|
+
const committed = await commitStaged({ root: docsDir, command: "seed", rerun: req.rerun, target: "landscape" }, staged, "seeded");
|
|
68
|
+
if (!committed.ok) {
|
|
69
|
+
// `committed.raced` is deliberately NOT remapped: the lock serialises
|
|
70
|
+
// loam writers, so a fired CAS or a lost exclusive create here means an
|
|
71
|
+
// out-of-band editor, and seed has no softer race answer the way `new`
|
|
72
|
+
// has already-exists — merge-failed's "nothing was written, re-run" is
|
|
73
|
+
// the honest sentence either way.
|
|
74
|
+
return { ok: false, code: committed.code, message: committed.message };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
ok: true,
|
|
78
|
+
landscape: landscape.disposition,
|
|
79
|
+
written: plan.writes.filter((w) => w.content !== null).map((w) => w.path),
|
|
80
|
+
removed: plan.writes.filter((w) => w.content === null).map((w) => w.path),
|
|
81
|
+
services: plan.services,
|
|
82
|
+
recovered,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await releaseLock();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* May seed write the landscape? Absent and untouched-scaffold-stub are the
|
|
91
|
+
* two states with provably no human work in them; a matching stamp digest is
|
|
92
|
+
* the third (`core/c4/seed/stamp.ts`). Everything else refuses.
|
|
93
|
+
*/
|
|
94
|
+
async function classifyLandscape(docsDir) {
|
|
95
|
+
const path = landscapePath(docsDir);
|
|
96
|
+
if (!existsSync(path))
|
|
97
|
+
return { disposition: "created" };
|
|
98
|
+
const text = (await readFile(path)).toString("utf8");
|
|
99
|
+
// `isLandscapeStub`, never a byte-compare of our own: that function IS the
|
|
100
|
+
// repo's answer to "is this still the scaffold's untouched map", it is what
|
|
101
|
+
// `loam status` reads before telling the reader to run seed, and it is
|
|
102
|
+
// suffix-matched and CRLF-normalised for two reasons seed shares —
|
|
103
|
+
// `migrate-openspec` prepends a preamble to the identical stub, and a
|
|
104
|
+
// checkout under `core.autocrlf` (Git for Windows' installer default)
|
|
105
|
+
// rewrites every line without touching a fact. A stricter compare here made
|
|
106
|
+
// status and seed contradict each other about the same file.
|
|
107
|
+
if (isLandscapeStub(text))
|
|
108
|
+
return { disposition: "replaced-stub" };
|
|
109
|
+
const provenance = landscapeProvenance(text);
|
|
110
|
+
if (provenance === "seed-stamped")
|
|
111
|
+
return { disposition: "regenerated" };
|
|
112
|
+
return {
|
|
113
|
+
refusal: `architecture/landscape.likec4 ${provenance === "seed-edited"
|
|
114
|
+
? "was seeded but has been hand-edited since — the line-1 stamp's digest no longer matches the content"
|
|
115
|
+
: "was not written by `loam seed`: it carries no line-1 stamp and is not the scaffold's untouched stub"}. Seed never overwrites human work; nothing was written. Either fold the edits into ` +
|
|
116
|
+
`fleet.yaml and delete the file, then re-run — or keep the hand-authored map and stop ` +
|
|
117
|
+
`using seed here.`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The fleet-consistency preflight. Seed regenerates the landscape WHOLESALE —
|
|
122
|
+
* that is the only posture the stamp can prove safe — so every existing
|
|
123
|
+
* services/<id>/ must be named in fleet.yaml, or the regenerate would orphan
|
|
124
|
+
* it into `landscape.service-unmodelled`. And the file's names must not
|
|
125
|
+
* collide with what the tree already holds under a different kind: one flat
|
|
126
|
+
* namespace, the same rule the walk enforces.
|
|
127
|
+
*/
|
|
128
|
+
function preflight(tree, seed) {
|
|
129
|
+
const treeSubs = new Set(tree.subsystems.map((s) => s.name));
|
|
130
|
+
const treeIds = new Set(tree.services.map((s) => s.id));
|
|
131
|
+
// The collision scan runs BEFORE the missing-service scan, and the order is
|
|
132
|
+
// load-bearing. A name the file declares as a subsystem or an external while
|
|
133
|
+
// the tree holds it as a service DIRECTORY is both facts at once: it is a
|
|
134
|
+
// namespace collision, and it is also an existing service the file does not
|
|
135
|
+
// name. With the missing scan first, the caller was told to paste that name
|
|
136
|
+
// into `services:` — advice that walks straight into `seed-duplicate-service`
|
|
137
|
+
// on the next run, because the file already declares it as something else.
|
|
138
|
+
const collisions = [];
|
|
139
|
+
for (const sub of seed.subsystems) {
|
|
140
|
+
if (treeIds.has(sub))
|
|
141
|
+
collisions.push(`subsystem '${sub}' is an existing service directory`);
|
|
142
|
+
}
|
|
143
|
+
for (const s of seed.services) {
|
|
144
|
+
if (treeSubs.has(s.id))
|
|
145
|
+
collisions.push(`service '${s.id}' is an existing subsystem directory`);
|
|
146
|
+
}
|
|
147
|
+
for (const x of seed.externals) {
|
|
148
|
+
if (treeIds.has(x))
|
|
149
|
+
collisions.push(`external '${x}' is an existing service directory`);
|
|
150
|
+
if (treeSubs.has(x))
|
|
151
|
+
collisions.push(`external '${x}' is an existing subsystem directory`);
|
|
152
|
+
}
|
|
153
|
+
if (collisions.length > 0) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
code: "seed-file-invalid",
|
|
157
|
+
message: `fleet.yaml collides with the services/ tree — service ids, subsystem names and externals ` +
|
|
158
|
+
`share one flat namespace: ${collisions.join("; ")}. Rename the fleet.yaml side (or the ` +
|
|
159
|
+
`directory, with \`loam subsystem rename <from> <to>\`), then re-run. Nothing was written.`,
|
|
160
|
+
missingServices: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const ids = new Set(seed.services.map((s) => s.id));
|
|
164
|
+
const missing = tree.services.map((s) => s.id).filter((id) => !ids.has(id)).sort();
|
|
165
|
+
if (missing.length === 0)
|
|
166
|
+
return null;
|
|
167
|
+
// The ids are partitioned because one half cannot take the other's advice.
|
|
168
|
+
// `tree.services` carries RAW ids — a directory whose name fails the service
|
|
169
|
+
// grammar is still a service to the walk, by design — and "add it to
|
|
170
|
+
// services: exactly as spelled" is impossible for one of those: the fleet
|
|
171
|
+
// file would refuse the entry. Without this split, seed was simply unusable
|
|
172
|
+
// on such a repo and never said so.
|
|
173
|
+
const legal = missing.filter((id) => parseServiceId(id, "services entry").ok);
|
|
174
|
+
const illegal = missing.filter((id) => !parseServiceId(id, "services entry").ok);
|
|
175
|
+
const fixes = [];
|
|
176
|
+
if (legal.length > 0)
|
|
177
|
+
fixes.push(`add ${legal.join(", ")} to services:, exactly as spelled`);
|
|
178
|
+
if (illegal.length > 0) {
|
|
179
|
+
fixes.push(`rename the ${illegal.length === 1 ? "directory" : "directories"} ${illegal.join(", ")} ` +
|
|
180
|
+
`first — ${illegal.length === 1 ? "that name is not a legal service id" : "those names are not legal service ids"}, ` +
|
|
181
|
+
`so no services: entry can name ${illegal.length === 1 ? "it" : "them"}`);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
code: "seed-file-invalid",
|
|
186
|
+
message: `fleet.yaml must name every existing service — seed regenerates the landscape wholesale, ` +
|
|
187
|
+
`and these services/ directories are not in it: ${missing.join(", ")}. ` +
|
|
188
|
+
`To fix: ${fixes.join("; and ")}. Then re-run. Nothing was written.`,
|
|
189
|
+
missingServices: missing,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/seed/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Seed's commit window: lock, recover, the under-lock provenance and\n * fleet-consistency checks, one journaled transaction. Modeled line for line\n * on commands/new/commit.ts and on the same seam: everything in here holds\n * the docs lock, nothing outside it does, and the module prints nothing —\n * the caller renders every arm.\n *\n * The checks live UNDER the lock on purpose: the never-overwrite promise is a\n * statement about the landscape as it is at the moment of the write, and an\n * unlocked classification would be a TOCTOU against every other docs writer.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { FleetSeed } from \"../../core/c4/seed/fleet-file.js\";\nimport { landscapeProvenance } from \"../../core/c4/seed/stamp.js\";\nimport { isLandscapeStub } from \"../../core/scaffold/landscape.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { parseServiceId } from \"../../core/kernel/ids/service.js\";\nimport { landscapePath } from \"../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../core/repo/repo.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport type { FleetTree } from \"../../core/repo/tree/walk.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { planSeedWrites, type SeedPlanInput } from \"./plan.js\";\n\n/** The plan's input, plus the one fact only the transaction needs. */\nexport interface SeedCommitRequest extends SeedPlanInput {\n /** How a human re-runs this command — the transaction journal's `rerun`. */\n rerun: string;\n}\n\n/** What happened to architecture/landscape.likec4 — the three writes seed may prove safe. */\nexport type LandscapeDisposition = \"created\" | \"replaced-stub\" | \"regenerated\";\n\nexport type SeedCommit =\n | {\n ok: true;\n landscape: LandscapeDisposition;\n /** Absolute path of every file the transaction wrote, in write order. */\n written: string[];\n /**\n * Absolute path of every file the transaction REMOVED — only ever the\n * generated views file, when the resulting tree has no subsystems left\n * to view. Separate from `written` because \"+ path\" is a lie about a\n * delete.\n */\n removed: string[];\n /** Service ids split by whether this run created the directory. */\n services: { created: string[]; existing: string[] };\n recovered: CommitRecovery | null;\n }\n | {\n ok: false;\n code:\n | \"docs-busy\"\n | \"commit-interrupted\"\n | \"merge-failed\"\n | \"rollback-incomplete\"\n | \"seed-landscape-edited\";\n message: string;\n }\n /** The under-lock fleet-consistency preflight refusing; see `missingServices`. */\n | { ok: false; code: \"seed-file-invalid\"; message: string; missingServices: string[] }\n /** The docs repo went away between the command's gate and the locked read — new/commit.ts says why this is not a `code:` arm. */\n | { ok: false; repoGone: DocsRepoUnavailableError };\n\nexport async function commitSeed(req: SeedCommitRequest): Promise<SeedCommit> {\n const { docsDir } = req;\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLockWaiting(docsDir, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n return { ok: false, code: \"commit-interrupted\", message: err.message };\n }\n\n // Provenance first, then the tree: both read the repo as this run will\n // leave it, and both refusals must prove nothing was written.\n const landscape = await classifyLandscape(docsDir);\n if (\"refusal\" in landscape) {\n return { ok: false, code: \"seed-landscape-edited\", message: landscape.refusal };\n }\n\n let tree: FleetTree;\n try {\n tree = await listFleetTree(docsDir);\n } catch (err) {\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return { ok: false, repoGone: err };\n }\n const inconsistency = preflight(tree, req.seed);\n if (inconsistency !== null) return inconsistency;\n\n const plan = await planSeedWrites(req, tree);\n const staged = await stageWrites(plan.writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"seed\", rerun: req.rerun, target: \"landscape\" },\n staged,\n \"seeded\",\n );\n if (!committed.ok) {\n // `committed.raced` is deliberately NOT remapped: the lock serialises\n // loam writers, so a fired CAS or a lost exclusive create here means an\n // out-of-band editor, and seed has no softer race answer the way `new`\n // has already-exists — merge-failed's \"nothing was written, re-run\" is\n // the honest sentence either way.\n return { ok: false, code: committed.code, message: committed.message };\n }\n return {\n ok: true,\n landscape: landscape.disposition,\n written: plan.writes.filter((w) => w.content !== null).map((w) => w.path),\n removed: plan.writes.filter((w) => w.content === null).map((w) => w.path),\n services: plan.services,\n recovered,\n };\n } finally {\n await releaseLock();\n }\n}\n\n/**\n * May seed write the landscape? Absent and untouched-scaffold-stub are the\n * two states with provably no human work in them; a matching stamp digest is\n * the third (`core/c4/seed/stamp.ts`). Everything else refuses.\n */\nasync function classifyLandscape(\n docsDir: DocsDir,\n): Promise<{ disposition: LandscapeDisposition } | { refusal: string }> {\n const path = landscapePath(docsDir);\n if (!existsSync(path)) return { disposition: \"created\" };\n const text = (await readFile(path)).toString(\"utf8\");\n // `isLandscapeStub`, never a byte-compare of our own: that function IS the\n // repo's answer to \"is this still the scaffold's untouched map\", it is what\n // `loam status` reads before telling the reader to run seed, and it is\n // suffix-matched and CRLF-normalised for two reasons seed shares —\n // `migrate-openspec` prepends a preamble to the identical stub, and a\n // checkout under `core.autocrlf` (Git for Windows' installer default)\n // rewrites every line without touching a fact. A stricter compare here made\n // status and seed contradict each other about the same file.\n if (isLandscapeStub(text)) return { disposition: \"replaced-stub\" };\n const provenance = landscapeProvenance(text);\n if (provenance === \"seed-stamped\") return { disposition: \"regenerated\" };\n return {\n refusal:\n `architecture/landscape.likec4 ${\n provenance === \"seed-edited\"\n ? \"was seeded but has been hand-edited since — the line-1 stamp's digest no longer matches the content\"\n : \"was not written by `loam seed`: it carries no line-1 stamp and is not the scaffold's untouched stub\"\n }. Seed never overwrites human work; nothing was written. Either fold the edits into ` +\n `fleet.yaml and delete the file, then re-run — or keep the hand-authored map and stop ` +\n `using seed here.`,\n };\n}\n\n/**\n * The fleet-consistency preflight. Seed regenerates the landscape WHOLESALE —\n * that is the only posture the stamp can prove safe — so every existing\n * services/<id>/ must be named in fleet.yaml, or the regenerate would orphan\n * it into `landscape.service-unmodelled`. And the file's names must not\n * collide with what the tree already holds under a different kind: one flat\n * namespace, the same rule the walk enforces.\n */\nfunction preflight(tree: FleetTree, seed: FleetSeed): SeedCommit | null {\n const treeSubs = new Set(tree.subsystems.map((s) => s.name));\n const treeIds = new Set(tree.services.map((s) => s.id as string));\n\n // The collision scan runs BEFORE the missing-service scan, and the order is\n // load-bearing. A name the file declares as a subsystem or an external while\n // the tree holds it as a service DIRECTORY is both facts at once: it is a\n // namespace collision, and it is also an existing service the file does not\n // name. With the missing scan first, the caller was told to paste that name\n // into `services:` — advice that walks straight into `seed-duplicate-service`\n // on the next run, because the file already declares it as something else.\n const collisions: string[] = [];\n for (const sub of seed.subsystems) {\n if (treeIds.has(sub as string)) collisions.push(`subsystem '${sub}' is an existing service directory`);\n }\n for (const s of seed.services) {\n if (treeSubs.has(s.id as string)) collisions.push(`service '${s.id}' is an existing subsystem directory`);\n }\n for (const x of seed.externals) {\n if (treeIds.has(x)) collisions.push(`external '${x}' is an existing service directory`);\n if (treeSubs.has(x)) collisions.push(`external '${x}' is an existing subsystem directory`);\n }\n if (collisions.length > 0) {\n return {\n ok: false,\n code: \"seed-file-invalid\",\n message:\n `fleet.yaml collides with the services/ tree — service ids, subsystem names and externals ` +\n `share one flat namespace: ${collisions.join(\"; \")}. Rename the fleet.yaml side (or the ` +\n `directory, with \\`loam subsystem rename <from> <to>\\`), then re-run. Nothing was written.`,\n missingServices: [],\n };\n }\n\n const ids = new Set(seed.services.map((s) => s.id as string));\n const missing = tree.services.map((s) => s.id as string).filter((id) => !ids.has(id)).sort();\n if (missing.length === 0) return null;\n // The ids are partitioned because one half cannot take the other's advice.\n // `tree.services` carries RAW ids — a directory whose name fails the service\n // grammar is still a service to the walk, by design — and \"add it to\n // services: exactly as spelled\" is impossible for one of those: the fleet\n // file would refuse the entry. Without this split, seed was simply unusable\n // on such a repo and never said so.\n const legal = missing.filter((id) => parseServiceId(id, \"services entry\").ok);\n const illegal = missing.filter((id) => !parseServiceId(id, \"services entry\").ok);\n const fixes: string[] = [];\n if (legal.length > 0) fixes.push(`add ${legal.join(\", \")} to services:, exactly as spelled`);\n if (illegal.length > 0) {\n fixes.push(\n `rename the ${illegal.length === 1 ? \"directory\" : \"directories\"} ${illegal.join(\", \")} ` +\n `first — ${illegal.length === 1 ? \"that name is not a legal service id\" : \"those names are not legal service ids\"}, ` +\n `so no services: entry can name ${illegal.length === 1 ? \"it\" : \"them\"}`,\n );\n }\n return {\n ok: false,\n code: \"seed-file-invalid\",\n message:\n `fleet.yaml must name every existing service — seed regenerates the landscape wholesale, ` +\n `and these services/ directories are not in it: ${missing.join(\", \")}. ` +\n `To fix: ${fixes.join(\"; and \")}. Then re-run. Nothing was written.`,\n missingServices: missing,\n };\n}\n"]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a seed run will write, decided in full before a byte lands. Split from
|
|
3
|
+
* `./commit.ts` on the phase seam: that module owns the WINDOW — the lock, the
|
|
4
|
+
* interrupted-commit recovery, the two refusals and the transaction — while
|
|
5
|
+
* this one answers the single question "given the tree as it is and the fleet
|
|
6
|
+
* file as it reads, which files change and how". Nothing here acquires
|
|
7
|
+
* anything, and nothing here writes: it is called under the lock and hands
|
|
8
|
+
* back a plan the transaction executes or rolls back whole.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { serviceDirOf } from "../../core/kernel/ids/dirs.js";
|
|
14
|
+
import { landscapePath, servicePathsUnder, servicesDir, subsystemPathUnder, subsystemViewsPath, unfiledServicePaths, } from "../../core/repo/paths.js";
|
|
15
|
+
import { SUBSYSTEM_MARKER } from "../../core/repo/tree/marker.js";
|
|
16
|
+
import { renderSubsystemViews, viewsAgree } from "../../core/repo/tree/views.js";
|
|
17
|
+
import { TEMP_FILE_RE } from "../../core/staging/commit.js";
|
|
18
|
+
import { planWrite } from "../../core/staging/writes.js";
|
|
19
|
+
/**
|
|
20
|
+
* Does this existing service directory hold nothing git would keep? An empty
|
|
21
|
+
* directory is a service to the walk but nothing at all to git, so a landscape
|
|
22
|
+
* declaring it clones into a repo where `validate --all` fails on a service
|
|
23
|
+
* that exists only on the author's disk. Seed's own crash window produces
|
|
24
|
+
* exactly this tree — `stageWrites` creates directories before the journal is
|
|
25
|
+
* written, so a kill in between leaves directories no recovery knows about —
|
|
26
|
+
* and so does a plain `mkdir`. Staging temps do not count as content: they are
|
|
27
|
+
* that same crash's litter, not somebody's file.
|
|
28
|
+
*/
|
|
29
|
+
async function keepsNothing(dir) {
|
|
30
|
+
try {
|
|
31
|
+
return (await readdir(dir)).every((entry) => TEMP_FILE_RE.test(entry));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Unreadable is not empty: the walk found this directory a moment ago, and
|
|
35
|
+
// inventing a write into a directory we cannot list is the wrong guess.
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The complete plan, in memory, before a byte lands: the sealed landscape, one
|
|
41
|
+
* marker per NEW subsystem, one `.gitkeep` per service directory this commit
|
|
42
|
+
* creates — and per existing one holding nothing git would keep (see
|
|
43
|
+
* `keepsNothing`) — and the generated views file rendered from the tree AS
|
|
44
|
+
* THIS COMMIT LEAVES IT, never from a re-walk between two states
|
|
45
|
+
* (commands/subsystem/txn/views.ts's `movedTree` doctrine). That last one is
|
|
46
|
+
* the only write that can be a DELETE: a tree with no subsystems owes no views
|
|
47
|
+
* file, and a leftover is a finding.
|
|
48
|
+
*
|
|
49
|
+
* Existing directories are never moved: fleet.yaml's placement is ignored for
|
|
50
|
+
* a service that already exists — moving is `loam subsystem move <id> --into
|
|
51
|
+
* <name>`'s job, under its own guards — and nothing anywhere is deleted except
|
|
52
|
+
* that one generated file.
|
|
53
|
+
*/
|
|
54
|
+
export async function planSeedWrites(req, tree) {
|
|
55
|
+
const { docsDir, seed } = req;
|
|
56
|
+
const writes = [planWrite(landscapePath(docsDir), req.landscape)];
|
|
57
|
+
const existingSubs = new Map(tree.subsystems.map((s) => [s.name, s]));
|
|
58
|
+
const newSubs = [];
|
|
59
|
+
for (const sub of seed.subsystems) {
|
|
60
|
+
if (existingSubs.has(sub))
|
|
61
|
+
continue;
|
|
62
|
+
const dir = subsystemPathUnder(servicesDir(docsDir), sub);
|
|
63
|
+
// A comment line parses to null — a valid empty marker (tree/marker.ts).
|
|
64
|
+
writes.push(planWrite(join(dir, SUBSYSTEM_MARKER), "# subsystem marker\n"));
|
|
65
|
+
newSubs.push({ name: sub, path: [sub], dir, meta: {} });
|
|
66
|
+
}
|
|
67
|
+
const treeDirs = new Map(tree.services.map((s) => [s.id, s.dir]));
|
|
68
|
+
// Every existing directory inspected at once — independent reads, so the
|
|
69
|
+
// loop below stays synchronous.
|
|
70
|
+
const invisible = new Map(await Promise.all([...treeDirs].map(async ([id, dir]) => [id, await keepsNothing(dir)])));
|
|
71
|
+
const created = [];
|
|
72
|
+
const existing = [];
|
|
73
|
+
const newWalked = [];
|
|
74
|
+
for (const s of seed.services) {
|
|
75
|
+
const already = treeDirs.get(s.id);
|
|
76
|
+
if (already !== undefined) {
|
|
77
|
+
existing.push(s.id);
|
|
78
|
+
// Existing, but git-invisible: give it the `.gitkeep` a created service
|
|
79
|
+
// gets, or the landscape about to be written declares a directory the
|
|
80
|
+
// next clone will not have. `planWrite` makes this a no-op when the file
|
|
81
|
+
// is already there, so a real service is never touched.
|
|
82
|
+
if (invisible.get(s.id) === true)
|
|
83
|
+
writes.push(planWrite(join(already, ".gitkeep"), ""));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
created.push(s.id);
|
|
87
|
+
const parent = s.subsystem === null ? null : existingSubs.get(s.subsystem) ?? null;
|
|
88
|
+
const dir = s.subsystem === null
|
|
89
|
+
? unfiledServicePaths(docsDir, s.id).dir
|
|
90
|
+
: parent !== null
|
|
91
|
+
? servicePathsUnder(parent.dir, s.id).dir
|
|
92
|
+
: // A subsystem this same commit creates: the creation spelling,
|
|
93
|
+
// through the same branded joins the subsystem verbs use.
|
|
94
|
+
serviceDirOf(join(subsystemPathUnder(servicesDir(docsDir), s.subsystem), s.id));
|
|
95
|
+
writes.push(planWrite(join(dir, ".gitkeep"), ""));
|
|
96
|
+
newWalked.push({
|
|
97
|
+
// A ServiceId IS a RawServiceId (the checked subtype), so no re-cast.
|
|
98
|
+
id: s.id,
|
|
99
|
+
dir,
|
|
100
|
+
subsystem: s.subsystem === null ? [] : parent !== null ? parent.path : [s.subsystem],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const plannedTree = {
|
|
104
|
+
findings: [],
|
|
105
|
+
services: [...tree.services, ...newWalked],
|
|
106
|
+
subsystems: [...tree.subsystems, ...newSubs],
|
|
107
|
+
};
|
|
108
|
+
// The generated views file, including its ABSENCE. `renderSubsystemViews`
|
|
109
|
+
// returning null means the tree has no subsystems, and its contract is that
|
|
110
|
+
// the file must then not exist — `validate` grades a leftover as
|
|
111
|
+
// `subsystem.views-stale`, so a seed that ignored the file would exit 0 and
|
|
112
|
+
// send the caller, via its own `next` list, into a failing gate over a file
|
|
113
|
+
// it had in hand. `content: null` is the delete the transaction understands
|
|
114
|
+
// (commands/subsystem/txn/txn.ts's `"removed"`), and a file that already
|
|
115
|
+
// SAYS this is left out of the journal entirely rather than written back
|
|
116
|
+
// over itself.
|
|
117
|
+
//
|
|
118
|
+
// "Already says this" is content, not bytes: `viewsAgree` performs the
|
|
119
|
+
// comparison and records the Windows reason for it, beside the generator
|
|
120
|
+
// that mints the bytes. What gets WRITTEN is unchanged — the LF render, as a
|
|
121
|
+
// Buffer — so this decode is a comparison only, never the string round trip
|
|
122
|
+
// to disk that writes.ts forbids; a views file whose bytes are not UTF-8
|
|
123
|
+
// therefore fails the compare and is regenerated, which is the right answer
|
|
124
|
+
// for it anyway.
|
|
125
|
+
const viewsPath = subsystemViewsPath(docsDir);
|
|
126
|
+
const views = renderSubsystemViews(plannedTree, req.elements);
|
|
127
|
+
const wanted = views === null ? null : Buffer.from(views, "utf8");
|
|
128
|
+
const onDisk = existsSync(viewsPath) ? await readFile(viewsPath, "utf8") : null;
|
|
129
|
+
if (!viewsAgree(onDisk, views))
|
|
130
|
+
writes.push({ path: viewsPath, content: wanted });
|
|
131
|
+
return { writes, services: { created: created.sort(), existing: existing.sort() } };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/seed/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a seed run will write, decided in full before a byte lands. Split from\n * `./commit.ts` on the phase seam: that module owns the WINDOW — the lock, the\n * interrupted-commit recovery, the two refusals and the transaction — while\n * this one answers the single question \"given the tree as it is and the fleet\n * file as it reads, which files change and how\". Nothing here acquires\n * anything, and nothing here writes: it is called under the lock and hands\n * back a plan the transaction executes or rolls back whole.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Elem } from \"../../core/c4/likec4.js\";\nimport type { FleetSeed } from \"../../core/c4/seed/fleet-file.js\";\nimport { serviceDirOf, type DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport {\n landscapePath,\n servicePathsUnder,\n servicesDir,\n subsystemPathUnder,\n subsystemViewsPath,\n unfiledServicePaths,\n} from \"../../core/repo/paths.js\";\nimport { SUBSYSTEM_MARKER } from \"../../core/repo/tree/marker.js\";\nimport { renderSubsystemViews, viewsAgree } from \"../../core/repo/tree/views.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"../../core/repo/tree/walk.js\";\nimport { TEMP_FILE_RE } from \"../../core/staging/commit.js\";\nimport { planWrite, type PlannedWrite } from \"../../core/staging/writes.js\";\n\n/** Everything the plan reads. The commit request extends this and adds only `rerun`. */\nexport interface SeedPlanInput {\n docsDir: DocsDir;\n seed: FleetSeed;\n /** The sealed landscape text (stamp on line 1), already proven to parse. */\n landscape: string;\n /**\n * That same text's parsed elements — the loadSource self-check's output,\n * reused for the views join so the generated views file agrees byte for byte\n * with what `validate --all` will recompute from the committed landscape.\n */\n elements: Elem[];\n}\n\nexport interface SeedPlan {\n writes: PlannedWrite[];\n /** Service ids split by whether this run creates the directory. */\n services: { created: string[]; existing: string[] };\n}\n\n/**\n * Does this existing service directory hold nothing git would keep? An empty\n * directory is a service to the walk but nothing at all to git, so a landscape\n * declaring it clones into a repo where `validate --all` fails on a service\n * that exists only on the author's disk. Seed's own crash window produces\n * exactly this tree — `stageWrites` creates directories before the journal is\n * written, so a kill in between leaves directories no recovery knows about —\n * and so does a plain `mkdir`. Staging temps do not count as content: they are\n * that same crash's litter, not somebody's file.\n */\nasync function keepsNothing(dir: string): Promise<boolean> {\n try {\n return (await readdir(dir)).every((entry) => TEMP_FILE_RE.test(entry));\n } catch {\n // Unreadable is not empty: the walk found this directory a moment ago, and\n // inventing a write into a directory we cannot list is the wrong guess.\n return false;\n }\n}\n\n/**\n * The complete plan, in memory, before a byte lands: the sealed landscape, one\n * marker per NEW subsystem, one `.gitkeep` per service directory this commit\n * creates — and per existing one holding nothing git would keep (see\n * `keepsNothing`) — and the generated views file rendered from the tree AS\n * THIS COMMIT LEAVES IT, never from a re-walk between two states\n * (commands/subsystem/txn/views.ts's `movedTree` doctrine). That last one is\n * the only write that can be a DELETE: a tree with no subsystems owes no views\n * file, and a leftover is a finding.\n *\n * Existing directories are never moved: fleet.yaml's placement is ignored for\n * a service that already exists — moving is `loam subsystem move <id> --into\n * <name>`'s job, under its own guards — and nothing anywhere is deleted except\n * that one generated file.\n */\nexport async function planSeedWrites(req: SeedPlanInput, tree: FleetTree): Promise<SeedPlan> {\n const { docsDir, seed } = req;\n const writes: PlannedWrite[] = [planWrite(landscapePath(docsDir), req.landscape)];\n\n const existingSubs = new Map(tree.subsystems.map((s) => [s.name, s]));\n const newSubs: SubsystemEntry[] = [];\n for (const sub of seed.subsystems) {\n if (existingSubs.has(sub as string)) continue;\n const dir = subsystemPathUnder(servicesDir(docsDir), sub);\n // A comment line parses to null — a valid empty marker (tree/marker.ts).\n writes.push(planWrite(join(dir, SUBSYSTEM_MARKER), \"# subsystem marker\\n\"));\n newSubs.push({ name: sub as string, path: [sub as string], dir, meta: {} });\n }\n\n const treeDirs = new Map(tree.services.map((s) => [s.id as string, s.dir]));\n // Every existing directory inspected at once — independent reads, so the\n // loop below stays synchronous.\n const invisible = new Map(\n await Promise.all([...treeDirs].map(async ([id, dir]) => [id, await keepsNothing(dir)] as const)),\n );\n const created: string[] = [];\n const existing: string[] = [];\n const newWalked: WalkedService[] = [];\n for (const s of seed.services) {\n const already = treeDirs.get(s.id as string);\n if (already !== undefined) {\n existing.push(s.id);\n // Existing, but git-invisible: give it the `.gitkeep` a created service\n // gets, or the landscape about to be written declares a directory the\n // next clone will not have. `planWrite` makes this a no-op when the file\n // is already there, so a real service is never touched.\n if (invisible.get(s.id as string) === true) writes.push(planWrite(join(already, \".gitkeep\"), \"\"));\n continue;\n }\n created.push(s.id);\n const parent = s.subsystem === null ? null : existingSubs.get(s.subsystem as string) ?? null;\n const dir =\n s.subsystem === null\n ? unfiledServicePaths(docsDir, s.id).dir\n : parent !== null\n ? servicePathsUnder(parent.dir, s.id).dir\n : // A subsystem this same commit creates: the creation spelling,\n // through the same branded joins the subsystem verbs use.\n serviceDirOf(join(subsystemPathUnder(servicesDir(docsDir), s.subsystem), s.id));\n writes.push(planWrite(join(dir, \".gitkeep\"), \"\"));\n newWalked.push({\n // A ServiceId IS a RawServiceId (the checked subtype), so no re-cast.\n id: s.id,\n dir,\n subsystem: s.subsystem === null ? [] : parent !== null ? parent.path : [s.subsystem as string],\n });\n }\n\n const plannedTree: FleetTree = {\n findings: [],\n services: [...tree.services, ...newWalked],\n subsystems: [...tree.subsystems, ...newSubs],\n };\n // The generated views file, including its ABSENCE. `renderSubsystemViews`\n // returning null means the tree has no subsystems, and its contract is that\n // the file must then not exist — `validate` grades a leftover as\n // `subsystem.views-stale`, so a seed that ignored the file would exit 0 and\n // send the caller, via its own `next` list, into a failing gate over a file\n // it had in hand. `content: null` is the delete the transaction understands\n // (commands/subsystem/txn/txn.ts's `\"removed\"`), and a file that already\n // SAYS this is left out of the journal entirely rather than written back\n // over itself.\n //\n // \"Already says this\" is content, not bytes: `viewsAgree` performs the\n // comparison and records the Windows reason for it, beside the generator\n // that mints the bytes. What gets WRITTEN is unchanged — the LF render, as a\n // Buffer — so this decode is a comparison only, never the string round trip\n // to disk that writes.ts forbids; a views file whose bytes are not UTF-8\n // therefore fails the compare and is regenerated, which is the right answer\n // for it anyway.\n const viewsPath = subsystemViewsPath(docsDir);\n const views = renderSubsystemViews(plannedTree, req.elements);\n const wanted = views === null ? null : Buffer.from(views, \"utf8\");\n const onDisk = existsSync(viewsPath) ? await readFile(viewsPath, \"utf8\") : null;\n if (!viewsAgree(onDisk, views)) writes.push({ path: viewsPath, content: wanted });\n\n return { writes, services: { created: created.sort(), existing: existing.sort() } };\n}\n"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { loadSource } from "../../core/c4/likec4.js";
|
|
3
|
+
import { readFleetFile } from "../../core/c4/seed/fleet-file.js";
|
|
4
|
+
import { sealLandscape } from "../../core/c4/seed/stamp.js";
|
|
5
|
+
import { renderLandscape } from "../../core/c4/seed/template.js";
|
|
6
|
+
import { loadConfig } from "../../core/envelope/config.js";
|
|
7
|
+
import { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from "../../core/envelope/json.js";
|
|
8
|
+
import { NotUtf8Error, readUtf8 } from "../../core/staging/writes.js";
|
|
9
|
+
import { sayRecovered } from "../policy/format.js";
|
|
10
|
+
import { docsRepoReady, reportDocsRepoError } from "../policy/gate.js";
|
|
11
|
+
import { serviceTreePathOf } from "../../core/repo/service-target.js";
|
|
12
|
+
import { commitSeed } from "./commit.js";
|
|
13
|
+
export function registerSeed(program) {
|
|
14
|
+
program
|
|
15
|
+
.command("seed")
|
|
16
|
+
.description("Template the fleet map and service directories from a tiny human-authored fleet.yaml")
|
|
17
|
+
.option("--from <file>", "the fleet file to read (default: fleet.yaml in the current directory)")
|
|
18
|
+
.option("--json", "emit the machine contract instead of the human view")
|
|
19
|
+
.action(async (opts) => {
|
|
20
|
+
const json = opts.json === true;
|
|
21
|
+
const fromArg = opts.from ?? "fleet.yaml";
|
|
22
|
+
const loaded = await loadConfig();
|
|
23
|
+
if (loaded.kind !== "loaded") {
|
|
24
|
+
reportNoConfig(json, loaded);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const { docsDir } = loaded.config;
|
|
28
|
+
// `services`, not `docs`: every id in fleet.yaml becomes a
|
|
29
|
+
// services/<id>/ directory, and the under-lock preflight enumerates the
|
|
30
|
+
// tree — a run without the directory that IS the fleet is guessing.
|
|
31
|
+
if (!docsRepoReady(json, docsDir, "services"))
|
|
32
|
+
return;
|
|
33
|
+
// The fleet file is INPUT only — read once, from wherever the caller
|
|
34
|
+
// keeps it (it may legitimately live outside both repos), never stored
|
|
35
|
+
// and never written back. Refusals name the spelling the caller typed.
|
|
36
|
+
let text;
|
|
37
|
+
try {
|
|
38
|
+
text = await readUtf8(resolve(fromArg));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (err instanceof NotUtf8Error)
|
|
42
|
+
return fail(json, "seed-file-invalid", err.message);
|
|
43
|
+
const e = err;
|
|
44
|
+
if (e.code === "ENOENT") {
|
|
45
|
+
return fail(json, "seed-file-invalid", `${fromArg} does not exist — write the fleet file (a services: list of ids, plus ` +
|
|
46
|
+
`optional subsystems:, externals: and calls: like 'checkout -> payments'), or ` +
|
|
47
|
+
`point --from at where it lives. Nothing was written.`);
|
|
48
|
+
}
|
|
49
|
+
// An errno is a filesystem answer (EISDIR, EACCES, …) about the named
|
|
50
|
+
// file; anything without one is a real bug and stays `internal`.
|
|
51
|
+
if (e.path !== undefined || typeof e.errno === "number") {
|
|
52
|
+
return fail(json, "seed-file-invalid", `${fromArg} could not be read as the fleet file — ${e.message}. Nothing was written.`);
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
const read = readFleetFile(text, fromArg);
|
|
57
|
+
if (!read.ok)
|
|
58
|
+
return refuseFleetFile(json, read.problem);
|
|
59
|
+
const seed = read.seed;
|
|
60
|
+
// The self-check: the emitted DSL is parsed in memory BEFORE anything
|
|
61
|
+
// is written, so a templater bug can never land as a broken landscape.
|
|
62
|
+
// Its parsed elements are reused for the generated views join.
|
|
63
|
+
const sealed = sealLandscape(renderLandscape(seed));
|
|
64
|
+
const doc = await loadSource(sealed);
|
|
65
|
+
const firstError = doc.errors[0];
|
|
66
|
+
if (firstError !== undefined) {
|
|
67
|
+
return fail(json, "internal", `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +
|
|
68
|
+
`in ${fromArg}; nothing was written. First error: ${firstError.message}`);
|
|
69
|
+
}
|
|
70
|
+
const committed = await commitSeed({
|
|
71
|
+
docsDir,
|
|
72
|
+
seed,
|
|
73
|
+
landscape: sealed,
|
|
74
|
+
elements: doc.elements,
|
|
75
|
+
rerun: `loam seed --from ${fromArg}`,
|
|
76
|
+
});
|
|
77
|
+
if (!committed.ok)
|
|
78
|
+
return refuseCommit(json, committed);
|
|
79
|
+
const written = committed.written.map((w) => repoPath(docsDir, w));
|
|
80
|
+
const removed = committed.removed.map((w) => repoPath(docsDir, w));
|
|
81
|
+
const firstCreated = committed.services.created[0];
|
|
82
|
+
const next = [
|
|
83
|
+
"loam validate --all",
|
|
84
|
+
"loam list --needs-work",
|
|
85
|
+
// Interpolation is agent-safe: the id already passed the grammar.
|
|
86
|
+
...(firstCreated === undefined ? [] : [`loam adopt --service ${firstCreated}`]),
|
|
87
|
+
];
|
|
88
|
+
if (json) {
|
|
89
|
+
emitJson({
|
|
90
|
+
command: "seed",
|
|
91
|
+
fleetFile: fromArg,
|
|
92
|
+
landscape: committed.landscape,
|
|
93
|
+
created: written,
|
|
94
|
+
removed,
|
|
95
|
+
services: committed.services,
|
|
96
|
+
subsystems: [...seed.subsystems].sort(),
|
|
97
|
+
externals: [...seed.externals].sort(),
|
|
98
|
+
calls: seed.calls.length,
|
|
99
|
+
next,
|
|
100
|
+
...(committed.recovered === null ? {} : { recovered: committed.recovered }),
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (committed.recovered !== null && committed.recovered.outcome !== "consistent") {
|
|
105
|
+
console.log(`note: ${sayRecovered(committed.recovered)}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(`architecture/landscape.likec4 ${LANDSCAPE_SENTENCE[committed.landscape]} — ` +
|
|
108
|
+
`${seed.services.length} service(s), ${seed.calls.length} call(s), from ${fromArg}`);
|
|
109
|
+
for (const w of written)
|
|
110
|
+
console.log(` + ${w}`);
|
|
111
|
+
// Only ever the generated views file, and only when the resulting tree
|
|
112
|
+
// has no subsystems left to view — but a removal printed as `+` would be
|
|
113
|
+
// a lie, and an unmentioned one a surprise in `git status`.
|
|
114
|
+
for (const w of removed)
|
|
115
|
+
console.log(` - ${w} (generated, and this tree has no subsystems)`);
|
|
116
|
+
for (const id of committed.services.existing) {
|
|
117
|
+
// Spelled from the enumeration, and this line is the one place it matters
|
|
118
|
+
// most: the sentence is ABOUT placement, so naming the root form would
|
|
119
|
+
// say "left exactly where it is" while pointing at somewhere it is not.
|
|
120
|
+
const at = await serviceTreePathOf(docsDir, id);
|
|
121
|
+
console.log(` = ${at}/ already exists — left exactly where it is (fleet.yaml's ` +
|
|
122
|
+
`placement is ignored for an existing service; \`loam subsystem move ${id} ` +
|
|
123
|
+
`--into <name>\` is how one moves)`);
|
|
124
|
+
}
|
|
125
|
+
console.log("\nNext:");
|
|
126
|
+
for (const n of next)
|
|
127
|
+
console.log(` ${n}`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const LANDSCAPE_SENTENCE = {
|
|
131
|
+
created: "created",
|
|
132
|
+
"replaced-stub": "written (replaced the scaffold's untouched stub)",
|
|
133
|
+
regenerated: "regenerated (the line-1 stamp proved no hand edits)",
|
|
134
|
+
};
|
|
135
|
+
/** One stable code per problem kind — the mapping the fleet-file union exists for. */
|
|
136
|
+
function refuseFleetFile(json, problem) {
|
|
137
|
+
switch (problem.kind) {
|
|
138
|
+
case "invalid":
|
|
139
|
+
return fail(json, "seed-file-invalid", problem.message);
|
|
140
|
+
case "duplicate":
|
|
141
|
+
return fail(json, "seed-duplicate-service", problem.message);
|
|
142
|
+
case "unknown-subsystem":
|
|
143
|
+
return fail(json, "seed-unknown-subsystem", problem.message);
|
|
144
|
+
case "unknown-endpoint":
|
|
145
|
+
// The existing code, not a fifth new one: the caller's action is the
|
|
146
|
+
// same as for every other unknown service — fix the name, re-run.
|
|
147
|
+
return fail(json, "unknown-service", problem.message);
|
|
148
|
+
}
|
|
149
|
+
// Exhaustiveness checked, not assumed, and the branch is genuinely dead:
|
|
150
|
+
// without it a fifth `kind` falls out of the switch printing nothing and
|
|
151
|
+
// leaving `process.exitCode` unset — a refused fleet file exiting 0, which
|
|
152
|
+
// is the worst answer available.
|
|
153
|
+
const unreachable = problem;
|
|
154
|
+
throw new Error(`refuseFleetFile: no code for fleet-file problem '${JSON.stringify(unreachable)}'`);
|
|
155
|
+
}
|
|
156
|
+
function refuseCommit(json, committed) {
|
|
157
|
+
if ("repoGone" in committed)
|
|
158
|
+
return reportDocsRepoError(json, committed.repoGone);
|
|
159
|
+
if (committed.code === "seed-file-invalid") {
|
|
160
|
+
// The exact ids to paste ride as data beside the prose, so an agent can
|
|
161
|
+
// repair fleet.yaml without parsing a sentence.
|
|
162
|
+
if (json) {
|
|
163
|
+
emitJsonError("seed-file-invalid", committed.message, {
|
|
164
|
+
missingServices: committed.missingServices,
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
return fail(json, "seed-file-invalid", committed.message);
|
|
169
|
+
}
|
|
170
|
+
return fail(json, committed.code, committed.message);
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/commands/seed/seed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam seed` — the fleet map's first hour, mechanically: a tiny\n * human-authored fleet.yaml (service ids, optional subsystems, externals and\n * `a -> b` calls) templated into architecture/landscape.likec4 plus one\n * services/<id>/ directory per service, in one journaled transaction.\n *\n * NOT an extractor, deliberately: a human stated every fact in the file —\n * which services exist and who calls whom is the one thing no generator can\n * read off a repository (core/docs.ts's landscape doctrine) — and loam does\n * mechanical templating over those statements, the same category as\n * `loam new`. Seed never overwrites human work: it writes the landscape only\n * when the file is absent, is the scaffold's untouched stub, or carries its\n * own verified line-1 stamp (`core/c4/seed/stamp.ts`); everything else\n * refuses `seed-landscape-edited`.\n */\nimport type { Command } from \"commander\";\nimport { resolve } from \"node:path\";\nimport { loadSource } from \"../../core/c4/likec4.js\";\nimport { readFleetFile, type SeedFileProblem } from \"../../core/c4/seed/fleet-file.js\";\nimport { sealLandscape } from \"../../core/c4/seed/stamp.js\";\nimport { renderLandscape } from \"../../core/c4/seed/template.js\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { NotUtf8Error, readUtf8 } from \"../../core/staging/writes.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { serviceTreePathOf } from \"../../core/repo/service-target.js\";\nimport { commitSeed, type LandscapeDisposition, type SeedCommit } from \"./commit.js\";\n\ninterface SeedOptions {\n from?: string;\n json?: boolean;\n}\n\nexport function registerSeed(program: Command): void {\n program\n .command(\"seed\")\n .description(\n \"Template the fleet map and service directories from a tiny human-authored fleet.yaml\",\n )\n .option(\"--from <file>\", \"the fleet file to read (default: fleet.yaml in the current directory)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: SeedOptions) => {\n const json = opts.json === true;\n const fromArg = opts.from ?? \"fleet.yaml\";\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const { docsDir } = loaded.config;\n // `services`, not `docs`: every id in fleet.yaml becomes a\n // services/<id>/ directory, and the under-lock preflight enumerates the\n // tree — a run without the directory that IS the fleet is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // The fleet file is INPUT only — read once, from wherever the caller\n // keeps it (it may legitimately live outside both repos), never stored\n // and never written back. Refusals name the spelling the caller typed.\n let text: string;\n try {\n text = await readUtf8(resolve(fromArg));\n } catch (err) {\n if (err instanceof NotUtf8Error) return fail(json, \"seed-file-invalid\", err.message);\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} does not exist — write the fleet file (a services: list of ids, plus ` +\n `optional subsystems:, externals: and calls: like 'checkout -> payments'), or ` +\n `point --from at where it lives. Nothing was written.`,\n );\n }\n // An errno is a filesystem answer (EISDIR, EACCES, …) about the named\n // file; anything without one is a real bug and stays `internal`.\n if (e.path !== undefined || typeof e.errno === \"number\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} could not be read as the fleet file — ${e.message}. Nothing was written.`,\n );\n }\n throw err;\n }\n\n const read = readFleetFile(text, fromArg);\n if (!read.ok) return refuseFleetFile(json, read.problem);\n const seed = read.seed;\n\n // The self-check: the emitted DSL is parsed in memory BEFORE anything\n // is written, so a templater bug can never land as a broken landscape.\n // Its parsed elements are reused for the generated views join.\n const sealed = sealLandscape(renderLandscape(seed));\n const doc = await loadSource(sealed);\n const firstError = doc.errors[0];\n if (firstError !== undefined) {\n return fail(\n json,\n \"internal\",\n `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +\n `in ${fromArg}; nothing was written. First error: ${firstError.message}`,\n );\n }\n\n const committed = await commitSeed({\n docsDir,\n seed,\n landscape: sealed,\n elements: doc.elements,\n rerun: `loam seed --from ${fromArg}`,\n });\n if (!committed.ok) return refuseCommit(json, committed);\n\n const written = committed.written.map((w) => repoPath(docsDir, w));\n const removed = committed.removed.map((w) => repoPath(docsDir, w));\n const firstCreated = committed.services.created[0];\n const next = [\n \"loam validate --all\",\n \"loam list --needs-work\",\n // Interpolation is agent-safe: the id already passed the grammar.\n ...(firstCreated === undefined ? [] : [`loam adopt --service ${firstCreated}`]),\n ];\n\n if (json) {\n emitJson({\n command: \"seed\",\n fleetFile: fromArg,\n landscape: committed.landscape,\n created: written,\n removed,\n services: committed.services,\n subsystems: [...seed.subsystems].sort(),\n externals: [...seed.externals].sort(),\n calls: seed.calls.length,\n next,\n ...(committed.recovered === null ? {} : { recovered: committed.recovered }),\n });\n return;\n }\n if (committed.recovered !== null && committed.recovered.outcome !== \"consistent\") {\n console.log(`note: ${sayRecovered(committed.recovered)}`);\n }\n console.log(\n `architecture/landscape.likec4 ${LANDSCAPE_SENTENCE[committed.landscape]} — ` +\n `${seed.services.length} service(s), ${seed.calls.length} call(s), from ${fromArg}`,\n );\n for (const w of written) console.log(` + ${w}`);\n // Only ever the generated views file, and only when the resulting tree\n // has no subsystems left to view — but a removal printed as `+` would be\n // a lie, and an unmentioned one a surprise in `git status`.\n for (const w of removed) console.log(` - ${w} (generated, and this tree has no subsystems)`);\n for (const id of committed.services.existing) {\n // Spelled from the enumeration, and this line is the one place it matters\n // most: the sentence is ABOUT placement, so naming the root form would\n // say \"left exactly where it is\" while pointing at somewhere it is not.\n const at = await serviceTreePathOf(docsDir, id);\n console.log(\n ` = ${at}/ already exists — left exactly where it is (fleet.yaml's ` +\n `placement is ignored for an existing service; \\`loam subsystem move ${id} ` +\n `--into <name>\\` is how one moves)`,\n );\n }\n console.log(\"\\nNext:\");\n for (const n of next) console.log(` ${n}`);\n });\n}\n\nconst LANDSCAPE_SENTENCE: Record<LandscapeDisposition, string> = {\n created: \"created\",\n \"replaced-stub\": \"written (replaced the scaffold's untouched stub)\",\n regenerated: \"regenerated (the line-1 stamp proved no hand edits)\",\n};\n\n/** One stable code per problem kind — the mapping the fleet-file union exists for. */\nfunction refuseFleetFile(json: boolean, problem: SeedFileProblem): void {\n switch (problem.kind) {\n case \"invalid\":\n return fail(json, \"seed-file-invalid\", problem.message);\n case \"duplicate\":\n return fail(json, \"seed-duplicate-service\", problem.message);\n case \"unknown-subsystem\":\n return fail(json, \"seed-unknown-subsystem\", problem.message);\n case \"unknown-endpoint\":\n // The existing code, not a fifth new one: the caller's action is the\n // same as for every other unknown service — fix the name, re-run.\n return fail(json, \"unknown-service\", problem.message);\n }\n // Exhaustiveness checked, not assumed, and the branch is genuinely dead:\n // without it a fifth `kind` falls out of the switch printing nothing and\n // leaving `process.exitCode` unset — a refused fleet file exiting 0, which\n // is the worst answer available.\n const unreachable: never = problem;\n throw new Error(`refuseFleetFile: no code for fleet-file problem '${JSON.stringify(unreachable)}'`);\n}\n\nfunction refuseCommit(json: boolean, committed: Exclude<SeedCommit, { ok: true }>): void {\n if (\"repoGone\" in committed) return reportDocsRepoError(json, committed.repoGone);\n if (committed.code === \"seed-file-invalid\") {\n // The exact ids to paste ride as data beside the prose, so an agent can\n // repair fleet.yaml without parsing a sentence.\n if (json) {\n emitJsonError(\"seed-file-invalid\", committed.message, {\n missingServices: committed.missingServices,\n });\n return;\n }\n return fail(json, \"seed-file-invalid\", committed.message);\n }\n return fail(json, committed.code, committed.message);\n}\n"]}
|