@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9
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 +1477 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +108 -11
- package/SCHEMA.md +721 -130
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +82 -33
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +64 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +60 -25
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +141 -71
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/report.js +55 -5
- package/dist/commands/subsystem/txn/report.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +121 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +82 -12
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +26 -3
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +35 -32
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +7 -12
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +16 -5
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/consumers.js +43 -122
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/ids.js +33 -7
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +17 -35
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/fleet/views/stale.js +31 -14
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +57 -102
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +31 -17
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +37 -21
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +25 -10
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +25 -22
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +62 -24
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +30 -25
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +79 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +1 -1
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/architecture.js +31 -9
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/documents.js +36 -10
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +14 -21
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +7 -3
- package/dist/core/c4/project/staged.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +15 -16
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +10 -3
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +64 -19
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +34 -29
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +19 -2
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +102 -33
- package/dist/core/repo/tree/render/views.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +45 -10
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +53 -3
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +28 -9
- package/examples/docs/architecture/landscape.likec4 +21 -7
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +10 -8
- package/examples/docs/services/payment-service/model.likec4 +6 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
package/dist/commands/open.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
2
|
import { writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { emitJson, fail, reportNoConfig } from "../core/envelope/json.js";
|
|
4
|
+
import { configOrRefuse, emitJson, fail } from "../core/envelope/json.js";
|
|
6
5
|
import { DocsRepoUnavailableError } from "../core/repo/state.js";
|
|
7
6
|
import { discoverMembers } from "../core/workspace/discover.js";
|
|
8
7
|
import { renderWorkspace } from "../core/workspace/render.js";
|
|
@@ -41,12 +40,9 @@ export function registerOpen(program) {
|
|
|
41
40
|
.option("--json", "emit the machine contract instead of the human view")
|
|
42
41
|
.action(async (opts) => {
|
|
43
42
|
const json = opts.json === true;
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
reportNoConfig(json, loaded);
|
|
43
|
+
const config = await configOrRefuse(json);
|
|
44
|
+
if (config === null)
|
|
47
45
|
return;
|
|
48
|
-
}
|
|
49
|
-
const config = loaded.config;
|
|
50
46
|
const { docsDir } = config;
|
|
51
47
|
// "docs", not "services": this command enumerates checkouts on the LOCAL
|
|
52
48
|
// filesystem, not services/ in the docs repo, and a docs repo nobody has
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { statSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { loadConfig } from \"../core/envelope/config.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { discoverMembers, type Discovery } from \"../core/workspace/discover.js\";\nimport { renderWorkspace, type RenderedFolder } from \"../core/workspace/render.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"./policy/gate.js\";\n\ninterface OpenOptions {\n json?: boolean;\n /** --root, collected: each value REPLACES the default scan roots. */\n root?: string[];\n out?: string;\n force?: boolean;\n}\n\n/**\n * A fixed default name, not `<basename(docsDir)>.code-workspace`: predictable\n * beats collision-proof for a file that is regenerable on demand, and `--out`\n * is the lever for anyone holding two fleets' workspaces in one directory.\n */\nconst DEFAULT_OUT = \"loam.code-workspace\";\n\n/**\n * Is there a directory at `path`? A probe that cannot answer says \"no\": for a\n * value the user typed, \"cannot be read\" earns the same `invalid-option`\n * refusal as \"is not there\" — never an `internal` escape. throwIfNoEntry folds\n * the absent case into the same answer with no window between probe and use;\n * the catch covers the errnos statSync can still raise (EACCES, ENOTDIR — a\n * path routed through a file).\n */\nfunction directoryAt(path: string): boolean {\n try {\n return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;\n } catch {\n return false;\n }\n}\n\n/** The write-path errnos that mean the OUT PATH is wrong, not loam. */\nconst OUT_PATH_ERRNOS = new Set([\"EISDIR\", \"EACCES\", \"EPERM\", \"ENOTDIR\", \"EINVAL\"]);\n\nexport function registerOpen(program: Command): void {\n program\n .command(\"open\")\n .description(\"Write a .code-workspace joining the docs repo and every bound service checkout\")\n .option(\n \"--root <dir>\",\n \"scan this directory for bound checkouts instead of the defaults; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--out <file>\", `workspace file to write (default ${DEFAULT_OUT} in the current directory)`)\n .option(\"--force\", \"overwrite the workspace file if it already exists\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: OpenOptions) => {\n const json = opts.json === true;\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // \"docs\", not \"services\": this command enumerates checkouts on the LOCAL\n // filesystem, not services/ in the docs repo, and a docs repo nobody has\n // run `loam adopt` against is still worth opening an editor on.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n\n // Both typed paths are validated before any discovery runs: a --root or\n // --out typo is a fact about the INVOCATION, not the fleet, and it must\n // refuse before the scan — not after verdicts were computed over\n // directories nobody meant, and not from the write at the very end.\n const roots = (opts.root ?? []).map((dir) => resolve(dir));\n for (const dir of roots) {\n if (!directoryAt(dir)) {\n fail(json, \"invalid-option\", `--root does not name a readable directory: ${dir}`);\n return;\n }\n }\n const out = resolve(opts.out ?? DEFAULT_OUT);\n const parent = dirname(out);\n if (!directoryAt(parent)) {\n fail(json, \"invalid-option\", `--out points into a directory that does not exist: ${parent}`);\n return;\n }\n\n let discovery: Discovery;\n try {\n discovery = await discoverMembers({\n docsDir,\n // parseConfig always derives `root`; the fallback only documents\n // which absence the optional type would otherwise leave a guess.\n configRoot: config.root ?? process.cwd(),\n ...(config.service === undefined ? {} : { service: config.service }),\n ...(roots.length === 0 ? {} : { roots }),\n });\n } catch (err) {\n // The gate above passed, so this is the repo vanishing mid-run —\n // the same breach the enumeration reports, spelled the same way.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n reportDocsRepoError(json, err);\n return;\n }\n\n // An explicit --root that cannot be LISTED refuses: the caller named the\n // directory, so \"I could not look\" must never come back as an empty\n // scan. The default roots keep degrading to empty — a CI sandbox that\n // denies the docs repo's parent is survivable, and both views still\n // report the degrade below.\n if (roots.length > 0 && discovery.unreadableRoots.length > 0) {\n const listing = discovery.unreadableRoots\n .map((entry) => `${entry.root} (${entry.problem})`)\n .join(\"; \");\n fail(json, \"invalid-option\", `--root cannot be scanned: ${listing}`);\n return;\n }\n\n if (discovery.duplicates.length > 0) {\n const claims = discovery.duplicates.map(\n (d) => `'${d.service}' is declared by ${d.paths.join(\" and \")}`,\n );\n fail(\n json,\n \"binding-duplicate\",\n `Two checkouts declare the same service for this docs repo: ${claims.join(\"; \")}. ` +\n \"loam will not guess which checkout speaks for the service — narrow the scan \" +\n \"with --root, or fix the stray loam.json, then re-run.\",\n );\n return;\n }\n // Only reachable from the docs repo itself — a service-repo invocation\n // always contributes its own checkout as a member. Skipped candidates\n // ride the message: a checkout whose binding would not parse is the\n // likeliest reason the scan came up empty, and the real fix is that\n // file, not --root.\n if (!discovery.members.some((member) => member.via !== \"docs-repo\")) {\n const unread = discovery.skipped.length === 0\n ? \"\"\n : ` ${discovery.skipped.length} candidate binding(s) could not be read and did not` +\n ` count: ${discovery.skipped.map((s) => `${s.path} (${s.problem})`).join(\"; \")}.`;\n fail(\n json,\n \"no-members\",\n `No service checkout bound to ${docsDir} was found under ` +\n `${discovery.scannedRoots.join(\", \")} — the workspace would hold only the docs ` +\n \"repo. Clone a bound service repo beside it, run `loam open` from a service \" +\n `repo, or pass --root naming the directory the checkouts live in.${unread}`,\n );\n return;\n }\n\n const rendered = renderWorkspace(discovery.members, out);\n try {\n // \"wx\" makes probe-and-write one operation, so a concurrent writer\n // cannot land between an existsSync and the write. --force is honest\n // here where init's generated files needed skip-forever: a workspace\n // file is wholly derived, so overwriting it loses nothing authored.\n await writeFile(out, rendered.text, {\n encoding: \"utf8\",\n flag: opts.force === true ? \"w\" : \"wx\",\n });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"EEXIST\") {\n fail(\n json,\n \"already-exists\",\n `${out} already exists. Pass --out naming another file, or --force to overwrite it.`,\n );\n return;\n }\n // The path-shaped failures: --out names a directory (EISDIR — the\n // exact target the EEXIST remedy above just told the caller to\n // --force), or a place this process may not write. Each is a fact\n // about the out path, so it earns invalid-option, not internal.\n if (typeof e.code === \"string\" && OUT_PATH_ERRNOS.has(e.code)) {\n fail(\n json,\n \"invalid-option\",\n `The workspace file cannot be written at ${out}: ${e.message}. ` +\n \"Pass --out naming a writable file path.\",\n );\n return;\n }\n throw err;\n }\n\n if (json) {\n emitJson({\n command: \"open\",\n docsDir,\n out,\n written: true,\n members: rendered.folders.map((folder) => ({\n path: folder.member.path,\n // The exact spelling written into the workspace file, so a\n // consumer never re-derives (and mis-derives) the relative form.\n folder: folder.path,\n name: folder.name,\n service: folder.member.service,\n via: folder.member.via,\n ...(folder.member.via === \"scan\" ? { root: folder.member.root } : {}),\n })),\n scannedRoots: discovery.scannedRoots,\n unreadableRoots: discovery.unreadableRoots,\n skipped: discovery.skipped,\n });\n return;\n }\n printWorkspace(out, discovery, rendered.folders);\n });\n}\n\nfunction viaText(folder: RenderedFolder): string {\n const member = folder.member;\n if (member.via === \"docs-repo\") return \"docs repo\";\n if (member.via === \"current-repo\") return \"this repo\";\n return `found under ${member.root}`;\n}\n\nfunction printWorkspace(out: string, discovery: Discovery, folders: RenderedFolder[]): void {\n console.log(`workspace: ${out} (${folders.length} folders)`);\n for (const folder of folders) {\n console.log(` ${folder.name} ${folder.path} (${viaText(folder)})`);\n }\n // Reported beside the result, not folded into it: an unreadable binding or\n // an unlistable default root proves nothing, but silence about either would\n // read as \"not bound\" / \"nothing there\".\n for (const entry of discovery.unreadableRoots) {\n console.log(` ! could not scan ${entry.root}: ${entry.problem}`);\n }\n for (const entry of discovery.skipped) {\n console.log(` ! skipped ${entry.path}: ${entry.problem}`);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { statSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { configOrRefuse, emitJson, fail } from \"../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { discoverMembers, type Discovery } from \"../core/workspace/discover.js\";\nimport { renderWorkspace, type RenderedFolder } from \"../core/workspace/render.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"./policy/gate.js\";\n\ninterface OpenOptions {\n json?: boolean;\n /** --root, collected: each value REPLACES the default scan roots. */\n root?: string[];\n out?: string;\n force?: boolean;\n}\n\n/**\n * A fixed default name, not `<basename(docsDir)>.code-workspace`: predictable\n * beats collision-proof for a file that is regenerable on demand, and `--out`\n * is the lever for anyone holding two fleets' workspaces in one directory.\n */\nconst DEFAULT_OUT = \"loam.code-workspace\";\n\n/**\n * Is there a directory at `path`? A probe that cannot answer says \"no\": for a\n * value the user typed, \"cannot be read\" earns the same `invalid-option`\n * refusal as \"is not there\" — never an `internal` escape. throwIfNoEntry folds\n * the absent case into the same answer with no window between probe and use;\n * the catch covers the errnos statSync can still raise (EACCES, ENOTDIR — a\n * path routed through a file).\n */\nfunction directoryAt(path: string): boolean {\n try {\n return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;\n } catch {\n return false;\n }\n}\n\n/** The write-path errnos that mean the OUT PATH is wrong, not loam. */\nconst OUT_PATH_ERRNOS = new Set([\"EISDIR\", \"EACCES\", \"EPERM\", \"ENOTDIR\", \"EINVAL\"]);\n\nexport function registerOpen(program: Command): void {\n program\n .command(\"open\")\n .description(\"Write a .code-workspace joining the docs repo and every bound service checkout\")\n .option(\n \"--root <dir>\",\n \"scan this directory for bound checkouts instead of the defaults; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--out <file>\", `workspace file to write (default ${DEFAULT_OUT} in the current directory)`)\n .option(\"--force\", \"overwrite the workspace file if it already exists\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: OpenOptions) => {\n const json = opts.json === true;\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // \"docs\", not \"services\": this command enumerates checkouts on the LOCAL\n // filesystem, not services/ in the docs repo, and a docs repo nobody has\n // run `loam adopt` against is still worth opening an editor on.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n\n // Both typed paths are validated before any discovery runs: a --root or\n // --out typo is a fact about the INVOCATION, not the fleet, and it must\n // refuse before the scan — not after verdicts were computed over\n // directories nobody meant, and not from the write at the very end.\n const roots = (opts.root ?? []).map((dir) => resolve(dir));\n for (const dir of roots) {\n if (!directoryAt(dir)) {\n fail(json, \"invalid-option\", `--root does not name a readable directory: ${dir}`);\n return;\n }\n }\n const out = resolve(opts.out ?? DEFAULT_OUT);\n const parent = dirname(out);\n if (!directoryAt(parent)) {\n fail(json, \"invalid-option\", `--out points into a directory that does not exist: ${parent}`);\n return;\n }\n\n let discovery: Discovery;\n try {\n discovery = await discoverMembers({\n docsDir,\n // parseConfig always derives `root`; the fallback only documents\n // which absence the optional type would otherwise leave a guess.\n configRoot: config.root ?? process.cwd(),\n ...(config.service === undefined ? {} : { service: config.service }),\n ...(roots.length === 0 ? {} : { roots }),\n });\n } catch (err) {\n // The gate above passed, so this is the repo vanishing mid-run —\n // the same breach the enumeration reports, spelled the same way.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n reportDocsRepoError(json, err);\n return;\n }\n\n // An explicit --root that cannot be LISTED refuses: the caller named the\n // directory, so \"I could not look\" must never come back as an empty\n // scan. The default roots keep degrading to empty — a CI sandbox that\n // denies the docs repo's parent is survivable, and both views still\n // report the degrade below.\n if (roots.length > 0 && discovery.unreadableRoots.length > 0) {\n const listing = discovery.unreadableRoots\n .map((entry) => `${entry.root} (${entry.problem})`)\n .join(\"; \");\n fail(json, \"invalid-option\", `--root cannot be scanned: ${listing}`);\n return;\n }\n\n if (discovery.duplicates.length > 0) {\n const claims = discovery.duplicates.map(\n (d) => `'${d.service}' is declared by ${d.paths.join(\" and \")}`,\n );\n fail(\n json,\n \"binding-duplicate\",\n `Two checkouts declare the same service for this docs repo: ${claims.join(\"; \")}. ` +\n \"loam will not guess which checkout speaks for the service — narrow the scan \" +\n \"with --root, or fix the stray loam.json, then re-run.\",\n );\n return;\n }\n // Only reachable from the docs repo itself — a service-repo invocation\n // always contributes its own checkout as a member. Skipped candidates\n // ride the message: a checkout whose binding would not parse is the\n // likeliest reason the scan came up empty, and the real fix is that\n // file, not --root.\n if (!discovery.members.some((member) => member.via !== \"docs-repo\")) {\n const unread = discovery.skipped.length === 0\n ? \"\"\n : ` ${discovery.skipped.length} candidate binding(s) could not be read and did not` +\n ` count: ${discovery.skipped.map((s) => `${s.path} (${s.problem})`).join(\"; \")}.`;\n fail(\n json,\n \"no-members\",\n `No service checkout bound to ${docsDir} was found under ` +\n `${discovery.scannedRoots.join(\", \")} — the workspace would hold only the docs ` +\n \"repo. Clone a bound service repo beside it, run `loam open` from a service \" +\n `repo, or pass --root naming the directory the checkouts live in.${unread}`,\n );\n return;\n }\n\n const rendered = renderWorkspace(discovery.members, out);\n try {\n // \"wx\" makes probe-and-write one operation, so a concurrent writer\n // cannot land between an existsSync and the write. --force is honest\n // here where init's generated files needed skip-forever: a workspace\n // file is wholly derived, so overwriting it loses nothing authored.\n await writeFile(out, rendered.text, {\n encoding: \"utf8\",\n flag: opts.force === true ? \"w\" : \"wx\",\n });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"EEXIST\") {\n fail(\n json,\n \"already-exists\",\n `${out} already exists. Pass --out naming another file, or --force to overwrite it.`,\n );\n return;\n }\n // The path-shaped failures: --out names a directory (EISDIR — the\n // exact target the EEXIST remedy above just told the caller to\n // --force), or a place this process may not write. Each is a fact\n // about the out path, so it earns invalid-option, not internal.\n if (typeof e.code === \"string\" && OUT_PATH_ERRNOS.has(e.code)) {\n fail(\n json,\n \"invalid-option\",\n `The workspace file cannot be written at ${out}: ${e.message}. ` +\n \"Pass --out naming a writable file path.\",\n );\n return;\n }\n throw err;\n }\n\n if (json) {\n emitJson({\n command: \"open\",\n docsDir,\n out,\n written: true,\n members: rendered.folders.map((folder) => ({\n path: folder.member.path,\n // The exact spelling written into the workspace file, so a\n // consumer never re-derives (and mis-derives) the relative form.\n folder: folder.path,\n name: folder.name,\n service: folder.member.service,\n via: folder.member.via,\n ...(folder.member.via === \"scan\" ? { root: folder.member.root } : {}),\n })),\n scannedRoots: discovery.scannedRoots,\n unreadableRoots: discovery.unreadableRoots,\n skipped: discovery.skipped,\n });\n return;\n }\n printWorkspace(out, discovery, rendered.folders);\n });\n}\n\nfunction viaText(folder: RenderedFolder): string {\n const member = folder.member;\n if (member.via === \"docs-repo\") return \"docs repo\";\n if (member.via === \"current-repo\") return \"this repo\";\n return `found under ${member.root}`;\n}\n\nfunction printWorkspace(out: string, discovery: Discovery, folders: RenderedFolder[]): void {\n console.log(`workspace: ${out} (${folders.length} folders)`);\n for (const folder of folders) {\n console.log(` ${folder.name} ${folder.path} (${viaText(folder)})`);\n }\n // Reported beside the result, not folded into it: an unreadable binding or\n // an unlistable default root proves nothing, but silence about either would\n // read as \"not bound\" / \"nothing there\".\n for (const entry of discovery.unreadableRoots) {\n console.log(` ! could not scan ${entry.root}: ${entry.problem}`);\n }\n for (const entry of discovery.skipped) {\n console.log(` ! skipped ${entry.path}: ${entry.problem}`);\n }\n}\n"]}
|
|
@@ -13,8 +13,13 @@
|
|
|
13
13
|
* writes to stdout or stderr and sets the exit code. That is a decision about
|
|
14
14
|
* how this process ends, and core modules compute answers rather than end
|
|
15
15
|
* processes.
|
|
16
|
+
*
|
|
17
|
+
* A fourth sits at the bottom for the commands that read the docs repo AT a
|
|
18
|
+
* git ref — `baseOrRefuse`, the same doctrine one step on: a ref git cannot
|
|
19
|
+
* answer for is "could not look", never an empty base.
|
|
16
20
|
*/
|
|
17
21
|
import { fail } from "../../core/envelope/json.js";
|
|
22
|
+
import { resolveBase } from "../../core/diff/base-git.js";
|
|
18
23
|
import { docsRepoState } from "../../core/repo/state.js";
|
|
19
24
|
/**
|
|
20
25
|
* The refusal every READ command owes a `docsDir` that is not a docs repo.
|
|
@@ -101,4 +106,26 @@ export function reportRepositoryUnavailable(json, err, consequence, docsDir) {
|
|
|
101
106
|
`A file under ${docsDir} could not be read, so ${consequence}. ${why}. ` +
|
|
102
107
|
`The failure named no path — that is how Node reports a directory sitting where a file belongs.`);
|
|
103
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The resolved base, or null after refusing: `repository-unavailable` when
|
|
111
|
+
* the docs repo is not somewhere git can answer from, `unknown-target` when
|
|
112
|
+
* the ref names no commit there (an option-shaped ref included —
|
|
113
|
+
* `resolveBase` refuses it before it can reach an argv).
|
|
114
|
+
*/
|
|
115
|
+
export async function baseOrRefuse(json, docsDir, req) {
|
|
116
|
+
const base = await resolveBase(docsDir, req.ref);
|
|
117
|
+
if (base.kind === "no-git") {
|
|
118
|
+
fail(json, "repository-unavailable", `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +
|
|
119
|
+
`${req.reads} with read-only git questions (rev-parse/ls-tree/show), so it needs the docs repo ` +
|
|
120
|
+
`to be a git checkout with the ${req.flag} ref present. Clone the docs repo with its history, then re-run.`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (base.kind === "no-ref") {
|
|
124
|
+
fail(json, "unknown-target", `${req.flag} ${req.ref} does not resolve to a commit in the docs repo at ${docsDir}` +
|
|
125
|
+
`${base.detail === "" ? "" : ` (${base.detail})`}. ` +
|
|
126
|
+
`Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return { docsDir, commit: base.commit, prefix: base.prefix };
|
|
130
|
+
}
|
|
104
131
|
//# sourceMappingURL=gate.js.map
|
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/commands/policy/gate.ts"],"names":[],"mappings":"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","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 *\n * A fourth sits at the bottom for the commands that read the docs repo AT a\n * git ref — `baseOrRefuse`, the same doctrine one step on: a ref git cannot\n * answer for is \"could not look\", never an empty base.\n */\nimport { fail } from \"../../core/envelope/json.js\";\nimport { resolveBase, type ResolvedBase } from \"../../core/diff/base-git.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.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\n/**\n * The refusals a command owes a docs-repo git ref it cannot read at.\n *\n * Two commands read the docs repo AT a ref: `loam diff --base <ref>` (the\n * whole fleet, to diff against) and `loam gate --since <ref>` (which features\n * had already shipped, so the ones archived after it are graded too). Both\n * REFUSE when git cannot answer, unlike provenance's \"git will not say\": a\n * missing base is not an empty one, and reading it as empty is a fail-open in\n * both — diff would report the whole fleet as added, gate would pass a deploy\n * over every feature archived since a ref it never resolved. One helper, so\n * the two refusals are one sentence each with the flag swapped in, rather\n * than two copies drifting.\n */\nexport interface BaseRequest {\n /** The flag the ref arrived on, as the user typed it: `--base`, `--since`. */\n flag: string;\n ref: string;\n /** What the command reads at the ref, as the subject of a sentence: \"loam diff reads the base state\". */\n reads: string;\n}\n\n/**\n * The resolved base, or null after refusing: `repository-unavailable` when\n * the docs repo is not somewhere git can answer from, `unknown-target` when\n * the ref names no commit there (an option-shaped ref included —\n * `resolveBase` refuses it before it can reach an argv).\n */\nexport async function baseOrRefuse(\n json: boolean,\n docsDir: DocsDir,\n req: BaseRequest,\n): Promise<ResolvedBase | null> {\n const base = await resolveBase(docsDir, req.ref);\n if (base.kind === \"no-git\") {\n fail(\n json,\n \"repository-unavailable\",\n `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +\n `${req.reads} with read-only git questions (rev-parse/ls-tree/show), so it needs the docs repo ` +\n `to be a git checkout with the ${req.flag} ref present. Clone the docs repo with its history, then re-run.`,\n );\n return null;\n }\n if (base.kind === \"no-ref\") {\n fail(\n json,\n \"unknown-target\",\n `${req.flag} ${req.ref} does not resolve to a commit in the docs repo at ${docsDir}` +\n `${base.detail === \"\" ? \"\" : ` (${base.detail})`}. ` +\n `Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,\n );\n return null;\n }\n return { docsDir, commit: base.commit, prefix: base.prefix };\n}\n"]}
|
|
@@ -127,7 +127,7 @@ export async function planOpenapi(repo, service, openapiPath) {
|
|
|
127
127
|
// writes one file once. Order is safe: a surface digest never includes an
|
|
128
128
|
// operation pin (`restatedSurfaces` skips the methods entirely), and the
|
|
129
129
|
// root record never enters an operation digest.
|
|
130
|
-
baselines = planOpenapiBaselines(plan.text ?? delta, living
|
|
130
|
+
baselines = planOpenapiBaselines(plan.text ?? delta, living);
|
|
131
131
|
}
|
|
132
132
|
catch (err) {
|
|
133
133
|
// A document loam cannot read is validate's diagnosis to make
|
|
@@ -193,7 +193,7 @@ export async function planAsyncapi(repo, service, asyncapiPath) {
|
|
|
193
193
|
// directions: a surface digest never includes a slot pin (`asyncapiSurfaces`
|
|
194
194
|
// skips `components.messages` entirely, and no other section is a
|
|
195
195
|
// surface), and the root record never enters a slot digest.
|
|
196
|
-
baselines = planAsyncapiBaselines(plan.text ?? delta, living
|
|
196
|
+
baselines = planAsyncapiBaselines(plan.text ?? delta, living);
|
|
197
197
|
}
|
|
198
198
|
catch (err) {
|
|
199
199
|
// A document loam cannot read is validate's diagnosis to make
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/rebase/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What each axis is missing a pin for, computed before anything is written.\n *\n * The requirement axis and the contract axis are planned by the same shape and\n * separately on purpose: a requirement is pinned by a `Based-On:` line inserted\n * by surgery, and an operation by an `x-loam-based-on` key the YAML writer\n * emits. Only the first has to preserve the author's sections and prose, which\n * is why it edits lines rather than reserializing.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { decodeDocument } from \"../../core/kernel/document-bytes.js\";\nimport { type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { type SpecAxis } from \"../../core/repo/paths.js\";\nimport { livingCapabilityPaths } from \"../../core/repo/authored/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../core/document/parse.js\";\nimport { requirementDigest, type Requirement } from \"../../core/document/spec.js\";\nimport { OpenapiMergeError } from \"../../core/openapi/merge/error.js\";\nimport { pinOpenapiOperations, type OpenapiPinPlan } from \"../../core/openapi/merge/pin.js\";\nimport { planOpenapiBaselines, type OpenapiBaselinePlan } from \"../../core/openapi/baseline/plan.js\";\nimport { AsyncapiMergeError } from \"../../core/asyncapi/merge/error.js\";\nimport { pinAsyncapiSlots, type AsyncapiPinPlan } from \"../../core/asyncapi/merge/pin.js\";\nimport { planAsyncapiBaselines, type AsyncapiBaselinePlan } from \"../../core/asyncapi/baseline/plan.js\";\nimport { applyEdits, pinEdit, type LineEdit } from \"./edit.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../core/fleet-context.js\";\n\n/**\n * How a planner reaches the living documents: the repo root plus the\n * command's shared read index. One record on purpose — `rebase` calls these\n * planners in a services × axes loop, and each `locateServicePaths` without\n * a context is a FULL fleet walk (readdir + a frontmatter read per service),\n * so a 20-service feature paid ~80 re-enumerations of a fleet that did not\n * change between them. The context makes that one walk per invocation.\n */\nexport interface RepoRead {\n docsDir: DocsDir;\n fleet?: FleetContext;\n}\n\nexport type PinStatus =\n /** It had none and now has one. */\n | \"pinned\"\n /** It had a pin for an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /**\n * It addresses nothing living — a MODIFIED of a requirement another feature\n * in flight still introduces, or an operation this feature is adding. There\n * is no version to be based on, so inventing a pin would be inventing a\n * baseline; `loam validate` already reports the ordering where there is one\n * (`delta.modified-pending`).\n */\n | \"unresolved\"\n /**\n * The operation is written as a YAML alias. One shared value backs every use\n * of an anchor, so stamping \"through\" it would pin every other use too —\n * the same reason the merge refuses to edit aliases in place. Rare, legal,\n * and named rather than silently skipped.\n */\n | \"unwritable\";\n\n/**\n * What happened to ONE pin, minus whose document it was in.\n *\n * Split from `PinOutcome` when the business corpus arrived: a capability delta\n * is pinned by the identical rule against an identical document, but it names\n * no service, and putting a capability id into a published field called\n * `service` would bend the `--json` contract into a lie. Everything a reader\n * acts on lives here; the two wrappers below add only the identity.\n */\nexport interface PinRecord {\n /** The axis's filename — \"spec.md\", \"arch.spec.md\", \"openapi.yaml\" or \"asyncapi.yaml\". */\n file: string;\n /**\n * MODIFIED/REMOVED for a requirement; the upper-case HTTP method for an\n * operation; COMPONENT for a `components/<kind>/<name>`; PATH-ITEM for a\n * path-item non-method key; the AsyncAPI section name (`channels`,\n * `operations`, `components.messages`) for an event slot.\n */\n kind: string;\n /** The requirement heading, `/path ('operationId')`, `<kind>/<name>`, `/path '<key>'`, or the asyncapi slot key. */\n target: string;\n status: PinStatus;\n /** The pin as it was, or null when there was none. */\n from: string | null;\n /** The pin as it now stands, or null when nothing living could be hashed. */\n to: string | null;\n}\n\nexport interface PinOutcome extends PinRecord {\n service: string;\n}\n\n/** The same record on the business corpus, keyed by the promise instead of the service. */\nexport interface CapabilityPinOutcome extends PinRecord {\n capability: string;\n}\n\nexport interface AxisPlan {\n outcomes: PinOutcome[];\n /** The rewritten document, or null when nothing about it changes. */\n content: string | null;\n}\n\nexport interface CapabilityAxisPlan {\n outcomes: CapabilityPinOutcome[];\n content: string | null;\n}\n\n/** Pin every MODIFIED/REMOVED requirement in one delta file against one living document. */\nexport async function planAxis(\n repo: RepoRead,\n service: PathableService,\n axis: SpecAxis,\n specPath: string,\n): Promise<AxisPlan> {\n const livingPath = (await locateServicePaths(repo.docsDir, service, repo.fleet))[axis.key];\n const planned = await planRequirementPins(specPath, livingPath, axis.file);\n return { outcomes: planned.pins.map((pin) => ({ service, ...pin })), content: planned.content };\n}\n\n/**\n * The same, for one capability delta against its living document.\n *\n * NOT OPTIONAL, and not symmetry for its own sake: `delta.baseline-missing` is\n * a warning that GATES archive, and its message ends \"Run `loam rebase <FEAT>`\n * to pin it\". A capability delta graded by that rule while `rebase` skipped the\n * axis would have loam send an author to a command that does not pin the file\n * loam just named — a refusal with no exit, which is the one thing a gating\n * message may never be.\n *\n * The living path resolves through `livingCapabilityPaths` for the reason that\n * function states: the id's nesting is spelled by directories, so an unsplit\n * join reads an empty living document and every pin comes out `unresolved`\n * over a promise the fleet already made.\n */\nexport async function planCapabilityAxis(\n repo: RepoRead,\n capability: string,\n specPath: string,\n): Promise<CapabilityAxisPlan> {\n const planned = await planRequirementPins(specPath, livingCapabilityPaths(repo.docsDir, capability).spec, \"spec.md\");\n return { outcomes: planned.pins.map((pin) => ({ capability, ...pin })), content: planned.content };\n}\n\n/**\n * The pin rule itself: every MODIFIED/REMOVED requirement in one delta document,\n * against one living document. Whose documents they are is the caller's\n * business — this is the part that must not fork, because a second copy is a\n * second answer to \"was this delta written against what is there now\".\n */\nasync function planRequirementPins(\n specPath: string,\n livingPath: string,\n file: string,\n): Promise<{ pins: PinRecord[]; content: string | null }> {\n // Both sides through the decoding read: the delta because line surgery over a\n // document loam mis-decoded would write bytes nobody authored, the living\n // because it is what every digest below is taken over.\n const raw = await readRequirementsDocument(specPath);\n const reqs = parseRequirements(raw);\n const living = existsSync(livingPath)\n ? parseRequirements(await readRequirementsDocument(livingPath))\n : [];\n\n const pins: PinRecord[] = [];\n const edits: LineEdit[] = [];\n for (const r of reqs) {\n if (r.kind !== \"MODIFIED\" && r.kind !== \"REMOVED\") continue;\n // Every requirement parsed from a document carries its heading line\n // (core/document/parse.ts). One built in memory has no document to write into, and\n // there is nothing truthful to report about pinning it.\n const line = r.line;\n if (line === undefined) continue;\n const base = { file, kind: r.kind, target: r.name };\n const selected = selectLiving(living, r);\n if (selected === undefined) {\n pins.push({ ...base, status: \"unresolved\", from: r.basedOn ?? null, to: null });\n continue;\n }\n const digest = requirementDigest(selected);\n if (r.basedOn === digest) {\n pins.push({ ...base, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n pins.push({\n ...base,\n status: r.basedOn === undefined ? \"pinned\" : \"repinned\",\n from: r.basedOn ?? null,\n to: digest,\n });\n edits.push(pinEdit(r, line, digest));\n }\n\n return { pins, content: edits.length === 0 ? null : applyEdits(raw, edits) };\n}\n\n/**\n * Pin every operation in one feature contract against the living contract.\n *\n * The pin is always `operationDigest` of the LIVING operation, never of the\n * delta's own. That single rule produces both merge verdicts on its own: an\n * operation the author only QUOTED is byte-equal to living, so its pin equals\n * its own content and the merge skips it; one the author EDITED differs from\n * its pin, so the merge writes it. Nothing here has to guess at intent — the\n * document already records it, and the pin makes it legible.\n *\n * Slot-keyed (path + method), exactly as the merge upserts. An operationId that\n * moved to another slot is a new slot with nothing living behind it, which is\n * `unresolved` and correct: there is no living version of an operation at a\n * path the contract does not serve yet.\n */\nexport async function planOpenapi(repo: RepoRead, service: PathableService, openapiPath: string): Promise<AxisPlan> {\n const livingPath = (await locateServicePaths(repo.docsDir, service, repo.fleet)).openapi;\n // Decoded, not `readFile(…, \"utf8\")`, for the requirement axes' reason: a\n // contract read with U+FFFD substituted in defines no operation loam can\n // match, so every pin would come out `unresolved` — \"this feature adds them\n // all\" — over a living contract that already serves them. Read outside the\n // try below, whose job is the merge's own diagnosis and not this one.\n const living = existsSync(livingPath) ? decodeDocument(await readFile(livingPath), livingPath) : \"\";\n const delta = decodeDocument(await readFile(openapiPath), openapiPath);\n let plan: OpenapiPinPlan;\n let baselines: OpenapiBaselinePlan;\n try {\n plan = pinOpenapiOperations(delta, living, service);\n // The surface record, planned over the OPERATION pass's output so one run\n // writes one file once. Order is safe: a surface digest never includes an\n // operation pin (`restatedSurfaces` skips the methods entirely), and the\n // root record never enters an operation digest.\n baselines = planOpenapiBaselines(plan.text ?? delta, living, service);\n } catch (err) {\n // A document loam cannot read is validate's diagnosis to make\n // (`openapi.invalid`), not this command's to guess at — and stamping into a\n // broken parse would write a file nobody asked for.\n if (!(err instanceof OpenapiMergeError)) throw err;\n return { outcomes: [], content: null };\n }\n return {\n outcomes: [\n ...plan.pins.map((pin) => ({\n service,\n file: \"openapi.yaml\",\n kind: pin.method.toUpperCase(),\n target: pin.operationId.length === 0 ? pin.path : `${pin.path} ('${pin.operationId}')`,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ...baselines.pins.map((pin) => ({\n service,\n file: \"openapi.yaml\",\n kind: pin.kind === \"component\" ? \"COMPONENT\" : \"PATH-ITEM\",\n target: pin.target,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ],\n content: baselines.text ?? plan.text,\n };\n}\n\n/**\n * Pin every slot AND every component surface in one feature event contract\n * against the living contract — `planOpenapi`'s mirror on the event axis, in\n * both of its halves.\n *\n * The in-value `x-loam-based-on` reaches every SLOT, because a slot value is\n * always a mapping (core/asyncapi/digest.ts owns the slot model; inline\n * channel messages are channel interior and never pinned on their own). It\n * reaches nothing else: a `components/schemas/<name>` value is a JSON Schema,\n * where an in-value loam key would be a schema keyword — so the surfaces are\n * pinned by the same root `x-loam-baselines` record the OpenAPI axis uses, and\n * this function writes both. (This comment used to say the event pin has no\n * root record. It did not, and that is why a `components.schemas`-only delta\n * merged nothing at all.)\n */\nexport async function planAsyncapi(\n repo: RepoRead,\n service: PathableService,\n asyncapiPath: string,\n): Promise<AxisPlan> {\n const livingPath = (await locateServicePaths(repo.docsDir, service, repo.fleet)).asyncapi;\n // Decoded, not `readFile(…, \"utf8\")`, for planOpenapi's reason: a contract\n // read with U+FFFD substituted in defines no slot loam can match, so every\n // pin would come out `unresolved` over a living contract that already\n // carries them.\n const living = existsSync(livingPath) ? decodeDocument(await readFile(livingPath), livingPath) : \"\";\n const delta = decodeDocument(await readFile(asyncapiPath), asyncapiPath);\n let plan: AsyncapiPinPlan;\n let baselines: AsyncapiBaselinePlan;\n try {\n plan = pinAsyncapiSlots(delta, living, service);\n // The surface record, planned over the SLOT pass's output so one run\n // writes one file once. Order is safe, and load-bearing in both\n // directions: a surface digest never includes a slot pin (`asyncapiSurfaces`\n // skips `components.messages` entirely, and no other section is a\n // surface), and the root record never enters a slot digest.\n baselines = planAsyncapiBaselines(plan.text ?? delta, living, service);\n } catch (err) {\n // A document loam cannot read is validate's diagnosis to make\n // (`asyncapi.invalid`), not this command's to guess at — and stamping\n // into a broken parse would write a file nobody asked for.\n if (!(err instanceof AsyncapiMergeError)) throw err;\n return { outcomes: [], content: null };\n }\n return {\n outcomes: [\n ...plan.pins.map((pin) => ({\n service,\n file: \"asyncapi.yaml\",\n kind: pin.section,\n target: pin.key,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ...baselines.pins.map((pin) => ({\n service,\n file: \"asyncapi.yaml\",\n kind: \"COMPONENT\",\n target: pin.target,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ],\n content: baselines.text ?? plan.text,\n };\n}\n\n/**\n * The living requirement a delta requirement addresses — the merge's own\n * resolution order (core/document/apply.ts `applyRequirementDelta`), so a pin is taken\n * over exactly the requirement archive would rewrite: the stable ID when the\n * delta carries one, the exact heading otherwise, and the FIRST match either\n * way (duplicates are refused upstream by `delta.living-duplicate-requirement`).\n */\nexport function selectLiving(living: Requirement[], r: Requirement): Requirement | undefined {\n if (r.id !== undefined) return living.find((candidate) => candidate.id === r.id);\n return living.find((candidate) => candidate.name === r.name);\n}\n\n/* ------------------------------------------------------------------ */\n/* Line surgery */\n/* ------------------------------------------------------------------ */\n"]}
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/rebase/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What each axis is missing a pin for, computed before anything is written.\n *\n * The requirement axis and the contract axis are planned by the same shape and\n * separately on purpose: a requirement is pinned by a `Based-On:` line inserted\n * by surgery, and an operation by an `x-loam-based-on` key the YAML writer\n * emits. Only the first has to preserve the author's sections and prose, which\n * is why it edits lines rather than reserializing.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { decodeDocument } from \"../../core/kernel/document-bytes.js\";\nimport { type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { type SpecAxis } from \"../../core/repo/paths.js\";\nimport { livingCapabilityPaths } from \"../../core/repo/authored/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../core/document/parse.js\";\nimport { requirementDigest, type Requirement } from \"../../core/document/spec.js\";\nimport { OpenapiMergeError } from \"../../core/openapi/merge/error.js\";\nimport { pinOpenapiOperations, type OpenapiPinPlan } from \"../../core/openapi/merge/pin.js\";\nimport { planOpenapiBaselines, type OpenapiBaselinePlan } from \"../../core/openapi/baseline/plan.js\";\nimport { AsyncapiMergeError } from \"../../core/asyncapi/merge/error.js\";\nimport { pinAsyncapiSlots, type AsyncapiPinPlan } from \"../../core/asyncapi/merge/pin.js\";\nimport { planAsyncapiBaselines } from \"../../core/asyncapi/baseline/plan.js\";\nimport { applyEdits, pinEdit, type LineEdit } from \"./edit.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../core/fleet-context.js\";\n\n/**\n * How a planner reaches the living documents: the repo root plus the\n * command's shared read index. One record on purpose — `rebase` calls these\n * planners in a services × axes loop, and each `locateServicePaths` without\n * a context is a FULL fleet walk (readdir + a frontmatter read per service),\n * so a 20-service feature paid ~80 re-enumerations of a fleet that did not\n * change between them. The context makes that one walk per invocation.\n */\nexport interface RepoRead {\n docsDir: DocsDir;\n fleet?: FleetContext;\n}\n\nexport type PinStatus =\n /** It had none and now has one. */\n | \"pinned\"\n /** It had a pin for an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /**\n * It addresses nothing living — a MODIFIED of a requirement another feature\n * in flight still introduces, or an operation this feature is adding. There\n * is no version to be based on, so inventing a pin would be inventing a\n * baseline; `loam validate` already reports the ordering where there is one\n * (`delta.modified-pending`).\n */\n | \"unresolved\"\n /**\n * The operation is written as a YAML alias. One shared value backs every use\n * of an anchor, so stamping \"through\" it would pin every other use too —\n * the same reason the merge refuses to edit aliases in place. Rare, legal,\n * and named rather than silently skipped.\n */\n | \"unwritable\";\n\n/**\n * What happened to ONE pin, minus whose document it was in.\n *\n * Split from `PinOutcome` when the business corpus arrived: a capability delta\n * is pinned by the identical rule against an identical document, but it names\n * no service, and putting a capability id into a published field called\n * `service` would bend the `--json` contract into a lie. Everything a reader\n * acts on lives here; the two wrappers below add only the identity.\n */\nexport interface PinRecord {\n /** The axis's filename — \"spec.md\", \"arch.spec.md\", \"openapi.yaml\" or \"asyncapi.yaml\". */\n file: string;\n /**\n * MODIFIED/REMOVED for a requirement; the upper-case HTTP method for an\n * operation; COMPONENT for a `components/<kind>/<name>`; PATH-ITEM for a\n * path-item non-method key; the AsyncAPI section name (`channels`,\n * `operations`, `components.messages`) for an event slot.\n */\n kind: string;\n /** The requirement heading, `/path ('operationId')`, `<kind>/<name>`, `/path '<key>'`, or the asyncapi slot key. */\n target: string;\n status: PinStatus;\n /** The pin as it was, or null when there was none. */\n from: string | null;\n /** The pin as it now stands, or null when nothing living could be hashed. */\n to: string | null;\n}\n\nexport interface PinOutcome extends PinRecord {\n service: string;\n}\n\n/** The same record on the business corpus, keyed by the promise instead of the service. */\nexport interface CapabilityPinOutcome extends PinRecord {\n capability: string;\n}\n\nexport interface AxisPlan {\n outcomes: PinOutcome[];\n /** The rewritten document, or null when nothing about it changes. */\n content: string | null;\n}\n\nexport interface CapabilityAxisPlan {\n outcomes: CapabilityPinOutcome[];\n content: string | null;\n}\n\n/** Pin every MODIFIED/REMOVED requirement in one delta file against one living document. */\nexport async function planAxis(\n repo: RepoRead,\n service: PathableService,\n axis: SpecAxis,\n specPath: string,\n): Promise<AxisPlan> {\n const livingPath = (await locateServicePaths(repo.docsDir, service, repo.fleet))[axis.key];\n const planned = await planRequirementPins(specPath, livingPath, axis.file);\n return { outcomes: planned.pins.map((pin) => ({ service, ...pin })), content: planned.content };\n}\n\n/**\n * The same, for one capability delta against its living document.\n *\n * NOT OPTIONAL, and not symmetry for its own sake: `delta.baseline-missing` is\n * a warning that GATES archive, and its message ends \"Run `loam rebase <FEAT>`\n * to pin it\". A capability delta graded by that rule while `rebase` skipped the\n * axis would have loam send an author to a command that does not pin the file\n * loam just named — a refusal with no exit, which is the one thing a gating\n * message may never be.\n *\n * The living path resolves through `livingCapabilityPaths` for the reason that\n * function states: the id's nesting is spelled by directories, so an unsplit\n * join reads an empty living document and every pin comes out `unresolved`\n * over a promise the fleet already made.\n */\nexport async function planCapabilityAxis(\n repo: RepoRead,\n capability: string,\n specPath: string,\n): Promise<CapabilityAxisPlan> {\n const planned = await planRequirementPins(specPath, livingCapabilityPaths(repo.docsDir, capability).spec, \"spec.md\");\n return { outcomes: planned.pins.map((pin) => ({ capability, ...pin })), content: planned.content };\n}\n\n/**\n * The pin rule itself: every MODIFIED/REMOVED requirement in one delta document,\n * against one living document. Whose documents they are is the caller's\n * business — this is the part that must not fork, because a second copy is a\n * second answer to \"was this delta written against what is there now\".\n */\nasync function planRequirementPins(\n specPath: string,\n livingPath: string,\n file: string,\n): Promise<{ pins: PinRecord[]; content: string | null }> {\n // Both sides through the decoding read: the delta because line surgery over a\n // document loam mis-decoded would write bytes nobody authored, the living\n // because it is what every digest below is taken over.\n const raw = await readRequirementsDocument(specPath);\n const reqs = parseRequirements(raw);\n const living = existsSync(livingPath)\n ? parseRequirements(await readRequirementsDocument(livingPath))\n : [];\n\n const pins: PinRecord[] = [];\n const edits: LineEdit[] = [];\n for (const r of reqs) {\n if (r.kind !== \"MODIFIED\" && r.kind !== \"REMOVED\") continue;\n // Every requirement parsed from a document carries its heading line\n // (core/document/parse.ts). One built in memory has no document to write into, and\n // there is nothing truthful to report about pinning it.\n const line = r.line;\n if (line === undefined) continue;\n const base = { file, kind: r.kind, target: r.name };\n const selected = selectLiving(living, r);\n if (selected === undefined) {\n pins.push({ ...base, status: \"unresolved\", from: r.basedOn ?? null, to: null });\n continue;\n }\n const digest = requirementDigest(selected);\n if (r.basedOn === digest) {\n pins.push({ ...base, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n pins.push({\n ...base,\n status: r.basedOn === undefined ? \"pinned\" : \"repinned\",\n from: r.basedOn ?? null,\n to: digest,\n });\n edits.push(pinEdit(r, line, digest));\n }\n\n return { pins, content: edits.length === 0 ? null : applyEdits(raw, edits) };\n}\n\n/**\n * Pin every operation in one feature contract against the living contract.\n *\n * The pin is always `operationDigest` of the LIVING operation, never of the\n * delta's own. That single rule produces both merge verdicts on its own: an\n * operation the author only QUOTED is byte-equal to living, so its pin equals\n * its own content and the merge skips it; one the author EDITED differs from\n * its pin, so the merge writes it. Nothing here has to guess at intent — the\n * document already records it, and the pin makes it legible.\n *\n * Slot-keyed (path + method), exactly as the merge upserts. An operationId that\n * moved to another slot is a new slot with nothing living behind it, which is\n * `unresolved` and correct: there is no living version of an operation at a\n * path the contract does not serve yet.\n */\nexport async function planOpenapi(repo: RepoRead, service: PathableService, openapiPath: string): Promise<AxisPlan> {\n const livingPath = (await locateServicePaths(repo.docsDir, service, repo.fleet)).openapi;\n // Decoded, not `readFile(…, \"utf8\")`, for the requirement axes' reason: a\n // contract read with U+FFFD substituted in defines no operation loam can\n // match, so every pin would come out `unresolved` — \"this feature adds them\n // all\" — over a living contract that already serves them. Read outside the\n // try below, whose job is the merge's own diagnosis and not this one.\n const living = existsSync(livingPath) ? decodeDocument(await readFile(livingPath), livingPath) : \"\";\n const delta = decodeDocument(await readFile(openapiPath), openapiPath);\n let plan: OpenapiPinPlan;\n let baselines: OpenapiBaselinePlan;\n try {\n plan = pinOpenapiOperations(delta, living, service);\n // The surface record, planned over the OPERATION pass's output so one run\n // writes one file once. Order is safe: a surface digest never includes an\n // operation pin (`restatedSurfaces` skips the methods entirely), and the\n // root record never enters an operation digest.\n baselines = planOpenapiBaselines(plan.text ?? delta, living);\n } catch (err) {\n // A document loam cannot read is validate's diagnosis to make\n // (`openapi.invalid`), not this command's to guess at — and stamping into a\n // broken parse would write a file nobody asked for.\n if (!(err instanceof OpenapiMergeError)) throw err;\n return { outcomes: [], content: null };\n }\n return {\n outcomes: [\n ...plan.pins.map((pin) => ({\n service,\n file: \"openapi.yaml\",\n kind: pin.method.toUpperCase(),\n target: pin.operationId.length === 0 ? pin.path : `${pin.path} ('${pin.operationId}')`,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ...baselines.pins.map((pin) => ({\n service,\n file: \"openapi.yaml\",\n kind: pin.kind === \"component\" ? \"COMPONENT\" : \"PATH-ITEM\",\n target: pin.target,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ],\n content: baselines.text ?? plan.text,\n };\n}\n\n/**\n * Pin every slot AND every component surface in one feature event contract\n * against the living contract — `planOpenapi`'s mirror on the event axis, in\n * both of its halves.\n *\n * The in-value `x-loam-based-on` reaches every SLOT, because a slot value is\n * always a mapping (core/asyncapi/digest.ts owns the slot model; inline\n * channel messages are channel interior and never pinned on their own). It\n * reaches nothing else: a `components/schemas/<name>` value is a JSON Schema,\n * where an in-value loam key would be a schema keyword — so the surfaces are\n * pinned by the same root `x-loam-baselines` record the OpenAPI axis uses, and\n * this function writes both. (This comment used to say the event pin has no\n * root record. It did not, and that is why a `components.schemas`-only delta\n * merged nothing at all.)\n */\nexport async function planAsyncapi(\n repo: RepoRead,\n service: PathableService,\n asyncapiPath: string,\n): Promise<AxisPlan> {\n const livingPath = (await locateServicePaths(repo.docsDir, service, repo.fleet)).asyncapi;\n // Decoded, not `readFile(…, \"utf8\")`, for planOpenapi's reason: a contract\n // read with U+FFFD substituted in defines no slot loam can match, so every\n // pin would come out `unresolved` over a living contract that already\n // carries them.\n const living = existsSync(livingPath) ? decodeDocument(await readFile(livingPath), livingPath) : \"\";\n const delta = decodeDocument(await readFile(asyncapiPath), asyncapiPath);\n let plan: AsyncapiPinPlan;\n let baselines: OpenapiBaselinePlan;\n try {\n plan = pinAsyncapiSlots(delta, living, service);\n // The surface record, planned over the SLOT pass's output so one run\n // writes one file once. Order is safe, and load-bearing in both\n // directions: a surface digest never includes a slot pin (`asyncapiSurfaces`\n // skips `components.messages` entirely, and no other section is a\n // surface), and the root record never enters a slot digest.\n baselines = planAsyncapiBaselines(plan.text ?? delta, living);\n } catch (err) {\n // A document loam cannot read is validate's diagnosis to make\n // (`asyncapi.invalid`), not this command's to guess at — and stamping\n // into a broken parse would write a file nobody asked for.\n if (!(err instanceof AsyncapiMergeError)) throw err;\n return { outcomes: [], content: null };\n }\n return {\n outcomes: [\n ...plan.pins.map((pin) => ({\n service,\n file: \"asyncapi.yaml\",\n kind: pin.section,\n target: pin.key,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ...baselines.pins.map((pin) => ({\n service,\n file: \"asyncapi.yaml\",\n kind: \"COMPONENT\",\n target: pin.target,\n status: pin.status,\n from: pin.from,\n to: pin.to,\n })),\n ],\n content: baselines.text ?? plan.text,\n };\n}\n\n/**\n * The living requirement a delta requirement addresses — the merge's own\n * resolution order (core/document/apply.ts `applyRequirementDelta`), so a pin is taken\n * over exactly the requirement archive would rewrite: the stable ID when the\n * delta carries one, the exact heading otherwise, and the FIRST match either\n * way (duplicates are refused upstream by `delta.living-duplicate-requirement`).\n */\nexport function selectLiving(living: Requirement[], r: Requirement): Requirement | undefined {\n if (r.id !== undefined) return living.find((candidate) => candidate.id === r.id);\n return living.find((candidate) => candidate.name === r.name);\n}\n\n/* ------------------------------------------------------------------ */\n/* Line surgery */\n/* ------------------------------------------------------------------ */\n"]}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { loadConfig } from "../../core/envelope/config.js";
|
|
3
2
|
import { NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
|
|
4
3
|
import { InvalidIdError, assertServiceId } from "../../core/kernel/ids/service.js";
|
|
5
|
-
import { emitJson, fail, repoPath
|
|
4
|
+
import { configOrRefuse, emitJson, fail, repoPath } from "../../core/envelope/json.js";
|
|
6
5
|
import { compareIds } from "../../core/repo/entries.js";
|
|
7
6
|
import { featureSpecPaths, SPEC_AXES } from "../../core/repo/paths.js";
|
|
8
7
|
import { missingFeatureMessage, resolveFeature } from "../../core/repo/repo.js";
|
|
9
8
|
import { stageWrites } from "../../core/staging/commit.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
|
|
9
|
+
import {} from "../../core/staging/interrupted.js";
|
|
10
|
+
import { openCommitWindow } from "../../core/staging/recovery/recover.js";
|
|
13
11
|
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
14
12
|
import {} from "../../core/staging/writes.js";
|
|
15
13
|
import { plural, sayRecovered } from "../policy/format.js";
|
|
16
14
|
import { FleetContext } from "../../core/fleet-context.js";
|
|
17
15
|
import { planAsyncapi, planAxis, planCapabilityAxis, planOpenapi, } from "./plan.js";
|
|
16
|
+
import { issueFinding } from "../../core/vocabulary/issue.js";
|
|
17
|
+
import { retiredLookup } from "../../core/coherence/retired/archived.js";
|
|
18
|
+
import { serviceRevivals } from "../../core/coherence/retired/revival/revived.js";
|
|
18
19
|
import { rebaseLivingLocked } from "./living.js";
|
|
19
20
|
/** What happened to one pin. */
|
|
20
21
|
export function registerRebase(program) {
|
|
@@ -31,12 +32,9 @@ export function registerRebase(program) {
|
|
|
31
32
|
.option("--json", "emit the machine contract instead of the human view")
|
|
32
33
|
.action(async (featureId, opts) => {
|
|
33
34
|
const json = opts.json === true;
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
reportNoConfig(json, loaded);
|
|
35
|
+
const config = await configOrRefuse(json);
|
|
36
|
+
if (config === null)
|
|
37
37
|
return;
|
|
38
|
-
}
|
|
39
|
-
const config = loaded.config;
|
|
40
38
|
// The id grammar on the RAW argument, before it reaches a path join —
|
|
41
39
|
// one grammar for the whole tool (core/kernel/ids/service.ts).
|
|
42
40
|
if (opts.service !== undefined) {
|
|
@@ -54,29 +52,15 @@ export function registerRebase(program) {
|
|
|
54
52
|
// replaced a millisecond later is precisely the false "I read this" the
|
|
55
53
|
// pin exists to make impossible. One writer per docs repo, refusing
|
|
56
54
|
// rather than queueing, exactly as archive takes it.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return fail(json,
|
|
65
|
-
}
|
|
55
|
+
//
|
|
56
|
+
// A journal in the repo means the LAST write never finished — reading
|
|
57
|
+
// living documents to compute pins over a half-commit would digest
|
|
58
|
+
// bytes no run ever produced. Recover (or refuse) first, under the
|
|
59
|
+
// same lock the commit will hold.
|
|
60
|
+
const opened = await openCommitWindow(config.docsDir, 0);
|
|
61
|
+
if (!opened.ok)
|
|
62
|
+
return fail(json, opened.code, opened.message);
|
|
66
63
|
try {
|
|
67
|
-
// A journal in the repo means the LAST write never finished — reading
|
|
68
|
-
// living documents to compute pins over a half-commit would digest
|
|
69
|
-
// bytes no run ever produced. Recover (or refuse) first, under the
|
|
70
|
-
// same lock the commit will hold.
|
|
71
|
-
let recovered;
|
|
72
|
-
try {
|
|
73
|
-
recovered = await recoverInterruptedCommit(config.docsDir);
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
if (!(err instanceof InterruptedCommitError))
|
|
77
|
-
throw err;
|
|
78
|
-
return fail(json, "commit-interrupted", err.message);
|
|
79
|
-
}
|
|
80
64
|
if (opts.living === true) {
|
|
81
65
|
await rebaseLivingLocked(config.docsDir, opts);
|
|
82
66
|
return;
|
|
@@ -87,10 +71,10 @@ export function registerRebase(program) {
|
|
|
87
71
|
// keeps one rule: every pin says which documents it read.
|
|
88
72
|
return fail(json, "invalid-option", "loam rebase needs a feature id — e.g. `loam rebase FEAT-101`. To pin the living corpus's `Realizes:` entries instead, run `loam rebase --living`.");
|
|
89
73
|
}
|
|
90
|
-
await rebaseLocked(config.docsDir, featureId, { ...opts, recovered });
|
|
74
|
+
await rebaseLocked(config.docsDir, featureId, { ...opts, recovered: opened.recovered });
|
|
91
75
|
}
|
|
92
76
|
finally {
|
|
93
|
-
await release();
|
|
77
|
+
await opened.release();
|
|
94
78
|
}
|
|
95
79
|
});
|
|
96
80
|
}
|
|
@@ -123,6 +107,7 @@ async function rebaseLocked(docsDir, featureId, opts) {
|
|
|
123
107
|
const outcomes = [];
|
|
124
108
|
const capabilityOutcomes = [];
|
|
125
109
|
const writes = [];
|
|
110
|
+
const revived = [];
|
|
126
111
|
// The business corpus this feature changes. Skipped entirely under `--service`:
|
|
127
112
|
// that flag means "restrict to one service", and a capability names none — so
|
|
128
113
|
// pinning capability deltas under it would write files the caller narrowed the
|
|
@@ -170,6 +155,21 @@ async function rebaseLocked(docsDir, featureId, opts) {
|
|
|
170
155
|
if (planned.content !== null)
|
|
171
156
|
writes.push({ path: doc.spec, content: planned.content });
|
|
172
157
|
}
|
|
158
|
+
// An operation or event slot the living docs do not hold reads `unresolved`
|
|
159
|
+
// above — "this feature is adding it". For one an ARCHIVED feature retired,
|
|
160
|
+
// that is the false sentence that put retired operations back: unpinned, the
|
|
161
|
+
// merge upserts it as an addition. Rebase pins nothing for it, so what it
|
|
162
|
+
// owes is the same finding the gate raises, said here before the archive
|
|
163
|
+
// refuses it, and the decision left to the human. Asked of the documents
|
|
164
|
+
// as this run leaves them (`planned`): a repin over whatever lives at an
|
|
165
|
+
// address now turns a quote the merge skipped into an edit it writes, and
|
|
166
|
+
// a revival that edit makes is this run's to report — the next validate
|
|
167
|
+
// reports it, and a dry run must say what the real one would leave.
|
|
168
|
+
const retired = retiredLookup(docsDir, repo.fleet);
|
|
169
|
+
const planned = new Map(writes.flatMap((w) => (typeof w.content === "string" ? [[w.path, w.content]] : [])));
|
|
170
|
+
for (const service of services) {
|
|
171
|
+
revived.push(...(await serviceRevivals({ docsDir, featureDir: feature.dir, service, retired, context: repo.fleet, planned })));
|
|
172
|
+
}
|
|
173
173
|
}
|
|
174
174
|
catch (err) {
|
|
175
175
|
if (!(err instanceof NotUtf8DocumentError))
|
|
@@ -219,6 +219,11 @@ async function rebaseLocked(docsDir, featureId, opts) {
|
|
|
219
219
|
// something false to every consumer that already reads it.
|
|
220
220
|
capabilities: capabilities.map((c) => c.id),
|
|
221
221
|
capabilityPins: capabilityOutcomes,
|
|
222
|
+
// Additive: the `openapi.op-revived` / `asyncapi.slot-revived` findings
|
|
223
|
+
// for what this feature spells that an archived feature retired. Their
|
|
224
|
+
// `pins[]` rows still say `unresolved` — true, and published — and this
|
|
225
|
+
// key is what says those rows are not additions.
|
|
226
|
+
revived: revived.map(issueFinding),
|
|
222
227
|
written: dryRun ? [] : writes.map((w) => repoPath(docsDir, w.path)),
|
|
223
228
|
...(opts.recovered === null ? {} : { recovered: opts.recovered }),
|
|
224
229
|
});
|
|
@@ -226,6 +231,7 @@ async function rebaseLocked(docsDir, featureId, opts) {
|
|
|
226
231
|
}
|
|
227
232
|
if (lines.length === 0) {
|
|
228
233
|
console.log(`${id}: nothing to pin — a baseline only means something for a requirement, an operation, a path-level key, a component or an event slot that already exists in the living docs.`);
|
|
234
|
+
sayRevived(revived);
|
|
229
235
|
return;
|
|
230
236
|
}
|
|
231
237
|
if (opts.recovered !== null)
|
|
@@ -269,7 +275,26 @@ async function rebaseLocked(docsDir, featureId, opts) {
|
|
|
269
275
|
}
|
|
270
276
|
if (unresolved.length > 0) {
|
|
271
277
|
console.log(`\n${plural(unresolved.length, "item")} could not be pinned because the living docs do not have them yet — ` +
|
|
272
|
-
`for a requirement, \`loam dependencies ${id}\` says which feature introduces it first; for an operation, this feature is adding it
|
|
278
|
+
`for a requirement, \`loam dependencies ${id}\` says which feature introduces it first; for an operation, this feature is adding it` +
|
|
279
|
+
(revived.length === 0 ? "." : ", unless an archived feature retired it or moved it away — those are listed below, and they are not new."));
|
|
273
280
|
}
|
|
281
|
+
sayRevived(revived);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* The revival findings, in the gate's own words. Printed rather than acted on:
|
|
285
|
+
* deleting a quote and re-adding a retired operation on purpose are both
|
|
286
|
+
* legitimate, and which one this is is the author's call. The sentence once
|
|
287
|
+
* said "nothing was pinned" for them — true of a retired entry, which has no
|
|
288
|
+
* living version, and false of a quote this run repinned over whatever lives
|
|
289
|
+
* at its address now, the very pin that made it a revival.
|
|
290
|
+
*/
|
|
291
|
+
function sayRevived(revived) {
|
|
292
|
+
if (revived.length === 0)
|
|
293
|
+
return;
|
|
294
|
+
const one = revived.length === 1;
|
|
295
|
+
console.log(`\n⚠ ${plural(revived.length, "item")} this feature spells ${one ? "was" : "were"} RETIRED by an archived feature, or moved to another address by one — ` +
|
|
296
|
+
`${one ? "it is" : "they are"} not new, pinned or not, and \`loam archive\` refuses ${one ? "it" : "them"} until you decide:`);
|
|
297
|
+
for (const issue of revived)
|
|
298
|
+
console.log(` ⚠ ${issue.message}`);
|
|
274
299
|
}
|
|
275
300
|
//# sourceMappingURL=rebase.js.map
|