@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,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shaping helpers for the renderers.
|
|
3
|
+
*
|
|
4
|
+
* They live in `commands/` and not in `core/`: nothing here computes an answer,
|
|
5
|
+
* it only decides how an answer READS. That was for a long time true of people
|
|
6
|
+
* only — the header used to say "the `--json` payloads never touch this file",
|
|
7
|
+
* and `fixesFor` below is the exception that retired the sentence. It is here
|
|
8
|
+
* for the same reason the rest is: a fix string is not a verdict, it is the
|
|
9
|
+
* shaping of one that has already been decided, and the module that owns the
|
|
10
|
+
* text pointer to `loam explain` should own its machine form too, or the two
|
|
11
|
+
* drift the way every second copy in this codebase eventually has.
|
|
12
|
+
*/
|
|
13
|
+
import { explainSubject } from "../../core/explain/lookup.js";
|
|
14
|
+
import {} from "../../core/verify/build.js";
|
|
15
|
+
/**
|
|
16
|
+
* "1 service", "2 services". The regular English plural, spelled once because
|
|
17
|
+
* five renderers had drifted into five copies of the same ternary and a sixth
|
|
18
|
+
* would have been written the next time somebody counted something. A noun with
|
|
19
|
+
* an irregular plural does not belong here — say both forms at the call site.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The one spelling of "this run first recovered a predecessor's commit" — it
|
|
23
|
+
* existed in three copies (rebase, gherkin, verify --record) before vouch and
|
|
24
|
+
* new needed a fourth and fifth, which is the extraction rule's third strike
|
|
25
|
+
* twice over. Reported at all because docs changing beyond the command's own
|
|
26
|
+
* writes would otherwise read as its doing.
|
|
27
|
+
*/
|
|
28
|
+
export function sayRecovered(r) {
|
|
29
|
+
return (`recovered an interrupted \`loam ${r.command}\` first (${r.outcome}` +
|
|
30
|
+
`${r.repaired.length === 0 ? "" : `: ${r.repaired.join(", ")}`}).`);
|
|
31
|
+
}
|
|
32
|
+
export function plural(n, noun) {
|
|
33
|
+
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The one spelling of the vocabulary pointer `validate` and `verify` append to
|
|
37
|
+
* a TEXT report that printed findings — one string here rather than one per
|
|
38
|
+
* renderer, because a hand-written second copy is exactly the drift the
|
|
39
|
+
* pointer's target exists to prevent.
|
|
40
|
+
*
|
|
41
|
+
* It used to open with "codes ride in `--json`", and that clause existed for
|
|
42
|
+
* one reason only: validate's text lines carried messages and no code strings,
|
|
43
|
+
* so a footer asking the reader to explain "a code above" would have asked for
|
|
44
|
+
* something never on their screen. That is no longer true — validate appends
|
|
45
|
+
* the code to every non-`ok` finding and verify's notices have always led with
|
|
46
|
+
* theirs (validate/report.ts, verify/report.ts) — so the clause has been
|
|
47
|
+
* dropped rather than reworded: telling a reader to go re-run the command with
|
|
48
|
+
* `--json` to find what is already two words to the left is worse than saying
|
|
49
|
+
* nothing.
|
|
50
|
+
*
|
|
51
|
+
* Text mode only, and still: this SENTENCE never enters a payload. What
|
|
52
|
+
* `--json` carries instead is the answer the sentence points at — `fixesFor`
|
|
53
|
+
* below — because a machine reader has no use for being told a second command
|
|
54
|
+
* exists, only for what it would have said.
|
|
55
|
+
*/
|
|
56
|
+
export const EXPLAIN_FOOTER = "→ `loam explain <code>` says what a code means and how to fix it";
|
|
57
|
+
/**
|
|
58
|
+
* The machine form of the footer above: the codes a run actually raised,
|
|
59
|
+
* resolved to what their fix tables say to do about them.
|
|
60
|
+
*
|
|
61
|
+
* The footer tells a PERSON that `loam explain <code>` exists. A `--json`
|
|
62
|
+
* consumer got no such sentence — the payload is codes and prose messages, and
|
|
63
|
+
* nothing in a finding says a fix is a lookup away — so an agent holding
|
|
64
|
+
* `validate --all --json` either had the (now narrowable) /loam-check fix
|
|
65
|
+
* tables in context already or spawned one `loam explain` process per code,
|
|
66
|
+
* having first guessed that it could. This closes the gap by answering the
|
|
67
|
+
* lookup in the same envelope.
|
|
68
|
+
*
|
|
69
|
+
* Resolved through `explainSubject` — the very function `loam explain <code>`
|
|
70
|
+
* calls — and NOT through a second table, so the two surfaces cannot disagree
|
|
71
|
+
* about what to do about a code. The cost is one walk of the shipped workflow
|
|
72
|
+
* bodies per DISTINCT code (measured: 0.43ms each, so ~4ms for the nine codes
|
|
73
|
+
* a fleet run typically raises). `listCodes()` would do one walk for all of
|
|
74
|
+
* them, and was not used: it builds 227 answers to spend nine, and it would
|
|
75
|
+
* only start paying off on a run that raised most of the vocabulary — which
|
|
76
|
+
* is a repo with worse problems than the cost of its report.
|
|
77
|
+
*
|
|
78
|
+
* A code the tables do not grade is simply ABSENT from the result. There is no
|
|
79
|
+
* placeholder string and there must not be one: the named backlog in
|
|
80
|
+
* test/explain.test.ts is the record of which code families still have no row,
|
|
81
|
+
* and a fabricated "no fix recorded" here would satisfy the eye at exactly the
|
|
82
|
+
* place that record is supposed to itch.
|
|
83
|
+
*
|
|
84
|
+
* Two shaping rules, both forced by the tables rather than chosen:
|
|
85
|
+
* - Keys are sorted and deduplicated, so two runs over the same findings
|
|
86
|
+
* produce byte-identical maps and a diff of two reports shows only what
|
|
87
|
+
* moved.
|
|
88
|
+
* - Ten of the 227 finding codes are graded by more than one table, and nine
|
|
89
|
+
* of those ten say something DIFFERENT to do depending on the scope
|
|
90
|
+
* (`openapi.ref-unresolved` is one thing under `--service` and another at
|
|
91
|
+
* archive plan time). A single-fix code is emitted verbatim; a code whose
|
|
92
|
+
* tables disagree is emitted as its scope-labelled blocks, in table order,
|
|
93
|
+
* the same way `loam explain` prints it. Picking the first table's fix would
|
|
94
|
+
* be shorter and would sometimes be the wrong instruction, which is the one
|
|
95
|
+
* thing a fix string may not be.
|
|
96
|
+
*/
|
|
97
|
+
export function fixesFor(codes) {
|
|
98
|
+
const fixes = {};
|
|
99
|
+
for (const code of [...new Set(codes)].sort()) {
|
|
100
|
+
const explanation = explainSubject(code);
|
|
101
|
+
// Only the finding arm contributes. A refusal code is an `error.code` and
|
|
102
|
+
// never appears in a findings array, and a concept term is not a code at
|
|
103
|
+
// all — the three key sets are pinned pairwise disjoint by
|
|
104
|
+
// test/explain.test.ts, so this narrowing is a type guard, not a policy.
|
|
105
|
+
if (explanation === null || explanation.kind !== "finding")
|
|
106
|
+
continue;
|
|
107
|
+
// Keyed by the fix so a code graded identically by two tables says it
|
|
108
|
+
// once, and valued by the FIRST scope that gave it so the labelled form
|
|
109
|
+
// reads in table order.
|
|
110
|
+
const byFix = new Map();
|
|
111
|
+
for (const entry of explanation.entries) {
|
|
112
|
+
// A row whose "what to do" cell is empty states a consequence, not an
|
|
113
|
+
// action; it contributes nothing a caller could act on.
|
|
114
|
+
if (entry.fix !== "" && !byFix.has(entry.fix))
|
|
115
|
+
byFix.set(entry.fix, entry.scope);
|
|
116
|
+
}
|
|
117
|
+
const distinct = [...byFix];
|
|
118
|
+
if (distinct.length === 0)
|
|
119
|
+
continue;
|
|
120
|
+
fixes[code] =
|
|
121
|
+
distinct.length === 1
|
|
122
|
+
? distinct[0][0]
|
|
123
|
+
: distinct.map(([fix, scope]) => `[${scope}] ${fix}`).join(" ");
|
|
124
|
+
}
|
|
125
|
+
return fixes;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* What the previous record answered and the new one does not — printed, never
|
|
129
|
+
* decided here (`buildFederatedVerification` decides; `verify --record`
|
|
130
|
+
* reports). A federated write is a partial write, so the first one over an
|
|
131
|
+
* all-at-once record drops every answer it cannot attribute to a commit —
|
|
132
|
+
* correct, but silence there reads as loam having lost the answers, and nobody
|
|
133
|
+
* goes looking for what to re-record. It lives beside `sayRecovered` because
|
|
134
|
+
* it is the same kind of helper: one spelling of an account the command owes a
|
|
135
|
+
* person about something the run did beyond its own answer.
|
|
136
|
+
*/
|
|
137
|
+
export function sayDiscarded(discarded, recordPath, feature) {
|
|
138
|
+
if (discarded.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
const off = discarded.filter((d) => d.reason === "off-checklist").length;
|
|
141
|
+
console.log(`\n ${plural(discarded.length, "earlier answer")} from ${recordPath} ${discarded.length === 1 ? "is" : "are"} not carried into this record:`);
|
|
142
|
+
for (const d of discarded) {
|
|
143
|
+
console.log(` - ${d.id}${d.subject === undefined ? "" : ` [${d.subject}]`} ${d.claim}` +
|
|
144
|
+
(d.reason === "off-checklist"
|
|
145
|
+
? " (the feature changed; nothing asks this any more)"
|
|
146
|
+
: " (no commit attestation binds it — its service must record it again)"));
|
|
147
|
+
}
|
|
148
|
+
if (discarded.length > off) {
|
|
149
|
+
console.log(` Each owning service records its own with \`loam verify ${feature} --record answers.json --service <svc>\` in its repo.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/commands/policy/format.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Small shaping helpers for the renderers.\n *\n * They live in `commands/` and not in `core/`: nothing here computes an answer,\n * it only decides how an answer READS. That was for a long time true of people\n * only — the header used to say \"the `--json` payloads never touch this file\",\n * and `fixesFor` below is the exception that retired the sentence. It is here\n * for the same reason the rest is: a fix string is not a verdict, it is the\n * shaping of one that has already been decided, and the module that owns the\n * text pointer to `loam explain` should own its machine form too, or the two\n * drift the way every second copy in this codebase eventually has.\n */\nimport { explainSubject } from \"../../core/explain/lookup.js\";\nimport { type DiscardedAnswer } from \"../../core/verify/build.js\";\n\n/**\n * \"1 service\", \"2 services\". The regular English plural, spelled once because\n * five renderers had drifted into five copies of the same ternary and a sixth\n * would have been written the next time somebody counted something. A noun with\n * an irregular plural does not belong here — say both forms at the call site.\n */\n/**\n * The one spelling of \"this run first recovered a predecessor's commit\" — it\n * existed in three copies (rebase, gherkin, verify --record) before vouch and\n * new needed a fourth and fifth, which is the extraction rule's third strike\n * twice over. Reported at all because docs changing beyond the command's own\n * writes would otherwise read as its doing.\n */\nexport function sayRecovered(r: { command: string; outcome: string; repaired: string[] }): string {\n return (\n `recovered an interrupted \\`loam ${r.command}\\` first (${r.outcome}` +\n `${r.repaired.length === 0 ? \"\" : `: ${r.repaired.join(\", \")}`}).`\n );\n}\n\nexport function plural(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n\n/**\n * The one spelling of the vocabulary pointer `validate` and `verify` append to\n * a TEXT report that printed findings — one string here rather than one per\n * renderer, because a hand-written second copy is exactly the drift the\n * pointer's target exists to prevent.\n *\n * It used to open with \"codes ride in `--json`\", and that clause existed for\n * one reason only: validate's text lines carried messages and no code strings,\n * so a footer asking the reader to explain \"a code above\" would have asked for\n * something never on their screen. That is no longer true — validate appends\n * the code to every non-`ok` finding and verify's notices have always led with\n * theirs (validate/report.ts, verify/report.ts) — so the clause has been\n * dropped rather than reworded: telling a reader to go re-run the command with\n * `--json` to find what is already two words to the left is worse than saying\n * nothing.\n *\n * Text mode only, and still: this SENTENCE never enters a payload. What\n * `--json` carries instead is the answer the sentence points at — `fixesFor`\n * below — because a machine reader has no use for being told a second command\n * exists, only for what it would have said.\n */\nexport const EXPLAIN_FOOTER =\n \"→ `loam explain <code>` says what a code means and how to fix it\";\n\n/**\n * The machine form of the footer above: the codes a run actually raised,\n * resolved to what their fix tables say to do about them.\n *\n * The footer tells a PERSON that `loam explain <code>` exists. A `--json`\n * consumer got no such sentence — the payload is codes and prose messages, and\n * nothing in a finding says a fix is a lookup away — so an agent holding\n * `validate --all --json` either had the (now narrowable) /loam-check fix\n * tables in context already or spawned one `loam explain` process per code,\n * having first guessed that it could. This closes the gap by answering the\n * lookup in the same envelope.\n *\n * Resolved through `explainSubject` — the very function `loam explain <code>`\n * calls — and NOT through a second table, so the two surfaces cannot disagree\n * about what to do about a code. The cost is one walk of the shipped workflow\n * bodies per DISTINCT code (measured: 0.43ms each, so ~4ms for the nine codes\n * a fleet run typically raises). `listCodes()` would do one walk for all of\n * them, and was not used: it builds 227 answers to spend nine, and it would\n * only start paying off on a run that raised most of the vocabulary — which\n * is a repo with worse problems than the cost of its report.\n *\n * A code the tables do not grade is simply ABSENT from the result. There is no\n * placeholder string and there must not be one: the named backlog in\n * test/explain.test.ts is the record of which code families still have no row,\n * and a fabricated \"no fix recorded\" here would satisfy the eye at exactly the\n * place that record is supposed to itch.\n *\n * Two shaping rules, both forced by the tables rather than chosen:\n * - Keys are sorted and deduplicated, so two runs over the same findings\n * produce byte-identical maps and a diff of two reports shows only what\n * moved.\n * - Ten of the 227 finding codes are graded by more than one table, and nine\n * of those ten say something DIFFERENT to do depending on the scope\n * (`openapi.ref-unresolved` is one thing under `--service` and another at\n * archive plan time). A single-fix code is emitted verbatim; a code whose\n * tables disagree is emitted as its scope-labelled blocks, in table order,\n * the same way `loam explain` prints it. Picking the first table's fix would\n * be shorter and would sometimes be the wrong instruction, which is the one\n * thing a fix string may not be.\n */\nexport function fixesFor(codes: readonly string[]): Record<string, string> {\n const fixes: Record<string, string> = {};\n for (const code of [...new Set(codes)].sort()) {\n const explanation = explainSubject(code);\n // Only the finding arm contributes. A refusal code is an `error.code` and\n // never appears in a findings array, and a concept term is not a code at\n // all — the three key sets are pinned pairwise disjoint by\n // test/explain.test.ts, so this narrowing is a type guard, not a policy.\n if (explanation === null || explanation.kind !== \"finding\") continue;\n // Keyed by the fix so a code graded identically by two tables says it\n // once, and valued by the FIRST scope that gave it so the labelled form\n // reads in table order.\n const byFix = new Map<string, string>();\n for (const entry of explanation.entries) {\n // A row whose \"what to do\" cell is empty states a consequence, not an\n // action; it contributes nothing a caller could act on.\n if (entry.fix !== \"\" && !byFix.has(entry.fix)) byFix.set(entry.fix, entry.scope);\n }\n const distinct = [...byFix];\n if (distinct.length === 0) continue;\n fixes[code] =\n distinct.length === 1\n ? distinct[0]![0]\n : distinct.map(([fix, scope]) => `[${scope}] ${fix}`).join(\" \");\n }\n return fixes;\n}\n\n/**\n * What the previous record answered and the new one does not — printed, never\n * decided here (`buildFederatedVerification` decides; `verify --record`\n * reports). A federated write is a partial write, so the first one over an\n * all-at-once record drops every answer it cannot attribute to a commit —\n * correct, but silence there reads as loam having lost the answers, and nobody\n * goes looking for what to re-record. It lives beside `sayRecovered` because\n * it is the same kind of helper: one spelling of an account the command owes a\n * person about something the run did beyond its own answer.\n */\nexport function sayDiscarded(discarded: DiscardedAnswer[], recordPath: string, feature: string): void {\n if (discarded.length === 0) return;\n const off = discarded.filter((d) => d.reason === \"off-checklist\").length;\n console.log(\n `\\n ${plural(discarded.length, \"earlier answer\")} from ${recordPath} ${discarded.length === 1 ? \"is\" : \"are\"} not carried into this record:`,\n );\n for (const d of discarded) {\n console.log(\n ` - ${d.id}${d.subject === undefined ? \"\" : ` [${d.subject}]`} ${d.claim}` +\n (d.reason === \"off-checklist\"\n ? \" (the feature changed; nothing asks this any more)\"\n : \" (no commit attestation binds it — its service must record it again)\"),\n );\n }\n if (discarded.length > off) {\n console.log(\n ` Each owning service records its own with \\`loam verify ${feature} --record answers.json --service <svc>\\` in its repo.`,\n );\n }\n}\n"]}
|
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
* One doctrine, three ways of reaching it: the gate a command passes before it
|
|
5
5
|
* enumerates anything, the error the enumeration throws when the repo goes away
|
|
6
6
|
* underneath it, and the errno that comes back when the repo is real but one
|
|
7
|
-
* file under it will not open.
|
|
8
|
-
*
|
|
9
|
-
* collecting them is that all
|
|
7
|
+
* file under it will not open. Every command that reads a docs repo reaches
|
|
8
|
+
* for one or another of them (`rg docsRepoReady` is the always-current list),
|
|
9
|
+
* and the whole point of collecting them is that they all answer with the same
|
|
10
|
+
* sentences.
|
|
10
11
|
*
|
|
11
12
|
* They live in `commands/` rather than in `core/`: each one calls `fail`, which
|
|
12
13
|
* writes to stdout or stderr and sets the exit code. That is a decision about
|
|
13
14
|
* how this process ends, and core modules compute answers rather than end
|
|
14
15
|
* processes.
|
|
15
16
|
*/
|
|
16
|
-
import { fail } from "
|
|
17
|
-
import { docsRepoState } from "
|
|
17
|
+
import { fail } from "../../core/envelope/json.js";
|
|
18
|
+
import { docsRepoState } from "../../core/repo/state.js";
|
|
18
19
|
/**
|
|
19
20
|
* The refusal every READ command owes a `docsDir` that is not a docs repo.
|
|
20
21
|
*
|
|
@@ -37,11 +38,10 @@ import { docsRepoState } from "../core/repo.js";
|
|
|
37
38
|
* the directory that IS the list of services.
|
|
38
39
|
*
|
|
39
40
|
* It used to live in validate.ts, on the reasoning that validate is the fleet
|
|
40
|
-
* gate this doctrine is about.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* its callers like the rest.
|
|
41
|
+
* gate this doctrine is about. Enough commands import it now that the doctrine
|
|
42
|
+
* outgrew its host: `loam show` was structurally depending on the whole
|
|
43
|
+
* validate implementation for twenty lines of policy. The rule is the module
|
|
44
|
+
* now, and validate is one of its callers like the rest.
|
|
45
45
|
*/
|
|
46
46
|
export function docsRepoReady(json, docsDir, need) {
|
|
47
47
|
const state = docsRepoState(docsDir);
|
|
@@ -101,4 +101,4 @@ export function reportRepositoryUnavailable(json, err, consequence, docsDir) {
|
|
|
101
101
|
`A file under ${docsDir} could not be read, so ${consequence}. ${why}. ` +
|
|
102
102
|
`The failure named no path — that is how Node reports a directory sitting where a file belongs.`);
|
|
103
103
|
}
|
|
104
|
-
//# sourceMappingURL=
|
|
104
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/commands/policy/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,aAAa,EAAiC,MAAM,0BAA0B,CAAC;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa,EAAE,OAAe,EAAE,IAAyB;IACrF,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1F,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,CACF,IAAI,EACJ,cAAc,EACd,4CAA4C,KAAK,CAAC,IAAI,IAAI;YACxD,yDAAyD;YACzD,6DAA6D,CAChE,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CACF,IAAI,EACJ,kBAAkB,EAClB,GAAG,KAAK,CAAC,IAAI,uDAAuD;QAClE,wDAAwD;QACxD,uDAAuD,CAC1D,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa,EAAE,GAA6B;IAC9E,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;AAC9F,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,2BAA2B,CACzC,IAAa,EACb,GAAY,EACZ,WAAmB,EACnB,OAAe;IAEf,MAAM,CAAC,GAAG,GAA4B,CAAC;IACvC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,MAAM,GAAG,CAAC;IACnE,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,IAAI,CACF,IAAI,EACJ,wBAAwB,EACxB,CAAC,CAAC,IAAI,KAAK,SAAS;QAClB,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,0BAA0B,WAAW,KAAK,GAAG,EAAE;QAC1D,CAAC,CAAC,uEAAuE;YACvE,wEAAwE;YACxE,wEAAwE;YACxE,wCAAwC;YACxC,gBAAgB,OAAO,0BAA0B,WAAW,KAAK,GAAG,IAAI;gBACxE,gGAAgG,CACrG,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The three refusals every command owes a docs repo it cannot read.\n *\n * One doctrine, three ways of reaching it: the gate a command passes before it\n * enumerates anything, the error the enumeration throws when the repo goes away\n * underneath it, and the errno that comes back when the repo is real but one\n * file under it will not open. Every command that reads a docs repo reaches\n * for one or another of them (`rg docsRepoReady` is the always-current list),\n * and the whole point of collecting them is that they all answer with the same\n * sentences.\n *\n * They live in `commands/` rather than in `core/`: each one calls `fail`, which\n * writes to stdout or stderr and sets the exit code. That is a decision about\n * how this process ends, and core modules compute answers rather than end\n * processes.\n */\nimport { fail } from \"../../core/envelope/json.js\";\nimport { docsRepoState, type DocsRepoUnavailableError } from \"../../core/repo/state.js\";\n\n/**\n * The refusal every READ command owes a `docsDir` that is not a docs repo.\n *\n * It is not a formality. `validate --all` and `list` used to answer a docsDir\n * that does not exist with a green report over zero services, which is the one\n * output a fleet gate must never produce: \"nothing is wrong\" and \"I could not\n * look\" are opposite facts, and in CI the first one merges. The two codes are\n * kept apart because the fixes point in different directions — a wrong path in\n * loam.json versus a docs repo that was never cloned or scaffolded — exactly\n * the way `no-config` and `config-invalid` are kept apart.\n *\n * \"Zero services\" stays reachable, and only from a REAL docs repo whose\n * `services/` is empty: that is a legitimate state (before the first\n * `loam adopt`) and it must keep exiting 0.\n *\n * `need` says how much of a docs repo this particular run has to have. A run\n * that only reads features genuinely does not need `services/` — repo.ts takes\n * the same position for `listFeatures` — and refusing there would turn one\n * diagnosis into two contradictory ones. A run that enumerates services needs\n * the directory that IS the list of services.\n *\n * It used to live in validate.ts, on the reasoning that validate is the fleet\n * gate this doctrine is about. Enough commands import it now that the doctrine\n * outgrew its host: `loam show` was structurally depending on the whole\n * validate implementation for twenty lines of policy. The rule is the module\n * now, and validate is one of its callers like the rest.\n */\nexport function docsRepoReady(json: boolean, docsDir: string, need: \"docs\" | \"services\"): boolean {\n const state = docsRepoState(docsDir);\n if (state.kind === \"ok\" || (state.kind === \"no-services\" && need === \"docs\")) return true;\n if (state.kind === \"missing\") {\n fail(\n json,\n \"docs-missing\",\n `The configured docs repo does not exist: ${state.path}. ` +\n \"Fix `docsDir` in loam.json, clone the docs repo there, \" +\n \"or run `loam init --docs <dir> --create` to make a new one.\",\n );\n return false;\n }\n fail(\n json,\n \"services-missing\",\n `${state.path} is not a docs repo — it has no services/ directory. ` +\n \"Point `docsDir` in loam.json at the shared docs repo, \" +\n \"or run `loam init --docs <dir> --create` to make one.\",\n );\n return false;\n}\n\n/**\n * The same refusal, reached the other way: `listServices`/`listFeatures` throw\n * `DocsRepoUnavailableError` when the repo goes away mid-run (or when a caller\n * reaches enumeration without passing the gate above). Mapping it back onto the\n * same two codes keeps one breach spelled one way, instead of surfacing as the\n * `internal` catch-all in cli.ts.\n */\nexport function reportDocsRepoError(json: boolean, err: DocsRepoUnavailableError): void {\n fail(json, err.state.kind === \"missing\" ? \"docs-missing\" : \"services-missing\", err.message);\n}\n\n/**\n * The third refusal a reading command owes: the docs repo IS a docs repo, but\n * one file under it cannot be read. `list`, `show`, `status` and `validate` all\n * walk a directory tree to answer, so all four owe the same sentence — and all\n * four kept their own copy of it, which is exactly how three of them ended up\n * wrong. Sharing it here, beside `reportDocsRepoError`, is the same trade: one\n * breach spelled one way instead of four that drift.\n *\n * A filesystem failure is recognised by its errno, NOT by carrying a `path`.\n * Node reports EISDIR from `read()` with no path at all — the directory opened,\n * the read failed — so a guard that looked only for `path` was defeated by the\n * commonest way a malformed docs repo breaks: one directory sitting where a\n * file belongs escaped as a bare `internal: EISDIR`, naming neither the file\n * nor the target. Anything carrying no errno at all is a real bug, and still\n * escapes untouched.\n *\n * `consequence` is the clause after \"so\" — what the caller could not answer\n * because of this. It is the only part that differs between the four.\n */\nexport function reportRepositoryUnavailable(\n json: boolean,\n err: unknown,\n consequence: string,\n docsDir: string,\n): void {\n const e = err as NodeJS.ErrnoException;\n if (e.path === undefined && typeof e.errno !== \"number\") throw err;\n const why = err instanceof Error ? err.message : String(err);\n fail(\n json,\n \"repository-unavailable\",\n e.path !== undefined\n ? `${e.path} could not be read, so ${consequence}. ${why}`\n : // Naming the target's own directory here would be a guess, and a guess\n // that is wrong precisely when it matters: one malformed artifact makes\n // every OTHER target's answer fail too, and each would have accused its\n // own files. Say what is known instead.\n `A file under ${docsDir} could not be read, so ${consequence}. ${why}. ` +\n `The failure named no path — that is how Node reports a directory sitting where a file belongs.`,\n );\n}\n"]}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line surgery: insert one `Based-On:` line without touching anything else.
|
|
3
|
+
*
|
|
4
|
+
* `loam rebase` rewrites a pin inside a document a person wrote. Reserializing
|
|
5
|
+
* through `serializeRequirements` would be simpler and would flatten every
|
|
6
|
+
* section heading and paragraph the author put between requirements — so the
|
|
7
|
+
* parser records the heading LINE of each requirement and this module edits
|
|
8
|
+
* exactly there, leaving every other byte as found.
|
|
9
|
+
*/
|
|
10
|
+
import { REALIZES_LINE_RE } from "../../core/document/spec.js";
|
|
11
|
+
/**
|
|
12
|
+
* Where this requirement's `Based-On:` line goes, and whether it replaces one.
|
|
13
|
+
*
|
|
14
|
+
* `headingLine` is 1-based and `text` captures every body line after it
|
|
15
|
+
* contiguously (core/document/parse.ts), so body line `i` is the 0-based document line
|
|
16
|
+
* `headingLine + i` — no rescan of the document, and no second opinion about
|
|
17
|
+
* where a requirement begins. A new pin lands directly under `Requirement-ID:`
|
|
18
|
+
* when there is one, matching what `serializeRequirements` writes, and
|
|
19
|
+
* otherwise as the first body line: the two identity lines are read together,
|
|
20
|
+
* so they are kept together.
|
|
21
|
+
*/
|
|
22
|
+
export function pinEdit(r, headingLine, digest) {
|
|
23
|
+
const text = `Based-On: ${digest}`;
|
|
24
|
+
const existing = r.text.findIndex((line) => /^\s*Based-On:/i.test(line));
|
|
25
|
+
if (existing >= 0)
|
|
26
|
+
return { at: headingLine + existing, mode: "replace", text };
|
|
27
|
+
const afterId = r.text.findIndex((line) => /^\s*Requirement-ID:/i.test(line));
|
|
28
|
+
return { at: headingLine + afterId + 1, mode: "insert", text };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Rewrite one requirement's `Realizes:` line so every entry carries the pin
|
|
32
|
+
* `pinned` supplies for it, or `null` when the line already reads that way.
|
|
33
|
+
*
|
|
34
|
+
* ALWAYS A REPLACE, never an insert, and that is the difference from `pinEdit`
|
|
35
|
+
* above: a `Based-On:` line is bookkeeping loam may add to a requirement that
|
|
36
|
+
* has none, while a `Realizes:` line is a claim a human made. loam re-spells
|
|
37
|
+
* the claim it finds and never invents one — a requirement that realizes
|
|
38
|
+
* nothing has nothing to pin, and writing a line there would be loam asserting
|
|
39
|
+
* a join on somebody's behalf.
|
|
40
|
+
*
|
|
41
|
+
* Entry order, spacing after the colon and the author's own separator run are
|
|
42
|
+
* preserved by rebuilding from the captured text rather than from the parsed
|
|
43
|
+
* array: this is line surgery inside a document somebody wrote, and a
|
|
44
|
+
* normalization nobody asked for would land in every diff.
|
|
45
|
+
*/
|
|
46
|
+
export function realizesPinEdit(r, headingLine, pinned) {
|
|
47
|
+
const at = r.text.findIndex((line) => REALIZES_LINE_RE.test(line));
|
|
48
|
+
if (at < 0)
|
|
49
|
+
return null;
|
|
50
|
+
const line = r.text[at];
|
|
51
|
+
const match = REALIZES_LINE_RE.exec(line);
|
|
52
|
+
const rewritten = match[1]
|
|
53
|
+
.split(",")
|
|
54
|
+
.map((entry) => {
|
|
55
|
+
const trimmed = entry.trim();
|
|
56
|
+
return entry.replace(trimmed, pinned(trimmed));
|
|
57
|
+
})
|
|
58
|
+
.join(",");
|
|
59
|
+
if (rewritten === match[1])
|
|
60
|
+
return null;
|
|
61
|
+
return { at: headingLine + at, mode: "replace", text: line.replace(match[1], rewritten) };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Split into lines that remember their own terminator, indexed exactly as
|
|
65
|
+
* `parseRequirements` counts them.
|
|
66
|
+
*/
|
|
67
|
+
export function toLines(raw) {
|
|
68
|
+
// split with a captured separator yields [content, sep, content, sep, …, content].
|
|
69
|
+
const parts = raw.split(/(\r?\n)/);
|
|
70
|
+
const lines = [];
|
|
71
|
+
for (let i = 0; i < parts.length; i += 2)
|
|
72
|
+
lines.push({ text: parts[i], eol: parts[i + 1] ?? "" });
|
|
73
|
+
return lines;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Apply line edits to a document, preserving every byte they do not name.
|
|
77
|
+
*
|
|
78
|
+
* Each line keeps its own terminator, so a CRLF document stays CRLF and a file
|
|
79
|
+
* that ends without a newline does not grow one — rejoining on a single
|
|
80
|
+
* "detected" newline would rewrite the line endings of a whole file whose
|
|
81
|
+
* author asked for one line to change. Edits are applied bottom-up so an
|
|
82
|
+
* insertion never shifts the index of one still to come.
|
|
83
|
+
*/
|
|
84
|
+
export function applyEdits(raw, edits) {
|
|
85
|
+
const lines = toLines(raw);
|
|
86
|
+
const eol = lines.find((line) => line.eol !== "")?.eol ?? "\n";
|
|
87
|
+
for (const edit of [...edits].sort((a, b) => b.at - a.at)) {
|
|
88
|
+
if (edit.mode === "replace") {
|
|
89
|
+
lines[edit.at].text = edit.text;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const before = edit.at > 0 ? lines[edit.at - 1] : undefined;
|
|
93
|
+
if (before !== undefined && before.eol === "") {
|
|
94
|
+
// Inserting past a file that ends without a newline: the old last line
|
|
95
|
+
// needs a terminator, and the new line inherits its unterminated end so
|
|
96
|
+
// the file keeps the shape its author gave it.
|
|
97
|
+
before.eol = eol;
|
|
98
|
+
lines.splice(edit.at, 0, { text: edit.text, eol: "" });
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
lines.splice(edit.at, 0, { text: edit.text, eol: before?.eol ?? eol });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return lines.map((line) => line.text + line.eol).join("");
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../../../src/commands/rebase/edit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Line surgery: insert one `Based-On:` line without touching anything else.\n *\n * `loam rebase` rewrites a pin inside a document a person wrote. Reserializing\n * through `serializeRequirements` would be simpler and would flatten every\n * section heading and paragraph the author put between requirements — so the\n * parser records the heading LINE of each requirement and this module edits\n * exactly there, leaving every other byte as found.\n */\nimport { REALIZES_LINE_RE, type Requirement } from \"../../core/document/spec.js\";\n\nexport interface LineEdit {\n /** 0-based line index, as `parseRequirements` counts lines. */\n at: number;\n /** Replace that line, or insert a new one before it. */\n mode: \"replace\" | \"insert\";\n text: string;\n}\n\n/**\n * Where this requirement's `Based-On:` line goes, and whether it replaces one.\n *\n * `headingLine` is 1-based and `text` captures every body line after it\n * contiguously (core/document/parse.ts), so body line `i` is the 0-based document line\n * `headingLine + i` — no rescan of the document, and no second opinion about\n * where a requirement begins. A new pin lands directly under `Requirement-ID:`\n * when there is one, matching what `serializeRequirements` writes, and\n * otherwise as the first body line: the two identity lines are read together,\n * so they are kept together.\n */\nexport function pinEdit(r: Requirement, headingLine: number, digest: string): LineEdit {\n const text = `Based-On: ${digest}`;\n const existing = r.text.findIndex((line) => /^\\s*Based-On:/i.test(line));\n if (existing >= 0) return { at: headingLine + existing, mode: \"replace\", text };\n const afterId = r.text.findIndex((line) => /^\\s*Requirement-ID:/i.test(line));\n return { at: headingLine + afterId + 1, mode: \"insert\", text };\n}\n\n/**\n * Rewrite one requirement's `Realizes:` line so every entry carries the pin\n * `pinned` supplies for it, or `null` when the line already reads that way.\n *\n * ALWAYS A REPLACE, never an insert, and that is the difference from `pinEdit`\n * above: a `Based-On:` line is bookkeeping loam may add to a requirement that\n * has none, while a `Realizes:` line is a claim a human made. loam re-spells\n * the claim it finds and never invents one — a requirement that realizes\n * nothing has nothing to pin, and writing a line there would be loam asserting\n * a join on somebody's behalf.\n *\n * Entry order, spacing after the colon and the author's own separator run are\n * preserved by rebuilding from the captured text rather than from the parsed\n * array: this is line surgery inside a document somebody wrote, and a\n * normalization nobody asked for would land in every diff.\n */\nexport function realizesPinEdit(\n r: Requirement,\n headingLine: number,\n pinned: (entry: string) => string,\n): LineEdit | null {\n const at = r.text.findIndex((line) => REALIZES_LINE_RE.test(line));\n if (at < 0) return null;\n const line = r.text[at]!;\n const match = REALIZES_LINE_RE.exec(line)!;\n const rewritten = match[1]!\n .split(\",\")\n .map((entry) => {\n const trimmed = entry.trim();\n return entry.replace(trimmed, pinned(trimmed));\n })\n .join(\",\");\n if (rewritten === match[1]!) return null;\n return { at: headingLine + at, mode: \"replace\", text: line.replace(match[1]!, rewritten) };\n}\n\n/** One line of the document with the terminator it was written with. */\nexport interface Line {\n text: string;\n /** \"\\n\", \"\\r\\n\", or \"\" for a last line the file does not terminate. */\n eol: string;\n}\n\n/**\n * Split into lines that remember their own terminator, indexed exactly as\n * `parseRequirements` counts them.\n */\nexport function toLines(raw: string): Line[] {\n // split with a captured separator yields [content, sep, content, sep, …, content].\n const parts = raw.split(/(\\r?\\n)/);\n const lines: Line[] = [];\n for (let i = 0; i < parts.length; i += 2) lines.push({ text: parts[i]!, eol: parts[i + 1] ?? \"\" });\n return lines;\n}\n\n/**\n * Apply line edits to a document, preserving every byte they do not name.\n *\n * Each line keeps its own terminator, so a CRLF document stays CRLF and a file\n * that ends without a newline does not grow one — rejoining on a single\n * \"detected\" newline would rewrite the line endings of a whole file whose\n * author asked for one line to change. Edits are applied bottom-up so an\n * insertion never shifts the index of one still to come.\n */\nexport function applyEdits(raw: string, edits: LineEdit[]): string {\n const lines = toLines(raw);\n const eol = lines.find((line) => line.eol !== \"\")?.eol ?? \"\\n\";\n for (const edit of [...edits].sort((a, b) => b.at - a.at)) {\n if (edit.mode === \"replace\") {\n lines[edit.at]!.text = edit.text;\n continue;\n }\n const before = edit.at > 0 ? lines[edit.at - 1] : undefined;\n if (before !== undefined && before.eol === \"\") {\n // Inserting past a file that ends without a newline: the old last line\n // needs a terminator, and the new line inherits its unterminated end so\n // the file keeps the shape its author gave it.\n before.eol = eol;\n lines.splice(edit.at, 0, { text: edit.text, eol: \"\" });\n } else {\n lines.splice(edit.at, 0, { text: edit.text, eol: before?.eol ?? eol });\n }\n }\n return lines.map((line) => line.text + line.eol).join(\"\");\n}\n"]}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loam rebase --living` — re-pin the LIVING corpus's `Realizes:` entries to the
|
|
3
|
+
* capability requirements they currently name.
|
|
4
|
+
*
|
|
5
|
+
* The other half of `capability.realizes-stale`. That finding says a promise
|
|
6
|
+
* moved under a claim; this is the act that records having re-read it. The two
|
|
7
|
+
* are deliberately separate commands and not one self-healing pass, for the
|
|
8
|
+
* reason the finding's own comment gives: a stale pin has three legitimate
|
|
9
|
+
* outcomes and only one of them is "the requirement was right as it stands".
|
|
10
|
+
* loam cannot tell which, so it reports, and a human runs this when they have
|
|
11
|
+
* decided.
|
|
12
|
+
*
|
|
13
|
+
* WHY IT IS NOT PART OF `rebase <FEAT>`. That command pins a FEATURE's deltas
|
|
14
|
+
* against living text — bookkeeping inside a change window, thrown away when
|
|
15
|
+
* the feature archives. This pins the living documents themselves, against the
|
|
16
|
+
* living business tree, with no feature involved at all. Same verb because the
|
|
17
|
+
* act is the same one (record which version I read), different scope because
|
|
18
|
+
* the documents are.
|
|
19
|
+
*
|
|
20
|
+
* IT WRITES NOTHING ELSE. No `Based-On:`, no contract pins, no reserialize —
|
|
21
|
+
* one `Realizes:` line per requirement that has one, through the same line
|
|
22
|
+
* surgery `./edit.ts` performs for every other pin, so a living spec's headings,
|
|
23
|
+
* prose and ordering survive byte for byte.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { parseRequirements, readRequirementsDocument } from "../../core/document/parse.js";
|
|
27
|
+
import { NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
|
|
28
|
+
import { splitRealizesPin } from "../../core/document/spec.js";
|
|
29
|
+
import { resolveRealizes, splitRealizesEntry, } from "../../core/capabilities/realizes/join.js";
|
|
30
|
+
import { capabilityRequirementIndex } from "../../core/capabilities/findings.js";
|
|
31
|
+
import { emitJson, fail } from "../../core/envelope/json.js";
|
|
32
|
+
import { FleetContext } from "../../core/fleet-context.js";
|
|
33
|
+
import { listServices } from "../../core/repo/repo.js";
|
|
34
|
+
import { servicePathsAt, SPEC_AXES } from "../../core/repo/paths.js";
|
|
35
|
+
import { stageWrites } from "../../core/staging/commit.js";
|
|
36
|
+
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
37
|
+
import { plural } from "../policy/format.js";
|
|
38
|
+
import { applyEdits, realizesPinEdit } from "./edit.js";
|
|
39
|
+
/**
|
|
40
|
+
* Plan one living requirements document's pins against the capability index.
|
|
41
|
+
*
|
|
42
|
+
* The index is the caller's, built once for the whole run: it is the same
|
|
43
|
+
* fleet-wide read `validate --all` takes, and rebuilding it per service would
|
|
44
|
+
* re-parse every capability document once per service in the fleet.
|
|
45
|
+
*/
|
|
46
|
+
export async function planLivingRealizes(service, file, specPath, index) {
|
|
47
|
+
if (!existsSync(specPath))
|
|
48
|
+
return { outcomes: [], content: null };
|
|
49
|
+
// The decoding read, for `planRequirementPins`'s reason one module over: line
|
|
50
|
+
// surgery over bytes loam mis-decoded would write text nobody authored.
|
|
51
|
+
const raw = await readRequirementsDocument(specPath);
|
|
52
|
+
const reqs = parseRequirements(raw);
|
|
53
|
+
const outcomes = [];
|
|
54
|
+
const edits = [];
|
|
55
|
+
for (const r of reqs) {
|
|
56
|
+
// REMOVED is skipped wherever `Realizes:` is read; a requirement built in
|
|
57
|
+
// memory has no document line to write into, so there is nothing truthful
|
|
58
|
+
// to report about pinning it.
|
|
59
|
+
if (r.kind === "REMOVED" || r.line === undefined)
|
|
60
|
+
continue;
|
|
61
|
+
if (r.realizes.length === 0)
|
|
62
|
+
continue;
|
|
63
|
+
// The resolution and the rewrite must agree entry for entry, so both read
|
|
64
|
+
// the SAME claim list rather than each splitting the strings again.
|
|
65
|
+
const wanted = new Map();
|
|
66
|
+
for (const claim of resolveRealizes(r.realizes, index)) {
|
|
67
|
+
const base = { service, file, target: r.name, entry: claim.entry };
|
|
68
|
+
if (claim.kind !== "resolved") {
|
|
69
|
+
outcomes.push({ ...base, status: "unresolved", from: splitRealizesPin(claim.entry).pin, to: null });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const { pin, current } = claim;
|
|
73
|
+
if (current === null) {
|
|
74
|
+
// Resolved against an index that carries no digest for it — the feature
|
|
75
|
+
// overlay's shape, and not a state a living run reaches. Reported as
|
|
76
|
+
// unresolved rather than silently skipped: a pin loam declined to write
|
|
77
|
+
// must never look like a pin it wrote.
|
|
78
|
+
outcomes.push({ ...base, status: "unresolved", from: pin, to: null });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (pin === current) {
|
|
82
|
+
outcomes.push({ ...base, status: "unchanged", from: pin, to: pin });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
wanted.set(claim.entry, `${splitRealizesPin(claim.entry).target}@${current}`);
|
|
86
|
+
outcomes.push({
|
|
87
|
+
...base,
|
|
88
|
+
status: pin === null ? "pinned" : "repinned",
|
|
89
|
+
from: pin,
|
|
90
|
+
to: current,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (wanted.size === 0)
|
|
94
|
+
continue;
|
|
95
|
+
const edit = realizesPinEdit(r, r.line, (entry) => wanted.get(entry) ?? entry);
|
|
96
|
+
if (edit !== null)
|
|
97
|
+
edits.push(edit);
|
|
98
|
+
}
|
|
99
|
+
return { outcomes, content: edits.length === 0 ? null : applyEdits(raw, edits) };
|
|
100
|
+
}
|
|
101
|
+
/** Entries that would change — what the human view counts and `--dry-run` prints. */
|
|
102
|
+
export function changedPins(outcomes) {
|
|
103
|
+
return outcomes.filter((o) => o.status === "pinned" || o.status === "repinned");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The bare target of an entry — exported so a caller can report what an
|
|
107
|
+
* unresolved entry pointed at without re-deriving the separator rule.
|
|
108
|
+
*/
|
|
109
|
+
export function entryTarget(entry) {
|
|
110
|
+
const target = splitRealizesEntry(entry);
|
|
111
|
+
return target === null ? entry : `${target.capability}#${target.requirement}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The whole `--living` run, under the lock the caller already holds.
|
|
115
|
+
*
|
|
116
|
+
* Order of operations mirrors `rebaseLocked`: read everything, plan everything,
|
|
117
|
+
* then commit once. A fleet's `Realizes:` corpus is one claim about one version
|
|
118
|
+
* of the business tree, so pinning four services against a tree that moved
|
|
119
|
+
* between the second and the third is exactly the split reading the pin exists
|
|
120
|
+
* to make impossible.
|
|
121
|
+
*/
|
|
122
|
+
export async function rebaseLivingLocked(docsDir, opts) {
|
|
123
|
+
const json = opts.json === true;
|
|
124
|
+
const dryRun = opts.dryRun === true;
|
|
125
|
+
const fleet = new FleetContext();
|
|
126
|
+
const capabilities = await fleet.capabilities(docsDir);
|
|
127
|
+
const index = await capabilityRequirementIndex(capabilities, (p) => fleet.readRequirements(p));
|
|
128
|
+
if (index.declared === null) {
|
|
129
|
+
// The ladder, one command over from where `../validate` applies it: a fleet
|
|
130
|
+
// with no gradable capability vocabulary has no versions to pin against,
|
|
131
|
+
// and stamping digests from a file loam could not read would be the false
|
|
132
|
+
// claim the whole pin mechanism exists to refuse.
|
|
133
|
+
return fail(json, "unknown-target", "this docs repo declares no readable capability vocabulary, so no `Realizes:` entry has a version to pin — " +
|
|
134
|
+
"write `architecture/capabilities.yaml` or a `capabilities/<id>/spec.md` first, or fix the one that does not parse (`loam validate --all`).");
|
|
135
|
+
}
|
|
136
|
+
const all = await listServices(docsDir, fleet);
|
|
137
|
+
const services = opts.service === undefined ? all : all.filter((s) => s.id === opts.service);
|
|
138
|
+
if (opts.service !== undefined && services.length === 0) {
|
|
139
|
+
return fail(json, "unknown-target", `no service '${opts.service}' in this docs repo` +
|
|
140
|
+
(all.length === 0 ? " — it holds none." : ` — it holds: ${all.map((s) => s.id).join(", ")}.`));
|
|
141
|
+
}
|
|
142
|
+
const outcomes = [];
|
|
143
|
+
const writes = [];
|
|
144
|
+
try {
|
|
145
|
+
for (const entry of services) {
|
|
146
|
+
const paths = servicePathsAt(entry.dir);
|
|
147
|
+
for (const axis of SPEC_AXES) {
|
|
148
|
+
const planned = await planLivingRealizes(entry.id, axis.file, paths[axis.key], index);
|
|
149
|
+
outcomes.push(...planned.outcomes);
|
|
150
|
+
if (planned.content !== null)
|
|
151
|
+
writes.push({ path: paths[axis.key], content: planned.content });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (!(err instanceof NotUtf8DocumentError))
|
|
157
|
+
throw err;
|
|
158
|
+
return fail(json, "merge-failed", `rebase --living failed — nothing was pinned: ${err.message}`);
|
|
159
|
+
}
|
|
160
|
+
const changed = changedPins(outcomes);
|
|
161
|
+
if (!dryRun && writes.length > 0) {
|
|
162
|
+
const staged = await stageWrites(writes);
|
|
163
|
+
const committed = await commitStaged({ root: docsDir, command: "rebase", rerun: "loam rebase --living", target: "living" }, staged, "pinned");
|
|
164
|
+
if (!committed.ok) {
|
|
165
|
+
return fail(json, "merge-failed", `rebase --living failed — ${committed.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (json) {
|
|
169
|
+
return emitJson({ command: "rebase", ok: true, mode: "living", dryRun, pinned: changed.length, outcomes });
|
|
170
|
+
}
|
|
171
|
+
const unresolved = outcomes.filter((o) => o.status === "unresolved");
|
|
172
|
+
console.log(changed.length === 0
|
|
173
|
+
? `Nothing to pin — every \`Realizes:\` entry in ${plural(services.length, "service")} already names the version it points at.`
|
|
174
|
+
: `${dryRun ? "Would pin" : "Pinned"} ${changed.length} entr${changed.length === 1 ? "y" : "ies"} across ${plural(writes.length, "document")}.`);
|
|
175
|
+
for (const o of changed) {
|
|
176
|
+
console.log(` ${o.status === "pinned" ? "+" : "~"} ${o.service}/${o.file}: '${o.target}' → ${entryTarget(o.entry)}@${o.to}`);
|
|
177
|
+
}
|
|
178
|
+
for (const o of unresolved) {
|
|
179
|
+
console.log(` ? ${o.service}/${o.file}: '${o.target}' realizes ${entryTarget(o.entry)}, which nothing declares — not pinned`);
|
|
180
|
+
}
|
|
181
|
+
if (changed.length > 0 && !dryRun) {
|
|
182
|
+
console.log("\nA pin says you re-read the promise. Restamping without reading it is the one way to make this a lie with a digest on it.");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=living.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"living.js","sourceRoot":"","sources":["../../../src/commands/rebase/living.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam rebase --living` — re-pin the LIVING corpus's `Realizes:` entries to the\n * capability requirements they currently name.\n *\n * The other half of `capability.realizes-stale`. That finding says a promise\n * moved under a claim; this is the act that records having re-read it. The two\n * are deliberately separate commands and not one self-healing pass, for the\n * reason the finding's own comment gives: a stale pin has three legitimate\n * outcomes and only one of them is \"the requirement was right as it stands\".\n * loam cannot tell which, so it reports, and a human runs this when they have\n * decided.\n *\n * WHY IT IS NOT PART OF `rebase <FEAT>`. That command pins a FEATURE's deltas\n * against living text — bookkeeping inside a change window, thrown away when\n * the feature archives. This pins the living documents themselves, against the\n * living business tree, with no feature involved at all. Same verb because the\n * act is the same one (record which version I read), different scope because\n * the documents are.\n *\n * IT WRITES NOTHING ELSE. No `Based-On:`, no contract pins, no reserialize —\n * one `Realizes:` line per requirement that has one, through the same line\n * surgery `./edit.ts` performs for every other pin, so a living spec's headings,\n * prose and ordering survive byte for byte.\n */\nimport { existsSync } from \"node:fs\";\nimport { parseRequirements, readRequirementsDocument } from \"../../core/document/parse.js\";\nimport { NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport { splitRealizesPin } from \"../../core/document/spec.js\";\nimport {\n resolveRealizes,\n splitRealizesEntry,\n type CapabilityRequirementIndex,\n} from \"../../core/capabilities/realizes/join.js\";\nimport { capabilityRequirementIndex } from \"../../core/capabilities/findings.js\";\nimport { emitJson, fail } from \"../../core/envelope/json.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { listServices } from \"../../core/repo/repo.js\";\nimport { servicePathsAt, SPEC_AXES } from \"../../core/repo/paths.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport type { PlannedWrite } from \"../../core/staging/writes.js\";\nimport { plural } from \"../policy/format.js\";\nimport { applyEdits, realizesPinEdit, type LineEdit } from \"./edit.js\";\n\n/** What happened to one living `Realizes:` entry. */\nexport interface LivingPinOutcome {\n service: string;\n /** `spec.md` or `arch.spec.md` — which axis carried the line. */\n file: string;\n /** The requirement's `### Requirement:` heading. */\n target: string;\n /** The entry as authored, pin and all. */\n entry: string;\n /**\n * `pinned` — it carried none and now does. `repinned` — the promise moved and\n * this records the re-read. `unchanged` — already correct, no write.\n * `unresolved` — the entry names nothing the tree declares, so there is no\n * version to pin; `capability.realizes-unknown` is that entry's finding and\n * inventing a digest for it would put loam's stamp on a broken join.\n */\n status: \"pinned\" | \"repinned\" | \"unchanged\" | \"unresolved\";\n from: string | null;\n to: string | null;\n}\n\nexport interface LivingAxisPlan {\n outcomes: LivingPinOutcome[];\n /** The rewritten document, or `null` when nothing changed. */\n content: string | null;\n}\n\n/**\n * Plan one living requirements document's pins against the capability index.\n *\n * The index is the caller's, built once for the whole run: it is the same\n * fleet-wide read `validate --all` takes, and rebuilding it per service would\n * re-parse every capability document once per service in the fleet.\n */\nexport async function planLivingRealizes(\n service: string,\n file: string,\n specPath: string,\n index: CapabilityRequirementIndex,\n): Promise<LivingAxisPlan> {\n if (!existsSync(specPath)) return { outcomes: [], content: null };\n // The decoding read, for `planRequirementPins`'s reason one module over: line\n // surgery over bytes loam mis-decoded would write text nobody authored.\n const raw = await readRequirementsDocument(specPath);\n const reqs = parseRequirements(raw);\n\n const outcomes: LivingPinOutcome[] = [];\n const edits: LineEdit[] = [];\n for (const r of reqs) {\n // REMOVED is skipped wherever `Realizes:` is read; a requirement built in\n // memory has no document line to write into, so there is nothing truthful\n // to report about pinning it.\n if (r.kind === \"REMOVED\" || r.line === undefined) continue;\n if (r.realizes.length === 0) continue;\n\n // The resolution and the rewrite must agree entry for entry, so both read\n // the SAME claim list rather than each splitting the strings again.\n const wanted = new Map<string, string>();\n for (const claim of resolveRealizes(r.realizes, index)) {\n const base = { service, file, target: r.name, entry: claim.entry };\n if (claim.kind !== \"resolved\") {\n outcomes.push({ ...base, status: \"unresolved\", from: splitRealizesPin(claim.entry).pin, to: null });\n continue;\n }\n const { pin, current } = claim;\n if (current === null) {\n // Resolved against an index that carries no digest for it — the feature\n // overlay's shape, and not a state a living run reaches. Reported as\n // unresolved rather than silently skipped: a pin loam declined to write\n // must never look like a pin it wrote.\n outcomes.push({ ...base, status: \"unresolved\", from: pin, to: null });\n continue;\n }\n if (pin === current) {\n outcomes.push({ ...base, status: \"unchanged\", from: pin, to: pin });\n continue;\n }\n wanted.set(claim.entry, `${splitRealizesPin(claim.entry).target}@${current}`);\n outcomes.push({\n ...base,\n status: pin === null ? \"pinned\" : \"repinned\",\n from: pin,\n to: current,\n });\n }\n if (wanted.size === 0) continue;\n const edit = realizesPinEdit(r, r.line, (entry) => wanted.get(entry) ?? entry);\n if (edit !== null) edits.push(edit);\n }\n\n return { outcomes, content: edits.length === 0 ? null : applyEdits(raw, edits) };\n}\n\n/** Entries that would change — what the human view counts and `--dry-run` prints. */\nexport function changedPins(outcomes: readonly LivingPinOutcome[]): LivingPinOutcome[] {\n return outcomes.filter((o) => o.status === \"pinned\" || o.status === \"repinned\");\n}\n\n/**\n * The bare target of an entry — exported so a caller can report what an\n * unresolved entry pointed at without re-deriving the separator rule.\n */\nexport function entryTarget(entry: string): string {\n const target = splitRealizesEntry(entry);\n return target === null ? entry : `${target.capability}#${target.requirement}`;\n}\n\n/**\n * The whole `--living` run, under the lock the caller already holds.\n *\n * Order of operations mirrors `rebaseLocked`: read everything, plan everything,\n * then commit once. A fleet's `Realizes:` corpus is one claim about one version\n * of the business tree, so pinning four services against a tree that moved\n * between the second and the third is exactly the split reading the pin exists\n * to make impossible.\n */\nexport async function rebaseLivingLocked(\n docsDir: DocsDir,\n opts: { service?: string; json?: boolean; dryRun?: boolean },\n): Promise<void> {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n const fleet = new FleetContext();\n\n const capabilities = await fleet.capabilities(docsDir);\n const index = await capabilityRequirementIndex(capabilities, (p) => fleet.readRequirements(p));\n if (index.declared === null) {\n // The ladder, one command over from where `../validate` applies it: a fleet\n // with no gradable capability vocabulary has no versions to pin against,\n // and stamping digests from a file loam could not read would be the false\n // claim the whole pin mechanism exists to refuse.\n return fail(\n json,\n \"unknown-target\",\n \"this docs repo declares no readable capability vocabulary, so no `Realizes:` entry has a version to pin — \" +\n \"write `architecture/capabilities.yaml` or a `capabilities/<id>/spec.md` first, or fix the one that does not parse (`loam validate --all`).\",\n );\n }\n\n const all = await listServices(docsDir, fleet);\n const services = opts.service === undefined ? all : all.filter((s) => s.id === opts.service);\n if (opts.service !== undefined && services.length === 0) {\n return fail(\n json,\n \"unknown-target\",\n `no service '${opts.service}' in this docs repo` +\n (all.length === 0 ? \" — it holds none.\" : ` — it holds: ${all.map((s) => s.id).join(\", \")}.`),\n );\n }\n\n const outcomes: LivingPinOutcome[] = [];\n const writes: PlannedWrite[] = [];\n try {\n for (const entry of services) {\n const paths = servicePathsAt(entry.dir);\n for (const axis of SPEC_AXES) {\n const planned = await planLivingRealizes(entry.id, axis.file, paths[axis.key], index);\n outcomes.push(...planned.outcomes);\n if (planned.content !== null) writes.push({ path: paths[axis.key], content: planned.content });\n }\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"merge-failed\", `rebase --living failed — nothing was pinned: ${err.message}`);\n }\n\n const changed = changedPins(outcomes);\n if (!dryRun && writes.length > 0) {\n const staged = await stageWrites(writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"rebase\", rerun: \"loam rebase --living\", target: \"living\" },\n staged,\n \"pinned\",\n );\n if (!committed.ok) {\n return fail(json, \"merge-failed\", `rebase --living failed — ${committed.message}`);\n }\n }\n\n if (json) {\n return emitJson({ command: \"rebase\", ok: true, mode: \"living\", dryRun, pinned: changed.length, outcomes });\n }\n const unresolved = outcomes.filter((o) => o.status === \"unresolved\");\n console.log(\n changed.length === 0\n ? `Nothing to pin — every \\`Realizes:\\` entry in ${plural(services.length, \"service\")} already names the version it points at.`\n : `${dryRun ? \"Would pin\" : \"Pinned\"} ${changed.length} entr${changed.length === 1 ? \"y\" : \"ies\"} across ${plural(writes.length, \"document\")}.`,\n );\n for (const o of changed) {\n console.log(` ${o.status === \"pinned\" ? \"+\" : \"~\"} ${o.service}/${o.file}: '${o.target}' → ${entryTarget(o.entry)}@${o.to}`);\n }\n for (const o of unresolved) {\n console.log(` ? ${o.service}/${o.file}: '${o.target}' realizes ${entryTarget(o.entry)}, which nothing declares — not pinned`);\n }\n if (changed.length > 0 && !dryRun) {\n console.log(\n \"\\nA pin says you re-read the promise. Restamping without reading it is the one way to make this a lie with a digest on it.\",\n );\n }\n}\n"]}
|