@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
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 +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- 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/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- 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 +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- 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 +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -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/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- 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 +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- 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 +37 -34
- 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 +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- 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 +103 -23
- 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 +106 -46
- 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 +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- 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 +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- 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/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- 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 +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- 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/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- 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/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- 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 +21 -107
- 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 +36 -14
- 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/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- 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 +55 -52
- 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 +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- 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 +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- 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/protocol.js +1 -1
- package/dist/core/agent/protocol.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/support/report.js +46 -5
- package/dist/core/agent/support/report.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 +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- 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/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- 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 +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -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 +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- 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/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- 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 +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- 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/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- 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/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- 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/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.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 +147 -16
- 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 +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- 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 +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- 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/docs.js +31 -22
- package/dist/core/docs.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/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- 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 +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- 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/delta.js +10 -7
- package/dist/core/glossary/delta.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/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.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 +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.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 +40 -10
- 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 +301 -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 +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -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 +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- 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/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -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 +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- 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 +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- 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/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- 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 +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.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 +199 -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/claims.js +31 -7
- package/dist/core/usecases/delta/claims.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/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- 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 +70 -12
- 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 +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- 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/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- 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 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/commands/explain/explain.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam explain <code|term>` — what a finding code, a refusal code or a loam\n * concept means, from the binary that emits it.\n *\n * Like `loam instructions` — and for the same reason — this command reads\n * NOTHING: no `loam.json`, no docs repo, no service, and deliberately no\n * docs-repo gate (add-command invariant 3). The vocabulary wall is hit in the\n * first minute in an unfamiliar repository, before or without wiring, and an\n * explanation that refused with `no-config` would be unreachable exactly when\n * a code is first met. The content is version-matched by construction: the\n * finding prose is parsed at runtime out of the same workflow bodies this\n * binary ships (core/explain/fix-tables.ts), so what `explain` says and what\n * /loam-check teaches cannot disagree.\n *\n * `--codes` adds the missing half: per-code lookup was always cheap, but\n * nothing could discover WHICH codes exist — the ~230 finding codes and 46\n * refusal codes were enumerable only from erased TypeScript unions or by\n * parsing the 83 KB prose body of `loam instructions loam-check`. It is opt-in\n * and off by default precisely so the term listing and every per-code page\n * stay byte-for-byte what they were: this is an addition to the contract, not\n * a change to it, and the term listing's own footer is left alone for the same\n * reason.\n */\nimport type { Command } from \"commander\";\nimport { emitJson, fail } from \"../../core/envelope/json.js\";\nimport { LOAM_VERSION } from \"../../core/envelope/version.js\";\nimport {\n explainSubject,\n firstSentence,\n knownSubjects,\n listCodes,\n listTerms,\n type CodeListing,\n type Explanation,\n} from \"../../core/explain/lookup.js\";\nimport { nearestIds } from \"../../core/repo/entries.js\";\n\ninterface ExplainOptions {\n json?: boolean;\n codes?: boolean;\n}\n\n// Interpolated from the registry that owns the terms, never hand-listed: a\n// copied list beside TERMS is a list that silently trails it.\nconst TERM_LIST = listTerms()\n .map(({ term }) => term)\n .join(\", \");\n\n/** The listing printed when no subject is asked for — also where codes are said to come from. */\nfunction printTermListing(json: boolean): void {\n const terms = listTerms();\n if (json) {\n emitJson({ command: \"explain\", version: LOAM_VERSION, terms: terms.map(({ term, summary }) => ({ term, description: summary })) });\n return;\n }\n console.log(`loam ${LOAM_VERSION} — the vocabulary\\n`);\n const width = Math.max(...terms.map(({ term }) => term.length));\n for (const { term, summary } of terms) console.log(` ${term.padEnd(width)} ${summary}`);\n console.log(\n \"\\nFinding codes come from `loam validate` and `loam verify` output; refusal codes arrive as error.code\" +\n \"\\nin any --json envelope. `loam explain <subject>` explains one.\",\n );\n}\n\n/**\n * How much of a code's meaning the inventory shows before it stops.\n *\n * The term listing prints whole first sentences because there are six of them\n * and each is under 200 characters. The code listing prints 270-odd rows whose\n * first sentences run to 377 characters, and a row that wraps four times is a\n * paragraph, not a line of a table — it destroys the one thing this listing is\n * for, which is scanning names. Nothing is lost: the sentence is verbatim in\n * `--json`, and `loam explain <code>` prints the whole entry.\n */\nconst SUMMARY_WIDTH = 72;\n\n/** The listing's one-line gloss: the first sentence of the meaning, cut at a word boundary if it runs long. */\nfunction summarize(listing: CodeListing): string {\n // A code graded in two tables is glossed by the FIRST table's meaning — the\n // listing answers \"what is this about\", and the per-scope difference (which\n // `explain <code>` prints in full, one block per table) is exactly the\n // detail a one-line row cannot carry honestly.\n const sentence = firstSentence(listing.kind === \"finding\" ? listing.entries[0]!.meaning : listing.meaning);\n if (sentence.length <= SUMMARY_WIDTH) return sentence;\n const cut = sentence.lastIndexOf(\" \", SUMMARY_WIDTH);\n return `${sentence.slice(0, cut === -1 ? SUMMARY_WIDTH : cut)}…`;\n}\n\n/**\n * `--codes`: the whole code vocabulary, the inventory that was previously\n * enumerable only out of TypeScript unions or the 83 KB /loam-check prose.\n * The JSON rows are the per-code answers verbatim (core/explain/lookup.ts\n * derives `CodeListing` from `Explanation`), so a consumer can build a\n * code-to-fix cache from one call instead of one call per code.\n */\nfunction printCodeListing(json: boolean): void {\n const codes = listCodes();\n if (json) {\n emitJson({ command: \"explain\", version: LOAM_VERSION, codes });\n return;\n }\n const findings = codes.filter((listing) => listing.kind === \"finding\");\n const refusals = codes.filter((listing) => listing.kind === \"refusal\");\n const width = Math.max(...codes.map(({ code }) => code.length));\n const print = (group: CodeListing[]): void => {\n for (const listing of group) console.log(` ${listing.code.padEnd(width)} ${summarize(listing)}`);\n };\n console.log(`loam ${LOAM_VERSION} — the code vocabulary (${findings.length} finding, ${refusals.length} refusal)\\n`);\n console.log(\"finding codes — graded by `loam validate` and `loam verify`, in their findings[]\");\n print(findings);\n console.log(\"\\nrefusal codes — the `error.code` of a --json envelope, exit 1\");\n print(refusals);\n console.log(\"\\n`loam explain <code>` prints one in full; `loam explain` with no argument lists the concept terms.\");\n}\n\nfunction printText(subject: string, explanation: Explanation): void {\n if (explanation.kind === \"term\") {\n console.log(`${explanation.term}\\n\\n${explanation.paragraph}`);\n return;\n }\n if (explanation.kind === \"refusal\") {\n console.log(`${subject} — a refusal code (error.code in the --json envelope, exit 1)\\n\\n${explanation.meaning}`);\n return;\n }\n // A finding: one block per table the code appears in, under the table's own\n // column names, so the page reads as the /loam-check row it is. The\n // severity note repeats on each scope block because it is a PER-TABLE fact:\n // `openapi.ref-unresolved` is a warn under `--service` and an error at\n // archive plan time, and a reader asking \"does this block my archive?\" must\n // not have to guess which note belongs to which context.\n const severities = [...new Set(explanation.entries.map((entry) => entry.severityNote))];\n console.log(`${subject} (${severities.join(\"; \")})${explanation.via === undefined ? \"\" : ` — surfaced by \\`${explanation.via}\\``}`);\n for (const entry of explanation.entries) {\n console.log(`\\n[${entry.scope}] (${entry.severityNote})`);\n console.log(` what it means: ${entry.meaning}`);\n if (entry.fix !== \"\") console.log(` what to do: ${entry.fix}`);\n }\n}\n\nexport function registerExplain(program: Command): void {\n program\n .command(\"explain\")\n .argument(\n \"[subject]\",\n `a finding code (spine.op-undefined), a refusal code (docs-busy), or a concept term (${TERM_LIST}); omit to list the terms`,\n )\n .description(\"Explain a finding code, refusal code or concept term, version-matched to this binary\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--codes\", \"list every finding and refusal code this binary can explain\")\n .action((subject: string | undefined, opts: ExplainOptions) => {\n const json = opts.json === true;\n if (opts.codes === true) {\n // Refused rather than quietly preferring one: `loam explain\n // spine.op-undefined --codes` asks two different questions, and a\n // caller who typed both wanted whichever one this run did NOT pick.\n if (subject !== undefined) {\n fail(\n json,\n \"invalid-option\",\n `--codes lists the whole vocabulary and takes no subject — drop '${subject}' for the inventory, or drop --codes to explain that one.`,\n );\n return;\n }\n printCodeListing(json);\n return;\n }\n if (subject === undefined) {\n printTermListing(json);\n return;\n }\n const explanation = explainSubject(subject);\n if (explanation === null) {\n // The same code `show` and `instructions` use for a name that resolves\n // to nothing, with `nearestIds` suggestions because the known set here\n // is ~200 strings — a caller one typo away should not have to diff the\n // whole vocabulary to find it.\n const near = nearestIds(subject, knownSubjects());\n fail(\n json,\n \"unknown-target\",\n `Nothing named '${subject}' — not a finding code, a refusal code, or a concept term.` +\n (near.length > 0 ? ` Close: ${near.join(\", \")}.` : \"\") +\n \" Run `loam explain` with no argument for the concept list; finding codes appear in `loam validate` and `loam verify` output.\",\n );\n return;\n }\n if (json) {\n emitJson({\n command: \"explain\",\n version: LOAM_VERSION,\n subject,\n kind: explanation.kind,\n ...(explanation.kind === \"finding\"\n ? { entries: explanation.entries, ...(explanation.via === undefined ? {} : { via: explanation.via }) }\n : explanation.kind === \"refusal\"\n ? { meaning: explanation.meaning }\n : { term: explanation.term, paragraph: explanation.paragraph }),\n });\n return;\n }\n printText(subject, explanation);\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/commands/explain/explain.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam explain <code|term>` — what a finding code, a refusal code or a loam\n * concept means, from the binary that emits it.\n *\n * Like `loam instructions` — and for the same reason — this command reads\n * NOTHING: no `loam.json`, no docs repo, no service, and deliberately no\n * docs-repo gate (add-command invariant 3). The vocabulary wall is hit in the\n * first minute in an unfamiliar repository, before or without wiring, and an\n * explanation that refused with `no-config` would be unreachable exactly when\n * a code is first met. The content is version-matched by construction: the\n * finding prose is parsed at runtime out of the same workflow bodies this\n * binary ships (core/explain/fix-tables.ts), so what `explain` says and what\n * /loam-check teaches cannot disagree.\n *\n * `--codes` adds the missing half: per-code lookup was always cheap, but\n * nothing could discover WHICH codes exist — the ~230 finding codes and 46\n * refusal codes were enumerable only from erased TypeScript unions or by\n * parsing the 83 KB prose body of `loam instructions loam-check`. It is opt-in\n * and off by default precisely so adding it changed neither the term listing\n * nor any per-code page: it was an addition to the contract, not a change to\n * it, and the term listing's own footer was left alone for the same reason.\n *\n * The TEXT of those two views has since moved, deliberately and only as text:\n * the term listing grew from six terms to the words loam prints to a person\n * and is grouped under headings, and a finding page's scope headers say the\n * invocation that reports the code instead of a bracketed flag. Every `--json`\n * shape is what it was — the listing's flat `terms[]`, and each finding's\n * `entries[].scope` with the table's own label.\n */\nimport type { Command } from \"commander\";\nimport { emitJson, fail } from \"../../core/envelope/json.js\";\nimport { LOAM_VERSION } from \"../../core/envelope/version.js\";\nimport {\n explainSubject,\n firstSentence,\n knownSubjects,\n listCodes,\n listTermGroups,\n listTerms,\n type CodeListing,\n type Explanation,\n type FindingEntry,\n} from \"../../core/explain/lookup.js\";\nimport { nearestIds } from \"../../core/repo/entries.js\";\n\ninterface ExplainOptions {\n json?: boolean;\n codes?: boolean;\n}\n\n// Interpolated from the registry that owns the terms, never hand-listed: a\n// copied list beside TERMS is a list that silently trails it.\nconst TERM_LIST = listTerms()\n .map(({ term }) => term)\n .join(\", \");\n\n/**\n * The listing printed when no subject is asked for — also where codes are said to come from.\n *\n * The text view prints the terms under their subject headings, one column wide\n * across every group so the summaries line up down the page; the `--json` view\n * stays the flat `terms[]` it has always been, because a heading is for a\n * reader scanning twenty terms and nothing a caller should branch on.\n */\nfunction printTermListing(json: boolean): void {\n if (json) {\n const terms = listTerms();\n emitJson({ command: \"explain\", version: LOAM_VERSION, terms: terms.map(({ term, summary }) => ({ term, description: summary })) });\n return;\n }\n const groups = listTermGroups();\n console.log(`loam ${LOAM_VERSION} — the vocabulary`);\n const width = Math.max(...groups.flatMap(({ terms }) => terms.map(({ term }) => term.length)));\n for (const { heading, terms } of groups) {\n console.log(`\\n${heading}`);\n for (const { term, summary } of terms) console.log(` ${term.padEnd(width)} ${summary}`);\n }\n console.log(\n \"\\nFinding codes come from `loam validate` and `loam verify` output; refusal codes arrive as error.code\" +\n \"\\nin any --json envelope. `loam explain <subject>` explains one.\",\n );\n}\n\n/**\n * How much of a code's meaning the inventory shows before it stops.\n *\n * The term listing prints whole first sentences because there are two dozen of\n * them, each under 200 characters. The code listing prints 270-odd rows whose\n * first sentences run to 377 characters, and a row that wraps four times is a\n * paragraph, not a line of a table — it destroys the one thing this listing is\n * for, which is scanning names. Nothing is lost: the sentence is verbatim in\n * `--json`, and `loam explain <code>` prints the whole entry.\n */\nconst SUMMARY_WIDTH = 72;\n\n/** The listing's one-line gloss: the first sentence of the meaning, cut at a word boundary if it runs long. */\nfunction summarize(listing: CodeListing): string {\n // A code graded in two tables is glossed by the FIRST table's meaning — the\n // listing answers \"what is this about\", and the per-scope difference (which\n // `explain <code>` prints in full, one block per table) is exactly the\n // detail a one-line row cannot carry honestly.\n const sentence = firstSentence(listing.kind === \"finding\" ? listing.entries[0]!.meaning : listing.meaning);\n if (sentence.length <= SUMMARY_WIDTH) return sentence;\n const cut = sentence.lastIndexOf(\" \", SUMMARY_WIDTH);\n return `${sentence.slice(0, cut === -1 ? SUMMARY_WIDTH : cut)}…`;\n}\n\n/**\n * `--codes`: the whole code vocabulary, the inventory that was previously\n * enumerable only out of TypeScript unions or the 83 KB /loam-check prose.\n * The JSON rows are the per-code answers verbatim (core/explain/lookup.ts\n * derives `CodeListing` from `Explanation`), so a consumer can build a\n * code-to-fix cache from one call instead of one call per code.\n */\nfunction printCodeListing(json: boolean): void {\n const codes = listCodes();\n if (json) {\n emitJson({ command: \"explain\", version: LOAM_VERSION, codes });\n return;\n }\n const findings = codes.filter((listing) => listing.kind === \"finding\");\n const refusals = codes.filter((listing) => listing.kind === \"refusal\");\n const width = Math.max(...codes.map(({ code }) => code.length));\n const print = (group: CodeListing[]): void => {\n for (const listing of group) console.log(` ${listing.code.padEnd(width)} ${summarize(listing)}`);\n };\n console.log(`loam ${LOAM_VERSION} — the code vocabulary (${findings.length} finding, ${refusals.length} refusal)\\n`);\n console.log(\"finding codes — graded by `loam validate` and `loam verify`, in their findings[]\");\n print(findings);\n console.log(\"\\nrefusal codes — the `error.code` of a --json envelope, exit 1\");\n print(refusals);\n console.log(\"\\n`loam explain <code>` prints one in full; `loam explain` with no argument lists the concept terms.\");\n}\n\n/**\n * The command a fix-table scope stands for, or null when the scope is a phrase.\n *\n * The tables label their scopes the way /loam-check's prose introduces them:\n * `--service <id>`, `--feature <FEAT-id>` and `--all` for `loam validate`'s\n * three modes, and a whole invocation (`loam archive`, `loam verify`, the\n * family registry's `loam status`) for every other command. Printed as they\n * stand, in brackets, the flag forms read as optional CLI syntax — `[--feature\n * <FEAT-id>]` is how a usage line spells an optional flag — rather than as\n * \"this is where it was reported\". So the text view says the invocation in\n * full; the `--json` `entries[].scope` keeps the table's own label, untouched.\n *\n * Two /loam-check tables are introduced by a phrase instead (\"frontmatter and\n * provenance\", \"confirmations\"); both grade inside `loam validate`, and the\n * caller says so. test/explain.test.ts holds the premise that makes that\n * reading safe: a scope that is neither a flag nor a `loam …` invocation comes\n * from the /loam-check body and from nowhere else.\n */\nfunction invocationOf(scope: string): string | null {\n if (scope.startsWith(\"--\")) return `loam validate ${scope}`;\n if (scope.startsWith(\"loam \")) return scope;\n return null;\n}\n\n/**\n * One scope block's header, in words: which command reports the code there,\n * and at what severity.\n *\n * `step` is the family registry's note for a `next.*` code, which no command\n * grades — `loam status` recommends it — so that one header says it is a\n * recommendation instead of printing \"step\" where a severity belongs.\n */\nfunction scopeHeader(entry: FindingEntry): string {\n const invocation = invocationOf(entry.scope);\n const where = invocation === null ? \"`loam validate`\" : `\\`${invocation}\\``;\n const table = invocation === null ? ` (${entry.scope})` : \"\";\n if (entry.severityNote === \"step\") return `when ${where} suggests it as a next step${table}`;\n return `when ${where} reports it${table} — ${entry.severityNote}`;\n}\n\n/**\n * The finding page's first line: what kind of code this is, and how many\n * contexts follow.\n *\n * It used to be `code (error; warn) — surfaced by …`: every scope's\n * severity joined into one parenthetical nobody could map back to a scope, and\n * the adoption brief's `via` repeating what the first block's header already\n * says. The severities now live on the block each one belongs to. The `via`\n * is kept only where no block names its invocation — a /loam-check table\n * introduced by a phrase, whose rows the brief still runs under one exact\n * command a reader would otherwise have to guess.\n */\nfunction findingHeadline(subject: string, finding: Extract<Explanation, { kind: \"finding\" }>): string {\n const { entries, via } = finding;\n if (entries.every((entry) => entry.severityNote === \"step\")) {\n return `${subject} — a next-step code: a recommendation \\`loam status\\` makes, not a finding`;\n }\n const places = entries.length > 1 ? `, reported in ${entries.length} places` : \"\";\n const unnamed = via !== undefined && !entries.some((entry) => invocationOf(entry.scope) === via);\n return `${subject} — a finding code${places}${unnamed ? `; \\`${via}\\` reports it` : \"\"}`;\n}\n\nfunction printText(subject: string, explanation: Explanation): void {\n if (explanation.kind === \"term\") {\n // An alias is answered by its term's entry, and the name printed on top is\n // the term's — so a reader who asked for `partial` and got `maturity` is\n // told that is the entry their word belongs to, not left to wonder.\n const asked = subject.toLowerCase() === explanation.term ? \"\" : ` — the entry for '${subject}'`;\n console.log(`${explanation.term}${asked}\\n\\n${explanation.paragraph}`);\n return;\n }\n if (explanation.kind === \"refusal\") {\n console.log(`${subject} — a refusal code (error.code in the --json envelope, exit 1)\\n\\n${explanation.meaning}`);\n return;\n }\n // A finding: one block per table the code appears in, under the table's own\n // column names, so the page reads as the /loam-check row it is. The\n // severity note repeats on each scope block because it is a PER-TABLE fact:\n // `openapi.ref-unresolved` is a warn under `--service` and an error at\n // archive plan time, and a reader asking \"does this block my archive?\" must\n // not have to guess which note belongs to which context.\n console.log(findingHeadline(subject, explanation));\n for (const entry of explanation.entries) {\n console.log(`\\n${scopeHeader(entry)}`);\n console.log(` what it means: ${entry.meaning}`);\n if (entry.fix !== \"\") console.log(` what to do: ${entry.fix}`);\n }\n}\n\nexport function registerExplain(program: Command): void {\n program\n .command(\"explain\")\n .argument(\n \"[subject]\",\n `a finding code (spine.op-undefined), a refusal code (docs-busy), or a concept term (${TERM_LIST}); omit to list the terms`,\n )\n .description(\"Explain a finding code, refusal code or concept term, version-matched to this binary\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--codes\", \"list every finding and refusal code this binary can explain\")\n .action((subject: string | undefined, opts: ExplainOptions) => {\n const json = opts.json === true;\n if (opts.codes === true) {\n // Refused rather than quietly preferring one: `loam explain\n // spine.op-undefined --codes` asks two different questions, and a\n // caller who typed both wanted whichever one this run did NOT pick.\n if (subject !== undefined) {\n fail(\n json,\n \"invalid-option\",\n `--codes lists the whole vocabulary and takes no subject — drop '${subject}' for the inventory, or drop --codes to explain that one.`,\n );\n return;\n }\n printCodeListing(json);\n return;\n }\n if (subject === undefined) {\n printTermListing(json);\n return;\n }\n const explanation = explainSubject(subject);\n if (explanation === null) {\n // The same code `show` and `instructions` use for a name that resolves\n // to nothing, with `nearestIds` suggestions because the known set here\n // is ~200 strings — a caller one typo away should not have to diff the\n // whole vocabulary to find it.\n const near = nearestIds(subject, knownSubjects());\n fail(\n json,\n \"unknown-target\",\n `Nothing named '${subject}' — not a finding code, a refusal code, or a concept term.` +\n (near.length > 0 ? ` Close: ${near.join(\", \")}.` : \"\") +\n \" Run `loam explain` with no argument for the concept list; finding codes appear in `loam validate` and `loam verify` output.\",\n );\n return;\n }\n if (json) {\n emitJson({\n command: \"explain\",\n version: LOAM_VERSION,\n subject,\n kind: explanation.kind,\n ...(explanation.kind === \"finding\"\n ? { entries: explanation.entries, ...(explanation.via === undefined ? {} : { via: explanation.via }) }\n : explanation.kind === \"refusal\"\n ? { meaning: explanation.meaning }\n : { term: explanation.term, paragraph: explanation.paragraph }),\n });\n return;\n }\n printText(subject, explanation);\n });\n}\n"]}
|
package/dist/commands/explore.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { loadConfig } from "../core/envelope/config.js";
|
|
2
1
|
import { explore } from "../core/explore/explore.js";
|
|
3
2
|
import { FleetContext } from "../core/fleet-context.js";
|
|
4
|
-
import { emitJson, fail
|
|
3
|
+
import { configOrRefuse, emitJson, fail } from "../core/envelope/json.js";
|
|
5
4
|
import { featureIdProblem } from "../core/kernel/ids/feature.js";
|
|
6
5
|
import { parseServiceIds } from "../core/kernel/ids/service.js";
|
|
7
6
|
import { DocsRepoUnavailableError } from "../core/repo/state.js";
|
|
@@ -59,7 +58,7 @@ export function registerExplore(program) {
|
|
|
59
58
|
}
|
|
60
59
|
// Rule 6 in docs/DESIGN.md: a raw argv string that reaches a path join is
|
|
61
60
|
// guarded at the command boundary. Every id here reaches the service path builders.
|
|
62
|
-
// Before
|
|
61
|
+
// Before the config is loaded, exactly as `loam new` guards its `--touches` list:
|
|
63
62
|
// the grammar is a fact about the argument, not about the repository, so
|
|
64
63
|
// an unusable id should answer the same whether or not you are standing
|
|
65
64
|
// in a wired repo.
|
|
@@ -72,12 +71,9 @@ export function registerExplore(program) {
|
|
|
72
71
|
fail(json, "invalid-option", seeds.problem);
|
|
73
72
|
return;
|
|
74
73
|
}
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
reportNoConfig(json, loaded);
|
|
74
|
+
const config = await configOrRefuse(json);
|
|
75
|
+
if (config === null)
|
|
78
76
|
return;
|
|
79
|
-
}
|
|
80
|
-
const config = loaded.config;
|
|
81
77
|
const { docsDir } = config;
|
|
82
78
|
// `services`, not `docs`: every answer here is a statement about the
|
|
83
79
|
// fleet, and an empty ring read out of a directory that is not a docs
|
|
@@ -117,8 +113,18 @@ function print(r) {
|
|
|
117
113
|
// Said first and without hedging: with no readable map there IS no ring,
|
|
118
114
|
// and a reader who takes the empty neighbour list at face value concludes
|
|
119
115
|
// the change is contained when nothing checked that.
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
// Same rule as `context`'s printer: name the document that actually failed.
|
|
117
|
+
// `parses` answers for the `architecture/` PROJECT, so the document at fault
|
|
118
|
+
// is very often a SIBLING beside a landscape that reads perfectly, and
|
|
119
|
+
// naming the landscape was a claim about bytes loam read fine (verification
|
|
120
|
+
// 2026-09-04). `present` is still about the FILE — it is the write target
|
|
121
|
+
// every brief asks for edits to — so the absent arm keeps the file name.
|
|
122
|
+
const named = !r.landscape.present
|
|
123
|
+
? "architecture/landscape.likec4 does not exist"
|
|
124
|
+
: r.landscape.broken.length === 0
|
|
125
|
+
? "the fleet map (architecture/) does not parse"
|
|
126
|
+
: `${r.landscape.broken.join(", ")} ${r.landscape.broken.length > 1 ? "do" : "does"} not parse`;
|
|
127
|
+
console.log(`! ${named} — no neighbour below is derived, and none is missing either\n`);
|
|
122
128
|
}
|
|
123
129
|
for (const s of r.services) {
|
|
124
130
|
console.log(`${s.id} ${label(s)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explore.js","sourceRoot":"","sources":["../../src/commands/explore.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../core/envelope/config.js\";\nimport { explore, type Exploration, type ExploreService } from \"../core/explore/explore.js\";\nimport { FleetContext } from \"../core/fleet-context.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/envelope/json.js\";\nimport { featureIdProblem } from \"../core/kernel/ids/feature.js\";\nimport { parseServiceIds } from \"../core/kernel/ids/service.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"./policy/gate.js\";\nimport { plural } from \"./policy/format.js\";\n\n/**\n * The feature id in the suggested command line when `--as` did not say.\n *\n * It has to satisfy the feature-id grammar, because `scaffold` is documented as\n * the literal `loam new` line and AGENTS.md's rule is that an instruction loam\n * prints and loam refuses is a defect. `FEAT-NNN` — the obvious placeholder —\n * is exactly that: `loam new FEAT-NNN` comes back `invalid-option`, so the last\n * line of every default run was a command that could not be run.\n *\n * `FEAT-000` parses and is still unmistakably a placeholder, which buys the\n * property the refusal was standing in for: an agent that pastes it unedited\n * gets a feature called FEAT-000, visible in `loam list` within seconds and\n * deletable with `git rm -r`. A plausible id (FEAT-1) would not be.\n */\nconst PLACEHOLDER = \"FEAT-000\";\n\ninterface ExploreOptions {\n json?: boolean;\n op?: string[];\n capability?: string[];\n as?: string;\n}\n\nexport function registerExplore(program: Command): void {\n program\n .command(\"explore\")\n .argument(\"[service...]\", \"service ids the change starts from\")\n .description(\"Read the fleet around a change nobody has written down yet — writes nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\n \"--op <operationId>\",\n \"seed from an operation instead of a service; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\n \"--capability <id>\",\n \"seed from a declared capability's realizing services; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--as <FEAT>\", `feature id for the suggested command line (default: ${PLACEHOLDER})`)\n .action(async (serviceArgs: string[], opts: ExploreOptions) => {\n const json = opts.json === true;\n const services = serviceArgs ?? [];\n const operations = opts.op ?? [];\n const capabilities = opts.capability ?? [];\n\n // `--as` is interpolated into the `loam new` line this command prints\n // under `next:` and returns as `scaffold`, and `loam-feature` teaches an\n // agent to pass `$1` straight into it. Unchecked, `explore` hands back a\n // command `loam new` then refuses — loam instructing an agent to run\n // something loam rejects, which is the one thing the printed-command\n // guard exists to prevent and the one shape it cannot see, because it\n // reads literal source strings and this line is assembled from argv.\n // Same grammar as `new`'s, from core/kernel/ids/feature.ts, so the two cannot disagree.\n if (opts.as !== undefined) {\n const problem = featureIdProblem(opts.as, \"feature id for --as\");\n if (problem !== null) {\n fail(json, \"invalid-option\", problem);\n return;\n }\n }\n if (services.length === 0 && operations.length === 0 && capabilities.length === 0) {\n fail(\n json,\n \"invalid-option\",\n \"Nothing to explore from. Name at least one service, or pass --op <operationId> or --capability <id>. \" +\n \"`loam list services` is the fleet; `loam show <service>` is one of them; \" +\n \"`loam list capabilities` is what the fleet promises.\",\n );\n return;\n }\n\n // Rule 6 in docs/DESIGN.md: a raw argv string that reaches a path join is\n // guarded at the command boundary. Every id here reaches the service path builders.\n // Before `loadConfig`, exactly as `loam new` guards its `--touches` list:\n // the grammar is a fact about the argument, not about the repository, so\n // an unusable id should answer the same whether or not you are standing\n // in a wired repo.\n //\n // A seed naming a service that does not EXIST is fine and reported as\n // `unknown` — the feature may be introducing it — but one that could not\n // be a service directory at all is a typo, not an intention.\n const seeds = parseServiceIds(services, \"service\");\n if (!seeds.ok) {\n fail(json, \"invalid-option\", seeds.problem);\n return;\n }\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // `services`, not `docs`: every answer here is a statement about the\n // fleet, and an empty ring read out of a directory that is not a docs\n // repo says \"nothing calls this service\" about a repo nobody looked in.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n try {\n const result = await explore({\n docsDir,\n services: seeds.ids,\n operations,\n capabilities,\n featureId: opts.as ?? PLACEHOLDER,\n context: new FleetContext(),\n });\n if (json) {\n emitJson({ command: \"explore\", docsDir, ...result });\n return;\n }\n print(result);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // The scan is all-or-nothing — it reads every seed's contract to build\n // one table — so the honest answer to an unreadable file is a refusal\n // naming the path, not a ring with a silent hole in it.\n reportRepositoryUnavailable(json, err, \"the exploration would be missing a service\", docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n\nfunction print(r: Exploration): void {\n if (!r.landscape.present || !r.landscape.parses) {\n // Said first and without hedging: with no readable map there IS no ring,\n // and a reader who takes the empty neighbour list at face value concludes\n // the change is contained when nothing checked that.\n const why = r.landscape.present ? \"does not parse\" : \"does not exist\";\n console.log(\n `! architecture/landscape.likec4 ${why} — no neighbour below is derived, and none is missing either\\n`,\n );\n }\n\n for (const s of r.services) {\n console.log(`${s.id} ${label(s)}`);\n if (!s.known) {\n // Two different facts wear the same absence. A SEED nobody typed by\n // accident is a service the feature introduces; one typed by accident is\n // a typo, and the near-miss list is what separates them. A neighbour is\n // neither — the fleet map drew an edge to something with no\n // `services/<id>/`, which is usually an external system and occasionally\n // a service nobody has adopted. Saying \"typo\" about that one sends a\n // reader looking for a mistake they did not make.\n const near = r.unknown.find((u) => u.id === s.id)?.nearest ?? [];\n const why =\n s.reason === \"seed\"\n ? \"the feature introduces it, or the id is a typo\"\n : \"an external system, or a service nobody has adopted\";\n console.log(\n ` no services/${s.id}/ — ${why}` +\n (near.length > 0 ? `; closest existing: ${near.join(\", \")}` : \"\"),\n );\n } else {\n if (s.missing.length > 0) console.log(` missing: ${s.missing.join(\", \")}`);\n if (!s.modelled) console.log(` not in the fleet map — no edge into it can be checked`);\n // Said before `exposes`, and said at all: an unreadable contract exposes\n // an empty list, and printing nothing there reads as \"this service offers\n // no endpoints\" — the answer that turns a MODIFIED requirement into an\n // ADDED one.\n if (s.openapi.unreadable) {\n console.log(` ! openapi.yaml does not parse — nothing here lists what ${s.id} exposes`);\n if (s.openapi.error !== undefined) console.log(` ${s.openapi.error}`);\n } else if (s.operations.length > 0) {\n console.log(` exposes: ${s.operations.join(\", \")}`);\n }\n }\n for (const e of s.inbound) console.log(` ← ${e.service}${edgeNote(e.op, e.title)}`);\n for (const e of s.outbound) console.log(` → ${e.service}${edgeNote(e.op, e.title)}`);\n console.log(\"\");\n }\n\n if (r.unresolvedOperations.length > 0) {\n console.log(\n `! no living contract defines ${r.unresolvedOperations.join(\", \")} — ` +\n `the feature is adding ${r.unresolvedOperations.length === 1 ? \"it\" : \"them\"}, or the id is wrong\\n`,\n );\n }\n\n if (r.unresolvedCapabilities.length > 0) {\n console.log(\n `! no realizing service seeds ${r.unresolvedCapabilities.join(\", \")} — ` +\n `not declared in architecture/capabilities.yaml, or declared and realized by nothing\\n`,\n );\n }\n\n if (r.neighbours.length > 0) {\n // The whole point of the command, so it is stated as a question rather than\n // a list: these are candidates for --touches, and loam does not know which\n // of them this change actually touches.\n console.log(\n `${plural(r.neighbours.length, \"neighbour\")} one hop out: ${r.neighbours.join(\", \")}`,\n );\n console.log(\" weigh each one — loam knows they are connected, not whether you change them\\n\");\n }\n\n if (r.overlaps.length > 0) {\n console.log(\"already in flight over this ground\");\n for (const o of r.overlaps) console.log(` ${o.feature} ${o.services.join(\", \")}`);\n console.log(\" `loam dependencies` says which must archive first, and what no ordering fixes\\n\");\n }\n\n console.log(`next: ${r.scaffold}`);\n}\n\nfunction label(s: ExploreService): string {\n const where = s.reason === \"seed\" ? \"seed\" : s.reason === \"calls-seed\" ? \"calls a seed\" : \"called by a seed\";\n // `known`, matching the branch in `print` — not `maturity === null`. The two\n // are set together and cannot disagree today, but discriminating on different\n // fields for the same question is how they start to: a reader fixing one\n // branch has no reason to look at the other.\n return s.known ? `${where} · ${s.maturity}` : `${where} · not in the docs repo`;\n}\n\nfunction edgeNote(op: string | null, title: string | null): string {\n if (op !== null) return ` ${op}`;\n return title !== null ? ` \"${title}\"` : \"\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"explore.js","sourceRoot":"","sources":["../../src/commands/explore.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAyC,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AACnG,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,GAAG,UAAU,CAAC;AAS/B,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;SACJ,OAAO,CAAC,SAAS,CAAC;SAClB,QAAQ,CAAC,cAAc,EAAE,oCAAoC,CAAC;SAC9D,WAAW,CAAC,6EAA6E,CAAC;SAC1F,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CACL,oBAAoB,EACpB,yDAAyD,EACzD,CAAC,KAAa,EAAE,IAAI,GAAa,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CACzD;SACA,MAAM,CACL,mBAAmB,EACnB,kEAAkE,EAClE,CAAC,KAAa,EAAE,IAAI,GAAa,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CACzD;SACA,MAAM,CAAC,aAAa,EAAE,uDAAuD,WAAW,GAAG,CAAC;SAC5F,MAAM,CAAC,KAAK,EAAE,WAAqB,EAAE,IAAoB,EAAE,EAAE;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,MAAM,QAAQ,GAAG,WAAW,IAAI,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;QAE3C,sEAAsE;QACtE,yEAAyE;QACzE,yEAAyE;QACzE,qEAAqE;QACrE,qEAAqE;QACrE,sEAAsE;QACtE,qEAAqE;QACrE,wFAAwF;QACxF,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,qBAAqB,CAAC,CAAC;YACjE,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;QACH,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClF,IAAI,CACF,IAAI,EACJ,gBAAgB,EAChB,uGAAuG;gBACrG,2EAA2E;gBAC3E,sDAAsD,CACzD,CAAC;YACF,OAAO;QACT,CAAC;QAED,0EAA0E;QAC1E,oFAAoF;QACpF,kFAAkF;QAClF,yEAAyE;QACzE,wEAAwE;QACxE,mBAAmB;QACnB,EAAE;QACF,sEAAsE;QACtE,yEAAyE;QACzE,6DAA6D;QAC7D,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;YACd,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAC3B,qEAAqE;QACrE,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC;YAAE,OAAO;QAEtD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,OAAO;gBACP,QAAQ,EAAE,KAAK,CAAC,GAAG;gBACnB,UAAU;gBACV,YAAY;gBACZ,SAAS,EAAE,IAAI,CAAC,EAAE,IAAI,WAAW;gBACjC,OAAO,EAAE,IAAI,YAAY,EAAE;aAC5B,CAAC,CAAC;YACH,IAAI,IAAI,EAAE,CAAC;gBACT,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;gBACrD,OAAO;YACT,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,wBAAwB,EAAE,CAAC;gBAC5C,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,uEAAuE;YACvE,sEAAsE;YACtE,wDAAwD;YACxD,2BAA2B,CAAC,IAAI,EAAE,GAAG,EAAE,4CAA4C,EAAE,OAAO,CAAC,CAAC;QAChG,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,wEAAwE;AAExE,SAAS,KAAK,CAAC,CAAc;IAC3B,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QAChD,yEAAyE;QACzE,0EAA0E;QAC1E,qDAAqD;QACrD,4EAA4E;QAC5E,6EAA6E;QAC7E,uEAAuE;QACvE,4EAA4E;QAC5E,0EAA0E;QAC1E,yEAAyE;QACzE,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO;YAChC,CAAC,CAAC,8CAA8C;YAChD,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;gBAC/B,CAAC,CAAC,8CAA8C;gBAChD,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC;QACpG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,gEAAgE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;YACb,oEAAoE;YACpE,yEAAyE;YACzE,wEAAwE;YACxE,4DAA4D;YAC5D,yEAAyE;YACzE,qEAAqE;YACrE,kDAAkD;YAClD,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;YACjE,MAAM,GAAG,GACP,CAAC,CAAC,MAAM,KAAK,MAAM;gBACjB,CAAC,CAAC,gDAAgD;gBAClD,CAAC,CAAC,qDAAqD,CAAC;YAC5D,OAAO,CAAC,GAAG,CACT,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,EAAE;gBACjC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAuB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9E,IAAI,CAAC,CAAC,CAAC,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;YAC1F,yEAAyE;YACzE,0EAA0E;YAC1E,uEAAuE;YACvE,aAAa;YACb,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;gBAC3F,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS;oBAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7E,CAAC;iBAAM,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvF,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,GAAG,CACT,gCAAgC,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACpE,yBAAyB,CAAC,CAAC,oBAAoB,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,wBAAwB,CACvG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,CAAC,sBAAsB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CACT,gCAAgC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACtE,uFAAuF,CAC1F,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,4EAA4E;QAC5E,2EAA2E;QAC3E,wCAAwC;QACxC,OAAO,CAAC,GAAG,CACT,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACtF,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IACjG,CAAC;IAED,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,OAAO,CAAC,GAAG,CAAC,mFAAmF,CAAC,CAAC;IACnG,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,KAAK,CAAC,CAAiB;IAC9B,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC7G,6EAA6E;IAC7E,8EAA8E;IAC9E,yEAAyE;IACzE,6CAA6C;IAC7C,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,yBAAyB,CAAC;AAClF,CAAC;AAED,SAAS,QAAQ,CAAC,EAAiB,EAAE,KAAoB;IACvD,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,KAAK,EAAE,EAAE,CAAC;IAClC,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9C,CAAC","sourcesContent":["import type { Command } from \"commander\";\nimport { explore, type Exploration, type ExploreService } from \"../core/explore/explore.js\";\nimport { FleetContext } from \"../core/fleet-context.js\";\nimport { configOrRefuse, emitJson, fail } from \"../core/envelope/json.js\";\nimport { featureIdProblem } from \"../core/kernel/ids/feature.js\";\nimport { parseServiceIds } from \"../core/kernel/ids/service.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"./policy/gate.js\";\nimport { plural } from \"./policy/format.js\";\n\n/**\n * The feature id in the suggested command line when `--as` did not say.\n *\n * It has to satisfy the feature-id grammar, because `scaffold` is documented as\n * the literal `loam new` line and AGENTS.md's rule is that an instruction loam\n * prints and loam refuses is a defect. `FEAT-NNN` — the obvious placeholder —\n * is exactly that: `loam new FEAT-NNN` comes back `invalid-option`, so the last\n * line of every default run was a command that could not be run.\n *\n * `FEAT-000` parses and is still unmistakably a placeholder, which buys the\n * property the refusal was standing in for: an agent that pastes it unedited\n * gets a feature called FEAT-000, visible in `loam list` within seconds and\n * deletable with `git rm -r`. A plausible id (FEAT-1) would not be.\n */\nconst PLACEHOLDER = \"FEAT-000\";\n\ninterface ExploreOptions {\n json?: boolean;\n op?: string[];\n capability?: string[];\n as?: string;\n}\n\nexport function registerExplore(program: Command): void {\n program\n .command(\"explore\")\n .argument(\"[service...]\", \"service ids the change starts from\")\n .description(\"Read the fleet around a change nobody has written down yet — writes nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\n \"--op <operationId>\",\n \"seed from an operation instead of a service; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\n \"--capability <id>\",\n \"seed from a declared capability's realizing services; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--as <FEAT>\", `feature id for the suggested command line (default: ${PLACEHOLDER})`)\n .action(async (serviceArgs: string[], opts: ExploreOptions) => {\n const json = opts.json === true;\n const services = serviceArgs ?? [];\n const operations = opts.op ?? [];\n const capabilities = opts.capability ?? [];\n\n // `--as` is interpolated into the `loam new` line this command prints\n // under `next:` and returns as `scaffold`, and `loam-feature` teaches an\n // agent to pass `$1` straight into it. Unchecked, `explore` hands back a\n // command `loam new` then refuses — loam instructing an agent to run\n // something loam rejects, which is the one thing the printed-command\n // guard exists to prevent and the one shape it cannot see, because it\n // reads literal source strings and this line is assembled from argv.\n // Same grammar as `new`'s, from core/kernel/ids/feature.ts, so the two cannot disagree.\n if (opts.as !== undefined) {\n const problem = featureIdProblem(opts.as, \"feature id for --as\");\n if (problem !== null) {\n fail(json, \"invalid-option\", problem);\n return;\n }\n }\n if (services.length === 0 && operations.length === 0 && capabilities.length === 0) {\n fail(\n json,\n \"invalid-option\",\n \"Nothing to explore from. Name at least one service, or pass --op <operationId> or --capability <id>. \" +\n \"`loam list services` is the fleet; `loam show <service>` is one of them; \" +\n \"`loam list capabilities` is what the fleet promises.\",\n );\n return;\n }\n\n // Rule 6 in docs/DESIGN.md: a raw argv string that reaches a path join is\n // guarded at the command boundary. Every id here reaches the service path builders.\n // Before the config is loaded, exactly as `loam new` guards its `--touches` list:\n // the grammar is a fact about the argument, not about the repository, so\n // an unusable id should answer the same whether or not you are standing\n // in a wired repo.\n //\n // A seed naming a service that does not EXIST is fine and reported as\n // `unknown` — the feature may be introducing it — but one that could not\n // be a service directory at all is a typo, not an intention.\n const seeds = parseServiceIds(services, \"service\");\n if (!seeds.ok) {\n fail(json, \"invalid-option\", seeds.problem);\n return;\n }\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // `services`, not `docs`: every answer here is a statement about the\n // fleet, and an empty ring read out of a directory that is not a docs\n // repo says \"nothing calls this service\" about a repo nobody looked in.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n try {\n const result = await explore({\n docsDir,\n services: seeds.ids,\n operations,\n capabilities,\n featureId: opts.as ?? PLACEHOLDER,\n context: new FleetContext(),\n });\n if (json) {\n emitJson({ command: \"explore\", docsDir, ...result });\n return;\n }\n print(result);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // The scan is all-or-nothing — it reads every seed's contract to build\n // one table — so the honest answer to an unreadable file is a refusal\n // naming the path, not a ring with a silent hole in it.\n reportRepositoryUnavailable(json, err, \"the exploration would be missing a service\", docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n\nfunction print(r: Exploration): void {\n if (!r.landscape.present || !r.landscape.parses) {\n // Said first and without hedging: with no readable map there IS no ring,\n // and a reader who takes the empty neighbour list at face value concludes\n // the change is contained when nothing checked that.\n // Same rule as `context`'s printer: name the document that actually failed.\n // `parses` answers for the `architecture/` PROJECT, so the document at fault\n // is very often a SIBLING beside a landscape that reads perfectly, and\n // naming the landscape was a claim about bytes loam read fine (verification\n // 2026-09-04). `present` is still about the FILE — it is the write target\n // every brief asks for edits to — so the absent arm keeps the file name.\n const named = !r.landscape.present\n ? \"architecture/landscape.likec4 does not exist\"\n : r.landscape.broken.length === 0\n ? \"the fleet map (architecture/) does not parse\"\n : `${r.landscape.broken.join(\", \")} ${r.landscape.broken.length > 1 ? \"do\" : \"does\"} not parse`;\n console.log(`! ${named} — no neighbour below is derived, and none is missing either\\n`);\n }\n\n for (const s of r.services) {\n console.log(`${s.id} ${label(s)}`);\n if (!s.known) {\n // Two different facts wear the same absence. A SEED nobody typed by\n // accident is a service the feature introduces; one typed by accident is\n // a typo, and the near-miss list is what separates them. A neighbour is\n // neither — the fleet map drew an edge to something with no\n // `services/<id>/`, which is usually an external system and occasionally\n // a service nobody has adopted. Saying \"typo\" about that one sends a\n // reader looking for a mistake they did not make.\n const near = r.unknown.find((u) => u.id === s.id)?.nearest ?? [];\n const why =\n s.reason === \"seed\"\n ? \"the feature introduces it, or the id is a typo\"\n : \"an external system, or a service nobody has adopted\";\n console.log(\n ` no services/${s.id}/ — ${why}` +\n (near.length > 0 ? `; closest existing: ${near.join(\", \")}` : \"\"),\n );\n } else {\n if (s.missing.length > 0) console.log(` missing: ${s.missing.join(\", \")}`);\n if (!s.modelled) console.log(` not in the fleet map — no edge into it can be checked`);\n // Said before `exposes`, and said at all: an unreadable contract exposes\n // an empty list, and printing nothing there reads as \"this service offers\n // no endpoints\" — the answer that turns a MODIFIED requirement into an\n // ADDED one.\n if (s.openapi.unreadable) {\n console.log(` ! openapi.yaml does not parse — nothing here lists what ${s.id} exposes`);\n if (s.openapi.error !== undefined) console.log(` ${s.openapi.error}`);\n } else if (s.operations.length > 0) {\n console.log(` exposes: ${s.operations.join(\", \")}`);\n }\n }\n for (const e of s.inbound) console.log(` ← ${e.service}${edgeNote(e.op, e.title)}`);\n for (const e of s.outbound) console.log(` → ${e.service}${edgeNote(e.op, e.title)}`);\n console.log(\"\");\n }\n\n if (r.unresolvedOperations.length > 0) {\n console.log(\n `! no living contract defines ${r.unresolvedOperations.join(\", \")} — ` +\n `the feature is adding ${r.unresolvedOperations.length === 1 ? \"it\" : \"them\"}, or the id is wrong\\n`,\n );\n }\n\n if (r.unresolvedCapabilities.length > 0) {\n console.log(\n `! no realizing service seeds ${r.unresolvedCapabilities.join(\", \")} — ` +\n `not declared in architecture/capabilities.yaml, or declared and realized by nothing\\n`,\n );\n }\n\n if (r.neighbours.length > 0) {\n // The whole point of the command, so it is stated as a question rather than\n // a list: these are candidates for --touches, and loam does not know which\n // of them this change actually touches.\n console.log(\n `${plural(r.neighbours.length, \"neighbour\")} one hop out: ${r.neighbours.join(\", \")}`,\n );\n console.log(\" weigh each one — loam knows they are connected, not whether you change them\\n\");\n }\n\n if (r.overlaps.length > 0) {\n console.log(\"already in flight over this ground\");\n for (const o of r.overlaps) console.log(` ${o.feature} ${o.services.join(\", \")}`);\n console.log(\" `loam dependencies` says which must archive first, and what no ordering fixes\\n\");\n }\n\n console.log(`next: ${r.scaffold}`);\n}\n\nfunction label(s: ExploreService): string {\n const where = s.reason === \"seed\" ? \"seed\" : s.reason === \"calls-seed\" ? \"calls a seed\" : \"called by a seed\";\n // `known`, matching the branch in `print` — not `maturity === null`. The two\n // are set together and cannot disagree today, but discriminating on different\n // fields for the same question is how they start to: a reader fixing one\n // branch has no reason to look at the other.\n return s.known ? `${where} · ${s.maturity}` : `${where} · not in the docs repo`;\n}\n\nfunction edgeNote(op: string | null, title: string | null): string {\n if (op !== null) return ` ${op}`;\n return title !== null ? ` \"${title}\"` : \"\";\n}\n"]}
|
|
@@ -1,29 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { emitJson, fail, NO_SERVICE_MESSAGE, reportNoConfig } from "../../core/envelope/json.js";
|
|
1
|
+
import { configOrRefuse, emitJson, fail, NO_SERVICE_MESSAGE } from "../../core/envelope/json.js";
|
|
3
2
|
import { FleetContext } from "../../core/fleet-context.js";
|
|
3
|
+
import { listBaseSubdirs, subdirsChangedSince } from "../../core/diff/base-git.js";
|
|
4
4
|
import { gateReport } from "../../core/gate/checks.js";
|
|
5
5
|
import { gateSummary, gateVerdict } from "../../core/gate/report.js";
|
|
6
6
|
import { nearestIds } from "../../core/repo/entries.js";
|
|
7
|
+
import { archiveDir } from "../../core/repo/paths.js";
|
|
7
8
|
import { enumeratedServiceIds, resolveServiceTarget } from "../../core/repo/service-target.js";
|
|
8
9
|
import { DocsRepoUnavailableError } from "../../core/repo/state.js";
|
|
9
10
|
import { findingJson } from "../../core/vocabulary/report.js";
|
|
10
|
-
import { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable, } from "../policy/gate.js";
|
|
11
|
+
import { baseOrRefuse, docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable, } from "../policy/gate.js";
|
|
11
12
|
import { printGate } from "./print.js";
|
|
13
|
+
/**
|
|
14
|
+
* `--since <ref>` resolved against the docs repo's history, or null after
|
|
15
|
+
* refusing. A listing git declines is a refusal too, never an empty one: "no
|
|
16
|
+
* archive at the ref" grades every archived feature in the repo as shipped
|
|
17
|
+
* since, and reading "could not list" as that would be a guess. The second
|
|
18
|
+
* question — which of those directories changed since — has a safe answer to
|
|
19
|
+
* fall to, and falls to it (see below).
|
|
20
|
+
*/
|
|
21
|
+
async function readSince(json, docsDir, ref) {
|
|
22
|
+
const base = await baseOrRefuse(json, docsDir, {
|
|
23
|
+
flag: "--since",
|
|
24
|
+
ref,
|
|
25
|
+
reads: "loam gate --since reads which features were already archived at that ref",
|
|
26
|
+
});
|
|
27
|
+
if (base === null)
|
|
28
|
+
return null;
|
|
29
|
+
const listing = await listBaseSubdirs(base, archiveDir(docsDir));
|
|
30
|
+
if (listing.kind === "failed") {
|
|
31
|
+
fail(json, "repository-unavailable", `git could not list features/archive/ at ${ref} in the docs repo at ${docsDir} (${listing.detail}) — ` +
|
|
32
|
+
`without it there is no telling which features shipped since, and the gate will not guess. Re-run once git can answer.`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// Present at the ref is not enough to have shipped by then: a feature
|
|
36
|
+
// unarchived, edited and archived again sits at the same path with
|
|
37
|
+
// different contents, and it is this deploy's to answer for. Only a
|
|
38
|
+
// directory whose contents are what they were at the ref stays out. When
|
|
39
|
+
// git will not say what changed, every one of them is graded — the
|
|
40
|
+
// direction this gate fails in is "looked at too much", never "skipped a
|
|
41
|
+
// ship" — unlike the listing above, which has no safe default to fall to.
|
|
42
|
+
const changed = await subdirsChangedSince(base, archiveDir(docsDir));
|
|
43
|
+
const moved = changed.kind === "ok" ? changed.names : new Set(listing.names);
|
|
44
|
+
return { ref, commit: base.commit, shippedAtRef: new Set(listing.names.filter((name) => !moved.has(name))) };
|
|
45
|
+
}
|
|
12
46
|
export function registerGate(program) {
|
|
13
47
|
program
|
|
14
48
|
.command("gate")
|
|
15
49
|
.description("Can this service deploy? A read-only query over recorded evidence — advisory by default")
|
|
16
50
|
.option("--service <id>", "the service being deployed (defaults to the configured service)")
|
|
51
|
+
.option("--since <ref>", "also grade the features archived after this docs-repo git ref (e.g. the docs commit your last deploy recorded) — without it only in-flight features are read")
|
|
17
52
|
.option("--strict", "exit 1 on any warning too — a per-invocation CI lever; the report and --json payload do not change")
|
|
18
53
|
.option("--json", "emit the machine contract instead of the human view")
|
|
19
54
|
.action(async (opts) => {
|
|
20
55
|
const json = opts.json === true;
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
reportNoConfig(json, loaded);
|
|
56
|
+
const config = await configOrRefuse(json);
|
|
57
|
+
if (config === null)
|
|
24
58
|
return;
|
|
25
|
-
}
|
|
26
|
-
const config = loaded.config;
|
|
27
59
|
const { docsDir } = config;
|
|
28
60
|
// The gate joins partners and enumerates features against services/ —
|
|
29
61
|
// a docsDir that is not a docs repo must refuse, never answer "pass"
|
|
@@ -37,6 +69,9 @@ export function registerGate(program) {
|
|
|
37
69
|
fail(json, "invalid-option", NO_SERVICE_MESSAGE);
|
|
38
70
|
return;
|
|
39
71
|
}
|
|
72
|
+
const since = opts.since === undefined ? undefined : await readSince(json, docsDir, opts.since);
|
|
73
|
+
if (since === null)
|
|
74
|
+
return;
|
|
40
75
|
// One invocation, one filesystem snapshot (validate's rule).
|
|
41
76
|
const fleet = new FleetContext();
|
|
42
77
|
try {
|
|
@@ -68,6 +103,7 @@ export function registerGate(program) {
|
|
|
68
103
|
service: resolved.id,
|
|
69
104
|
repoDir: bound === resolved.id ? (config.root ?? process.cwd()) : undefined,
|
|
70
105
|
fleet,
|
|
106
|
+
since,
|
|
71
107
|
});
|
|
72
108
|
const verdict = gateVerdict(report.checks);
|
|
73
109
|
const summary = gateSummary(report.checks);
|
|
@@ -78,6 +114,7 @@ export function registerGate(program) {
|
|
|
78
114
|
service: report.service,
|
|
79
115
|
verdict,
|
|
80
116
|
strict: opts.strict === true,
|
|
117
|
+
since: report.since,
|
|
81
118
|
landscape: report.landscape,
|
|
82
119
|
partners: report.partners,
|
|
83
120
|
features: report.features,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/commands/gate/gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gate` — can this service deploy, as far as RECORDED evidence can say?\n *\n * A pure query for DEPLOY pipelines outside loam's own lifecycle (Pact's\n * can-i-deploy insight: the gate executes nothing and queries evidence\n * previously recorded). It reads, never writes, never takes the docs lock, and\n * deliberately changes NOTHING about what gates `loam archive` — verify still\n * never gates the merge (WORKFLOW.md, \"What actually gates\"); this command's\n * verdict is advice to a pipeline loam does not own. Advisory by default:\n * warnings exit 0, errors exit 1, and `--strict` is the CI lever that\n * escalates warnings — validate's exact idiom, not a second spelling of it.\n *\n * Not to be confused with `commands/policy/gate.ts`, which is the docs-repo\n * READINESS doctrine (docsRepoReady and friends) every reading command shares\n * — including this one, one import below.\n */\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { emitJson, fail, NO_SERVICE_MESSAGE, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { gateReport } from \"../../core/gate/checks.js\";\nimport { gateSummary, gateVerdict } from \"../../core/gate/report.js\";\nimport { nearestIds } from \"../../core/repo/entries.js\";\nimport { enumeratedServiceIds, resolveServiceTarget } from \"../../core/repo/service-target.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { findingJson } from \"../../core/vocabulary/report.js\";\nimport {\n docsRepoReady,\n reportDocsRepoError,\n reportRepositoryUnavailable,\n} from \"../policy/gate.js\";\nimport { printGate } from \"./print.js\";\n\ninterface GateOptions {\n service?: string;\n strict?: boolean;\n json?: boolean;\n}\n\nexport function registerGate(program: Command): void {\n program\n .command(\"gate\")\n .description(\"Can this service deploy? A read-only query over recorded evidence — advisory by default\")\n .option(\"--service <id>\", \"the service being deployed (defaults to the configured service)\")\n .option(\"--strict\", \"exit 1 on any warning too — a per-invocation CI lever; the report and --json payload do not change\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: GateOptions) => {\n const json = opts.json === true;\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // The gate joins partners and enumerates features against services/ —\n // a docsDir that is not a docs repo must refuse, never answer \"pass\"\n // over an empty fleet (docsRepoReady's whole doctrine).\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n // The deploy-pipeline invocation is literally `loam gate --json` in a\n // bound service repo: the binding IS the service being deployed.\n const service = opts.service ?? config.service;\n if (!service) {\n fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n return;\n }\n // One invocation, one filesystem snapshot (validate's rule).\n const fleet = new FleetContext();\n\n try {\n // Enumeration first, grammar second — service-target.ts's banner: the\n // one directory a caller most needs to ask about may carry a name the\n // grammar refuses, so the raw argument is never assertServiceId'd here.\n const resolved = await resolveServiceTarget(docsDir, service, \"--service\", fleet);\n if (!resolved.ok) {\n fail(json, \"invalid-option\", resolved.problem);\n return;\n }\n if (resolved.dir === undefined) {\n // Grammar-legal but unenumerated: a question recorded evidence\n // cannot answer — Pact's gate answers unknown participants with a\n // refusal, not a pass (status's `unknown-service` precedent).\n const near = nearestIds(resolved.id, await enumeratedServiceIds(docsDir, fleet));\n fail(\n json,\n \"unknown-service\",\n `No service '${resolved.id}' under ${docsDir}/services/ — nothing is recorded for a service nobody adopted, so the deploy question is unanswerable.` +\n (near.length > 0 ? ` Did you mean: ${near.join(\", \")}?` : \"\") +\n ` Run \\`loam adopt --service ${resolved.id}\\` to write its baseline first.`,\n );\n return;\n }\n // `sources` only mean something from inside the service's own\n // repository — validate's repoOf rule. `config.root` rather than\n // process.cwd(): the config's own directory is the repo root, and a\n // run from src/deep/ must grade the same paths as one from the root.\n const bound: string | undefined = config.service;\n const report = await gateReport({\n docsDir,\n service: resolved.id,\n repoDir: bound === resolved.id ? (config.root ?? process.cwd()) : undefined,\n fleet,\n });\n const verdict = gateVerdict(report.checks);\n const summary = gateSummary(report.checks);\n if (json) {\n emitJson({\n command: \"gate\",\n docsDir,\n service: report.service,\n verdict,\n strict: opts.strict === true,\n landscape: report.landscape,\n partners: report.partners,\n features: report.features,\n checks: report.checks.map((c) => ({\n check: c.check,\n findings: c.findings.map((finding) => findingJson(finding)),\n })),\n summary,\n });\n } else {\n printGate(report, verdict, summary);\n }\n // --strict moves only the exit code, never the verdict or the payload\n // — validate's exact stance, documented on its own strict block: two\n // pipelines reading one repo may grade the same report differently\n // and both are telling the truth.\n if (verdict === \"fail\" || (opts.strict === true && summary.warnings > 0)) {\n process.exitCode = 1;\n }\n } catch (err) {\n // The docs repo went away between the gate above and the reads.\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // An IO failure the per-subject containment could not localise — it\n // happened in an enumeration, so no partial report exists to give.\n // The recognizer throws anything carrying no errno untouched.\n reportRepositoryUnavailable(\n json,\n err,\n `the deploy question for '${service}' is unanswerable`,\n docsDir,\n );\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/commands/gate/gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gate` — can this service deploy, as far as RECORDED evidence can say?\n *\n * A pure query for DEPLOY pipelines outside loam's own lifecycle (Pact's\n * can-i-deploy insight: the gate executes nothing and queries evidence\n * previously recorded). It reads, never writes, never takes the docs lock, and\n * deliberately changes NOTHING about what gates `loam archive` — verify still\n * never gates the merge (WORKFLOW.md, \"What actually gates\"); this command's\n * verdict is advice to a pipeline loam does not own. Advisory by default:\n * warnings exit 0, errors exit 1, and `--strict` is the CI lever that\n * escalates warnings — validate's exact idiom, not a second spelling of it.\n *\n * Without `--since` the verification check reads IN-FLIGHT features only: a\n * feature leaves it the moment it is archived, so a deploy that ships slices\n * after their feature archived would be gated without it. `--since <ref>` —\n * a docs-repo git ref, the commit the pipeline last deployed from — adds every\n * feature archived after it, read out of git exactly as `loam diff --base`\n * reads its base (`core/diff/base-git.ts`), with the same two refusals when\n * git cannot answer (`baseOrRefuse`). Nothing is stored: the pipeline already\n * knows its last-deployed ref, and loam keeps no deployment state.\n *\n * Not to be confused with `commands/policy/gate.ts`, which is the docs-repo\n * READINESS doctrine (docsRepoReady and friends, and `baseOrRefuse` for a git\n * ref) every reading command shares — including this one, one import below.\n */\nimport type { Command } from \"commander\";\nimport { configOrRefuse, emitJson, fail, NO_SERVICE_MESSAGE } from \"../../core/envelope/json.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { listBaseSubdirs, subdirsChangedSince } from \"../../core/diff/base-git.js\";\nimport { gateReport } from \"../../core/gate/checks.js\";\nimport { gateSummary, gateVerdict, type GateSince } from \"../../core/gate/report.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { nearestIds } from \"../../core/repo/entries.js\";\nimport { archiveDir } from \"../../core/repo/paths.js\";\nimport { enumeratedServiceIds, resolveServiceTarget } from \"../../core/repo/service-target.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { findingJson } from \"../../core/vocabulary/report.js\";\nimport {\n baseOrRefuse,\n docsRepoReady,\n reportDocsRepoError,\n reportRepositoryUnavailable,\n} from \"../policy/gate.js\";\nimport { printGate } from \"./print.js\";\n\ninterface GateOptions {\n service?: string;\n since?: string;\n strict?: boolean;\n json?: boolean;\n}\n\n/**\n * `--since <ref>` resolved against the docs repo's history, or null after\n * refusing. A listing git declines is a refusal too, never an empty one: \"no\n * archive at the ref\" grades every archived feature in the repo as shipped\n * since, and reading \"could not list\" as that would be a guess. The second\n * question — which of those directories changed since — has a safe answer to\n * fall to, and falls to it (see below).\n */\nasync function readSince(json: boolean, docsDir: DocsDir, ref: string): Promise<GateSince | null> {\n const base = await baseOrRefuse(json, docsDir, {\n flag: \"--since\",\n ref,\n reads: \"loam gate --since reads which features were already archived at that ref\",\n });\n if (base === null) return null;\n const listing = await listBaseSubdirs(base, archiveDir(docsDir));\n if (listing.kind === \"failed\") {\n fail(\n json,\n \"repository-unavailable\",\n `git could not list features/archive/ at ${ref} in the docs repo at ${docsDir} (${listing.detail}) — ` +\n `without it there is no telling which features shipped since, and the gate will not guess. Re-run once git can answer.`,\n );\n return null;\n }\n // Present at the ref is not enough to have shipped by then: a feature\n // unarchived, edited and archived again sits at the same path with\n // different contents, and it is this deploy's to answer for. Only a\n // directory whose contents are what they were at the ref stays out. When\n // git will not say what changed, every one of them is graded — the\n // direction this gate fails in is \"looked at too much\", never \"skipped a\n // ship\" — unlike the listing above, which has no safe default to fall to.\n const changed = await subdirsChangedSince(base, archiveDir(docsDir));\n const moved = changed.kind === \"ok\" ? changed.names : new Set(listing.names);\n return { ref, commit: base.commit, shippedAtRef: new Set(listing.names.filter((name) => !moved.has(name))) };\n}\n\nexport function registerGate(program: Command): void {\n program\n .command(\"gate\")\n .description(\"Can this service deploy? A read-only query over recorded evidence — advisory by default\")\n .option(\"--service <id>\", \"the service being deployed (defaults to the configured service)\")\n .option(\n \"--since <ref>\",\n \"also grade the features archived after this docs-repo git ref (e.g. the docs commit your last deploy recorded) — without it only in-flight features are read\",\n )\n .option(\"--strict\", \"exit 1 on any warning too — a per-invocation CI lever; the report and --json payload do not change\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: GateOptions) => {\n const json = opts.json === true;\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // The gate joins partners and enumerates features against services/ —\n // a docsDir that is not a docs repo must refuse, never answer \"pass\"\n // over an empty fleet (docsRepoReady's whole doctrine).\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n // The deploy-pipeline invocation is literally `loam gate --json` in a\n // bound service repo: the binding IS the service being deployed.\n const service = opts.service ?? config.service;\n if (!service) {\n fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n return;\n }\n const since = opts.since === undefined ? undefined : await readSince(json, docsDir, opts.since);\n if (since === null) return;\n // One invocation, one filesystem snapshot (validate's rule).\n const fleet = new FleetContext();\n\n try {\n // Enumeration first, grammar second — service-target.ts's banner: the\n // one directory a caller most needs to ask about may carry a name the\n // grammar refuses, so the raw argument is never assertServiceId'd here.\n const resolved = await resolveServiceTarget(docsDir, service, \"--service\", fleet);\n if (!resolved.ok) {\n fail(json, \"invalid-option\", resolved.problem);\n return;\n }\n if (resolved.dir === undefined) {\n // Grammar-legal but unenumerated: a question recorded evidence\n // cannot answer — Pact's gate answers unknown participants with a\n // refusal, not a pass (status's `unknown-service` precedent).\n const near = nearestIds(resolved.id, await enumeratedServiceIds(docsDir, fleet));\n fail(\n json,\n \"unknown-service\",\n `No service '${resolved.id}' under ${docsDir}/services/ — nothing is recorded for a service nobody adopted, so the deploy question is unanswerable.` +\n (near.length > 0 ? ` Did you mean: ${near.join(\", \")}?` : \"\") +\n ` Run \\`loam adopt --service ${resolved.id}\\` to write its baseline first.`,\n );\n return;\n }\n // `sources` only mean something from inside the service's own\n // repository — validate's repoOf rule. `config.root` rather than\n // process.cwd(): the config's own directory is the repo root, and a\n // run from src/deep/ must grade the same paths as one from the root.\n const bound: string | undefined = config.service;\n const report = await gateReport({\n docsDir,\n service: resolved.id,\n repoDir: bound === resolved.id ? (config.root ?? process.cwd()) : undefined,\n fleet,\n since,\n });\n const verdict = gateVerdict(report.checks);\n const summary = gateSummary(report.checks);\n if (json) {\n emitJson({\n command: \"gate\",\n docsDir,\n service: report.service,\n verdict,\n strict: opts.strict === true,\n since: report.since,\n landscape: report.landscape,\n partners: report.partners,\n features: report.features,\n checks: report.checks.map((c) => ({\n check: c.check,\n findings: c.findings.map((finding) => findingJson(finding)),\n })),\n summary,\n });\n } else {\n printGate(report, verdict, summary);\n }\n // --strict moves only the exit code, never the verdict or the payload\n // — validate's exact stance, documented on its own strict block: two\n // pipelines reading one repo may grade the same report differently\n // and both are telling the truth.\n if (verdict === \"fail\" || (opts.strict === true && summary.warnings > 0)) {\n process.exitCode = 1;\n }\n } catch (err) {\n // The docs repo went away between the gate above and the reads.\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // An IO failure the per-subject containment could not localise — it\n // happened in an enumeration, so no partial report exists to give.\n // The recognizer throws anything carrying no errno untouched.\n reportRepositoryUnavailable(\n json,\n err,\n `the deploy question for '${service}' is unanswerable`,\n docsDir,\n );\n }\n });\n}\n"]}
|
|
@@ -32,13 +32,25 @@ export function printGate(report, verdict, summary) {
|
|
|
32
32
|
if (freshness.length === 0)
|
|
33
33
|
console.log(` ${SEVERITY_MARK.ok} nothing recorded has gone stale`);
|
|
34
34
|
printFindings(freshness);
|
|
35
|
-
|
|
35
|
+
// Under --since the heading names the bound, so a deploy log says which
|
|
36
|
+
// shipped features it graded; without it, that only in-flight ones were.
|
|
37
|
+
const since = report.since;
|
|
38
|
+
console.log(since === null ? "\nverification" : `\nverification — in flight, and archived since ${since.ref}`);
|
|
36
39
|
for (const f of report.features) {
|
|
37
40
|
const attested = f.attested > 0 ? `, ${f.attested} on an agent's word` : "";
|
|
38
|
-
|
|
41
|
+
const shipped = f.archived === true ? " · archived" : "";
|
|
42
|
+
console.log(` ${f.id} · ${f.verdict} (${f.state}; ${f.confirmed}/${f.claims} confirmed${attested})${shipped}`);
|
|
43
|
+
// A record read as frozen history because its archive captured no
|
|
44
|
+
// checklist (`checklistAtArchive: null`) was not held to the feature that
|
|
45
|
+
// shipped; a deploy log must not read that row as one that was.
|
|
46
|
+
if (f.archived === true && f.checklistAtArchive === null && f.state === "recorded") {
|
|
47
|
+
console.log(" its archive captured no checklist (archived by an older loam), so whether the record still answered the feature when it shipped is unknown — read as frozen history");
|
|
48
|
+
}
|
|
39
49
|
}
|
|
40
50
|
if (report.features.length === 0) {
|
|
41
|
-
console.log(
|
|
51
|
+
console.log(since === null
|
|
52
|
+
? " (no active feature carries a delta for this service)"
|
|
53
|
+
: ` (no feature in flight or archived since ${since.ref} carries a delta for this service)`);
|
|
42
54
|
}
|
|
43
55
|
printFindings(check("verification"));
|
|
44
56
|
console.log("\ninterrupted");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print.js","sourceRoot":"","sources":["../../../src/commands/gate/print.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAgB,MAAM,iCAAiC,CAAC;AAE9E,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,SAAS,aAAa,CAAC,QAAmB;IACxC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3D,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,MAAkB,EAClB,OAAoB,EACpB,OAA6C;IAE7C,OAAO,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,OAAO,wDAAwD,CAAC,CAAC;IAE9F,MAAM,KAAK,GAAG,CAAC,IAA6C,EAAa,EAAE,CACzE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAC;IAE9D,OAAO,CAAC,GAAG,CAAC,0BAA0B,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAC1D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,wBAAwB,CAAC,CAAC;QAChF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,MAAM,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;IAC7E,CAAC;IACD,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,aAAa,CAAC,EAAE,kCAAkC,CAAC,CAAC;IACjG,aAAa,CAAC,SAAS,CAAC,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"print.js","sourceRoot":"","sources":["../../../src/commands/gate/print.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The human rendering of a gate report: one block per check, the partner and\n * feature tables as data lines, findings with the shared severity glyphs, and\n * the verdict last — the line a person scanning a deploy log reads first from\n * the bottom. The JSON contract never comes through here; `gate.ts` emits the\n * envelope from the same report, which is what keeps the two views one\n * derivation.\n */\nimport { SEVERITY_MARK, type Finding } from \"../../core/vocabulary/report.js\";\nimport type { GateReport, GateVerdict } from \"../../core/gate/report.js\";\nimport { plural } from \"../policy/format.js\";\n\nfunction printFindings(findings: Finding[]): void {\n for (const f of findings) {\n console.log(` ${SEVERITY_MARK[f.severity]} ${f.message}`);\n for (const d of f.details ?? []) console.log(` - ${d}`);\n }\n}\n\nexport function printGate(\n report: GateReport,\n verdict: GateVerdict,\n summary: { errors: number; warnings: number },\n): void {\n console.log(`gate — ${report.service} (a query over recorded evidence; it executes nothing)`);\n\n const check = (name: (typeof report.checks)[number][\"check\"]): Finding[] =>\n report.checks.find((c) => c.check === name)?.findings ?? [];\n\n console.log(`\\npartners — landscape ${report.landscape}`);\n for (const p of report.partners) {\n const rung = p.external ? \"external\" : (p.maturity ?? \"no services/ directory\");\n console.log(` ${p.service} · ${rung} · ${p.role} · ${p.via.join(\", \")}`);\n }\n if (report.partners.length === 0 && report.landscape === \"read\") {\n console.log(\" (the landscape draws no join into or out of this service)\");\n }\n printFindings(check(\"partners\"));\n\n console.log(\"\\nfreshness\");\n const freshness = check(\"freshness\");\n if (freshness.length === 0) console.log(` ${SEVERITY_MARK.ok} nothing recorded has gone stale`);\n printFindings(freshness);\n\n // Under --since the heading names the bound, so a deploy log says which\n // shipped features it graded; without it, that only in-flight ones were.\n const since = report.since;\n console.log(since === null ? \"\\nverification\" : `\\nverification — in flight, and archived since ${since.ref}`);\n for (const f of report.features) {\n const attested = f.attested > 0 ? `, ${f.attested} on an agent's word` : \"\";\n const shipped = f.archived === true ? \" · archived\" : \"\";\n console.log(` ${f.id} · ${f.verdict} (${f.state}; ${f.confirmed}/${f.claims} confirmed${attested})${shipped}`);\n // A record read as frozen history because its archive captured no\n // checklist (`checklistAtArchive: null`) was not held to the feature that\n // shipped; a deploy log must not read that row as one that was.\n if (f.archived === true && f.checklistAtArchive === null && f.state === \"recorded\") {\n console.log(\n \" its archive captured no checklist (archived by an older loam), so whether the record still answered the feature when it shipped is unknown — read as frozen history\",\n );\n }\n }\n if (report.features.length === 0) {\n console.log(\n since === null\n ? \" (no active feature carries a delta for this service)\"\n : ` (no feature in flight or archived since ${since.ref} carries a delta for this service)`,\n );\n }\n printFindings(check(\"verification\"));\n\n console.log(\"\\ninterrupted\");\n const interrupted = check(\"interrupted\");\n if (interrupted.length === 0) console.log(` ${SEVERITY_MARK.ok} no interrupted commit in the docs repo`);\n printFindings(interrupted);\n\n console.log(\n `\\nverdict: ${verdict} — ${plural(summary.errors, \"error\")}, ${plural(summary.warnings, \"warning\")}` +\n (verdict === \"pass\" && summary.warnings > 0\n ? \" (advisory — `--strict` turns warnings into exit 1)\"\n : \"\"),\n );\n}\n"]}
|
|
@@ -13,41 +13,12 @@ import { existsSync } from "node:fs";
|
|
|
13
13
|
import { mkdir } from "node:fs/promises";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { quietRmdir, rollbackStaged, stageWrites } from "../../core/staging/commit.js";
|
|
16
|
-
import { COMMIT_INTENT
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { COMMIT_INTENT } from "../../core/staging/interrupted.js";
|
|
17
|
+
import { LOCK_WAIT_MS } from "../../core/staging/lock.js";
|
|
18
|
+
import { openCommitWindow } from "../../core/staging/recovery/recover.js";
|
|
19
19
|
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
20
20
|
import {} from "../../core/staging/writes.js";
|
|
21
21
|
import {} from "./reconcile.js";
|
|
22
|
-
/**
|
|
23
|
-
* Roll a predecessor's interrupted emission forward before ANYTHING reads the
|
|
24
|
-
* suite — reconcile grades the very bytes a half-commit corrupts. Cheap when
|
|
25
|
-
* there is nothing to do: no journal, no lock, no I/O beyond one existsSync.
|
|
26
|
-
*/
|
|
27
|
-
export async function recoverEmissionRoot(root) {
|
|
28
|
-
if (!existsSync(join(root, COMMIT_INTENT)))
|
|
29
|
-
return { ok: true, recovered: null };
|
|
30
|
-
let release;
|
|
31
|
-
try {
|
|
32
|
-
release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
if (err instanceof DocsBusyError)
|
|
36
|
-
return { ok: false, code: "docs-busy", message: err.message };
|
|
37
|
-
throw err;
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
return { ok: true, recovered: await recoverInterruptedCommit(root) };
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
if (err instanceof InterruptedCommitError)
|
|
44
|
-
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
await release();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
22
|
/**
|
|
52
23
|
* Stage, compare, journal, swap. The compare is against the bytes RECONCILE
|
|
53
24
|
* read: a `.feature` that changed (or appeared) between the grading and this
|
|
@@ -60,32 +31,20 @@ export async function commitEmission(spec, plan) {
|
|
|
60
31
|
// The lock file lives in the root, so the root must exist first. If the
|
|
61
32
|
// commit then fails having created it, the empty directory is removed
|
|
62
33
|
// again — an empty loam/ flips the repo into "opted in" and tells
|
|
63
|
-
// `loam validate` the whole living suite is missing.
|
|
34
|
+
// `loam validate` the whole living suite is missing. A window that does not
|
|
35
|
+
// open is such a failure too, so the removal sits in the one `finally`
|
|
36
|
+
// every path out of here takes.
|
|
64
37
|
const rootExisted = existsSync(root);
|
|
65
38
|
await mkdir(root, { recursive: true });
|
|
66
|
-
let release;
|
|
67
|
-
try {
|
|
68
|
-
release = await acquireDocsLockWaiting(root, LOCK_WAIT_MS);
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
if (!rootExisted)
|
|
72
|
-
await quietRmdir(root);
|
|
73
|
-
if (err instanceof DocsBusyError)
|
|
74
|
-
return { ok: false, code: "docs-busy", message: err.message };
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
39
|
+
let release = null;
|
|
77
40
|
try {
|
|
78
41
|
// A writer can have crashed between the command-start recovery and this
|
|
79
42
|
// window; the re-check is one existsSync when nothing happened.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (err instanceof InterruptedCommitError)
|
|
86
|
-
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
43
|
+
const opened = await openCommitWindow(root, LOCK_WAIT_MS);
|
|
44
|
+
if (!opened.ok)
|
|
45
|
+
return opened;
|
|
46
|
+
release = opened.release;
|
|
47
|
+
const { recovered } = opened;
|
|
89
48
|
const planned = [
|
|
90
49
|
// A "written" row was graded against an ABSENT file, so its create is
|
|
91
50
|
// exclusive: a file appearing after planning loses the link(2) race and
|
|
@@ -138,7 +97,7 @@ export async function commitEmission(spec, plan) {
|
|
|
138
97
|
return { ok: true, recovered };
|
|
139
98
|
}
|
|
140
99
|
finally {
|
|
141
|
-
await release();
|
|
100
|
+
await release?.();
|
|
142
101
|
if (!rootExisted && !existsSync(join(root, COMMIT_INTENT)))
|
|
143
102
|
await quietRmdir(root);
|
|
144
103
|
}
|