@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/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { Command, CommanderError } from \"commander\";\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { registerInit } from \"./commands/init/init.js\";\nimport { registerAdopt } from \"./commands/adopt/adopt.js\";\nimport { registerList } from \"./commands/list/list.js\";\nimport { registerNew } from \"./commands/new/new.js\";\nimport { registerSeed } from \"./commands/seed/seed.js\";\nimport { registerShow } from \"./commands/show/show.js\";\nimport { registerStatus } from \"./commands/status/status.js\";\nimport { registerSubsystem } from \"./commands/subsystem/subsystem.js\";\nimport { registerDelta } from \"./commands/delta/delta.js\";\nimport { registerDiff } from \"./commands/diff/diff.js\";\nimport { registerGherkin } from \"./commands/gherkin/gherkin.js\";\nimport { registerRebase } from \"./commands/rebase/rebase.js\";\nimport { registerArchive } from \"./commands/archive/archive.js\";\nimport { registerUnarchive } from \"./commands/unarchive/unarchive.js\";\nimport { registerValidate } from \"./commands/validate/validate.js\";\nimport { registerVerify } from \"./commands/verify/verify.js\";\nimport { registerVouch } from \"./commands/vouch/vouch.js\";\nimport { registerGate } from \"./commands/gate/gate.js\";\nimport { registerDoctor } from \"./commands/doctor.js\";\nimport { registerDependencies } from \"./commands/dependencies.js\";\nimport { registerSteps } from \"./commands/steps/steps.js\";\nimport { registerExplore } from \"./commands/explore.js\";\nimport { registerContext } from \"./commands/context/context.js\";\nimport { registerMcp } from \"./commands/mcp/mcp.js\";\nimport { registerOpen } from \"./commands/open.js\";\nimport { registerInstructions } from \"./commands/instructions.js\";\nimport { registerExplain } from \"./commands/explain/explain.js\";\nimport { registerMigrateOpenSpec } from \"./commands/migrate-openspec/migrate-openspec.js\";\nimport { emitJsonError } from \"./core/envelope/json.js\";\nimport { LOAM_VERSION } from \"./core/envelope/version.js\";\n\n/**\n * The whole CLI surface, as a program nobody has parsed yet.\n *\n * Exported because loam PRINTS commands — every `loam …` in AGENTS.md, in the\n * generated command and skill bodies, in a `doctor` finding's `fix` and in a\n * `status` next[] step is an instruction somebody (usually an agent) will type\n * back. The only way to prove one of those parses is to hand it to the program\n * that would receive it; a second copy of the registrations in a test agrees\n * with itself and with nothing else, which is exactly how\n * `Run \\`loam adopt <id>\\`` shipped as the first instruction a bound service\n * repo received. `test/agent-commands-runnable.test.ts` is the consumer.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"loam\")\n .description(\"Architecture-first semantic integrity and change governance for evolving software systems\")\n .version(LOAM_VERSION)\n // The blank line is part of the attachment, not part of the text: commander\n // writes this straight under the `Commands:` block, and two sentences flush\n // against `help [command]` read as two more commands.\n .addHelpText(\"after\", `\\n${HELP_EPILOG}`);\n\n // Make commander throw a CommanderError instead of process.exit()ing, so a\n // usage error (mistyped flag, unknown command) can still honour the envelope\n // invariant in the catch below instead of dying with an empty stdout.\n program.exitOverride();\n\n registerInit(program);\n registerAdopt(program);\n registerList(program);\n registerExplore(program);\n registerContext(program);\n registerNew(program);\n registerSeed(program);\n registerShow(program);\n registerStatus(program);\n registerSubsystem(program);\n registerDelta(program);\n registerDiff(program);\n registerGherkin(program);\n registerRebase(program);\n registerArchive(program);\n registerUnarchive(program);\n registerValidate(program);\n registerVerify(program);\n registerVouch(program);\n registerGate(program);\n registerDoctor(program);\n registerDependencies(program);\n registerSteps(program);\n registerMcp(program);\n registerInstructions(program);\n registerExplain(program);\n registerOpen(program);\n registerMigrateOpenSpec(program);\n\n groupCommands(program);\n\n return program;\n}\n\n/**\n * The two lines under the command list: where to start, and what to run when\n * lost.\n *\n * {@link HELP_GROUPS} does nearly all of the orienting work — twenty-nine\n * commands under seven headings, in the order of the work — but it stops one\n * step short of the two facts a newcomer does not have. Nothing on the page\n * said that `init` must run before any of the other twenty-eight, and nothing\n * named the command that answers \"what now\"; the last thing a first\n * `loam --help` printed was `help [command]`.\n *\n * Two lines, and no more, because the grouping is what does the work and a\n * paragraph here would compete with it.\n *\n * Both lines name a command that PARSES, and that is not a style rule:\n * `test/agent-commands-runnable.test.ts` takes this constant BY IMPORT and\n * hands both invocations to the real program, exactly as it does the `init`\n * first-hour lines. `loam init --create` is the form that works with no prior\n * knowledge — bare `loam init` defaults `--docs` to a `.loam-docs` that does\n * not exist yet and refuses, which is correct for a repo joining an existing\n * docs repo and wrong as the first thing a newcomer is told to type.\n */\nexport const HELP_EPILOG = [\n \"New here? Run `loam init --create` first — every other command needs the docs repo it writes.\",\n \"Lost? Run `loam doctor` — it prints the state, the resolved path, the finding code and a `fix:` line.\",\n].join(\"\\n\");\n\n/**\n * Which heading each command sits under in `loam --help`.\n *\n * The order is the order of the work: wire the repo, read the fleet, adopt what\n * exists, change it, check it, ship it. It mirrors the six shipped workflow\n * protocols (`loam instructions`) rather than inventing a second taxonomy, so a\n * reader who has met one has met the other.\n *\n * Twenty-nine commands printed as one flat registration-ordered list tells a\n * new reader nothing about which four to run first. NOTHING IS HIDDEN — a\n * hidden command is a command an agent cannot discover, and every one of these\n * still parses, still appears, and still carries the same flags. This is\n * typography, not surface.\n */\nconst HELP_GROUPS: Record<string, string> = {\n init: \"Set up\", seed: \"Set up\", doctor: \"Set up\", open: \"Set up\", mcp: \"Set up\",\n list: \"Read the fleet\", status: \"Read the fleet\", show: \"Read the fleet\",\n explore: \"Read the fleet\", context: \"Read the fleet\", dependencies: \"Read the fleet\",\n explain: \"Read the fleet\", instructions: \"Read the fleet\", steps: \"Read the fleet\",\n adopt: \"Adopt what exists\", vouch: \"Adopt what exists\",\n new: \"Change it\", delta: \"Change it\", gherkin: \"Change it\", rebase: \"Change it\",\n subsystem: \"Change it\",\n validate: \"Check it\", diff: \"Check it\", verify: \"Check it\", gate: \"Check it\",\n archive: \"Ship it\", unarchive: \"Ship it\",\n \"audit-openspec\": \"Migrate\", \"migrate-openspec\": \"Migrate\",\n};\n\n/**\n * Apply {@link HELP_GROUPS}, and FAIL CLOSED on a command it does not name.\n *\n * The throw is the point. A thirtieth command added without a heading would\n * otherwise land silently in an \"unclassified\" bucket at the bottom of the\n * page, which is the drift this whole file's neighbours are written to prevent;\n * `test/help-groups.test.ts` catches it at gate time, and this catches it the\n * first time anybody runs the binary. Same discipline as\n * `test/codes-drift.test.ts` applies to codes.\n */\nfunction groupCommands(program: Command): void {\n for (const command of program.commands) {\n const group = HELP_GROUPS[command.name()];\n if (group === undefined) {\n throw new Error(`cli: command '${command.name()}' has no HELP_GROUPS heading — add one in src/cli.ts`);\n }\n command.helpGroup(group);\n }\n}\n\n/** CommanderError codes whose output (already printed) is the point, not a failure to wrap. */\nconst PASS_THROUGH = new Set([\"commander.help\", \"commander.helpDisplayed\", \"commander.version\"]);\n\nfunction main(): void {\n const program = buildProgram();\n\n // Set once a command's action actually runs: every subcommand declares its own\n // `--json`, so past that point the parsed option is the authoritative answer.\n let parsedJson: boolean | undefined;\n program.hook(\"preAction\", (_thisCommand, actionCommand) => {\n parsedJson = Boolean((actionCommand.optsWithGlobals() as { json?: boolean }).json);\n });\n\n /**\n * Did this invocation ask for --json? Prefers the parsed option once an action\n * has run; before that (commander usage errors abort mid-parse) falls back to\n * scanning raw argv. The scan is a heuristic and cannot be made exact: a token\n * spelled `--json` that was meant as an option VALUE (e.g. --title \"--json\")\n * is indistinguishable from the flag in raw argv. It gets the common cases\n * right: a `--json` token anywhere before a `--` terminator counts, and tokens\n * after `--` never do.\n */\n const jsonRequested = (): boolean => {\n if (parsedJson !== undefined) return parsedJson;\n const argv = process.argv.slice(2);\n const terminator = argv.indexOf(\"--\");\n return (terminator === -1 ? argv : argv.slice(0, terminator)).includes(\"--json\");\n };\n\n program.parseAsync(process.argv).catch((err: unknown) => {\n if (err instanceof CommanderError) {\n // Help/version output was already written by commander — pass it through\n // untouched with the exit code it always had.\n //\n // Anything else is a usage error (unknown option/command, missing or\n // invalid argument). Commander has already printed its diagnostic to\n // stderr, and that line stays: the invariant below is about stdout only.\n if (!PASS_THROUGH.has(err.code) && jsonRequested()) {\n emitJsonError(\"invalid-option\", err.message.replace(/^error: /, \"\"));\n }\n process.exitCode = err.exitCode;\n return;\n }\n const message = err instanceof Error ? err.message : String(err);\n // The envelope's one hard invariant is that stdout is JSON whenever --json\n // was asked for — an unexpected throw must not be the exception to it.\n if (jsonRequested()) {\n emitJsonError(\"internal\", message);\n } else {\n console.error(message);\n process.exitCode = 1;\n }\n });\n}\n\n/**\n * Was this file run, or merely imported? Realpath on both sides because the\n * installed entry point is reached through a symlink (`node_modules/.bin/loam`)\n * on every platform that has them, and a string compare against the link would\n * make the binary a no-op. A failure to resolve reads as \"not the entry\" only\n * for the import side; `process.argv[1]` that cannot be realpath'd is not a\n * loam invocation either.\n */\nfunction invokedDirectly(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n return false;\n }\n}\n\nif (invokedDirectly()) main();\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { Command, CommanderError } from \"commander\";\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { registerInit } from \"./commands/init/init.js\";\nimport { registerAdopt } from \"./commands/adopt/adopt.js\";\nimport { registerList } from \"./commands/list/list.js\";\nimport { registerNew } from \"./commands/new/new.js\";\nimport { registerSeed } from \"./commands/seed/seed.js\";\nimport { registerShow } from \"./commands/show/show.js\";\nimport { registerStatus } from \"./commands/status/status.js\";\nimport { registerSubsystem } from \"./commands/subsystem/subsystem.js\";\nimport { registerDelta } from \"./commands/delta/delta.js\";\nimport { registerTasks } from \"./commands/tasks/tasks.js\";\nimport { registerDiff } from \"./commands/diff/diff.js\";\nimport { registerGherkin } from \"./commands/gherkin/gherkin.js\";\nimport { registerRebase } from \"./commands/rebase/rebase.js\";\nimport { registerArchive } from \"./commands/archive/archive.js\";\nimport { registerUnarchive } from \"./commands/unarchive/unarchive.js\";\nimport { registerValidate } from \"./commands/validate/validate.js\";\nimport { registerVerify } from \"./commands/verify/verify.js\";\nimport { registerVouch } from \"./commands/vouch/vouch.js\";\nimport { registerGate } from \"./commands/gate/gate.js\";\nimport { registerDoctor } from \"./commands/doctor.js\";\nimport { registerDependencies } from \"./commands/dependencies.js\";\nimport { registerSteps } from \"./commands/steps/steps.js\";\nimport { registerExplore } from \"./commands/explore.js\";\nimport { registerContext } from \"./commands/context/context.js\";\nimport { registerMcp } from \"./commands/mcp/mcp.js\";\nimport { registerOpen } from \"./commands/open.js\";\nimport { registerInstructions } from \"./commands/instructions.js\";\nimport { registerExplain } from \"./commands/explain/explain.js\";\nimport { registerMigrateOpenSpec } from \"./commands/migrate-openspec/migrate-openspec.js\";\nimport { emitJsonError, sayExplain } from \"./core/envelope/json.js\";\nimport { LOAM_VERSION } from \"./core/envelope/version.js\";\n\n/**\n * The whole CLI surface, as a program nobody has parsed yet.\n *\n * Exported because loam PRINTS commands — every `loam …` in AGENTS.md, in the\n * generated command and skill bodies, in a `doctor` finding's `fix` and in a\n * `status` next[] step is an instruction somebody (usually an agent) will type\n * back. The only way to prove one of those parses is to hand it to the program\n * that would receive it; a second copy of the registrations in a test agrees\n * with itself and with nothing else, which is exactly how\n * `Run \\`loam adopt <id>\\`` shipped as the first instruction a bound service\n * repo received. `test/agent-commands-runnable.test.ts` is the consumer.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"loam\")\n .description(\"Architecture-first semantic integrity and change governance for evolving software systems\")\n .version(LOAM_VERSION)\n // The blank line is part of the attachment, not part of the text: commander\n // writes this straight under the `Commands:` block, and two sentences flush\n // against `help [command]` read as two more commands.\n .addHelpText(\"after\", `\\n${HELP_EPILOG}`);\n\n // Make commander throw a CommanderError instead of process.exit()ing, so a\n // usage error (mistyped flag, unknown command) can still honour the envelope\n // invariant in the catch below instead of dying with an empty stdout.\n program.exitOverride();\n\n registerInit(program);\n registerAdopt(program);\n registerList(program);\n registerExplore(program);\n registerContext(program);\n registerNew(program);\n registerSeed(program);\n registerShow(program);\n registerStatus(program);\n registerSubsystem(program);\n registerDelta(program);\n registerTasks(program);\n registerDiff(program);\n registerGherkin(program);\n registerRebase(program);\n registerArchive(program);\n registerUnarchive(program);\n registerValidate(program);\n registerVerify(program);\n registerVouch(program);\n registerGate(program);\n registerDoctor(program);\n registerDependencies(program);\n registerSteps(program);\n registerMcp(program);\n registerInstructions(program);\n registerExplain(program);\n registerOpen(program);\n registerMigrateOpenSpec(program);\n\n groupCommands(program);\n\n return program;\n}\n\n/**\n * The two lines under the command list: where to start, and what to run when\n * lost.\n *\n * {@link HELP_GROUPS} does nearly all of the orienting work — thirty\n * commands under seven headings, in the order of the work — but it stops one\n * step short of the two facts a newcomer does not have. Nothing on the page\n * said that `init` must run before any of the others, and nothing\n * named the command that answers \"what now\"; the last thing a first\n * `loam --help` printed was `help [command]`.\n *\n * Two lines, and no more, because the grouping is what does the work and a\n * paragraph here would compete with it.\n *\n * Both lines name a command that PARSES, and that is not a style rule:\n * `test/agent-commands-runnable.test.ts` takes this constant BY IMPORT and\n * hands both invocations to the real program, exactly as it does the `init`\n * first-hour lines. `loam init --create` is the form that works with no prior\n * knowledge — bare `loam init` defaults `--docs` to a `.loam-docs` that does\n * not exist yet and refuses, which is correct for a repo joining an existing\n * docs repo and wrong as the first thing a newcomer is told to type.\n */\nexport const HELP_EPILOG = [\n \"New here? Run `loam init --create` first — every other command needs the docs repo it writes.\",\n \"Lost? Run `loam doctor` — it prints the state, the resolved path, the finding code and a `fix:` line.\",\n].join(\"\\n\");\n\n/**\n * Which heading each command sits under in `loam --help`.\n *\n * The order is the order of the work: wire the repo, read the fleet, adopt what\n * exists, change it, check it, ship it. It mirrors the six shipped workflow\n * protocols (`loam instructions`) rather than inventing a second taxonomy, so a\n * reader who has met one has met the other.\n *\n * Thirty commands printed as one flat registration-ordered list tells a\n * new reader nothing about which four to run first. NOTHING IS HIDDEN — a\n * hidden command is a command an agent cannot discover, and every one of these\n * still parses, still appears, and still carries the same flags. This is\n * typography, not surface.\n */\nconst HELP_GROUPS: Record<string, string> = {\n init: \"Set up\", seed: \"Set up\", doctor: \"Set up\", open: \"Set up\", mcp: \"Set up\",\n list: \"Read the fleet\", status: \"Read the fleet\", show: \"Read the fleet\",\n explore: \"Read the fleet\", context: \"Read the fleet\", dependencies: \"Read the fleet\",\n explain: \"Read the fleet\", instructions: \"Read the fleet\", steps: \"Read the fleet\",\n adopt: \"Adopt what exists\", vouch: \"Adopt what exists\",\n new: \"Change it\", delta: \"Change it\", tasks: \"Change it\", gherkin: \"Change it\", rebase: \"Change it\",\n subsystem: \"Change it\",\n validate: \"Check it\", diff: \"Check it\", verify: \"Check it\", gate: \"Check it\",\n archive: \"Ship it\", unarchive: \"Ship it\",\n \"audit-openspec\": \"Migrate\", \"migrate-openspec\": \"Migrate\",\n};\n\n/**\n * Apply {@link HELP_GROUPS}, and FAIL CLOSED on a command it does not name.\n *\n * The throw is the point. A thirty-first command added without a heading would\n * otherwise land silently in an \"unclassified\" bucket at the bottom of the\n * page, which is the drift this whole file's neighbours are written to prevent;\n * `test/help-groups.test.ts` catches it at gate time, and this catches it the\n * first time anybody runs the binary. Same discipline as\n * `test/codes-drift.test.ts` applies to codes.\n */\nfunction groupCommands(program: Command): void {\n for (const command of program.commands) {\n const group = HELP_GROUPS[command.name()];\n if (group === undefined) {\n throw new Error(`cli: command '${command.name()}' has no HELP_GROUPS heading — add one in src/cli.ts`);\n }\n command.helpGroup(group);\n }\n}\n\n/** CommanderError codes whose output (already printed) is the point, not a failure to wrap. */\nconst PASS_THROUGH = new Set([\"commander.help\", \"commander.helpDisplayed\", \"commander.version\"]);\n\n/**\n * What a text-mode usage error prints under commander's own diagnostic: the\n * fix and the stable code, the two lines every other refusal carries through\n * `fail`. Without them `loam diff` with no `--base` printed commander's one\n * sentence and nothing else — the only refusals in the CLI a person could not\n * look up, while `--json` already reported them as `invalid-option`.\n */\nfunction sayUsageFix(program: Command): void {\n const name = process.argv.slice(2).find((token) => program.commands.some((c) => c.name() === token));\n const help = name === undefined ? \"`loam --help` lists the commands\" : `\\`loam ${name} --help\\` lists the arguments and options it takes`;\n console.error(`Fix the invocation: ${help}.`);\n sayExplain(\"invalid-option\");\n}\n\nfunction main(): void {\n const program = buildProgram();\n\n // Set once a command's action actually runs: every subcommand declares its own\n // `--json`, so past that point the parsed option is the authoritative answer.\n let parsedJson: boolean | undefined;\n program.hook(\"preAction\", (_thisCommand, actionCommand) => {\n parsedJson = Boolean((actionCommand.optsWithGlobals() as { json?: boolean }).json);\n });\n\n /**\n * Did this invocation ask for --json? Prefers the parsed option once an action\n * has run; before that (commander usage errors abort mid-parse) falls back to\n * scanning raw argv. The scan is a heuristic and cannot be made exact: a token\n * spelled `--json` that was meant as an option VALUE (e.g. --title \"--json\")\n * is indistinguishable from the flag in raw argv. It gets the common cases\n * right: a `--json` token anywhere before a `--` terminator counts, and tokens\n * after `--` never do.\n */\n const jsonRequested = (): boolean => {\n if (parsedJson !== undefined) return parsedJson;\n const argv = process.argv.slice(2);\n const terminator = argv.indexOf(\"--\");\n return (terminator === -1 ? argv : argv.slice(0, terminator)).includes(\"--json\");\n };\n\n program.parseAsync(process.argv).catch((err: unknown) => {\n if (err instanceof CommanderError) {\n // Help/version output was already written by commander — pass it through\n // untouched with the exit code it always had.\n //\n // Anything else is a usage error (unknown option/command, missing or\n // invalid argument). Commander has already printed its diagnostic to\n // stderr, and that line stays: the invariant below is about stdout only.\n if (!PASS_THROUGH.has(err.code)) {\n if (jsonRequested()) emitJsonError(\"invalid-option\", err.message.replace(/^error: /, \"\"));\n else sayUsageFix(program);\n }\n process.exitCode = err.exitCode;\n return;\n }\n const message = err instanceof Error ? err.message : String(err);\n // The envelope's one hard invariant is that stdout is JSON whenever --json\n // was asked for — an unexpected throw must not be the exception to it.\n if (jsonRequested()) {\n emitJsonError(\"internal\", message);\n } else {\n console.error(message);\n process.exitCode = 1;\n }\n });\n}\n\n/**\n * Was this file run, or merely imported? Realpath on both sides because the\n * installed entry point is reached through a symlink (`node_modules/.bin/loam`)\n * on every platform that has them, and a string compare against the link would\n * make the binary a no-op. A failure to resolve reads as \"not the entry\" only\n * for the import side; `process.argv[1]` that cannot be realpath'd is not a\n * loam invocation either.\n */\nfunction invokedDirectly(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n return false;\n }\n}\n\nif (invokedDirectly()) main();\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { closeIds } from "../../core/c4/arch.js";
|
|
2
|
-
import { loadConfig } from "../../core/envelope/config.js";
|
|
3
2
|
import { InvalidIdError, assertServiceId } from "../../core/kernel/ids/service.js";
|
|
4
|
-
import { emitJson, fail, NO_SERVICE_MESSAGE, repoPath
|
|
3
|
+
import { configOrRefuse, emitJson, fail, NO_SERVICE_MESSAGE, repoPath } from "../../core/envelope/json.js";
|
|
5
4
|
import { DocsRepoUnavailableError } from "../../core/repo/state.js";
|
|
6
5
|
import { listFleetTree, listServices } from "../../core/repo/repo.js";
|
|
7
|
-
import {
|
|
6
|
+
import { closeNamesHint, findInTree } from "../../core/repo/tree/find.js";
|
|
8
7
|
import { servicePathsUnder } from "../../core/repo/paths.js";
|
|
9
8
|
import { serviceBrief } from "../../core/brief/brief.js";
|
|
10
9
|
import { docsRepoReady } from "../policy/gate.js";
|
|
@@ -21,9 +20,8 @@ async function subsystemTarget(docsDir, name, service, json) {
|
|
|
21
20
|
const tree = await listFleetTree(docsDir);
|
|
22
21
|
const hit = findInTree(tree, name);
|
|
23
22
|
if (hit === null) {
|
|
24
|
-
const close = nearestTreeNames(name, tree.subsystems.map((s) => s.name));
|
|
25
23
|
fail(json, "unknown-target", `No subsystem '${name}' in the tree.` +
|
|
26
|
-
(
|
|
24
|
+
closeNamesHint(name, tree.subsystems.map((s) => s.name), " `loam subsystem new` creates one; `loam subsystem list` shows what exists."));
|
|
27
25
|
return null;
|
|
28
26
|
}
|
|
29
27
|
if (hit.kind === "service") {
|
|
@@ -141,12 +139,9 @@ export function registerAdopt(program) {
|
|
|
141
139
|
.option("--json", "emit the machine contract instead of the human view")
|
|
142
140
|
.action(async (opts) => {
|
|
143
141
|
const json = opts.json === true;
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
reportNoConfig(json, loaded);
|
|
142
|
+
const config = await configOrRefuse(json);
|
|
143
|
+
if (config === null)
|
|
147
144
|
return;
|
|
148
|
-
}
|
|
149
|
-
const config = loaded.config;
|
|
150
145
|
// This command writes nothing, but it briefs an agent to write a whole
|
|
151
146
|
// baseline — so a docsDir that does not exist is not a harmless read: it
|
|
152
147
|
// hands over eight target paths under a directory nobody has, at exit 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../src/commands/adopt/adopt.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { closeIds } from \"../../core/c4/arch.js\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { InvalidIdError, assertServiceId, type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { emitJson, fail, NO_SERVICE_MESSAGE, repoPath, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { listFleetTree, listServices } from \"../../core/repo/repo.js\";\nimport { findInTree, nearestTreeNames } from \"../../core/repo/tree/find.js\";\nimport { servicePathsUnder, type ServicePaths } from \"../../core/repo/paths.js\";\nimport { serviceBrief, type Brief } from \"../../core/brief/brief.js\";\nimport { docsRepoReady } from \"../policy/gate.js\";\nimport { render } from \"./render.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\n/**\n * `loam adopt` — the bootstrap flow, as a contract with an agent.\n *\n * It emits a BRIEF, not an extraction: the artifacts one service's baseline is\n * made of, the grammar each has to be written in, what the fleet already says\n * about the service, the checks that follow, and the checks that do not exist.\n * The agent reads the code; loam states the work and then judges the result.\n *\n * It writes nothing. A command that produced the documents it is asking for\n * would be doing the half of the job it exists to hand over.\n */\n\ninterface AdoptOptions {\n service?: string;\n subsystem?: string;\n targets?: boolean;\n json?: boolean;\n}\n\n/**\n * Where `--subsystem` points the brief: the target directory inside the\n * group for a service that does not exist yet, or nothing — with a warning —\n * for one that already does, because adopt writes nothing and MOVES nothing\n * (`loam subsystem move` is the verb that relocates). An unknown subsystem\n * refuses with close-name hints, exactly as a mistyped service id warns: a\n * typo here would brief a whole baseline into a group nobody has.\n */\nasync function subsystemTarget(\n docsDir: DocsDir,\n name: string,\n service: PathableService,\n json: boolean,\n): Promise<{ at?: ServicePaths; warning?: string } | null> {\n const tree = await listFleetTree(docsDir);\n const hit = findInTree(tree, name);\n if (hit === null) {\n const close = nearestTreeNames(name, tree.subsystems.map((s) => s.name));\n fail(\n json,\n \"unknown-target\",\n `No subsystem '${name}' in the tree.` +\n (close.length > 0 ? ` Close names: ${close.join(\", \")}.` : \" `loam subsystem new` creates one; `loam subsystem list` shows what exists.\"),\n );\n return null;\n }\n if (hit.kind === \"service\") {\n fail(json, \"invalid-option\", `--subsystem names the service '${name}' — a service never contains other services.`);\n return null;\n }\n const filed = tree.services.find((s) => s.id === service);\n if (filed !== undefined) {\n return {\n warning:\n `'${service}' already exists at ${repoPath(docsDir, filed.dir)}/ — --subsystem does not move a service ` +\n `(\\`loam subsystem move ${service} --into ${name}\\` does), so the brief targets its existing directory.`,\n };\n }\n return { at: servicePathsUnder(hit.subsystem.dir, service) };\n}\n\n/**\n * What is suspicious about this invocation without being wrong.\n *\n * Adopting a service that has no `services/<id>/` yet is the NORMAL case — it\n * is the whole point of the command — so neither of these can be a refusal.\n * But both used to be silent, and both produce the same outcome: a complete,\n * validating baseline written under a directory name nobody meant. `adopt\n * --service biling-service` briefed a phantom service beside the real\n * `billing-service`, and an agent following the brief created it.\n */\nasync function invocationWarnings(\n docsDir: DocsDir,\n service: PathableService,\n bound: string | undefined,\n): Promise<string[]> {\n const warnings: string[] = [];\n\n if (bound !== undefined && bound !== service) {\n warnings.push(\n `This repository's loam.json declares service '${bound}', but you asked to adopt '${service}'. ` +\n `That is legal — one repo can author another service's baseline — but \\`loam vouch\\`, ` +\n `\\`loam gherkin\\` and \\`loam verify --service\\` all bind to '${bound}' here, so the baseline ` +\n `you write cannot be vouched from this repo. If '${service}' is a typo, fix it; otherwise run ` +\n `adopt from '${service}''s own repository.`,\n );\n }\n\n // \"Does the service already exist\" is enumeration MEMBERSHIP, not an\n // existsSync of services/<id>/ at the root: a service filed into a subsystem\n // exists wherever the tree walk found it, and the root probe read every\n // filed service as new — the near-miss warning then accused its own name of\n // being a typo. One enumeration answers both this and the near-miss list;\n // a repo that refuses enumeration is a different problem, reported by every\n // other command, and adopt has nothing useful to add to it.\n let known: string[] = [];\n try {\n known = (await listServices(docsDir)).map((s) => s.id);\n } catch (err) {\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return warnings;\n }\n if (known.includes(service)) return warnings;\n const close = closeIds(service, known);\n if (close.length > 0) {\n warnings.push(\n `There is no services/${service}/ yet, but the docs repo already has ${close.join(\", \")}. ` +\n `If '${service}' is a misspelling of one of those, stop now: an agent following this brief ` +\n `writes a complete, validating baseline for a service that does not exist, and nothing ` +\n `downstream can tell it from a real one. If it really is a new service, this is expected.`,\n );\n }\n return warnings;\n}\n\n/**\n * The half of the brief that is computed from THIS repository, for THIS\n * service — what `--targets` keeps.\n *\n * Everything omitted is a module-level constant: `walk` and `walkClose` are the\n * code-reading order, `frontmatter` the field rules, `checks` the named checks,\n * `unchecked` the statements of what nothing checks, `rule` the never-overwrite\n * instruction. None of them is a function of the service, so a fleet-wide\n * adoption pays for the same bytes once per service — measured on the shipped\n * example fleet, about 23 KB of the 42 KB brief, which is four fifths of what\n * an agent reads and none of what it could not have read once.\n *\n * The keys are picked one by one rather than deleted from a rest — a field\n * added to `Brief` later must land in the DEFAULT payload and stay out of this\n * one until somebody decides it varies by service. Getting that wrong the\n * other way round is how a narrowing flag silently becomes the full brief\n * again.\n */\nfunction narrowed(b: Brief): Pick<Brief, \"service\" | \"docsDir\" | \"path\" | \"targets\" | \"landscape\"> {\n return { service: b.service, docsDir: b.docsDir, path: b.path, targets: b.targets, landscape: b.landscape };\n}\n\n/**\n * The `full` pointer: the run that carries what `--targets` dropped, and the\n * reason it is worth making.\n *\n * It is REQUIRED in the narrowed envelope, and the second sentence is why. The\n * unchecked list is loam shipping, in the binary, the statements of what no\n * check will ever tell you — the one thing standing between a green\n * `loam validate` and being read as more than it means. Merely leaving it out\n * would not read as \"omitted for brevity\"; it would read as \"there is no such\n * list\", which is the failure `src/core/brief/unchecked.ts` exists to prevent\n * and the one promise the brief keeps that no validator supplies. So the\n * narrowed view never hides it — it says how many there are and where to read\n * them.\n *\n * Every count comes off the brief that was just assembled, never off a number\n * typed here: a literal would be correct until the next check is added and\n * wrong silently forever after.\n */\nfunction fullBrief(b: Brief): string {\n return (\n `loam adopt --service ${b.service} --json — everything this view leaves out: the ` +\n `${b.walk.length}-stop code walk, the frontmatter rules, the ${b.checks.length} named checks, ` +\n `and the ${b.unchecked.length} statements of what nothing checks. None of it varies by service, ` +\n `so run it once for the system rather than once per service — but do run it: those ` +\n `${b.unchecked.length} statements are the only place loam says where its checking stops.`\n );\n}\n\nexport function registerAdopt(program: Command): void {\n program\n .command(\"adopt\")\n .description(\"Brief an agent to write one service's baseline docs from its code, and say what will be checked\")\n .option(\"--service <id>\", \"service to adopt (defaults to the configured service)\")\n .option(\"--subsystem <name>\", \"file the new service's baseline into this subsystem instead of the services/ root\")\n .option(\"--targets\", \"narrow the brief to what varies by service; the walk, the checks and the unchecked list are omitted, and a `full` field names the run that carries them\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: AdoptOptions) => {\n const json = opts.json === true;\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n // This command writes nothing, but it briefs an agent to write a whole\n // baseline — so a docsDir that does not exist is not a harmless read: it\n // hands over eight target paths under a directory nobody has, at exit 0,\n // with the near-miss warning below silently switched off (listServices\n // throws, `invocationWarnings` swallows it and returns nothing). The same\n // refusal every enumerating command owes; see docs-repo-gate.ts.\n //\n // `docs`, not `services`: a docs repo that has no services/ yet is exactly\n // the repo adopt exists to fill, and refusing there would refuse the\n // command's own first use.\n if (!docsRepoReady(json, config.docsDir, \"docs\")) return;\n\n const service = opts.service ?? config.service;\n if (service === undefined) {\n fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n return;\n }\n // The id becomes `services/<id>/` in a shared repo, so it goes through the\n // one grammar before anything interpolates it into a path.\n try {\n assertServiceId(service, opts.service === undefined ? \"service (loam.json)\" : \"--service\");\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n\n const warnings = await invocationWarnings(config.docsDir, service, config.service);\n let at: ServicePaths | undefined;\n if (opts.subsystem !== undefined) {\n const target = await subsystemTarget(config.docsDir, opts.subsystem, service, json);\n if (target === null) return;\n at = target.at;\n if (target.warning !== undefined) warnings.push(target.warning);\n }\n const brief = await serviceBrief(config.docsDir, service, at);\n // Computed only under `--targets`, and it is what makes the narrowing\n // legible from inside the payload: a consumer holding `full` knows there\n // is more and what it is, and a consumer without it is holding all of it.\n // The narrowing happens HERE, by omission, so the brief itself is\n // unchanged and every field it can produce still exists.\n const full = opts.targets === true ? fullBrief(brief) : undefined;\n if (json) {\n // Two separate emits rather than one conditional spread: the default\n // payload is a frozen contract, and it must stay the same bytes in the\n // same order whatever this flag grows into.\n if (full !== undefined) {\n emitJson({ command: \"adopt\", ...narrowed(brief), warnings, full });\n return;\n }\n emitJson({ command: \"adopt\", ...brief, warnings });\n return;\n }\n render(brief, warnings, full);\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../src/commands/adopt/adopt.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { closeIds } from \"../../core/c4/arch.js\";\nimport { InvalidIdError, assertServiceId, type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { configOrRefuse, emitJson, fail, NO_SERVICE_MESSAGE, repoPath } from \"../../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { listFleetTree, listServices } from \"../../core/repo/repo.js\";\nimport { closeNamesHint, findInTree } from \"../../core/repo/tree/find.js\";\nimport { servicePathsUnder, type ServicePaths } from \"../../core/repo/paths.js\";\nimport { serviceBrief, type Brief } from \"../../core/brief/brief.js\";\nimport { docsRepoReady } from \"../policy/gate.js\";\nimport { render } from \"./render.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\n/**\n * `loam adopt` — the bootstrap flow, as a contract with an agent.\n *\n * It emits a BRIEF, not an extraction: the artifacts one service's baseline is\n * made of, the grammar each has to be written in, what the fleet already says\n * about the service, the checks that follow, and the checks that do not exist.\n * The agent reads the code; loam states the work and then judges the result.\n *\n * It writes nothing. A command that produced the documents it is asking for\n * would be doing the half of the job it exists to hand over.\n */\n\ninterface AdoptOptions {\n service?: string;\n subsystem?: string;\n targets?: boolean;\n json?: boolean;\n}\n\n/**\n * Where `--subsystem` points the brief: the target directory inside the\n * group for a service that does not exist yet, or nothing — with a warning —\n * for one that already does, because adopt writes nothing and MOVES nothing\n * (`loam subsystem move` is the verb that relocates). An unknown subsystem\n * refuses with close-name hints, exactly as a mistyped service id warns: a\n * typo here would brief a whole baseline into a group nobody has.\n */\nasync function subsystemTarget(\n docsDir: DocsDir,\n name: string,\n service: PathableService,\n json: boolean,\n): Promise<{ at?: ServicePaths; warning?: string } | null> {\n const tree = await listFleetTree(docsDir);\n const hit = findInTree(tree, name);\n if (hit === null) {\n fail(\n json,\n \"unknown-target\",\n `No subsystem '${name}' in the tree.` +\n closeNamesHint(name, tree.subsystems.map((s) => s.name), \" `loam subsystem new` creates one; `loam subsystem list` shows what exists.\"),\n );\n return null;\n }\n if (hit.kind === \"service\") {\n fail(json, \"invalid-option\", `--subsystem names the service '${name}' — a service never contains other services.`);\n return null;\n }\n const filed = tree.services.find((s) => s.id === service);\n if (filed !== undefined) {\n return {\n warning:\n `'${service}' already exists at ${repoPath(docsDir, filed.dir)}/ — --subsystem does not move a service ` +\n `(\\`loam subsystem move ${service} --into ${name}\\` does), so the brief targets its existing directory.`,\n };\n }\n return { at: servicePathsUnder(hit.subsystem.dir, service) };\n}\n\n/**\n * What is suspicious about this invocation without being wrong.\n *\n * Adopting a service that has no `services/<id>/` yet is the NORMAL case — it\n * is the whole point of the command — so neither of these can be a refusal.\n * But both used to be silent, and both produce the same outcome: a complete,\n * validating baseline written under a directory name nobody meant. `adopt\n * --service biling-service` briefed a phantom service beside the real\n * `billing-service`, and an agent following the brief created it.\n */\nasync function invocationWarnings(\n docsDir: DocsDir,\n service: PathableService,\n bound: string | undefined,\n): Promise<string[]> {\n const warnings: string[] = [];\n\n if (bound !== undefined && bound !== service) {\n warnings.push(\n `This repository's loam.json declares service '${bound}', but you asked to adopt '${service}'. ` +\n `That is legal — one repo can author another service's baseline — but \\`loam vouch\\`, ` +\n `\\`loam gherkin\\` and \\`loam verify --service\\` all bind to '${bound}' here, so the baseline ` +\n `you write cannot be vouched from this repo. If '${service}' is a typo, fix it; otherwise run ` +\n `adopt from '${service}''s own repository.`,\n );\n }\n\n // \"Does the service already exist\" is enumeration MEMBERSHIP, not an\n // existsSync of services/<id>/ at the root: a service filed into a subsystem\n // exists wherever the tree walk found it, and the root probe read every\n // filed service as new — the near-miss warning then accused its own name of\n // being a typo. One enumeration answers both this and the near-miss list;\n // a repo that refuses enumeration is a different problem, reported by every\n // other command, and adopt has nothing useful to add to it.\n let known: string[] = [];\n try {\n known = (await listServices(docsDir)).map((s) => s.id);\n } catch (err) {\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return warnings;\n }\n if (known.includes(service)) return warnings;\n const close = closeIds(service, known);\n if (close.length > 0) {\n warnings.push(\n `There is no services/${service}/ yet, but the docs repo already has ${close.join(\", \")}. ` +\n `If '${service}' is a misspelling of one of those, stop now: an agent following this brief ` +\n `writes a complete, validating baseline for a service that does not exist, and nothing ` +\n `downstream can tell it from a real one. If it really is a new service, this is expected.`,\n );\n }\n return warnings;\n}\n\n/**\n * The half of the brief that is computed from THIS repository, for THIS\n * service — what `--targets` keeps.\n *\n * Everything omitted is a module-level constant: `walk` and `walkClose` are the\n * code-reading order, `frontmatter` the field rules, `checks` the named checks,\n * `unchecked` the statements of what nothing checks, `rule` the never-overwrite\n * instruction. None of them is a function of the service, so a fleet-wide\n * adoption pays for the same bytes once per service — measured on the shipped\n * example fleet, about 23 KB of the 42 KB brief, which is four fifths of what\n * an agent reads and none of what it could not have read once.\n *\n * The keys are picked one by one rather than deleted from a rest — a field\n * added to `Brief` later must land in the DEFAULT payload and stay out of this\n * one until somebody decides it varies by service. Getting that wrong the\n * other way round is how a narrowing flag silently becomes the full brief\n * again.\n */\nfunction narrowed(b: Brief): Pick<Brief, \"service\" | \"docsDir\" | \"path\" | \"targets\" | \"landscape\"> {\n return { service: b.service, docsDir: b.docsDir, path: b.path, targets: b.targets, landscape: b.landscape };\n}\n\n/**\n * The `full` pointer: the run that carries what `--targets` dropped, and the\n * reason it is worth making.\n *\n * It is REQUIRED in the narrowed envelope, and the second sentence is why. The\n * unchecked list is loam shipping, in the binary, the statements of what no\n * check will ever tell you — the one thing standing between a green\n * `loam validate` and being read as more than it means. Merely leaving it out\n * would not read as \"omitted for brevity\"; it would read as \"there is no such\n * list\", which is the failure `src/core/brief/unchecked.ts` exists to prevent\n * and the one promise the brief keeps that no validator supplies. So the\n * narrowed view never hides it — it says how many there are and where to read\n * them.\n *\n * Every count comes off the brief that was just assembled, never off a number\n * typed here: a literal would be correct until the next check is added and\n * wrong silently forever after.\n */\nfunction fullBrief(b: Brief): string {\n return (\n `loam adopt --service ${b.service} --json — everything this view leaves out: the ` +\n `${b.walk.length}-stop code walk, the frontmatter rules, the ${b.checks.length} named checks, ` +\n `and the ${b.unchecked.length} statements of what nothing checks. None of it varies by service, ` +\n `so run it once for the system rather than once per service — but do run it: those ` +\n `${b.unchecked.length} statements are the only place loam says where its checking stops.`\n );\n}\n\nexport function registerAdopt(program: Command): void {\n program\n .command(\"adopt\")\n .description(\"Brief an agent to write one service's baseline docs from its code, and say what will be checked\")\n .option(\"--service <id>\", \"service to adopt (defaults to the configured service)\")\n .option(\"--subsystem <name>\", \"file the new service's baseline into this subsystem instead of the services/ root\")\n .option(\"--targets\", \"narrow the brief to what varies by service; the walk, the checks and the unchecked list are omitted, and a `full` field names the run that carries them\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: AdoptOptions) => {\n const json = opts.json === true;\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n // This command writes nothing, but it briefs an agent to write a whole\n // baseline — so a docsDir that does not exist is not a harmless read: it\n // hands over eight target paths under a directory nobody has, at exit 0,\n // with the near-miss warning below silently switched off (listServices\n // throws, `invocationWarnings` swallows it and returns nothing). The same\n // refusal every enumerating command owes; see docs-repo-gate.ts.\n //\n // `docs`, not `services`: a docs repo that has no services/ yet is exactly\n // the repo adopt exists to fill, and refusing there would refuse the\n // command's own first use.\n if (!docsRepoReady(json, config.docsDir, \"docs\")) return;\n\n const service = opts.service ?? config.service;\n if (service === undefined) {\n fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n return;\n }\n // The id becomes `services/<id>/` in a shared repo, so it goes through the\n // one grammar before anything interpolates it into a path.\n try {\n assertServiceId(service, opts.service === undefined ? \"service (loam.json)\" : \"--service\");\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n\n const warnings = await invocationWarnings(config.docsDir, service, config.service);\n let at: ServicePaths | undefined;\n if (opts.subsystem !== undefined) {\n const target = await subsystemTarget(config.docsDir, opts.subsystem, service, json);\n if (target === null) return;\n at = target.at;\n if (target.warning !== undefined) warnings.push(target.warning);\n }\n const brief = await serviceBrief(config.docsDir, service, at);\n // Computed only under `--targets`, and it is what makes the narrowing\n // legible from inside the payload: a consumer holding `full` knows there\n // is more and what it is, and a consumer without it is holding all of it.\n // The narrowing happens HERE, by omission, so the brief itself is\n // unchanged and every field it can produce still exists.\n const full = opts.targets === true ? fullBrief(brief) : undefined;\n if (json) {\n // Two separate emits rather than one conditional spread: the default\n // payload is a frozen contract, and it must stay the same bytes in the\n // same order whatever this flag grows into.\n if (full !== undefined) {\n emitJson({ command: \"adopt\", ...narrowed(brief), warnings, full });\n return;\n }\n emitJson({ command: \"adopt\", ...brief, warnings });\n return;\n }\n render(brief, warnings, full);\n });\n}\n"]}
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fail, reportNoConfig } from "../../core/envelope/json.js";
|
|
1
|
+
import { CodedFailure, configOrRefuse, fail } from "../../core/envelope/json.js";
|
|
3
2
|
import { message } from "../../core/staging/commit.js";
|
|
4
3
|
import { acquireDocsLock, DocsBusyError } from "../../core/staging/lock.js";
|
|
5
|
-
import {
|
|
4
|
+
import {} from "./plan/refusal.js";
|
|
6
5
|
import { archiveLocked } from "./run.js";
|
|
7
6
|
import { LandscapeSpliceError } from "../../core/c4/splice/contract.js";
|
|
8
7
|
import { NotUtf8Error } from "../../core/staging/writes.js";
|
|
9
8
|
import { OpenapiMergeError } from "../../core/openapi/merge/error.js";
|
|
10
9
|
import { InterruptedCommitError } from "../../core/staging/interrupted.js";
|
|
10
|
+
/**
|
|
11
|
+
* The code a failed archive answers with. A `CodedFailure` is thrown from the
|
|
12
|
+
* plan phase (nothing written yet) and from the commit's rollback path (whose
|
|
13
|
+
* message says whether the rollback held). Anything that escapes runArchive and
|
|
14
|
+
* matches no arm below is a bug, and reports as `internal`.
|
|
15
|
+
*/
|
|
11
16
|
function archiveErrorCode(err) {
|
|
12
|
-
if (err instanceof
|
|
17
|
+
if (err instanceof CodedFailure)
|
|
13
18
|
return err.code;
|
|
19
|
+
// Another writer holds the docs repo — the lock is taken refuse-fast below.
|
|
20
|
+
if (err instanceof DocsBusyError)
|
|
21
|
+
return "docs-busy";
|
|
14
22
|
// Every splice refusal is the mechanical merge answer — see the class.
|
|
15
23
|
if (err instanceof LandscapeSpliceError)
|
|
16
24
|
return "merge-failed";
|
|
@@ -52,21 +60,10 @@ export function registerArchive(program) {
|
|
|
52
60
|
}
|
|
53
61
|
async function runArchive(featureId, opts) {
|
|
54
62
|
const json = opts.json === true;
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
reportNoConfig(json, loaded);
|
|
63
|
+
const config = await configOrRefuse(json);
|
|
64
|
+
if (config === null)
|
|
58
65
|
return;
|
|
59
|
-
|
|
60
|
-
const config = loaded.config;
|
|
61
|
-
let release;
|
|
62
|
-
try {
|
|
63
|
-
release = await acquireDocsLock(config.docsDir);
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
if (err instanceof DocsBusyError)
|
|
67
|
-
throw new ArchiveFailure("docs-busy", err.message);
|
|
68
|
-
throw err;
|
|
69
|
-
}
|
|
66
|
+
const release = await acquireDocsLock(config.docsDir);
|
|
70
67
|
try {
|
|
71
68
|
await archiveLocked(config, featureId, opts);
|
|
72
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/commands/archive/archive.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/commands/archive/archive.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAkB,MAAM,6BAA6B,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAuB,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,GAAG,YAAY,YAAY;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC;IACjD,4EAA4E;IAC5E,IAAI,GAAG,YAAY,aAAa;QAAE,OAAO,WAAW,CAAC;IACrD,uEAAuE;IACvE,IAAI,GAAG,YAAY,oBAAoB;QAAE,OAAO,cAAc,CAAC;IAC/D,IAAI,GAAG,YAAY,YAAY;QAAE,OAAO,cAAc,CAAC;IACvD,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,yEAAyE;IACzE,6EAA6E;IAC7E,uBAAuB;IACvB,IAAI,GAAG,YAAY,iBAAiB;QAAE,OAAO,cAAc,CAAC;IAC5D,IAAI,GAAG,YAAY,sBAAsB;QAAE,OAAO,oBAAoB,CAAC;IACvE,OAAO,UAAU,CAAC;AACpB,CAAC;AAGD,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;SACJ,OAAO,CAAC,SAAS,CAAC;SAClB,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;SACpD,WAAW,CAAC,iFAAiF,CAAC;SAC9F,MAAM,CAAC,WAAW,EAAE,sGAAsG,CAAC;SAC3H,MAAM,CAAC,WAAW,EAAE,8CAA8C,CAAC;SACnE,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,KAAK,EAAE,SAAiB,EAAE,IAAoB,EAAE,EAAE;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,qEAAqE;YACrE,yEAAyE;YACzE,uEAAuE;YACvE,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,EAAE,WAAW,SAAS,YAAY,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,SAAiB,EAAE,IAAoB;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO;IAC5B,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["/**\n * `loam archive`: fold a finished feature's three axes into the living docs.\n *\n * Registration and the lock, nothing else. Everything the command actually does\n * is under `./plan/` and `./run.ts`, in that order — the gate refuses, the plan\n * computes, the commit swaps.\n */\nimport type { Command } from \"commander\";\nimport { CodedFailure, configOrRefuse, fail, type ErrorCode } from \"../../core/envelope/json.js\";\nimport { message } from \"../../core/staging/commit.js\";\nimport { acquireDocsLock, DocsBusyError } from \"../../core/staging/lock.js\";\nimport { type ArchiveOptions } from \"./plan/refusal.js\";\nimport { archiveLocked } from \"./run.js\";\nimport { LandscapeSpliceError } from \"../../core/c4/splice/contract.js\";\nimport { NotUtf8Error } from \"../../core/staging/writes.js\";\nimport { OpenapiMergeError } from \"../../core/openapi/merge/error.js\";\nimport { InterruptedCommitError } from \"../../core/staging/interrupted.js\";\n\n/**\n * The code a failed archive answers with. A `CodedFailure` is thrown from the\n * plan phase (nothing written yet) and from the commit's rollback path (whose\n * message says whether the rollback held). Anything that escapes runArchive and\n * matches no arm below is a bug, and reports as `internal`.\n */\nfunction archiveErrorCode(err: unknown): ErrorCode {\n if (err instanceof CodedFailure) return err.code;\n // Another writer holds the docs repo — the lock is taken refuse-fast below.\n if (err instanceof DocsBusyError) return \"docs-busy\";\n // Every splice refusal is the mechanical merge answer — see the class.\n if (err instanceof LandscapeSpliceError) return \"merge-failed\";\n if (err instanceof NotUtf8Error) return \"merge-failed\";\n // The merge branch wraps its own call and re-throws as merge-failed; the\n // create branch (`stripOpenapiRemovalMarkers`) does not, so one unreadable\n // feature contract answered merge-failed or `internal` depending on whether\n // the service already had a living openapi.yaml. The code answers \"can I\n // trust the repo?\", and that answer does not turn on which side of the merge\n // the document sat on.\n if (err instanceof OpenapiMergeError) return \"merge-failed\";\n if (err instanceof InterruptedCommitError) return \"commit-interrupted\";\n return \"internal\";\n}\n\n\nexport function registerArchive(program: Command): void {\n program\n .command(\"archive\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Merge a shipped feature's deltas into the living specs + model, then archive it\")\n .option(\"--approve\", \"archive despite GATING coherence issues (may corrupt the living docs); advisory warnings never block\")\n .option(\"--dry-run\", \"print the whole merge plan and write nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureId: string, opts: ArchiveOptions) => {\n const json = opts.json === true;\n try {\n await runArchive(featureId, opts);\n } catch (err) {\n // Plan-phase failures happen before any write; commit-phase failures are\n // rolled back by runArchive, which says so in the message it throws.\n // A file loam cannot decode is one it cannot merge, and that is the same\n // answer as any other merge it could not compute: nothing was written.\n fail(json, archiveErrorCode(err), `archive ${featureId} failed: ${message(err)}`);\n }\n });\n}\n\nasync function runArchive(featureId: string, opts: ArchiveOptions): Promise<void> {\n const json = opts.json === true;\n const config = await configOrRefuse(json);\n if (config === null) return;\n const release = await acquireDocsLock(config.docsDir);\n try {\n await archiveLocked(config, featureId, opts);\n } finally {\n await release();\n }\n}\n"]}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { parse } from "yaml";
|
|
11
|
-
import { repoPath } from "../../../../core/envelope/json.js";
|
|
11
|
+
import { CodedFailure, repoPath } from "../../../../core/envelope/json.js";
|
|
12
12
|
import { planWrite, readUtf8 } from "../../../../core/staging/writes.js";
|
|
13
13
|
import { featureSpecPaths } from "../../../../core/repo/paths.js";
|
|
14
14
|
import { locateServicePaths } from "../../../../core/repo/service-target.js";
|
|
@@ -18,8 +18,9 @@ import { asyncapiSurfaces } from "../../../../core/asyncapi/baseline/surfaces.js
|
|
|
18
18
|
import { AsyncapiMergeError } from "../../../../core/asyncapi/merge/error.js";
|
|
19
19
|
import { stripAsyncapiMarkers } from "../../../../core/asyncapi/merge/markers.js";
|
|
20
20
|
import { mergeAsyncapiSlots } from "../../../../core/asyncapi/merge/merge.js";
|
|
21
|
-
import { ArchiveFailure } from "../refusal.js";
|
|
22
21
|
import {} from "../state.js";
|
|
22
|
+
import { consumerClause } from "./consumers.js";
|
|
23
|
+
import { sayQuoted, sayStale, warnOverwrite } from "./verdict-lines.js";
|
|
23
24
|
/** The per-section overwrite code — three codes, one per slot shape, mirroring the OpenAPI axis's. */
|
|
24
25
|
function modifiedCode(section) {
|
|
25
26
|
if (section === "components.messages")
|
|
@@ -28,7 +29,7 @@ function modifiedCode(section) {
|
|
|
28
29
|
}
|
|
29
30
|
export async function planAsyncapiContracts(config, gated, plan, say) {
|
|
30
31
|
const { featureDir, deltaServices } = gated;
|
|
31
|
-
const { writes,
|
|
32
|
+
const { writes, planGates, asyncapiRemovals } = plan;
|
|
32
33
|
for (const svc of deltaServices) {
|
|
33
34
|
const featAsyncapi = featureSpecPaths(featureDir, svc).asyncapi;
|
|
34
35
|
if (!existsSync(featAsyncapi))
|
|
@@ -40,7 +41,7 @@ export async function planAsyncapiContracts(config, gated, plan, say) {
|
|
|
40
41
|
// loam cannot read is one it cannot merge, and that is the same answer as
|
|
41
42
|
// any other merge it could not compute — nothing is written.
|
|
42
43
|
if (featDoc.unreadable) {
|
|
43
|
-
throw new
|
|
44
|
+
throw new CodedFailure("merge-failed", `feature asyncapi ${repoPath(config.docsDir, featAsyncapi)} for ${svc} does not read as an AsyncAPI document (${featDoc.error ?? "not a YAML mapping"}) — the event axis cannot be merged; fix the file, or delete it if this feature has no contract delta`);
|
|
44
45
|
}
|
|
45
46
|
const names = featDoc.messages.filter((m) => m.remove !== true).map((m) => m.name);
|
|
46
47
|
// An `x-loam-remove` NESTED on an inline channel message retires nothing:
|
|
@@ -108,11 +109,11 @@ export async function planAsyncapiContracts(config, gated, plan, say) {
|
|
|
108
109
|
}
|
|
109
110
|
else if (surfaces.length > 0) {
|
|
110
111
|
// "The plan wrote less than my delta spells" is the sentence a reader
|
|
111
|
-
// cannot infer from an unchanged file —
|
|
112
|
-
// names it for slots, and a components-only
|
|
113
|
-
//
|
|
114
|
-
// nothing at all. Not a warning: every
|
|
115
|
-
// correct authoring and the correct merge.
|
|
112
|
+
// cannot infer from an unchanged file — the quote line's own comment
|
|
113
|
+
// (./verdict-lines.ts) names it for slots, and a components-only
|
|
114
|
+
// delta is the shape where silence is indistinguishable from the
|
|
115
|
+
// defect that made it merge nothing at all. Not a warning: every
|
|
116
|
+
// surface being a quote is correct authoring and the correct merge.
|
|
116
117
|
say(` asyncapi: ${svc} — nothing to write: every one of the ${surfaces.length} component surface(s) this delta declares already stands as living has it`);
|
|
117
118
|
}
|
|
118
119
|
if (merge.removed.length > 0) {
|
|
@@ -120,44 +121,29 @@ export async function planAsyncapiContracts(config, gated, plan, say) {
|
|
|
120
121
|
for (const slot of merge.removed)
|
|
121
122
|
say(` - removes ${slot.label}`);
|
|
122
123
|
}
|
|
123
|
-
//
|
|
124
|
-
// the
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
// The surface half's mirrors of the three lines around them. A component
|
|
131
|
-
// surface reaches the reader by exactly the same three sentences a slot
|
|
132
|
-
// does, because it is graded by exactly the same verdict.
|
|
133
|
-
for (const id of merge.componentsQuoted) {
|
|
134
|
-
say(` · quotes components.${id} — unchanged since it was pinned, left as living has it`);
|
|
135
|
-
}
|
|
136
|
-
// Stale surfaces reaching the merge at all means --approve pushed past
|
|
137
|
-
// the gate's asyncapi.baseline-stale — the plan still names what it cost.
|
|
138
|
-
for (const slot of merge.baselineStale) {
|
|
139
|
-
say(` ⚠ stale baseline on ${slot.label} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);
|
|
140
|
-
}
|
|
141
|
-
for (const id of merge.componentsStale) {
|
|
142
|
-
say(` ⚠ stale baseline on components.${id} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);
|
|
143
|
-
}
|
|
124
|
+
// The quote and stale lines (./verdict-lines.ts says why each is said),
|
|
125
|
+
// the component surfaces' lines mirroring the slots' around them.
|
|
126
|
+
sayQuoted(say, merge.quoted.map((slot) => slot.label));
|
|
127
|
+
sayQuoted(say, merge.componentsQuoted.map((id) => `components.${id}`));
|
|
128
|
+
sayStale(say, merge.baselineStale.map((slot) => slot.label));
|
|
129
|
+
sayStale(say, merge.componentsStale.map((id) => `components.${id}`));
|
|
144
130
|
for (const id of merge.componentsModified) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
code: "asyncapi.component-modified",
|
|
148
|
-
subject: svc,
|
|
131
|
+
warnOverwrite(plan, say, {
|
|
132
|
+
svc, code: "asyncapi.component-modified", label: `components.${id}`,
|
|
149
133
|
message: `${svc}: the delta declares components.${id}, which the living AsyncAPI already has with different content — the merge overwrites the living component wholesale`,
|
|
150
134
|
});
|
|
151
|
-
say(` ⚠ overwrites components.${id} — the living definition differs`);
|
|
152
135
|
}
|
|
153
136
|
for (const slot of merge.modified) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
137
|
+
// A message is the one slot a consumer names (`Consumes:` lines,
|
|
138
|
+
// `consumes` edges), so only `asyncapi.message-modified` says who the
|
|
139
|
+
// overwrite lands on — the OpenAPI planner's sentence, through the
|
|
140
|
+
// same scan (./consumers.ts). Channels and operations join by nothing.
|
|
141
|
+
const name = merge.modifiedMessageNames.get(slot.label);
|
|
142
|
+
const named = name === undefined ? null : consumerClause(await config.consumers.message(svc, name));
|
|
143
|
+
warnOverwrite(plan, say, {
|
|
144
|
+
svc, code: modifiedCode(slot.section), label: slot.label, named,
|
|
158
145
|
message: `${svc}: the delta redefines ${slot.label}, which the living AsyncAPI already has — the merge overwrites the living slot wholesale`,
|
|
159
146
|
});
|
|
160
|
-
say(` ⚠ overwrites ${slot.label} — the living definition differs`);
|
|
161
147
|
}
|
|
162
148
|
for (const ref of merge.unresolved) {
|
|
163
149
|
planGates.push({
|
|
@@ -170,7 +156,7 @@ export async function planAsyncapiContracts(config, gated, plan, say) {
|
|
|
170
156
|
}
|
|
171
157
|
catch (err) {
|
|
172
158
|
if (err instanceof AsyncapiMergeError)
|
|
173
|
-
throw new
|
|
159
|
+
throw new CodedFailure("merge-failed", err.message);
|
|
174
160
|
throw err;
|
|
175
161
|
}
|
|
176
162
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asyncapi.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/contracts/asyncapi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The AsyncAPI contract merge: the feature's asyncapi deltas folded into each\n * living event contract — the event axis's mirror of ./openapi.ts, slot-keyed\n * where that one is path-keyed.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { parse } from \"yaml\";\nimport { repoPath } from \"../../../../core/envelope/json.js\";\nimport { planWrite, readUtf8 } from \"../../../../core/staging/writes.js\";\nimport { featureSpecPaths } from \"../../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../../core/repo/service-target.js\";\nimport { readAsyncapi } from \"../../../../core/asyncapi/read.js\";\nimport { asyncapiSlots, type AsyncapiSection } from \"../../../../core/asyncapi/digest.js\";\nimport { asyncapiSurfaces } from \"../../../../core/asyncapi/baseline/surfaces.js\";\nimport { AsyncapiMergeError } from \"../../../../core/asyncapi/merge/error.js\";\nimport { stripAsyncapiMarkers } from \"../../../../core/asyncapi/merge/markers.js\";\nimport { mergeAsyncapiSlots, type AsyncapiMergeResult } from \"../../../../core/asyncapi/merge/merge.js\";\nimport { ArchiveFailure } from \"../refusal.js\";\nimport { type Gated, type Plan } from \"../state.js\";\nimport type { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { IssueCode } from \"../../../../core/vocabulary/issue.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\n\n/** The per-section overwrite code — three codes, one per slot shape, mirroring the OpenAPI axis's. */\nfunction modifiedCode(section: AsyncapiSection): IssueCode {\n if (section === \"components.messages\") return \"asyncapi.message-modified\";\n return section === \"channels\" ? \"asyncapi.channel-modified\" : \"asyncapi.operation-modified\";\n}\n\nexport async function planAsyncapiContracts(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes, planWarns, planGates, asyncapiRemovals } = plan;\n for (const svc of deltaServices) {\n const featAsyncapi = featureSpecPaths(featureDir, svc).asyncapi;\n if (!existsSync(featAsyncapi)) continue;\n const featText = await readUtf8(featAsyncapi);\n const featDoc = await readAsyncapi(featAsyncapi);\n // Coherence grades `asyncapi.invalid` and gates on it; the merge stays\n // mechanical under --approve for the OpenAPI planner's reason: a document\n // loam cannot read is one it cannot merge, and that is the same answer as\n // any other merge it could not compute — nothing is written.\n if (featDoc.unreadable) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `feature asyncapi ${repoPath(config.docsDir, featAsyncapi)} for ${svc} does not read as an AsyncAPI document (${featDoc.error ?? \"not a YAML mapping\"}) — the event axis cannot be merged; fix the file, or delete it if this feature has no contract delta`,\n );\n }\n const names = featDoc.messages.filter((m) => m.remove !== true).map((m) => m.name);\n // An `x-loam-remove` NESTED on an inline channel message retires nothing:\n // an inline message is channel-slot interior (SCHEMA.md's decision), so\n // the marker is just channel content — and when the restated channel is\n // otherwise identical to living, not even that: the merge deduplicates it\n // away and the marker lands in no bucket at all. The merge is safe either\n // way (the deep strip keeps the key out of living), but the author asked\n // for a removal that will not happen, and silence there is how a retired\n // message would stay on the wire — openapi.remove-marker-path-level's\n // lesson at the nested depth. Gated like the other plan-visible breaches,\n // --approve and all.\n for (const m of featDoc.messages) {\n if (m.remove !== true || !m.slot.startsWith(\"channels.\")) continue;\n const channel = m.slot.slice(\"channels.\".length, m.slot.lastIndexOf(\".messages.\"));\n planGates.push({\n severity: \"error\",\n code: \"asyncapi.remove-marker-inline\",\n subject: svc,\n message: `${svc}: '${m.slot}' carries x-loam-remove nested on an inline channel message — inline messages are channel interior, not slots, so this retires nothing. Retire the whole channel (x-loam-remove: true at channels.${channel}), or declare the message under components.messages (the channel $ref-ing it) and put the marker there.`,\n });\n }\n // The shared context, for the OpenAPI planner's reason: once per delta\n // service must not mean one fleet walk per delta service.\n const livingAsyncapi = (await locateServicePaths(config.docsDir, svc, config.fleet)).asyncapi;\n try {\n if (!existsSync(livingAsyncapi)) {\n // A removal against a non-existent contract is gated by coherence;\n // under --approve the strip drops the markers, so the one thing left\n // to decide is whether a document remains. It must not when nothing\n // survives: an asyncapi.yaml's very PRESENCE is graded on this axis\n // (absence is `service.no-asyncapi`'s question, silence otherwise),\n // so a slotless or all-markers delta creating an empty living\n // contract would put a presence claim on a service that has none.\n // SLOTS only, deliberately, now that component surfaces merge: a\n // document holding schemas and no channel, operation or message\n // declares no event at all, and creating it would make exactly the\n // presence claim this branch exists to refuse.\n const surviving = asyncapiSlots(parse(featText)).filter((slot) => !slot.remove);\n if (surviving.length === 0) {\n say(` asyncapi: ${svc} — nothing to merge (the delta declares no surviving slots), no living asyncapi.yaml created`);\n continue;\n }\n writes.push(planWrite(livingAsyncapi, stripAsyncapiMarkers(featText, svc)));\n say(` asyncapi: ${svc} — created (${names.join(\", \")})`);\n continue;\n }\n const merge: AsyncapiMergeResult = mergeAsyncapiSlots(await readUtf8(livingAsyncapi), featText, svc);\n // What the delta declares OUTSIDE the three slot sections — the one\n // question the merge result cannot answer for the silence below, because\n // a merge that wrote nothing reports nothing to distinguish \"the delta\n // was all quotes\" from \"the delta was all schemas and I dropped them\".\n const surfaces = asyncapiSurfaces(parse(featText));\n if (merge.text !== null) {\n writes.push(planWrite(livingAsyncapi, merge.text));\n // The parenthesised list names the MESSAGES the delta declares, and a\n // delta whose whole change is a component surface has none — so this\n // printed `merged ()`, the same empty-parenthesis lie the create\n // branch above is commented for: it reads as \"this merge wrote\n // nothing\" about a merge that just published a shared schema to every\n // consumer of the fleet. Drop the parentheses when there is nothing to\n // put in them; the surface lines below name what actually moved.\n say(` asyncapi: ${svc} — merged${names.length > 0 ? ` (${names.join(\", \")})` : \"\"}`);\n } else if (surfaces.length > 0) {\n // \"The plan wrote less than my delta spells\" is the sentence a reader\n // cannot infer from an unchanged file — this file's own comment below\n // names it for slots, and a components-only delta is the shape where\n // silence is indistinguishable from the defect that made it merge\n // nothing at all. Not a warning: every surface being a quote is\n // correct authoring and the correct merge.\n say(` asyncapi: ${svc} — nothing to write: every one of the ${surfaces.length} component surface(s) this delta declares already stands as living has it`);\n }\n if (merge.removed.length > 0) {\n asyncapiRemovals.push({ service: svc, slots: merge.removed.map((slot) => slot.label) });\n for (const slot of merge.removed) say(` - removes ${slot.label}`);\n }\n // Said out loud, because \"the plan wrote less than my delta spells\" is\n // the one thing a reader cannot infer from a merged file. Not a warning:\n // quoting the contract around your change is correct authoring, and\n // leaving the quote alone is the correct merge.\n for (const slot of merge.quoted) {\n say(` · quotes ${slot.label} — unchanged since it was pinned, left as living has it`);\n }\n // The surface half's mirrors of the three lines around them. A component\n // surface reaches the reader by exactly the same three sentences a slot\n // does, because it is graded by exactly the same verdict.\n for (const id of merge.componentsQuoted) {\n say(` · quotes components.${id} — unchanged since it was pinned, left as living has it`);\n }\n // Stale surfaces reaching the merge at all means --approve pushed past\n // the gate's asyncapi.baseline-stale — the plan still names what it cost.\n for (const slot of merge.baselineStale) {\n say(` ⚠ stale baseline on ${slot.label} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);\n }\n for (const id of merge.componentsStale) {\n say(` ⚠ stale baseline on components.${id} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);\n }\n for (const id of merge.componentsModified) {\n planWarns.push({\n severity: \"warn\",\n code: \"asyncapi.component-modified\",\n subject: svc,\n message: `${svc}: the delta declares components.${id}, which the living AsyncAPI already has with different content — the merge overwrites the living component wholesale`,\n });\n say(` ⚠ overwrites components.${id} — the living definition differs`);\n }\n for (const slot of merge.modified) {\n planWarns.push({\n severity: \"warn\",\n code: modifiedCode(slot.section),\n subject: svc,\n message: `${svc}: the delta redefines ${slot.label}, which the living AsyncAPI already has — the merge overwrites the living slot wholesale`,\n });\n say(` ⚠ overwrites ${slot.label} — the living definition differs`);\n }\n for (const ref of merge.unresolved) {\n planGates.push({\n severity: \"error\",\n code: \"asyncapi.ref-unresolved\",\n subject: svc,\n message: `${svc}: $ref '${ref}' resolves in neither the feature's asyncapi.yaml nor the living one — the merged document would carry a dangling reference`,\n });\n }\n } catch (err) {\n if (err instanceof AsyncapiMergeError) throw new ArchiveFailure(\"merge-failed\", err.message);\n throw err;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asyncapi.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/contracts/asyncapi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The AsyncAPI contract merge: the feature's asyncapi deltas folded into each\n * living event contract — the event axis's mirror of ./openapi.ts, slot-keyed\n * where that one is path-keyed.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { parse } from \"yaml\";\nimport { CodedFailure, repoPath } from \"../../../../core/envelope/json.js\";\nimport { planWrite, readUtf8 } from \"../../../../core/staging/writes.js\";\nimport { featureSpecPaths } from \"../../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../../core/repo/service-target.js\";\nimport { readAsyncapi } from \"../../../../core/asyncapi/read.js\";\nimport { asyncapiSlots, type AsyncapiSection } from \"../../../../core/asyncapi/digest.js\";\nimport { asyncapiSurfaces } from \"../../../../core/asyncapi/baseline/surfaces.js\";\nimport { AsyncapiMergeError } from \"../../../../core/asyncapi/merge/error.js\";\nimport { stripAsyncapiMarkers } from \"../../../../core/asyncapi/merge/markers.js\";\nimport { mergeAsyncapiSlots, type AsyncapiMergeResult } from \"../../../../core/asyncapi/merge/merge.js\";\nimport { type Gated, type Plan } from \"../state.js\";\nimport { consumerClause, type ContractConsumers } from \"./consumers.js\";\nimport { sayQuoted, sayStale, warnOverwrite } from \"./verdict-lines.js\";\nimport type { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { IssueCode } from \"../../../../core/vocabulary/issue.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\n\n/** The per-section overwrite code — three codes, one per slot shape, mirroring the OpenAPI axis's. */\nfunction modifiedCode(section: AsyncapiSection): IssueCode {\n if (section === \"components.messages\") return \"asyncapi.message-modified\";\n return section === \"channels\" ? \"asyncapi.channel-modified\" : \"asyncapi.operation-modified\";\n}\n\nexport async function planAsyncapiContracts(\n config: { docsDir: DocsDir; fleet: FleetContext; consumers: ContractConsumers },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes, planGates, asyncapiRemovals } = plan;\n for (const svc of deltaServices) {\n const featAsyncapi = featureSpecPaths(featureDir, svc).asyncapi;\n if (!existsSync(featAsyncapi)) continue;\n const featText = await readUtf8(featAsyncapi);\n const featDoc = await readAsyncapi(featAsyncapi);\n // Coherence grades `asyncapi.invalid` and gates on it; the merge stays\n // mechanical under --approve for the OpenAPI planner's reason: a document\n // loam cannot read is one it cannot merge, and that is the same answer as\n // any other merge it could not compute — nothing is written.\n if (featDoc.unreadable) {\n throw new CodedFailure(\n \"merge-failed\",\n `feature asyncapi ${repoPath(config.docsDir, featAsyncapi)} for ${svc} does not read as an AsyncAPI document (${featDoc.error ?? \"not a YAML mapping\"}) — the event axis cannot be merged; fix the file, or delete it if this feature has no contract delta`,\n );\n }\n const names = featDoc.messages.filter((m) => m.remove !== true).map((m) => m.name);\n // An `x-loam-remove` NESTED on an inline channel message retires nothing:\n // an inline message is channel-slot interior (SCHEMA.md's decision), so\n // the marker is just channel content — and when the restated channel is\n // otherwise identical to living, not even that: the merge deduplicates it\n // away and the marker lands in no bucket at all. The merge is safe either\n // way (the deep strip keeps the key out of living), but the author asked\n // for a removal that will not happen, and silence there is how a retired\n // message would stay on the wire — openapi.remove-marker-path-level's\n // lesson at the nested depth. Gated like the other plan-visible breaches,\n // --approve and all.\n for (const m of featDoc.messages) {\n if (m.remove !== true || !m.slot.startsWith(\"channels.\")) continue;\n const channel = m.slot.slice(\"channels.\".length, m.slot.lastIndexOf(\".messages.\"));\n planGates.push({\n severity: \"error\",\n code: \"asyncapi.remove-marker-inline\",\n subject: svc,\n message: `${svc}: '${m.slot}' carries x-loam-remove nested on an inline channel message — inline messages are channel interior, not slots, so this retires nothing. Retire the whole channel (x-loam-remove: true at channels.${channel}), or declare the message under components.messages (the channel $ref-ing it) and put the marker there.`,\n });\n }\n // The shared context, for the OpenAPI planner's reason: once per delta\n // service must not mean one fleet walk per delta service.\n const livingAsyncapi = (await locateServicePaths(config.docsDir, svc, config.fleet)).asyncapi;\n try {\n if (!existsSync(livingAsyncapi)) {\n // A removal against a non-existent contract is gated by coherence;\n // under --approve the strip drops the markers, so the one thing left\n // to decide is whether a document remains. It must not when nothing\n // survives: an asyncapi.yaml's very PRESENCE is graded on this axis\n // (absence is `service.no-asyncapi`'s question, silence otherwise),\n // so a slotless or all-markers delta creating an empty living\n // contract would put a presence claim on a service that has none.\n // SLOTS only, deliberately, now that component surfaces merge: a\n // document holding schemas and no channel, operation or message\n // declares no event at all, and creating it would make exactly the\n // presence claim this branch exists to refuse.\n const surviving = asyncapiSlots(parse(featText)).filter((slot) => !slot.remove);\n if (surviving.length === 0) {\n say(` asyncapi: ${svc} — nothing to merge (the delta declares no surviving slots), no living asyncapi.yaml created`);\n continue;\n }\n writes.push(planWrite(livingAsyncapi, stripAsyncapiMarkers(featText, svc)));\n say(` asyncapi: ${svc} — created (${names.join(\", \")})`);\n continue;\n }\n const merge: AsyncapiMergeResult = mergeAsyncapiSlots(await readUtf8(livingAsyncapi), featText, svc);\n // What the delta declares OUTSIDE the three slot sections — the one\n // question the merge result cannot answer for the silence below, because\n // a merge that wrote nothing reports nothing to distinguish \"the delta\n // was all quotes\" from \"the delta was all schemas and I dropped them\".\n const surfaces = asyncapiSurfaces(parse(featText));\n if (merge.text !== null) {\n writes.push(planWrite(livingAsyncapi, merge.text));\n // The parenthesised list names the MESSAGES the delta declares, and a\n // delta whose whole change is a component surface has none — so this\n // printed `merged ()`, the same empty-parenthesis lie the create\n // branch above is commented for: it reads as \"this merge wrote\n // nothing\" about a merge that just published a shared schema to every\n // consumer of the fleet. Drop the parentheses when there is nothing to\n // put in them; the surface lines below name what actually moved.\n say(` asyncapi: ${svc} — merged${names.length > 0 ? ` (${names.join(\", \")})` : \"\"}`);\n } else if (surfaces.length > 0) {\n // \"The plan wrote less than my delta spells\" is the sentence a reader\n // cannot infer from an unchanged file — the quote line's own comment\n // (./verdict-lines.ts) names it for slots, and a components-only\n // delta is the shape where silence is indistinguishable from the\n // defect that made it merge nothing at all. Not a warning: every\n // surface being a quote is correct authoring and the correct merge.\n say(` asyncapi: ${svc} — nothing to write: every one of the ${surfaces.length} component surface(s) this delta declares already stands as living has it`);\n }\n if (merge.removed.length > 0) {\n asyncapiRemovals.push({ service: svc, slots: merge.removed.map((slot) => slot.label) });\n for (const slot of merge.removed) say(` - removes ${slot.label}`);\n }\n // The quote and stale lines (./verdict-lines.ts says why each is said),\n // the component surfaces' lines mirroring the slots' around them.\n sayQuoted(say, merge.quoted.map((slot) => slot.label));\n sayQuoted(say, merge.componentsQuoted.map((id) => `components.${id}`));\n sayStale(say, merge.baselineStale.map((slot) => slot.label));\n sayStale(say, merge.componentsStale.map((id) => `components.${id}`));\n for (const id of merge.componentsModified) {\n warnOverwrite(plan, say, {\n svc, code: \"asyncapi.component-modified\", label: `components.${id}`,\n message: `${svc}: the delta declares components.${id}, which the living AsyncAPI already has with different content — the merge overwrites the living component wholesale`,\n });\n }\n for (const slot of merge.modified) {\n // A message is the one slot a consumer names (`Consumes:` lines,\n // `consumes` edges), so only `asyncapi.message-modified` says who the\n // overwrite lands on — the OpenAPI planner's sentence, through the\n // same scan (./consumers.ts). Channels and operations join by nothing.\n const name = merge.modifiedMessageNames.get(slot.label);\n const named = name === undefined ? null : consumerClause(await config.consumers.message(svc, name));\n warnOverwrite(plan, say, {\n svc, code: modifiedCode(slot.section), label: slot.label, named,\n message: `${svc}: the delta redefines ${slot.label}, which the living AsyncAPI already has — the merge overwrites the living slot wholesale`,\n });\n }\n for (const ref of merge.unresolved) {\n planGates.push({\n severity: \"error\",\n code: \"asyncapi.ref-unresolved\",\n subject: svc,\n message: `${svc}: $ref '${ref}' resolves in neither the feature's asyncapi.yaml nor the living one — the merged document would carry a dangling reference`,\n });\n }\n } catch (err) {\n if (err instanceof AsyncapiMergeError) throw new CodedFailure(\"merge-failed\", err.message);\n throw err;\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who in the CURRENT fleet consumes what an archive is about to overwrite — the
|
|
3
|
+
* consumers an `openapi.op-modified` / `asyncapi.message-modified` warning
|
|
4
|
+
* names, so "the merge overwrites the living operation wholesale" says whom
|
|
5
|
+
* that lands on instead of leaving the reader to go and find out.
|
|
6
|
+
*
|
|
7
|
+
* NOT A NEW JOIN. This is the one consumer index (core/consumers/consumers.ts)
|
|
8
|
+
* — the one `loam diff` names victims with and the coherence gate refuses on —
|
|
9
|
+
* asked over the living fleet as it stands before the merge: the same edges of
|
|
10
|
+
* the `architecture/` project, the same living requirements, the same use-case
|
|
11
|
+
* hops, the same suspensions for a consumer nobody could read or a caller
|
|
12
|
+
* drawn with no op, and the same sentence for the answer (`consumerStance`).
|
|
13
|
+
* Its `own` list (the provider's own documents) is the gate's to count and is
|
|
14
|
+
* not named here: a warning about whom an overwrite lands on is about OTHER
|
|
15
|
+
* services, as the diff's is.
|
|
16
|
+
*
|
|
17
|
+
* LAZY, AND PAID ONLY WHEN A WARNING FIRES. The index reads every service's
|
|
18
|
+
* four documents (`livingFleet`) and, at the first question, parses the
|
|
19
|
+
* `architecture/` project — through the run's read index, so a second question
|
|
20
|
+
* pays for neither again; the archive's own gate calls coherence without that
|
|
21
|
+
* index, so its read is not shared with this one. Measured 2026-10-10 on the
|
|
22
|
+
* 120-service benchmark fleet (scripts/bench-validate.ts's shape), with one
|
|
23
|
+
* feature overwriting one operation: `archive --dry-run --json` went from a
|
|
24
|
+
* 1216 ms to a 1346 ms median over five runs through tsx (~0.5 s of which is
|
|
25
|
+
* startup); taken apart, about 50 ms of document reads and about 120 ms of
|
|
26
|
+
* LikeC4. An archive that overwrites nothing never builds it.
|
|
27
|
+
*
|
|
28
|
+
* What it does NOT answer: `openapi.component-modified` and
|
|
29
|
+
* `asyncapi.component-modified`. A component's consumers are the operations or
|
|
30
|
+
* messages whose `$ref` closure reaches it, and then THEIR consumers; nothing
|
|
31
|
+
* indexes a contract by component today (an operation carries its closure's
|
|
32
|
+
* digest, not its members), and building that reverse index for a warning is
|
|
33
|
+
* the heavy new structure this reuse was allowed only without.
|
|
34
|
+
*/
|
|
35
|
+
import { consumerIndex } from "../../../../core/consumers/consumers.js";
|
|
36
|
+
import { consumerStance, scanDetails } from "../../../../core/consumers/scan.js";
|
|
37
|
+
import { FleetContext } from "../../../../core/fleet-context.js";
|
|
38
|
+
export function contractConsumers(config) {
|
|
39
|
+
// The run's own read index: the planners have already read the delta
|
|
40
|
+
// services' living contracts through it, and the index memoises its fleet
|
|
41
|
+
// and its map on it. Constructing the index reads nothing.
|
|
42
|
+
const index = consumerIndex({ docsDir: config.docsDir, context: config.fleet });
|
|
43
|
+
return {
|
|
44
|
+
op: (provider, operationId) => index.opConsumers(provider, operationId),
|
|
45
|
+
message: (provider, name) => index.messageConsumers(provider, name),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The clause an overwrite warning's message ends with, and the lines its
|
|
50
|
+
* `details` carry: positive evidence first, then what could not be checked —
|
|
51
|
+
* the order `loam diff` lists them in.
|
|
52
|
+
*/
|
|
53
|
+
export function consumerClause(scan) {
|
|
54
|
+
return { clause: consumerStance(scan), details: scanDetails(scan) };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=consumers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumers.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/contracts/consumers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAqB,MAAM,oCAAoC,CAAC;AACpG,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAWjE,MAAM,UAAU,iBAAiB,CAAC,MAAiD;IACjF,qEAAqE;IACrE,0EAA0E;IAC1E,2DAA2D;IAC3D,MAAM,KAAK,GAAG,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAChF,OAAO;QACL,EAAE,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC;QACvE,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC;KACpE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;AACtE,CAAC","sourcesContent":["/**\n * Who in the CURRENT fleet consumes what an archive is about to overwrite — the\n * consumers an `openapi.op-modified` / `asyncapi.message-modified` warning\n * names, so \"the merge overwrites the living operation wholesale\" says whom\n * that lands on instead of leaving the reader to go and find out.\n *\n * NOT A NEW JOIN. This is the one consumer index (core/consumers/consumers.ts)\n * — the one `loam diff` names victims with and the coherence gate refuses on —\n * asked over the living fleet as it stands before the merge: the same edges of\n * the `architecture/` project, the same living requirements, the same use-case\n * hops, the same suspensions for a consumer nobody could read or a caller\n * drawn with no op, and the same sentence for the answer (`consumerStance`).\n * Its `own` list (the provider's own documents) is the gate's to count and is\n * not named here: a warning about whom an overwrite lands on is about OTHER\n * services, as the diff's is.\n *\n * LAZY, AND PAID ONLY WHEN A WARNING FIRES. The index reads every service's\n * four documents (`livingFleet`) and, at the first question, parses the\n * `architecture/` project — through the run's read index, so a second question\n * pays for neither again; the archive's own gate calls coherence without that\n * index, so its read is not shared with this one. Measured 2026-10-10 on the\n * 120-service benchmark fleet (scripts/bench-validate.ts's shape), with one\n * feature overwriting one operation: `archive --dry-run --json` went from a\n * 1216 ms to a 1346 ms median over five runs through tsx (~0.5 s of which is\n * startup); taken apart, about 50 ms of document reads and about 120 ms of\n * LikeC4. An archive that overwrites nothing never builds it.\n *\n * What it does NOT answer: `openapi.component-modified` and\n * `asyncapi.component-modified`. A component's consumers are the operations or\n * messages whose `$ref` closure reaches it, and then THEIR consumers; nothing\n * indexes a contract by component today (an operation carries its closure's\n * digest, not its members), and building that reverse index for a warning is\n * the heavy new structure this reuse was allowed only without.\n */\nimport { consumerIndex } from \"../../../../core/consumers/consumers.js\";\nimport { consumerStance, scanDetails, type ConsumerScan } from \"../../../../core/consumers/scan.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\n\n/** The two questions the contract planners ask, each answered at most once per run's index. */\nexport interface ContractConsumers {\n /** Current consumers of `provider`'s operation `operationId`. */\n op(provider: string, operationId: string): Promise<ConsumerScan>;\n /** Current consumers of message `name` that `provider` declares. */\n message(provider: string, name: string): Promise<ConsumerScan>;\n}\n\nexport function contractConsumers(config: { docsDir: DocsDir; fleet: FleetContext }): ContractConsumers {\n // The run's own read index: the planners have already read the delta\n // services' living contracts through it, and the index memoises its fleet\n // and its map on it. Constructing the index reads nothing.\n const index = consumerIndex({ docsDir: config.docsDir, context: config.fleet });\n return {\n op: (provider, operationId) => index.opConsumers(provider, operationId),\n message: (provider, name) => index.messageConsumers(provider, name),\n };\n}\n\n/**\n * The clause an overwrite warning's message ends with, and the lines its\n * `details` carry: positive evidence first, then what could not be checked —\n * the order `loam diff` lists them in.\n */\nexport function consumerClause(scan: ConsumerScan): { clause: string; details: string[] } {\n return { clause: consumerStance(scan), details: scanDetails(scan) };\n}\n"]}
|