@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,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bytes a brand-new docs repo's fleet map starts with, and the one
|
|
3
|
+
* question anybody asks of them afterwards: has a human touched this yet?
|
|
4
|
+
*
|
|
5
|
+
* Split out of `../docs.ts` when the README joined the scaffold and the file
|
|
6
|
+
* reached its line limit, and the seam was already there: `docs.ts` says what
|
|
7
|
+
* a docs repo is MADE OF — which directories, which files, in what order —
|
|
8
|
+
* while this package holds the CONTENT of the authored templates it lays down.
|
|
9
|
+
* The two change for different reasons: a new artifact edits the first, better
|
|
10
|
+
* advice to a new fleet edits the second.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The fleet map, empty but valid.
|
|
14
|
+
*
|
|
15
|
+
* Nobody writes this file automatically — `adopt` deliberately does not touch
|
|
16
|
+
* it, because who calls whom is a human judgement and a generated landscape
|
|
17
|
+
* would be a guess presented as the map. But "nobody writes it" used to mean
|
|
18
|
+
* "it is simply absent", and an absent landscape is the one artifact whose
|
|
19
|
+
* absence silences the fleet-wide checks entirely: every cross-service breach
|
|
20
|
+
* `validate` exists to find is invisible on a repo with no landscape.
|
|
21
|
+
*
|
|
22
|
+
* So the scaffold lays down the empty map instead: the four element kinds a
|
|
23
|
+
* fleet actually uses are declared, the model is empty, and the comments say
|
|
24
|
+
* what to add and why. The first `loam adopt` then has somewhere to be drawn.
|
|
25
|
+
*
|
|
26
|
+
* A `views` block IS scaffolded — reversing the decision this comment used to
|
|
27
|
+
* record, which was: no views, because computing one is superlinear in edge
|
|
28
|
+
* count and a scaffolded `include *` handed every repo a fleet-sized bill for
|
|
29
|
+
* the first tool that renders. That cost was real and still is; what changed
|
|
30
|
+
* is the shape of the block. A fleet map stops being readable at the third
|
|
31
|
+
* service unless platform infrastructure is split out, and the split's
|
|
32
|
+
* `exclude element.tag = #platform` is exactly the
|
|
33
|
+
* pruning that keeps the fleet view's render affordable — the scaffold now
|
|
34
|
+
* pays the old objection instead of ignoring it. loam itself still reads
|
|
35
|
+
* elements and relationships and never a view. The platform view's predicate
|
|
36
|
+
* is scaffolded because it is the line users mistype: the obvious spelling
|
|
37
|
+
* draws boxes with no edges.
|
|
38
|
+
* The `views` block below has a deliberate second copy: `core/c4/seed/template.ts` emits the
|
|
39
|
+
* same two views into a seeded landscape (two strikes; extract on a third), and test/seed.test.ts
|
|
40
|
+
* compares the two blocks byte for byte — comments included — so whichever copy drifts fails.
|
|
41
|
+
*/
|
|
42
|
+
export const LANDSCAPE_STUB = `// The fleet map: every service in services/ appears here, and every call
|
|
43
|
+
// between two of them is an edge. This file is written by hand — loam never
|
|
44
|
+
// guesses it, because "who calls whom" is the one fact no generator can read
|
|
45
|
+
// off a repository.
|
|
46
|
+
//
|
|
47
|
+
// After \`loam adopt --service <id>\`, add the service here:
|
|
48
|
+
//
|
|
49
|
+
// paymentService = softwareSystem 'payment-service' {
|
|
50
|
+
// description 'Owns payment authorization/capture'
|
|
51
|
+
// metadata { service 'payment-service' } // binds the box to services/<id>/
|
|
52
|
+
// }
|
|
53
|
+
//
|
|
54
|
+
// and give each call the operationId it uses, so requirements, C4 and OpenAPI
|
|
55
|
+
// can be cross-checked:
|
|
56
|
+
//
|
|
57
|
+
// checkoutWeb -> paymentService 'Authorizes' {
|
|
58
|
+
// metadata { op 'authorizePayment' }
|
|
59
|
+
// }
|
|
60
|
+
//
|
|
61
|
+
// The \`views\` block at the bottom is for LikeC4's own tooling — loam reads
|
|
62
|
+
// the model and never a view. Render with \`npx likec4 start\` from the docs
|
|
63
|
+
// repo root, which likec4.config.json scopes to this directory. (A service
|
|
64
|
+
// model or a feature delta renders from its OWN directory, e.g.
|
|
65
|
+
// \`npx likec4 start services/<id>\`: each declares its own \`specification\`
|
|
66
|
+
// block, so the renderer can only be given one of them at a time.) Keep views
|
|
67
|
+
// scoped, because computing one is superlinear in the number of edges — the
|
|
68
|
+
// \`fleet\` view below stays affordable precisely because it excludes the
|
|
69
|
+
// platform hubs.
|
|
70
|
+
//
|
|
71
|
+
// One shape is worth getting right before the fleet is drawn: a shared broker.
|
|
72
|
+
// Kafka as a single element becomes the node every service points at, and any
|
|
73
|
+
// view over it is a star nobody can read. Model the TOPIC instead — a \`topic\`
|
|
74
|
+
// nested inside the broker's element, edges pointing at \`kafka.<topic>\` — which
|
|
75
|
+
// splits one hub of degree sixty into a dozen small ones and is the truer model
|
|
76
|
+
// besides. Tag the KIND, not just the broker: LikeC4 does not inherit tags, so a
|
|
77
|
+
// topic under an \`#external\` broker is not external itself and \`validate\` asks
|
|
78
|
+
// for a services/ directory nobody owes.
|
|
79
|
+
//
|
|
80
|
+
// specification {
|
|
81
|
+
// element topic {
|
|
82
|
+
// #external
|
|
83
|
+
// style { shape queue }
|
|
84
|
+
// }
|
|
85
|
+
// }
|
|
86
|
+
|
|
87
|
+
specification {
|
|
88
|
+
element person
|
|
89
|
+
element softwareSystem
|
|
90
|
+
element container
|
|
91
|
+
element database
|
|
92
|
+
|
|
93
|
+
// Ubiquitous infrastructure — logging Kafka, auth, service discovery — takes
|
|
94
|
+
// one inbound edge per service, and by the third service the fleet view is a
|
|
95
|
+
// hairball. Tag those elements #platform: the fleet view excludes them
|
|
96
|
+
// without losing "who depends on the Identity Provider", which the platform
|
|
97
|
+
// view answers.
|
|
98
|
+
tag platform
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
model {
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
views {
|
|
105
|
+
view fleet {
|
|
106
|
+
include *
|
|
107
|
+
exclude element.tag = #platform
|
|
108
|
+
}
|
|
109
|
+
// The obvious spelling — \`include element.tag = #platform\` — draws the
|
|
110
|
+
// platform boxes with NO edges and no consumers. The predicate that works is
|
|
111
|
+
// the relationship form below.
|
|
112
|
+
view platform {
|
|
113
|
+
include * -> element.tag = #platform
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
/**
|
|
118
|
+
* Whether the fleet map is still the scaffold's own bytes — the sentinel
|
|
119
|
+
* doctrine of core/coherence/authoring/sentinels.ts: the writer above and this
|
|
120
|
+
* checker read ONE string, so they cannot drift apart. `endsWith`, not
|
|
121
|
+
* equality, because `migrate-openspec` prepends a `landscapePreamble` to the
|
|
122
|
+
* identical stub; CRLF-normalised because a checkout under core.autocrlf is
|
|
123
|
+
* still untouched. A hand-edit inside the stub body breaks the suffix match —
|
|
124
|
+
* the safe failure mode is a false "authored", which merely drops one teaching
|
|
125
|
+
* step from `loam status`'s empty-fleet ladder (core/status/fleet/next.ts).
|
|
126
|
+
*/
|
|
127
|
+
export function isLandscapeStub(content) {
|
|
128
|
+
const normal = (s) => s.replace(/\r\n/g, "\n").trimEnd();
|
|
129
|
+
return normal(content).endsWith(normal(LANDSCAPE_STUB));
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=landscape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../src/core/scaffold/landscape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0E7B,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;IACzE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["/**\n * The bytes a brand-new docs repo's fleet map starts with, and the one\n * question anybody asks of them afterwards: has a human touched this yet?\n *\n * Split out of `../docs.ts` when the README joined the scaffold and the file\n * reached its line limit, and the seam was already there: `docs.ts` says what\n * a docs repo is MADE OF — which directories, which files, in what order —\n * while this package holds the CONTENT of the authored templates it lays down.\n * The two change for different reasons: a new artifact edits the first, better\n * advice to a new fleet edits the second.\n */\n\n/**\n * The fleet map, empty but valid.\n *\n * Nobody writes this file automatically — `adopt` deliberately does not touch\n * it, because who calls whom is a human judgement and a generated landscape\n * would be a guess presented as the map. But \"nobody writes it\" used to mean\n * \"it is simply absent\", and an absent landscape is the one artifact whose\n * absence silences the fleet-wide checks entirely: every cross-service breach\n * `validate` exists to find is invisible on a repo with no landscape.\n *\n * So the scaffold lays down the empty map instead: the four element kinds a\n * fleet actually uses are declared, the model is empty, and the comments say\n * what to add and why. The first `loam adopt` then has somewhere to be drawn.\n *\n * A `views` block IS scaffolded — reversing the decision this comment used to\n * record, which was: no views, because computing one is superlinear in edge\n * count and a scaffolded `include *` handed every repo a fleet-sized bill for\n * the first tool that renders. That cost was real and still is; what changed\n * is the shape of the block. A fleet map stops being readable at the third\n * service unless platform infrastructure is split out, and the split's\n * `exclude element.tag = #platform` is exactly the\n * pruning that keeps the fleet view's render affordable — the scaffold now\n * pays the old objection instead of ignoring it. loam itself still reads\n * elements and relationships and never a view. The platform view's predicate\n * is scaffolded because it is the line users mistype: the obvious spelling\n * draws boxes with no edges.\n * The `views` block below has a deliberate second copy: `core/c4/seed/template.ts` emits the\n * same two views into a seeded landscape (two strikes; extract on a third), and test/seed.test.ts\n * compares the two blocks byte for byte — comments included — so whichever copy drifts fails.\n */\nexport const LANDSCAPE_STUB = `// The fleet map: every service in services/ appears here, and every call\n// between two of them is an edge. This file is written by hand — loam never\n// guesses it, because \"who calls whom\" is the one fact no generator can read\n// off a repository.\n//\n// After \\`loam adopt --service <id>\\`, add the service here:\n//\n// paymentService = softwareSystem 'payment-service' {\n// description 'Owns payment authorization/capture'\n// metadata { service 'payment-service' } // binds the box to services/<id>/\n// }\n//\n// and give each call the operationId it uses, so requirements, C4 and OpenAPI\n// can be cross-checked:\n//\n// checkoutWeb -> paymentService 'Authorizes' {\n// metadata { op 'authorizePayment' }\n// }\n//\n// The \\`views\\` block at the bottom is for LikeC4's own tooling — loam reads\n// the model and never a view. Render with \\`npx likec4 start\\` from the docs\n// repo root, which likec4.config.json scopes to this directory. (A service\n// model or a feature delta renders from its OWN directory, e.g.\n// \\`npx likec4 start services/<id>\\`: each declares its own \\`specification\\`\n// block, so the renderer can only be given one of them at a time.) Keep views\n// scoped, because computing one is superlinear in the number of edges — the\n// \\`fleet\\` view below stays affordable precisely because it excludes the\n// platform hubs.\n//\n// One shape is worth getting right before the fleet is drawn: a shared broker.\n// Kafka as a single element becomes the node every service points at, and any\n// view over it is a star nobody can read. Model the TOPIC instead — a \\`topic\\`\n// nested inside the broker's element, edges pointing at \\`kafka.<topic>\\` — which\n// splits one hub of degree sixty into a dozen small ones and is the truer model\n// besides. Tag the KIND, not just the broker: LikeC4 does not inherit tags, so a\n// topic under an \\`#external\\` broker is not external itself and \\`validate\\` asks\n// for a services/ directory nobody owes.\n//\n// specification {\n// element topic {\n// #external\n// style { shape queue }\n// }\n// }\n\nspecification {\n element person\n element softwareSystem\n element container\n element database\n\n // Ubiquitous infrastructure — logging Kafka, auth, service discovery — takes\n // one inbound edge per service, and by the third service the fleet view is a\n // hairball. Tag those elements #platform: the fleet view excludes them\n // without losing \"who depends on the Identity Provider\", which the platform\n // view answers.\n tag platform\n}\n\nmodel {\n}\n\nviews {\n view fleet {\n include *\n exclude element.tag = #platform\n }\n // The obvious spelling — \\`include element.tag = #platform\\` — draws the\n // platform boxes with NO edges and no consumers. The predicate that works is\n // the relationship form below.\n view platform {\n include * -> element.tag = #platform\n }\n}\n`;\n\n/**\n * Whether the fleet map is still the scaffold's own bytes — the sentinel\n * doctrine of core/coherence/authoring/sentinels.ts: the writer above and this\n * checker read ONE string, so they cannot drift apart. `endsWith`, not\n * equality, because `migrate-openspec` prepends a `landscapePreamble` to the\n * identical stub; CRLF-normalised because a checkout under core.autocrlf is\n * still untouched. A hand-edit inside the stub body breaks the suffix match —\n * the safe failure mode is a false \"authored\", which merely drops one teaching\n * step from `loam status`'s empty-fleet ladder (core/status/fleet/next.ts).\n */\nexport function isLandscapeStub(content: string): boolean {\n const normal = (s: string): string => s.replace(/\\r\\n/g, \"\\n\").trimEnd();\n return normal(content).endsWith(normal(LANDSCAPE_STUB));\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The README a new docs repo starts with — the one file in it addressed to a
|
|
3
|
+
* person.
|
|
4
|
+
*
|
|
5
|
+
* Everything else the scaffold lays down is either a document about the
|
|
6
|
+
* governed system or `AGENTS.md`, which is 1500 lines written for a coding
|
|
7
|
+
* agent and named so that a human does not open it. A forge renders `README.md`
|
|
8
|
+
* as the repository's landing page and nothing else, so without this file a
|
|
9
|
+
* docs repo opens as a bare directory listing: four folders and two config
|
|
10
|
+
* files, and no sentence anywhere saying what any of it is.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately SHORT, and deliberately not a second copy of anything. The
|
|
13
|
+
* layout table and the cycle are AGENTS.md's, in full; this page says what the
|
|
14
|
+
* repo is, points at the three commands a person actually types, and hands off.
|
|
15
|
+
* A README that restated the contract would be a second thing to keep true, and
|
|
16
|
+
* the first one to go stale.
|
|
17
|
+
*/
|
|
18
|
+
export const README_FILENAME = "README.md";
|
|
19
|
+
export const README_MD = `# Architecture & specs
|
|
20
|
+
|
|
21
|
+
This repository is a governed system's **source of truth**: its architecture,
|
|
22
|
+
requirements, contracts, domain vocabulary and verification evidence, kept in
|
|
23
|
+
explicit agreement. It is plain files — Markdown, YAML and LikeC4 — reviewed like
|
|
24
|
+
code, in pull requests.
|
|
25
|
+
|
|
26
|
+
In paths and commands, loam calls one governed implementation boundary a
|
|
27
|
+
**service**. That boundary may be a modular monolith, a network service, a CLI or
|
|
28
|
+
a worker. Nested C4 elements remain modules or components unless they need their
|
|
29
|
+
own repository binding, contracts, evidence and deployment boundary.
|
|
30
|
+
|
|
31
|
+
It is read and written with [**loam**](https://github.com/ybotok/loam). Delete
|
|
32
|
+
loam and these documents still make sense; that is the point of keeping them this
|
|
33
|
+
way.
|
|
34
|
+
|
|
35
|
+
## What is where
|
|
36
|
+
|
|
37
|
+
| | |
|
|
38
|
+
|---|---|
|
|
39
|
+
| \`architecture/landscape.likec4\` | the system map — governed boundaries, nested structure and their relationships |
|
|
40
|
+
| \`services/<id>/\` | one directory per governed boundary: what it promises, exposes and verifies |
|
|
41
|
+
| \`features/<FEAT>/\` | a change in flight — why it exists, and the delta it makes to the documents above |
|
|
42
|
+
| \`features/archive/\` | changes that shipped; their deltas are already merged into the living documents |
|
|
43
|
+
|
|
44
|
+
A directory under \`services/\` that holds a \`subsystem.yaml\` is a **group**, not
|
|
45
|
+
a service — services can be filed inside one at any depth.
|
|
46
|
+
|
|
47
|
+
## If you are new here
|
|
48
|
+
|
|
49
|
+
\`\`\`bash
|
|
50
|
+
loam status
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
It reads the files and tells you where things stand and what to do next, ordered
|
|
54
|
+
most-unblocking first, with the exact command for each step. Nothing in this
|
|
55
|
+
repository is generated from a database or a server — if \`loam status\` says
|
|
56
|
+
something, a file said it first.
|
|
57
|
+
|
|
58
|
+
To check the whole repository the way CI does:
|
|
59
|
+
|
|
60
|
+
\`\`\`bash
|
|
61
|
+
loam validate --all
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
And when a finding's code means nothing to you:
|
|
65
|
+
|
|
66
|
+
\`\`\`bash
|
|
67
|
+
loam explain <code>
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
## Changing something
|
|
71
|
+
|
|
72
|
+
Documents here are edited through a **feature**: \`loam new <FEAT>\` scaffolds one,
|
|
73
|
+
you write the intent and the deltas, \`loam validate --feature <FEAT>\` grades it,
|
|
74
|
+
and \`loam archive <FEAT>\` merges it into the living documents. Living documents
|
|
75
|
+
are not edited directly once a boundary is adopted — the feature is the
|
|
76
|
+
reviewable unit, and the archive is what makes the change traceable afterwards.
|
|
77
|
+
|
|
78
|
+
## For coding agents
|
|
79
|
+
|
|
80
|
+
\`AGENTS.md\` in this directory is the full contract: the layout in detail, the
|
|
81
|
+
authoring rules \`loam validate\` enforces, the ID spine, the cycle, and what every
|
|
82
|
+
finding code means. It is generated by loam and kept in step with the binary.
|
|
83
|
+
`;
|
|
84
|
+
//# sourceMappingURL=readme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readme.js","sourceRoot":"","sources":["../../../src/core/scaffold/readme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAE3C,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgExB,CAAC","sourcesContent":["/**\n * The README a new docs repo starts with — the one file in it addressed to a\n * person.\n *\n * Everything else the scaffold lays down is either a document about the\n * governed system or `AGENTS.md`, which is 1500 lines written for a coding\n * agent and named so that a human does not open it. A forge renders `README.md`\n * as the repository's landing page and nothing else, so without this file a\n * docs repo opens as a bare directory listing: four folders and two config\n * files, and no sentence anywhere saying what any of it is.\n *\n * Deliberately SHORT, and deliberately not a second copy of anything. The\n * layout table and the cycle are AGENTS.md's, in full; this page says what the\n * repo is, points at the three commands a person actually types, and hands off.\n * A README that restated the contract would be a second thing to keep true, and\n * the first one to go stale.\n */\nexport const README_FILENAME = \"README.md\";\n\nexport const README_MD = `# Architecture & specs\n\nThis repository is a governed system's **source of truth**: its architecture,\nrequirements, contracts, domain vocabulary and verification evidence, kept in\nexplicit agreement. It is plain files — Markdown, YAML and LikeC4 — reviewed like\ncode, in pull requests.\n\nIn paths and commands, loam calls one governed implementation boundary a\n**service**. That boundary may be a modular monolith, a network service, a CLI or\na worker. Nested C4 elements remain modules or components unless they need their\nown repository binding, contracts, evidence and deployment boundary.\n\nIt is read and written with [**loam**](https://github.com/ybotok/loam). Delete\nloam and these documents still make sense; that is the point of keeping them this\nway.\n\n## What is where\n\n| | |\n|---|---|\n| \\`architecture/landscape.likec4\\` | the system map — governed boundaries, nested structure and their relationships |\n| \\`services/<id>/\\` | one directory per governed boundary: what it promises, exposes and verifies |\n| \\`features/<FEAT>/\\` | a change in flight — why it exists, and the delta it makes to the documents above |\n| \\`features/archive/\\` | changes that shipped; their deltas are already merged into the living documents |\n\nA directory under \\`services/\\` that holds a \\`subsystem.yaml\\` is a **group**, not\na service — services can be filed inside one at any depth.\n\n## If you are new here\n\n\\`\\`\\`bash\nloam status\n\\`\\`\\`\n\nIt reads the files and tells you where things stand and what to do next, ordered\nmost-unblocking first, with the exact command for each step. Nothing in this\nrepository is generated from a database or a server — if \\`loam status\\` says\nsomething, a file said it first.\n\nTo check the whole repository the way CI does:\n\n\\`\\`\\`bash\nloam validate --all\n\\`\\`\\`\n\nAnd when a finding's code means nothing to you:\n\n\\`\\`\\`bash\nloam explain <code>\n\\`\\`\\`\n\n## Changing something\n\nDocuments here are edited through a **feature**: \\`loam new <FEAT>\\` scaffolds one,\nyou write the intent and the deltas, \\`loam validate --feature <FEAT>\\` grades it,\nand \\`loam archive <FEAT>\\` merges it into the living documents. Living documents\nare not edited directly once a boundary is adopted — the feature is the\nreviewable unit, and the archive is what makes the change traceable afterwards.\n\n## For coding agents\n\n\\`AGENTS.md\\` in this directory is the full contract: the layout in detail, the\nauthoring rules \\`loam validate\\` enforces, the ID spine, the cycle, and what every\nfinding code means. It is generated by loam and kept in step with the binary.\n`;\n"]}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commit itself, in three steps: stage every new version beside its
|
|
3
|
+
* target, swap them in, roll back what already went in when a later step fails.
|
|
4
|
+
*
|
|
5
|
+
* This is what `archive` and `unarchive` share, and it moved out of archive.ts
|
|
6
|
+
* verbatim once unarchive became its second consumer. The MERGE computations
|
|
7
|
+
* stayed with archive, which is still their only caller — the split was about a
|
|
8
|
+
* second CONSUMER, never about archive being long.
|
|
9
|
+
*
|
|
10
|
+
* `StagingRaceError` belongs here rather than with the lock, though both are
|
|
11
|
+
* about a second writer: the lock is taken before a plan is computed, while
|
|
12
|
+
* this is the compare-and-set immediately before each rename — the second line
|
|
13
|
+
* of defence for exactly the window two racers who both broke the same stale
|
|
14
|
+
* lock would land in.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { link, mkdir, readFile, rename, rm, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
18
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
19
|
+
import { sameBytes, toBytes } from "./writes.js";
|
|
20
|
+
/**
|
|
21
|
+
* The target changed between the pre-image staging read and the swap.
|
|
22
|
+
*
|
|
23
|
+
* The pre-image is what a rollback restores and what `unarchive` is handed, so
|
|
24
|
+
* swapping over somebody else's newer bytes would both lose their work and
|
|
25
|
+
* arm a rollback that writes a third version nobody wrote. Commands map this to
|
|
26
|
+
* `merge-failed`: nothing of theirs went in, the docs are as the other writer
|
|
27
|
+
* left them, and re-running can work.
|
|
28
|
+
*/
|
|
29
|
+
export class StagingRaceError extends Error {
|
|
30
|
+
path;
|
|
31
|
+
name = "StagingRaceError";
|
|
32
|
+
constructor(path) {
|
|
33
|
+
super(`${path} changed while this command was planning its merge — nothing was written; re-run`);
|
|
34
|
+
this.path = path;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Write every new version to a temp file beside its target, remembering what the
|
|
39
|
+
* target said first. Nothing observable changes here: on failure the temp files
|
|
40
|
+
* are removed and the docs are exactly as they were.
|
|
41
|
+
*
|
|
42
|
+
* The pre-image is read eagerly and deliberately: it is what a rollback restores
|
|
43
|
+
* and what `unarchive` is given, so a file we cannot read is a file we cannot
|
|
44
|
+
* safely rewrite, and the merge must stop before it starts.
|
|
45
|
+
*/
|
|
46
|
+
export async function stageWrites(writes) {
|
|
47
|
+
const staged = [];
|
|
48
|
+
try {
|
|
49
|
+
for (const [i, write] of writes.entries()) {
|
|
50
|
+
// mkdir(recursive) reports the topmost directory it had to create — the
|
|
51
|
+
// handle a rollback needs to put the tree back the way it found it.
|
|
52
|
+
const createdDir = (await mkdir(dirname(write.path), { recursive: true })) ?? null;
|
|
53
|
+
const before = existsSync(write.path) ? await readFile(write.path) : null;
|
|
54
|
+
const content = toBytes(write.content);
|
|
55
|
+
let tmp = null;
|
|
56
|
+
if (content !== null) {
|
|
57
|
+
tmp = tempPath(write.path, i);
|
|
58
|
+
await writeFile(tmp, content);
|
|
59
|
+
}
|
|
60
|
+
staged.push({ write, content, tmp, before, createdDir, swapped: false });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
await discardStaged(staged);
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
return staged;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Swap the staged versions in, one file at a time. Each swap is a rename(2)
|
|
71
|
+
* within a single directory, so a concurrent reader sees either the old bytes or
|
|
72
|
+
* the new ones — never a half-written file. Across files it is not atomic: the
|
|
73
|
+
* caller rolls back what has already gone in.
|
|
74
|
+
*
|
|
75
|
+
* Every swap re-reads its target first and compares it with the pre-image the
|
|
76
|
+
* plan was computed from — a compare-and-set on the file's whole content. It is
|
|
77
|
+
* half a CAS on purpose: read and rename cannot be one syscall, so a writer
|
|
78
|
+
* landing inside that window still wins. What it does buy is the window that
|
|
79
|
+
* actually matters — the seconds a plan spends parsing LikeC4 and merging YAML
|
|
80
|
+
* between the pre-image read and this swap — and it costs one read per file.
|
|
81
|
+
*/
|
|
82
|
+
export async function swapStaged(staged) {
|
|
83
|
+
for (const s of staged) {
|
|
84
|
+
const now = existsSync(s.write.path) ? await readFile(s.write.path) : null;
|
|
85
|
+
if (!sameBytes(now, s.before))
|
|
86
|
+
throw new StagingRaceError(s.write.path);
|
|
87
|
+
if (s.tmp === null) {
|
|
88
|
+
if (s.before !== null)
|
|
89
|
+
await unlink(s.write.path);
|
|
90
|
+
}
|
|
91
|
+
else if (s.write.exclusive === true) {
|
|
92
|
+
// link(2) is the same-filesystem, atomic no-clobber counterpart to rename:
|
|
93
|
+
// it fails with EEXIST instead of replacing a file created after planning.
|
|
94
|
+
await link(s.tmp, s.write.path);
|
|
95
|
+
s.swapped = true;
|
|
96
|
+
await unlink(s.tmp);
|
|
97
|
+
s.tmp = null;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
await rename(s.tmp, s.write.path);
|
|
102
|
+
}
|
|
103
|
+
s.swapped = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Put back every file that was already swapped, newest first, from the bytes read
|
|
108
|
+
* before the swap. Returns the paths it could NOT restore — the caller must say so
|
|
109
|
+
* out loud rather than report a clean failure over a half-merged repo.
|
|
110
|
+
*
|
|
111
|
+
* A rollback only ever un-does ITS OWN write: if the file no longer holds the
|
|
112
|
+
* bytes this run put there, somebody else has written it since, and restoring
|
|
113
|
+
* the pre-image would destroy their work while reporting a clean failure. That
|
|
114
|
+
* file is left exactly as found and named in the failures, which is what turns
|
|
115
|
+
* the caller's answer from `merge-failed` ("trust the repo") into
|
|
116
|
+
* `rollback-incomplete` ("look at it by hand") — the honest answer, because the
|
|
117
|
+
* repo now holds one run's writes half-reverted next to another's.
|
|
118
|
+
*/
|
|
119
|
+
export async function rollbackStaged(staged) {
|
|
120
|
+
const failures = [];
|
|
121
|
+
for (const s of [...staged].reverse()) {
|
|
122
|
+
if (!s.swapped)
|
|
123
|
+
continue;
|
|
124
|
+
try {
|
|
125
|
+
const now = existsSync(s.write.path) ? await readFile(s.write.path) : null;
|
|
126
|
+
if (!sameBytes(now, s.content)) {
|
|
127
|
+
failures.push(`${s.write.path} (changed by another writer after this run wrote it — left as found)`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (s.before === null)
|
|
131
|
+
await rm(s.write.path, { force: true });
|
|
132
|
+
else
|
|
133
|
+
await atomicWrite(s.write.path, s.before);
|
|
134
|
+
s.swapped = false;
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
failures.push(`${s.write.path} (${message(err)})`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await discardStaged(staged);
|
|
141
|
+
return failures;
|
|
142
|
+
}
|
|
143
|
+
/** Write `content` to `path` through a temp file in the same directory. */
|
|
144
|
+
export async function atomicWrite(path, content) {
|
|
145
|
+
const tmp = tempPath(path, 0);
|
|
146
|
+
await writeFile(tmp, content);
|
|
147
|
+
await rename(tmp, path);
|
|
148
|
+
}
|
|
149
|
+
/** A hidden sibling of `path`: same directory, so the rename never crosses a filesystem. */
|
|
150
|
+
export function tempPath(path, n) {
|
|
151
|
+
return join(dirname(path), `.${basename(path)}.loam-${process.pid}-${n}-${Date.now()}.tmp`);
|
|
152
|
+
}
|
|
153
|
+
/** The shape `tempPath` writes — what a scan for leftovers of a killed run matches on. */
|
|
154
|
+
export const TEMP_FILE_RE = /^\..*\.loam-\d+-\d+-\d+\.tmp$/;
|
|
155
|
+
/**
|
|
156
|
+
* Everything staging left on disk: the temp files, and the directories it made
|
|
157
|
+
* for them.
|
|
158
|
+
*
|
|
159
|
+
* Directories go by rmdir, never by recursive remove, and the walk stops the
|
|
160
|
+
* moment one is not empty. The recursive form used to be reached for because
|
|
161
|
+
* "we created this directory, so it is ours" — but a directory created at plan
|
|
162
|
+
* time is shared by the time the rollback runs: a second archive can have put a
|
|
163
|
+
* whole feature inside `features/archive/` in between, and `rm -r` on our own
|
|
164
|
+
* created root took their work with it, silently, on OUR failure path.
|
|
165
|
+
*/
|
|
166
|
+
async function discardStaged(staged) {
|
|
167
|
+
for (const s of staged)
|
|
168
|
+
if (s.tmp !== null)
|
|
169
|
+
await quietRm(s.tmp);
|
|
170
|
+
const roots = staged.filter((s) => s.createdDir !== null).map((s) => resolve(s.createdDir));
|
|
171
|
+
for (const s of [...staged].reverse()) {
|
|
172
|
+
const root = roots.find((candidate) => contains(candidate, s.write.path));
|
|
173
|
+
if (root !== undefined)
|
|
174
|
+
await quietPruneEmptyParents(dirname(s.write.path), root);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function contains(parent, candidate) {
|
|
178
|
+
const rel = relative(parent, resolve(candidate));
|
|
179
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Undo the directories a write had to create, from `start` up to and including
|
|
183
|
+
* `boundary`. Remove only directories that are still EMPTY: anything else in
|
|
184
|
+
* there arrived after we made it, and belongs to whoever put it there.
|
|
185
|
+
*/
|
|
186
|
+
/** Remove `dir` if it is empty. Best effort — a directory left standing is noise, not corruption. */
|
|
187
|
+
export async function quietRmdir(dir) {
|
|
188
|
+
try {
|
|
189
|
+
await rmdir(dir);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Not empty, or not ours.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export async function quietPruneEmptyParents(start, boundary) {
|
|
196
|
+
let cursor = resolve(start);
|
|
197
|
+
const root = resolve(boundary);
|
|
198
|
+
while (contains(root, cursor)) {
|
|
199
|
+
try {
|
|
200
|
+
await rmdir(cursor);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (cursor === root)
|
|
206
|
+
return;
|
|
207
|
+
const parent = dirname(cursor);
|
|
208
|
+
if (parent === cursor)
|
|
209
|
+
return;
|
|
210
|
+
cursor = parent;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export async function quietRm(path) {
|
|
214
|
+
try {
|
|
215
|
+
await rm(path, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Best effort — a leftover temp file is noise, not corruption.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The one spelling of the sentence a rollback failure prints, parameterised
|
|
223
|
+
* only by the verb for what the files were mid-way through becoming. It
|
|
224
|
+
* existed in three near-copies (archive's "half-merged", vouch's
|
|
225
|
+
* "half-stamped", verify's "half-written") — the exact shape whose fix lands
|
|
226
|
+
* in one copy while an operator greps their logs for another.
|
|
227
|
+
*/
|
|
228
|
+
export function rollbackMessage(err, failures, what) {
|
|
229
|
+
return `${message(err)} — ROLLBACK INCOMPLETE, these files may be half-${what} and need checking by hand: ${failures.join(", ")}`;
|
|
230
|
+
}
|
|
231
|
+
/** Say what the failure cost: nothing, or a repo that needs looking at by hand. */
|
|
232
|
+
export function rollbackError(err, failures) {
|
|
233
|
+
if (failures.length === 0) {
|
|
234
|
+
return new Error(`${message(err)} — the living docs were rolled back, nothing was merged`);
|
|
235
|
+
}
|
|
236
|
+
return new Error(rollbackMessage(err, failures, "merged"));
|
|
237
|
+
}
|
|
238
|
+
export function message(err) {
|
|
239
|
+
return err instanceof Error ? err.message : String(err);
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/core/staging/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The commit itself, in three steps: stage every new version beside its\n * target, swap them in, roll back what already went in when a later step fails.\n *\n * This is what `archive` and `unarchive` share, and it moved out of archive.ts\n * verbatim once unarchive became its second consumer. The MERGE computations\n * stayed with archive, which is still their only caller — the split was about a\n * second CONSUMER, never about archive being long.\n *\n * `StagingRaceError` belongs here rather than with the lock, though both are\n * about a second writer: the lock is taken before a plan is computed, while\n * this is the compare-and-set immediately before each rename — the second line\n * of defence for exactly the window two racers who both broke the same stale\n * lock would land in.\n */\nimport { existsSync } from \"node:fs\";\nimport { link, mkdir, readFile, rename, rm, rmdir, unlink, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { sameBytes, toBytes, type PlannedWrite, type StagedWrite } from \"./writes.js\";\n\n/**\n * The target changed between the pre-image staging read and the swap.\n *\n * The pre-image is what a rollback restores and what `unarchive` is handed, so\n * swapping over somebody else's newer bytes would both lose their work and\n * arm a rollback that writes a third version nobody wrote. Commands map this to\n * `merge-failed`: nothing of theirs went in, the docs are as the other writer\n * left them, and re-running can work.\n */\nexport class StagingRaceError extends Error {\n override readonly name = \"StagingRaceError\";\n\n constructor(readonly path: string) {\n super(`${path} changed while this command was planning its merge — nothing was written; re-run`);\n }\n}\n\n/**\n * Write every new version to a temp file beside its target, remembering what the\n * target said first. Nothing observable changes here: on failure the temp files\n * are removed and the docs are exactly as they were.\n *\n * The pre-image is read eagerly and deliberately: it is what a rollback restores\n * and what `unarchive` is given, so a file we cannot read is a file we cannot\n * safely rewrite, and the merge must stop before it starts.\n */\nexport async function stageWrites(writes: PlannedWrite[]): Promise<StagedWrite[]> {\n const staged: StagedWrite[] = [];\n try {\n for (const [i, write] of writes.entries()) {\n // mkdir(recursive) reports the topmost directory it had to create — the\n // handle a rollback needs to put the tree back the way it found it.\n const createdDir = (await mkdir(dirname(write.path), { recursive: true })) ?? null;\n const before = existsSync(write.path) ? await readFile(write.path) : null;\n const content = toBytes(write.content);\n let tmp: string | null = null;\n if (content !== null) {\n tmp = tempPath(write.path, i);\n await writeFile(tmp, content);\n }\n staged.push({ write, content, tmp, before, createdDir, swapped: false });\n }\n } catch (err) {\n await discardStaged(staged);\n throw err;\n }\n return staged;\n}\n\n/**\n * Swap the staged versions in, one file at a time. Each swap is a rename(2)\n * within a single directory, so a concurrent reader sees either the old bytes or\n * the new ones — never a half-written file. Across files it is not atomic: the\n * caller rolls back what has already gone in.\n *\n * Every swap re-reads its target first and compares it with the pre-image the\n * plan was computed from — a compare-and-set on the file's whole content. It is\n * half a CAS on purpose: read and rename cannot be one syscall, so a writer\n * landing inside that window still wins. What it does buy is the window that\n * actually matters — the seconds a plan spends parsing LikeC4 and merging YAML\n * between the pre-image read and this swap — and it costs one read per file.\n */\nexport async function swapStaged(staged: StagedWrite[]): Promise<void> {\n for (const s of staged) {\n const now = existsSync(s.write.path) ? await readFile(s.write.path) : null;\n if (!sameBytes(now, s.before)) throw new StagingRaceError(s.write.path);\n if (s.tmp === null) {\n if (s.before !== null) await unlink(s.write.path);\n } else if (s.write.exclusive === true) {\n // link(2) is the same-filesystem, atomic no-clobber counterpart to rename:\n // it fails with EEXIST instead of replacing a file created after planning.\n await link(s.tmp, s.write.path);\n s.swapped = true;\n await unlink(s.tmp);\n s.tmp = null;\n continue;\n } else {\n await rename(s.tmp, s.write.path);\n }\n s.swapped = true;\n }\n}\n\n/**\n * Put back every file that was already swapped, newest first, from the bytes read\n * before the swap. Returns the paths it could NOT restore — the caller must say so\n * out loud rather than report a clean failure over a half-merged repo.\n *\n * A rollback only ever un-does ITS OWN write: if the file no longer holds the\n * bytes this run put there, somebody else has written it since, and restoring\n * the pre-image would destroy their work while reporting a clean failure. That\n * file is left exactly as found and named in the failures, which is what turns\n * the caller's answer from `merge-failed` (\"trust the repo\") into\n * `rollback-incomplete` (\"look at it by hand\") — the honest answer, because the\n * repo now holds one run's writes half-reverted next to another's.\n */\nexport async function rollbackStaged(staged: StagedWrite[]): Promise<string[]> {\n const failures: string[] = [];\n for (const s of [...staged].reverse()) {\n if (!s.swapped) continue;\n try {\n const now = existsSync(s.write.path) ? await readFile(s.write.path) : null;\n if (!sameBytes(now, s.content)) {\n failures.push(`${s.write.path} (changed by another writer after this run wrote it — left as found)`);\n continue;\n }\n if (s.before === null) await rm(s.write.path, { force: true });\n else await atomicWrite(s.write.path, s.before);\n s.swapped = false;\n } catch (err) {\n failures.push(`${s.write.path} (${message(err)})`);\n }\n }\n await discardStaged(staged);\n return failures;\n}\n\n/** Write `content` to `path` through a temp file in the same directory. */\nexport async function atomicWrite(path: string, content: Buffer): Promise<void> {\n const tmp = tempPath(path, 0);\n await writeFile(tmp, content);\n await rename(tmp, path);\n}\n\n/** A hidden sibling of `path`: same directory, so the rename never crosses a filesystem. */\nexport function tempPath(path: string, n: number): string {\n return join(dirname(path), `.${basename(path)}.loam-${process.pid}-${n}-${Date.now()}.tmp`);\n}\n\n/** The shape `tempPath` writes — what a scan for leftovers of a killed run matches on. */\nexport const TEMP_FILE_RE = /^\\..*\\.loam-\\d+-\\d+-\\d+\\.tmp$/;\n\n/**\n * Everything staging left on disk: the temp files, and the directories it made\n * for them.\n *\n * Directories go by rmdir, never by recursive remove, and the walk stops the\n * moment one is not empty. The recursive form used to be reached for because\n * \"we created this directory, so it is ours\" — but a directory created at plan\n * time is shared by the time the rollback runs: a second archive can have put a\n * whole feature inside `features/archive/` in between, and `rm -r` on our own\n * created root took their work with it, silently, on OUR failure path.\n */\nasync function discardStaged(staged: StagedWrite[]): Promise<void> {\n for (const s of staged) if (s.tmp !== null) await quietRm(s.tmp);\n const roots = staged.filter((s) => s.createdDir !== null).map((s) => resolve(s.createdDir!));\n for (const s of [...staged].reverse()) {\n const root = roots.find((candidate) => contains(candidate, s.write.path));\n if (root !== undefined) await quietPruneEmptyParents(dirname(s.write.path), root);\n }\n}\n\nfunction contains(parent: string, candidate: string): boolean {\n const rel = relative(parent, resolve(candidate));\n return rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\n/**\n * Undo the directories a write had to create, from `start` up to and including\n * `boundary`. Remove only directories that are still EMPTY: anything else in\n * there arrived after we made it, and belongs to whoever put it there.\n */\n/** Remove `dir` if it is empty. Best effort — a directory left standing is noise, not corruption. */\nexport async function quietRmdir(dir: string): Promise<void> {\n try {\n await rmdir(dir);\n } catch {\n // Not empty, or not ours.\n }\n}\n\nexport async function quietPruneEmptyParents(start: string, boundary: string): Promise<void> {\n let cursor = resolve(start);\n const root = resolve(boundary);\n while (contains(root, cursor)) {\n try {\n await rmdir(cursor);\n } catch {\n return;\n }\n if (cursor === root) return;\n const parent = dirname(cursor);\n if (parent === cursor) return;\n cursor = parent;\n }\n}\n\nexport async function quietRm(path: string): Promise<void> {\n try {\n await rm(path, { recursive: true, force: true });\n } catch {\n // Best effort — a leftover temp file is noise, not corruption.\n }\n}\n\n/**\n * The one spelling of the sentence a rollback failure prints, parameterised\n * only by the verb for what the files were mid-way through becoming. It\n * existed in three near-copies (archive's \"half-merged\", vouch's\n * \"half-stamped\", verify's \"half-written\") — the exact shape whose fix lands\n * in one copy while an operator greps their logs for another.\n */\nexport function rollbackMessage(err: unknown, failures: string[], what: string): string {\n return `${message(err)} — ROLLBACK INCOMPLETE, these files may be half-${what} and need checking by hand: ${failures.join(\", \")}`;\n}\n\n/** Say what the failure cost: nothing, or a repo that needs looking at by hand. */\nexport function rollbackError(err: unknown, failures: string[]): Error {\n if (failures.length === 0) {\n return new Error(`${message(err)} — the living docs were rolled back, nothing was merged`);\n }\n return new Error(\n rollbackMessage(err, failures, \"merged\"),\n );\n}\n\nexport function message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary every journaled commit shares, whichever journal version
|
|
3
|
+
* wrote it: the record's filename, the refusal an interrupted commit raises,
|
|
4
|
+
* the recovery report a caller prints, and the two disk disciplines (durable
|
|
5
|
+
* write, pid-scoped temp sweep) that recovery rests on.
|
|
6
|
+
*
|
|
7
|
+
* A staging-ROOT module, deliberately: `recovery/` (the version-1 undo path)
|
|
8
|
+
* and `txn/` (the version-2 roll-forward path) both need these VALUES, and a
|
|
9
|
+
* value import between those two packages in either direction would be the
|
|
10
|
+
* repo's first package cycle — `recovery/` already imports the staging root,
|
|
11
|
+
* and `txn/` must too. Shared values live where both edges already point.
|
|
12
|
+
*/
|
|
13
|
+
import { readdir, open } from "node:fs/promises";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { quietRm, TEMP_FILE_RE } from "./commit.js";
|
|
16
|
+
/**
|
|
17
|
+
* The journal that makes an interrupted commit detectable.
|
|
18
|
+
*
|
|
19
|
+
* `swapStaged` is N renames, and only each ONE of them is atomic. A kill
|
|
20
|
+
* between two of them leaves the tree half-merged — and nothing could see it:
|
|
21
|
+
* `doctor` and `status` called the repo healthy, `validate --all` blamed the
|
|
22
|
+
* delta, and the next archive cleared the one snapshot that could have
|
|
23
|
+
* repaired it. So every commit's swap loop runs inside a record written and
|
|
24
|
+
* fsynced BEFORE the first rename and removed after the last step: its mere
|
|
25
|
+
* presence says a commit was in flight, and its per-path digests say, file by
|
|
26
|
+
* file, whether that file's rename landed.
|
|
27
|
+
*
|
|
28
|
+
* It is deliberately NOT a source of truth and never disagrees with the
|
|
29
|
+
* files: every judgement it supports is made by re-reading the bytes and
|
|
30
|
+
* comparing digests, and every byte a repair writes is verified against the
|
|
31
|
+
* digest recorded before the crash. Outside the commit window it does not
|
|
32
|
+
* exist — except after a rollback that could not finish, where it is kept on
|
|
33
|
+
* purpose as the one durable record of which files need a human. Version 1
|
|
34
|
+
* (archive/unarchive) restores from a snapshot; version 2 (the smaller
|
|
35
|
+
* transaction) rolls forward from the staged temps. Both wear this name so
|
|
36
|
+
* one `doctor` scan finds either.
|
|
37
|
+
*/
|
|
38
|
+
export const COMMIT_INTENT = ".loam-commit";
|
|
39
|
+
/** An interrupted commit this loam must not silently write over. */
|
|
40
|
+
export class InterruptedCommitError extends Error {
|
|
41
|
+
name = "InterruptedCommitError";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Write `text` to `path` and get it onto the DISK, not just into the page
|
|
45
|
+
* cache: a journal that dies with the machine describes nothing. The
|
|
46
|
+
* directory entry is fsynced too where the platform allows opening a
|
|
47
|
+
* directory — without that the file's bytes can be durable while its name is
|
|
48
|
+
* not.
|
|
49
|
+
*/
|
|
50
|
+
export async function writeDurable(path, text) {
|
|
51
|
+
const handle = await open(path, "w");
|
|
52
|
+
try {
|
|
53
|
+
await handle.writeFile(text, "utf8");
|
|
54
|
+
await handle.sync();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await handle.close();
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const dir = await open(join(path, ".."), "r");
|
|
61
|
+
try {
|
|
62
|
+
await dir.sync();
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await dir.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Not every platform lets a directory be opened; the file's own bytes are
|
|
70
|
+
// flushed either way, which is the part that carries the digests.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Drop the staged temp files a killed run could not remove — scoped to that
|
|
75
|
+
* run's own pid and its own target directories, both carried in the temp
|
|
76
|
+
* name, so a leftover belonging to anything else is left for `doctor` to
|
|
77
|
+
* name. Without this a repair that put every byte back still left the repo
|
|
78
|
+
* dirty, which is the one thing a caller checks to decide whether it worked.
|
|
79
|
+
*/
|
|
80
|
+
export async function sweepPidTemps(dirs, pid) {
|
|
81
|
+
for (const dir of new Set(dirs)) {
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = await readdir(dir);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const name of entries) {
|
|
90
|
+
if (!TEMP_FILE_RE.test(name) || !name.includes(`.loam-${pid}-`))
|
|
91
|
+
continue;
|
|
92
|
+
await quietRm(join(dir, name));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=interrupted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interrupted.js","sourceRoot":"","sources":["../../../src/core/staging/interrupted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;AAE5C,oEAAoE;AACpE,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC7B,IAAI,GAAG,wBAAwB,CAAC;CACnD;AAsBD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY;IAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,kEAAkE;IACpE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAsB,EAAE,GAAW;IACrE,KAAK,MAAM,GAAG,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG,GAAG,CAAC;gBAAE,SAAS;YAC1E,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/**\n * The vocabulary every journaled commit shares, whichever journal version\n * wrote it: the record's filename, the refusal an interrupted commit raises,\n * the recovery report a caller prints, and the two disk disciplines (durable\n * write, pid-scoped temp sweep) that recovery rests on.\n *\n * A staging-ROOT module, deliberately: `recovery/` (the version-1 undo path)\n * and `txn/` (the version-2 roll-forward path) both need these VALUES, and a\n * value import between those two packages in either direction would be the\n * repo's first package cycle — `recovery/` already imports the staging root,\n * and `txn/` must too. Shared values live where both edges already point.\n */\nimport { readdir, open } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { quietRm, TEMP_FILE_RE } from \"./commit.js\";\n\n/**\n * The journal that makes an interrupted commit detectable.\n *\n * `swapStaged` is N renames, and only each ONE of them is atomic. A kill\n * between two of them leaves the tree half-merged — and nothing could see it:\n * `doctor` and `status` called the repo healthy, `validate --all` blamed the\n * delta, and the next archive cleared the one snapshot that could have\n * repaired it. So every commit's swap loop runs inside a record written and\n * fsynced BEFORE the first rename and removed after the last step: its mere\n * presence says a commit was in flight, and its per-path digests say, file by\n * file, whether that file's rename landed.\n *\n * It is deliberately NOT a source of truth and never disagrees with the\n * files: every judgement it supports is made by re-reading the bytes and\n * comparing digests, and every byte a repair writes is verified against the\n * digest recorded before the crash. Outside the commit window it does not\n * exist — except after a rollback that could not finish, where it is kept on\n * purpose as the one durable record of which files need a human. Version 1\n * (archive/unarchive) restores from a snapshot; version 2 (the smaller\n * transaction) rolls forward from the staged temps. Both wear this name so\n * one `doctor` scan finds either.\n */\nexport const COMMIT_INTENT = \".loam-commit\";\n\n/** An interrupted commit this loam must not silently write over. */\nexport class InterruptedCommitError extends Error {\n override readonly name = \"InterruptedCommitError\";\n}\n\n/** What recovery did, for the caller to print and put in `--json`. */\nexport interface CommitRecovery {\n /** Which command's commit was recovered — a sentence a human reads, not a branch. */\n command: string;\n /**\n * What that command was committing FOR: a feature id for archive, unarchive\n * and rebase; a service for vouch and gherkin. The field keeps archive's\n * original name because its `--json` shape shipped first.\n */\n feature: string;\n /**\n * `completed` — every step but the record's own removal landed;\n * `consistent` — no file was left in the other state;\n * `repaired` — files were put back (v1) or carried forward (v2), byte for byte.\n */\n outcome: \"completed\" | \"consistent\" | \"repaired\";\n /** Repo-relative paths this recovery rewrote or removed. */\n repaired: string[];\n}\n\n/**\n * Write `text` to `path` and get it onto the DISK, not just into the page\n * cache: a journal that dies with the machine describes nothing. The\n * directory entry is fsynced too where the platform allows opening a\n * directory — without that the file's bytes can be durable while its name is\n * not.\n */\nexport async function writeDurable(path: string, text: string): Promise<void> {\n const handle = await open(path, \"w\");\n try {\n await handle.writeFile(text, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n const dir = await open(join(path, \"..\"), \"r\");\n try {\n await dir.sync();\n } finally {\n await dir.close();\n }\n } catch {\n // Not every platform lets a directory be opened; the file's own bytes are\n // flushed either way, which is the part that carries the digests.\n }\n}\n\n/**\n * Drop the staged temp files a killed run could not remove — scoped to that\n * run's own pid and its own target directories, both carried in the temp\n * name, so a leftover belonging to anything else is left for `doctor` to\n * name. Without this a repair that put every byte back still left the repo\n * dirty, which is the one thing a caller checks to decide whether it worked.\n */\nexport async function sweepPidTemps(dirs: Iterable<string>, pid: number): Promise<void> {\n for (const dir of new Set(dirs)) {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of entries) {\n if (!TEMP_FILE_RE.test(name) || !name.includes(`.loam-${pid}-`)) continue;\n await quietRm(join(dir, name));\n }\n }\n}\n"]}
|