@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1315 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +83 -7
- package/SCHEMA.md +569 -98
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +33 -11
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +21 -25
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +3 -5
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +5 -8
- 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 +14 -41
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +17 -38
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +32 -5
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +23 -23
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +6 -10
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +1 -3
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +8 -28
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +56 -101
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +4 -3
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +32 -18
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +9 -3
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +20 -16
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +50 -18
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +6 -18
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +15 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/documents.js +31 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +6 -17
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +3 -6
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -2
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +4 -2
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +3 -0
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +12 -2
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +21 -6
- package/examples/docs/architecture/landscape.likec4 +8 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -21,9 +21,12 @@ import { dirname, join, relative } from "node:path";
|
|
|
21
21
|
import {} from "../../core/gherkin/emit.js";
|
|
22
22
|
import { parseStampedFeature } from "../../core/gherkin/read.js";
|
|
23
23
|
import { featureFilesUnder } from "../../core/gherkin/stale.js";
|
|
24
|
-
import { isPathInside, resolveInside
|
|
24
|
+
import { isPathInside, resolveInside } from "../../core/kernel/path-safety.js";
|
|
25
25
|
export async function reconcile(plan, where, scope, activeIds) {
|
|
26
26
|
const { root, repoDir } = where;
|
|
27
|
+
// Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns
|
|
28
|
+
// only its own emissions: stamped files carrying this feature's tag whose
|
|
29
|
+
// file is no longer in the plan (a requirement renamed or dropped).
|
|
27
30
|
// Living mode owns the whole suite — any .feature not in the plan goes —
|
|
28
31
|
// EXCEPT files tagged with a feature still in flight: those answer to
|
|
29
32
|
// their feature's delta until it archives, and `loam gherkin <FEAT>` is
|
|
@@ -100,18 +103,12 @@ export async function reconcile(plan, where, scope, activeIds) {
|
|
|
100
103
|
// or the requirement renamed.
|
|
101
104
|
const actions = [];
|
|
102
105
|
for (const f of plan) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
if (!(err instanceof UnsafePathError))
|
|
112
|
-
throw err;
|
|
113
|
-
throw err;
|
|
114
|
-
}
|
|
106
|
+
// Check the final file as well as the owned root: a pre-planted
|
|
107
|
+
// `<slug>.feature` symlink must not turn writeFile into an overwrite
|
|
108
|
+
// outside the repository. The UnsafePathError is left to escape: the one
|
|
109
|
+
// caller (./gherkin.ts) refuses it as `invalid-option`, and a catch here
|
|
110
|
+
// could only rethrow it.
|
|
111
|
+
const path = resolveInside(repoDir, relative(repoDir, join(root, f.fileName)), `gherkin file '${f.fileName}'`);
|
|
115
112
|
if (!existsSync(path)) {
|
|
116
113
|
actions.push({ ...f, path, action: "written" });
|
|
117
114
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/commands/gherkin/reconcile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The plan against what is already on disk: which files are orphans, which are\n * written, replaced, kept, or owned by another feature.\n *\n * A module of its own because it is the only part of `loam gherkin` that\n * DELETES, and the reasoning that makes deletion safe is all here: a candidate\n * is an orphan only when its containing directory really resolves inside the\n * owned root, and a root that does not resolve proves nothing about what lives\n * beneath it, so nothing is collected. Leaving an orphan behind is\n * self-repairing — `loam validate` grades it and the next run removes it — and a\n * deletion taken on an unproven path is not.\n *\n * The in-flight exemption is here for the same reason: files are named by\n * requirement slug in both modes, so a MODIFIED requirement's living emission\n * always collides with the active feature's file, and replacing it reverted the\n * delta's wording mid-flight with its digest stamps destroyed.\n */\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { type PlannedFeature } from \"../../core/gherkin/emit.js\";\nimport { parseStampedFeature, type StampedFeature } from \"../../core/gherkin/read.js\";\nimport { featureFilesUnder } from \"../../core/gherkin/stale.js\";\nimport { isPathInside, resolveInside, UnsafePathError } from \"../../core/kernel/path-safety.js\";\n\n/**\n * The run's scope as ONE value, not a mode with an id lying beside it that only\n * one of the modes ever fills in: the emission tag, the orphan filter, the\n * overwrite rule and the conflict refusal all want the feature id exactly when\n * the run is a feature run. A union states that; two variables could only be\n * asserted to agree.\n */\nexport type Scope = { mode: \"living\" } | { mode: \"feature\"; featureId: string };\n\nexport type Emission = PlannedFeature & { path: string };\n\n/**\n * The row's shape follows its action instead of carrying every field any action\n * might want: only a kept or a conflicting row has read the file that is\n * already there, and only a conflicting one has owners. Held as one flat row\n * with optional fields, every reader had to assert what the writer three lines\n * up already knew.\n */\nexport type ActionRow =\n | (Emission & { action: \"written\" })\n // A replaced row carries the exact bytes it was graded against, so the\n // commit can prove the file did not move between the grading and the swap —\n // planning and committing must compare the same read, or a mismatch is\n // buried instead of refused.\n | (Emission & { action: \"replaced\"; raw: Buffer })\n | (Emission & { action: \"kept\"; kept: StampedFeature })\n | (Emission & { action: \"conflict\"; kept: StampedFeature; owners: string[] });\n\nexport type Action = ActionRow[\"action\"];\n\n/** Where the suite lives: the owned root, inside the service repository. */\nexport interface EmissionRoot {\n root: string;\n repoDir: string;\n}\n\nexport interface Reconciliation {\n /** Each orphan with the bytes its grading read — the commit compares them before deleting. */\n orphans: { path: string; raw: Buffer }[];\n actions: ActionRow[];\n /** Non-empty only in feature mode; one entry refuses the whole emission. */\n conflicts: Extract<ActionRow, { action: \"conflict\" }>[];\n}\n\nexport async function reconcile(\n plan: PlannedFeature[],\n where: EmissionRoot,\n scope: Scope,\n activeIds: ReadonlySet<string>,\n): Promise<Reconciliation> {\n const { root, repoDir } = where;\n // Living mode owns the whole suite — any .feature not in the plan goes —\n // EXCEPT files tagged with a feature still in flight: those answer to\n // their feature's delta until it archives, and `loam gherkin <FEAT>` is\n // their regeneration.\n const planned = new Set(plan.map((f) => f.fileName));\n // \"Inside loam/\" has to hold after the links are resolved, not merely in\n // the spelling of the path. `featureFilesUnder` FOLLOWS symlinks and\n // recurses with the LINK's path, so a planted `<gherkinDir>/loam/sub ->\n // /outside` hands back candidates that read as ours while `unlink`\n // resolves them and destroys files that were never in this repo. Planned\n // names are flat, so nothing nested is ever spared by `planned.has(...)`,\n // and deletion is the one irreversible thing this command does: a\n // candidate is an orphan only when its CONTAINING directory really\n // resolves inside the owned root. The write path below refuses the same\n // attack for the same reason.\n //\n // The candidate FILE is deliberately NOT resolved. A symlinked .feature\n // sitting directly in loam/ is loam's to remove — unlinking it takes the\n // link and leaves its target alone — so only the directory chain has to\n // be ours.\n const realOrNull = (p: string): string | null => {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n };\n // A root that does not resolve — absent, dangling, or vanished mid-run —\n // proves nothing about what lives beneath it, so nothing is collected.\n // Leaving an orphan behind is self-repairing (`loam validate` grades it\n // `gherkin.orphaned` and the next run removes it); a deletion taken on an\n // unproven path is not.\n const rootReal = realOrNull(root);\n const orphans: { path: string; raw: Buffer }[] = [];\n if (rootReal !== null) {\n for (const abs of await featureFilesUnder(root)) {\n if (planned.has(relative(root, abs).split(/[\\\\/]/).join(\"/\"))) continue;\n const holder = realOrNull(dirname(abs));\n if (holder === null || !isPathInside(rootReal, holder)) continue;\n const raw = await readFile(abs);\n const stamped = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"feature\") {\n if (stamped !== null && stamped.tags.includes(scope.featureId)) orphans.push({ path: abs, raw });\n } else {\n if (stamped !== null && stamped.tags.some((t) => activeIds.has(t))) continue;\n orphans.push({ path: abs, raw });\n }\n }\n }\n\n // The in-flight exemption guards the OVERWRITE path too, not only the\n // orphan scan above: files are named by requirement slug in both modes,\n // so a MODIFIED requirement's living emission always collides with the\n // active feature's file — and replacing it reverted the delta's wording\n // mid-flight, feature tag and new digest stamps destroyed, invisibly\n // (the reverted file grades current against the living spec). A planned\n // path whose existing content is stamped and tagged with a feature still\n // in flight is KEPT and reported as such; it answers to its feature's\n // delta until the feature archives, and then living regeneration\n // replaces it normally.\n //\n // FEATURE mode has the same collision and cannot solve it by keeping:\n // two features in flight against one requirement slug want the same\n // file, and whichever runs second used to `replace` — silently reverting\n // the other feature's wording, feature tag and digest stamps, so its\n // `verify --results` could never confirm a scenario again. Nothing loam\n // can write is right here (the file holds ONE feature's delta), so the\n // run refuses and names the owner: the two features have to be sequenced,\n // or the requirement renamed.\n const actions: ActionRow[] = [];\n for (const f of plan) {\n let path: string;\n try {\n // Check the final file as well as the owned root: a pre-planted\n // `<slug>.feature` symlink must not turn writeFile into an overwrite\n // outside the repository.\n path = resolveInside(\n repoDir,\n relative(repoDir, join(root, f.fileName)),\n `gherkin file '${f.fileName}'`,\n );\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n throw err;\n }\n if (!existsSync(path)) {\n actions.push({ ...f, path, action: \"written\" });\n continue;\n }\n const raw = await readFile(path);\n const existing = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"living\") {\n if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {\n actions.push({ ...f, path, action: \"kept\", kept: existing });\n continue;\n }\n } else if (existing !== null) {\n // An unstamped file is nobody's delta — it owns nothing, so it is\n // replaced like any other, which is what the empty owner list used\n // to say the long way round.\n const owners = existing.tags.filter((t) => t !== scope.featureId && activeIds.has(t));\n if (owners.length > 0) {\n actions.push({ ...f, path, action: \"conflict\", kept: existing, owners });\n continue;\n }\n }\n actions.push({ ...f, path, action: \"replaced\", raw });\n }\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n const conflicts = scope.mode === \"feature\"\n ? actions.filter((a): a is Extract<ActionRow, { action: \"conflict\" }> => a.action === \"conflict\")\n : [];\n\n return { orphans, actions, conflicts };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/commands/gherkin/reconcile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The plan against what is already on disk: which files are orphans, which are\n * written, replaced, kept, or owned by another feature.\n *\n * A module of its own because it is the only part of `loam gherkin` that\n * DELETES, and the reasoning that makes deletion safe is all here: a candidate\n * is an orphan only when its containing directory really resolves inside the\n * owned root, and a root that does not resolve proves nothing about what lives\n * beneath it, so nothing is collected. Leaving an orphan behind is\n * self-repairing — `loam validate` grades it and the next run removes it — and a\n * deletion taken on an unproven path is not.\n *\n * The in-flight exemption is here for the same reason: files are named by\n * requirement slug in both modes, so a MODIFIED requirement's living emission\n * always collides with the active feature's file, and replacing it reverted the\n * delta's wording mid-flight with its digest stamps destroyed.\n */\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { type PlannedFeature } from \"../../core/gherkin/emit.js\";\nimport { parseStampedFeature, type StampedFeature } from \"../../core/gherkin/read.js\";\nimport { featureFilesUnder } from \"../../core/gherkin/stale.js\";\nimport { isPathInside, resolveInside } from \"../../core/kernel/path-safety.js\";\n\n/**\n * The run's scope as ONE value, not a mode with an id lying beside it that only\n * one of the modes ever fills in: the emission tag, the orphan filter, the\n * overwrite rule and the conflict refusal all want the feature id exactly when\n * the run is a feature run. A union states that; two variables could only be\n * asserted to agree.\n */\nexport type Scope = { mode: \"living\" } | { mode: \"feature\"; featureId: string };\n\nexport type Emission = PlannedFeature & { path: string };\n\n/**\n * The row's shape follows its action instead of carrying every field any action\n * might want: only a kept or a conflicting row has read the file that is\n * already there, and only a conflicting one has owners. Held as one flat row\n * with optional fields, every reader had to assert what the writer three lines\n * up already knew.\n */\nexport type ActionRow =\n | (Emission & { action: \"written\" })\n // A replaced row carries the exact bytes it was graded against, so the\n // commit can prove the file did not move between the grading and the swap —\n // planning and committing must compare the same read, or a mismatch is\n // buried instead of refused.\n | (Emission & { action: \"replaced\"; raw: Buffer })\n | (Emission & { action: \"kept\"; kept: StampedFeature })\n | (Emission & { action: \"conflict\"; kept: StampedFeature; owners: string[] });\n\nexport type Action = ActionRow[\"action\"];\n\n/** Where the suite lives: the owned root, inside the service repository. */\nexport interface EmissionRoot {\n root: string;\n repoDir: string;\n}\n\nexport interface Reconciliation {\n /** Each orphan with the bytes its grading read — the commit compares them before deleting. */\n orphans: { path: string; raw: Buffer }[];\n actions: ActionRow[];\n /** Non-empty only in feature mode; one entry refuses the whole emission. */\n conflicts: Extract<ActionRow, { action: \"conflict\" }>[];\n}\n\nexport async function reconcile(\n plan: PlannedFeature[],\n where: EmissionRoot,\n scope: Scope,\n activeIds: ReadonlySet<string>,\n): Promise<Reconciliation> {\n const { root, repoDir } = where;\n // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns\n // only its own emissions: stamped files carrying this feature's tag whose\n // file is no longer in the plan (a requirement renamed or dropped).\n // Living mode owns the whole suite — any .feature not in the plan goes —\n // EXCEPT files tagged with a feature still in flight: those answer to\n // their feature's delta until it archives, and `loam gherkin <FEAT>` is\n // their regeneration.\n const planned = new Set(plan.map((f) => f.fileName));\n // \"Inside loam/\" has to hold after the links are resolved, not merely in\n // the spelling of the path. `featureFilesUnder` FOLLOWS symlinks and\n // recurses with the LINK's path, so a planted `<gherkinDir>/loam/sub ->\n // /outside` hands back candidates that read as ours while `unlink`\n // resolves them and destroys files that were never in this repo. Planned\n // names are flat, so nothing nested is ever spared by `planned.has(...)`,\n // and deletion is the one irreversible thing this command does: a\n // candidate is an orphan only when its CONTAINING directory really\n // resolves inside the owned root. The write path below refuses the same\n // attack for the same reason.\n //\n // The candidate FILE is deliberately NOT resolved. A symlinked .feature\n // sitting directly in loam/ is loam's to remove — unlinking it takes the\n // link and leaves its target alone — so only the directory chain has to\n // be ours.\n const realOrNull = (p: string): string | null => {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n };\n // A root that does not resolve — absent, dangling, or vanished mid-run —\n // proves nothing about what lives beneath it, so nothing is collected.\n // Leaving an orphan behind is self-repairing (`loam validate` grades it\n // `gherkin.orphaned` and the next run removes it); a deletion taken on an\n // unproven path is not.\n const rootReal = realOrNull(root);\n const orphans: { path: string; raw: Buffer }[] = [];\n if (rootReal !== null) {\n for (const abs of await featureFilesUnder(root)) {\n if (planned.has(relative(root, abs).split(/[\\\\/]/).join(\"/\"))) continue;\n const holder = realOrNull(dirname(abs));\n if (holder === null || !isPathInside(rootReal, holder)) continue;\n const raw = await readFile(abs);\n const stamped = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"feature\") {\n if (stamped !== null && stamped.tags.includes(scope.featureId)) orphans.push({ path: abs, raw });\n } else {\n if (stamped !== null && stamped.tags.some((t) => activeIds.has(t))) continue;\n orphans.push({ path: abs, raw });\n }\n }\n }\n\n // The in-flight exemption guards the OVERWRITE path too, not only the\n // orphan scan above: files are named by requirement slug in both modes,\n // so a MODIFIED requirement's living emission always collides with the\n // active feature's file — and replacing it reverted the delta's wording\n // mid-flight, feature tag and new digest stamps destroyed, invisibly\n // (the reverted file grades current against the living spec). A planned\n // path whose existing content is stamped and tagged with a feature still\n // in flight is KEPT and reported as such; it answers to its feature's\n // delta until the feature archives, and then living regeneration\n // replaces it normally.\n //\n // FEATURE mode has the same collision and cannot solve it by keeping:\n // two features in flight against one requirement slug want the same\n // file, and whichever runs second used to `replace` — silently reverting\n // the other feature's wording, feature tag and digest stamps, so its\n // `verify --results` could never confirm a scenario again. Nothing loam\n // can write is right here (the file holds ONE feature's delta), so the\n // run refuses and names the owner: the two features have to be sequenced,\n // or the requirement renamed.\n const actions: ActionRow[] = [];\n for (const f of plan) {\n // Check the final file as well as the owned root: a pre-planted\n // `<slug>.feature` symlink must not turn writeFile into an overwrite\n // outside the repository. The UnsafePathError is left to escape: the one\n // caller (./gherkin.ts) refuses it as `invalid-option`, and a catch here\n // could only rethrow it.\n const path = resolveInside(\n repoDir,\n relative(repoDir, join(root, f.fileName)),\n `gherkin file '${f.fileName}'`,\n );\n if (!existsSync(path)) {\n actions.push({ ...f, path, action: \"written\" });\n continue;\n }\n const raw = await readFile(path);\n const existing = parseStampedFeature(raw.toString(\"utf8\"));\n if (scope.mode === \"living\") {\n if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {\n actions.push({ ...f, path, action: \"kept\", kept: existing });\n continue;\n }\n } else if (existing !== null) {\n // An unstamped file is nobody's delta — it owns nothing, so it is\n // replaced like any other, which is what the empty owner list used\n // to say the long way round.\n const owners = existing.tags.filter((t) => t !== scope.featureId && activeIds.has(t));\n if (owners.length > 0) {\n actions.push({ ...f, path, action: \"conflict\", kept: existing, owners });\n continue;\n }\n }\n actions.push({ ...f, path, action: \"replaced\", raw });\n }\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n const conflicts = scope.mode === \"feature\"\n ? actions.filter((a): a is Extract<ActionRow, { action: \"conflict\" }> => a.action === \"conflict\")\n : [];\n\n return { orphans, actions, conflicts };\n}\n"]}
|
|
@@ -25,4 +25,48 @@ export function firstHour(service) {
|
|
|
25
25
|
["loam status", "what to do next, derived from the files"],
|
|
26
26
|
];
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The first hour of a docs repo created with NO service bound — WORKFLOW.md's
|
|
30
|
+
* Day zero steps 2–4, and the same rungs `loam status` answers on an empty
|
|
31
|
+
* fleet (`next.author-landscape`, `next.bind-service`, `next.adopt-first`).
|
|
32
|
+
*
|
|
33
|
+
* This run used to print nothing after the scaffold list: the single-repo
|
|
34
|
+
* table above needs a service id to name, and the guard that withheld it read
|
|
35
|
+
* "nothing is bound" as "nothing to say". But this is the Day zero composition
|
|
36
|
+
* itself — the person who just created the fleet's shared repo — and its next
|
|
37
|
+
* steps are as fixed as the trial's: state the map, bind each service's own
|
|
38
|
+
* repo, adopt. The ids are placeholders because nothing here knows them yet,
|
|
39
|
+
* which is also why the runnable-commands suite substitutes them like every
|
|
40
|
+
* other `<id>` it parses. Exported for that suite, by import, for the reason
|
|
41
|
+
* the header gives.
|
|
42
|
+
*
|
|
43
|
+
* The bind line names `--agent-profile service` because it is run in a
|
|
44
|
+
* SERVICE checkout of a fleet whose features are authored and archived here,
|
|
45
|
+
* in this docs repo: the profile defaults to `full`, and `full` puts
|
|
46
|
+
* `loam-feature` and `loam-ship` into every service repo — the outcome
|
|
47
|
+
* README's "Pass the profile explicitly" warns against. Agents type the
|
|
48
|
+
* command loam prints, not the README.
|
|
49
|
+
*/
|
|
50
|
+
export function docsRepoFirstHour() {
|
|
51
|
+
return [
|
|
52
|
+
["loam seed --from fleet.yaml", "the fleet map from a fleet.yaml you write, or draw it by hand"],
|
|
53
|
+
["loam init --docs <docs-repo> --service <id> --agent-profile service",
|
|
54
|
+
"in each service's own repo: bind it, commit loam.json"],
|
|
55
|
+
["loam adopt --service <id> --json", "there: the brief, an agent writes the baseline as draft"],
|
|
56
|
+
["loam status", "what to do next, derived from the files"],
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The epilogue's printed lines — the heading and one aligned `command # why`
|
|
61
|
+
* row per step — for whichever first hour this run earned. Rendering only:
|
|
62
|
+
* init.ts decides WHETHER (never on a join), this decides which table and how
|
|
63
|
+
* it reads, so the two variants cannot drift into two layouts.
|
|
64
|
+
*/
|
|
65
|
+
export function firstHourLines(service) {
|
|
66
|
+
const [heading, steps] = service === undefined
|
|
67
|
+
? ["next — the docs repo's first hour:", docsRepoFirstHour()]
|
|
68
|
+
: ["next — the first hour:", firstHour(service)];
|
|
69
|
+
const width = Math.max(...steps.map(([command]) => command.length));
|
|
70
|
+
return [` ${heading}`, ...steps.map(([command, why]) => ` ${command.padEnd(width)} # ${why}`)];
|
|
71
|
+
}
|
|
28
72
|
//# sourceMappingURL=first-hour.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"first-hour.js","sourceRoot":"","sources":["../../../src/commands/init/first-hour.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO;QACL,CAAC,wBAAwB,OAAO,SAAS;YACvC,kEAAkE,CAAC;QACrE,CAAC,2BAA2B,OAAO,EAAE,EAAE,+BAA+B,CAAC;QACvE,CAAC,wBAAwB,OAAO,EAAE,EAAE,uCAAuC,CAAC;QAC5E,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAC3D,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The four commands of the single-repo trial's first hour, each with the\n * reason it is there. `init` prints them as its epilogue when one run both\n * creates the docs repo and leaves a service bound (init.ts holds that guard\n * and the why).\n *\n * A module of its own for two reasons. The seam: init.ts is the wiring — the\n * flag guards and the scaffold sequence — while this table is what the trial\n * user is told, and the table alone pushed init.ts past the 300-line limit.\n * The export: test/agent-commands-runnable.test.ts feeds these commands\n * through the real commander program by IMPORT, because the printed lines are\n * plain text inside template literals — no markdown backticks, no\n * single-quoted `loam` forms — so that suite's literal scrape cannot see\n * them, and they are the first instruction a single-repo trial user receives:\n * the exact class its header records shipping broken once as `loam adopt\n * <id>`. README.md's Quick start reprints the same four lines, and\n * test/cli.test.ts pins that copy to this table.\n */\nexport function firstHour(service: string): Array<[command: string, why: string]> {\n return [\n [`loam adopt --service ${service} --json`,\n \"the brief: an agent reads the code, writes the baseline as draft\"],\n [`loam validate --service ${service}`, \"grade the result in this repo\"],\n [`loam vouch --service ${service}`, \"you, not the agent: draft -> verified\"],\n [\"loam status\", \"what to do next, derived from the files\"],\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"first-hour.js","sourceRoot":"","sources":["../../../src/commands/init/first-hour.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO;QACL,CAAC,wBAAwB,OAAO,SAAS;YACvC,kEAAkE,CAAC;QACrE,CAAC,2BAA2B,OAAO,EAAE,EAAE,+BAA+B,CAAC;QACvE,CAAC,wBAAwB,OAAO,EAAE,EAAE,uCAAuC,CAAC;QAC5E,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAC3D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,CAAC,6BAA6B,EAAE,+DAA+D,CAAC;QAChG,CAAC,qEAAqE;YACpE,uDAAuD,CAAC;QAC1D,CAAC,kCAAkC,EAAE,yDAAyD,CAAC;QAC/F,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAC3D,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAA2B;IACxD,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,OAAO,KAAK,SAAS;QAC5C,CAAC,CAAC,CAAC,oCAAoC,EAAE,iBAAiB,EAAE,CAAC;QAC7D,CAAC,CAAC,CAAC,wBAAwB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,KAAK,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC;AACvG,CAAC","sourcesContent":["/**\n * The four commands of the single-repo trial's first hour, each with the\n * reason it is there. `init` prints them as its epilogue when one run both\n * creates the docs repo and leaves a service bound (init.ts holds that guard\n * and the why).\n *\n * A module of its own for two reasons. The seam: init.ts is the wiring — the\n * flag guards and the scaffold sequence — while this table is what the trial\n * user is told, and the table alone pushed init.ts past the 300-line limit.\n * The export: test/agent-commands-runnable.test.ts feeds these commands\n * through the real commander program by IMPORT, because the printed lines are\n * plain text inside template literals — no markdown backticks, no\n * single-quoted `loam` forms — so that suite's literal scrape cannot see\n * them, and they are the first instruction a single-repo trial user receives:\n * the exact class its header records shipping broken once as `loam adopt\n * <id>`. README.md's Quick start reprints the same four lines, and\n * test/cli.test.ts pins that copy to this table.\n */\nexport function firstHour(service: string): Array<[command: string, why: string]> {\n return [\n [`loam adopt --service ${service} --json`,\n \"the brief: an agent reads the code, writes the baseline as draft\"],\n [`loam validate --service ${service}`, \"grade the result in this repo\"],\n [`loam vouch --service ${service}`, \"you, not the agent: draft -> verified\"],\n [\"loam status\", \"what to do next, derived from the files\"],\n ];\n}\n\n/**\n * The first hour of a docs repo created with NO service bound — WORKFLOW.md's\n * Day zero steps 2–4, and the same rungs `loam status` answers on an empty\n * fleet (`next.author-landscape`, `next.bind-service`, `next.adopt-first`).\n *\n * This run used to print nothing after the scaffold list: the single-repo\n * table above needs a service id to name, and the guard that withheld it read\n * \"nothing is bound\" as \"nothing to say\". But this is the Day zero composition\n * itself — the person who just created the fleet's shared repo — and its next\n * steps are as fixed as the trial's: state the map, bind each service's own\n * repo, adopt. The ids are placeholders because nothing here knows them yet,\n * which is also why the runnable-commands suite substitutes them like every\n * other `<id>` it parses. Exported for that suite, by import, for the reason\n * the header gives.\n *\n * The bind line names `--agent-profile service` because it is run in a\n * SERVICE checkout of a fleet whose features are authored and archived here,\n * in this docs repo: the profile defaults to `full`, and `full` puts\n * `loam-feature` and `loam-ship` into every service repo — the outcome\n * README's \"Pass the profile explicitly\" warns against. Agents type the\n * command loam prints, not the README.\n */\nexport function docsRepoFirstHour(): Array<[command: string, why: string]> {\n return [\n [\"loam seed --from fleet.yaml\", \"the fleet map from a fleet.yaml you write, or draw it by hand\"],\n [\"loam init --docs <docs-repo> --service <id> --agent-profile service\",\n \"in each service's own repo: bind it, commit loam.json\"],\n [\"loam adopt --service <id> --json\", \"there: the brief, an agent writes the baseline as draft\"],\n [\"loam status\", \"what to do next, derived from the files\"],\n ];\n}\n\n/**\n * The epilogue's printed lines — the heading and one aligned `command # why`\n * row per step — for whichever first hour this run earned. Rendering only:\n * init.ts decides WHETHER (never on a join), this decides which table and how\n * it reads, so the two variants cannot drift into two layouts.\n */\nexport function firstHourLines(service: string | undefined): string[] {\n const [heading, steps] = service === undefined\n ? [\"next — the docs repo's first hour:\", docsRepoFirstHour()]\n : [\"next — the first hour:\", firstHour(service)];\n const width = Math.max(...steps.map(([command]) => command.length));\n return [` ${heading}`, ...steps.map(([command, why]) => ` ${command.padEnd(width)} # ${why}`)];\n}\n"]}
|
|
@@ -10,7 +10,7 @@ import { InvalidIdError, assertServiceId } from "../../core/kernel/ids/service.j
|
|
|
10
10
|
import { detectAgentTools, plannedCommandFiles, syncAgentCommands, updatedAgentFileManifest, } from "../../core/agent/scaffold.js";
|
|
11
11
|
import { AGENT_TOOLS } from "../../core/agent/tools/registry.js";
|
|
12
12
|
import { initExample } from "./example.js";
|
|
13
|
-
import {
|
|
13
|
+
import { firstHourLines } from "./first-hour.js";
|
|
14
14
|
import { MCP_CONFIG_FILENAME, mcpConfigPath, mcpServerSnippet, writeMcpConfig } from "./mcp.js";
|
|
15
15
|
import { isDocsRepo, resolveAgentProfile, resolveTools, storedDocsDir } from "./options.js";
|
|
16
16
|
export function registerInit(program) {
|
|
@@ -299,27 +299,23 @@ export function registerInit(program) {
|
|
|
299
299
|
for (const line of mcpServerSnippet(cwd, opts.mcpAuthor === true))
|
|
300
300
|
console.log(` ${line}`);
|
|
301
301
|
}
|
|
302
|
-
// Printed ONLY
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
for (const [command, why] of steps) {
|
|
320
|
-
console.log(` ${command.padEnd(width)} # ${why}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
302
|
+
// Printed ONLY when this run created the docs repo. With a service bound
|
|
303
|
+
// (the binding may predate the run — the config spread above carries an
|
|
304
|
+
// existing loam.json's `service` forward) it is the single-repo trial's
|
|
305
|
+
// hour. `loam status` is NOT mute here — its first-hour ladder
|
|
306
|
+
// (core/status/fleet/next.ts) already answers `next.adopt-bound` in
|
|
307
|
+
// exactly this state — but status has to be run to say so, and no status
|
|
308
|
+
// rung ever names `loam vouch`, the one step of this loop that is a
|
|
309
|
+
// person's; init's output is already on screen, so it prints the whole
|
|
310
|
+
// hour once. With none bound it is the docs repo's own hour (map, bind,
|
|
311
|
+
// adopt — first-hour.ts says why that used to be silence). A join stays
|
|
312
|
+
// silent on purpose: a joined fleet has other repositories already
|
|
313
|
+
// through this loop, and `loam status` in its docs repo is the working
|
|
314
|
+
// guide there. The --json path returned above, so the envelope is
|
|
315
|
+
// byte-identical to before this block existed: neither variant has a key.
|
|
316
|
+
if (!joining)
|
|
317
|
+
for (const line of firstHourLines(config.service))
|
|
318
|
+
console.log(line);
|
|
323
319
|
});
|
|
324
320
|
}
|
|
325
321
|
//# sourceMappingURL=init.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init/init.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n CONFIG_FILENAME,\n findConfigPath,\n loadConfig,\n localConfigPath,\n saveConfig,\n type StoredConfig,\n} from \"../../core/envelope/config.js\";\nimport { docsDirOf } from \"../../core/kernel/ids/dirs.js\";\nimport { emitJson, fail } from \"../../core/envelope/json.js\";\nimport { plannedDocsFiles, scaffoldDocs } from \"../../core/docs.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { listServices } from \"../../core/repo/repo.js\";\nimport { InvalidIdError, assertServiceId } from \"../../core/kernel/ids/service.js\";\nimport {\n detectAgentTools,\n plannedCommandFiles,\n syncAgentCommands,\n updatedAgentFileManifest,\n type Delivery,\n} from \"../../core/agent/scaffold.js\";\nimport { AGENT_TOOLS } from \"../../core/agent/tools/registry.js\";\nimport { initExample } from \"./example.js\";\nimport { firstHour } from \"./first-hour.js\";\nimport { MCP_CONFIG_FILENAME, mcpConfigPath, mcpServerSnippet, writeMcpConfig } from \"./mcp.js\";\nimport { isDocsRepo, resolveAgentProfile, resolveTools, storedDocsDir } from \"./options.js\";\n\ninterface InitOptions {\n docs: string;\n service?: string;\n /** --create: make a new docs repo at --docs instead of joining an existing one. */\n create?: boolean;\n /** --force: write loam.json here even though one already governs a parent directory. */\n force?: boolean;\n /** commander's --no-commands: true unless the flag is passed. */\n commands: boolean;\n /** commander's --no-skills: true unless the flag is passed. */\n skills: boolean;\n /**\n * --mcp: also write the repo-root `.mcp.json`. Opt-in and default off — a\n * third delivery, independent of the two `--no-*` flags rather than a\n * variation on them, so it neither contradicts them nor is suppressed by\n * either. See ./mcp.ts for why the file is opt-in at all.\n */\n mcp?: boolean;\n mcpAuthor?: boolean;\n /** --tools: comma-separated AGENT_TOOLS ids, or \"all\". Absent = autodetect. */\n tools?: string;\n /** Workflow subset to scaffold. */\n agentProfile?: string;\n /** --example: copy the packaged example fleet to this directory and do nothing else. */\n example?: string;\n json?: boolean;\n}\n\nexport function registerInit(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize loam: create/point at the shared docs repo and write local config\")\n .option(\"--docs <dir>\", \"path to the shared docs repo (the source of truth)\", \".loam-docs\")\n .option(\"--service <id>\", \"canonical id of the service in this repo\")\n .option(\"--create\", \"create a new docs repo at --docs instead of joining an existing one\")\n .option(\"--force\", `write ${CONFIG_FILENAME} here even though one governs a parent directory`)\n .option(\n \"--tools <ids>\",\n \"agent tools to generate for, overriding the scan of this directory: \" +\n `comma-separated (${Object.keys(AGENT_TOOLS).join(\", \")}) or \"all\"`,\n )\n .option(\"--agent-profile <profile>\", \"workflow set: full | service | docs\")\n .option(\n \"--example <dir>\",\n \"copy the packaged example fleet to <dir> and stop: no repository is bound, nothing is scaffolded\",\n )\n .option(\"--no-commands\", \"skip the slash commands for this repo entirely\")\n .option(\"--no-skills\", \"skip the agent skills for this repo entirely\")\n .option(\n \"--mcp\",\n `also write ${MCP_CONFIG_FILENAME} here, so an MCP host launches \\`loam mcp\\` for this repo`,\n )\n .option(\"--mcp-author\", `write ${MCP_CONFIG_FILENAME} with the opt-in authoring profile`)\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: InitOptions, command: Command) => {\n const json = opts.json === true;\n const cwd = process.cwd();\n\n // FIRST, before every guard below including the governing-config one:\n // `--example` writes a fresh tree, it does not join a system. A\n // loam.json in an ancestor governs the directories a repository is bound\n // INTO, and this flag binds nothing — refusing here would be the tool\n // declining to show itself working because of a config that has no\n // bearing on the copy. Before the --service id check too, so a run\n // combining the two answers the contradiction rather than grading an id\n // it is about to refuse to use.\n if (opts.example !== undefined) {\n await initExample({\n dir: opts.example,\n conflicts: [\n // `--docs` carries a default, so only its SOURCE can say whether\n // anybody typed it — the same question the docsDir precedence\n // below asks, for the same reason.\n ...(command.getOptionValueSource(\"docs\") !== \"default\" ? [\"--docs\"] : []),\n ...(opts.service !== undefined ? [\"--service\"] : []),\n ...(opts.create === true ? [\"--create\"] : []),\n ...(opts.tools !== undefined ? [\"--tools\"] : []),\n ...(opts.agentProfile !== undefined ? [\"--agent-profile\"] : []),\n ...(opts.mcpAuthor === true ? [\"--mcp-author\"] : []),\n ],\n json,\n });\n return;\n }\n\n // The id becomes services/<id>/ in the shared repo, so it is validated\n // before anything is written — by the same rule adopt, vouch and gherkin\n // use, never by a guard invented here.\n if (opts.service !== undefined) {\n try {\n assertServiceId(opts.service);\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n }\n\n // A loam.json in an ancestor already governs this directory: every command\n // run from here resolves to it. Writing a second one below it would shadow\n // the first for this subtree only — the kind of split-brain nobody\n // discovers until two commands disagree about which docs repo is the fleet.\n const governing = findConfigPath(cwd);\n if (governing !== null && governing !== localConfigPath(cwd) && opts.force !== true) {\n fail(\n json,\n \"already-exists\",\n `${governing} already governs this directory. ` +\n `Run loam from ${resolve(governing, \"..\")}, or pass --force to give this ` +\n `subdirectory its own ${CONFIG_FILENAME}.`,\n );\n return;\n }\n\n // Only THIS directory's config is spread forward. Under --force there is\n // a config in an ancestor too, and inheriting its `service` would bind\n // this repo to a service somebody else's repo declared.\n // `kind: \"invalid\"` reads as null on purpose: init REPAIRS a corrupt\n // loam.json by rewriting it, so a config nobody can read is treated\n // exactly like no config at all. The old stderr line about it was\n // core's side effect, promised nowhere.\n const load = existsSync(localConfigPath(cwd)) ? await loadConfig(cwd) : null;\n const existing = load?.kind === \"loaded\" ? load.config : null;\n\n // A re-run must not move a committed pointer. `--docs` has a default\n // (`.loam-docs`), so an init invoked for any OTHER reason — `--service`,\n // a new `--tools`, the fix `doctor.agent-files-missing` prints — used to\n // arrive here carrying a docsDir nobody typed: the default then named no\n // docs repo, the refusal steered the caller to `--create`, and `--create`\n // rewrote `docsDir` in the committed loam.json to a freshly scaffolded\n // empty one. `validate --all` went green over an empty fleet, which is\n // the exact outcome `--create` exists to prevent. So the flag wins only\n // when it was actually passed; otherwise the file this repo already\n // committed does.\n const committedDocs = existing?.docsDirAsWritten;\n const docsTyped = command.getOptionValueSource(\"docs\") !== \"default\";\n const docsSource: \"flag\" | \"config\" | \"default\" =\n docsTyped ? \"flag\" : committedDocs === undefined ? \"default\" : \"config\";\n const docsOption = !docsTyped && committedDocs !== undefined ? committedDocs : opts.docs;\n // Branded at the resolution, because the guards below are what validate\n // it — the brand's provenance is \"an explicit --docs a command\n // validated\", and this command is the one that does. The STORED spelling\n // (`storedDocsDir` below) deliberately stays a plain string.\n const docsDir = docsDirOf(resolve(cwd, docsOption));\n\n // The one predictable failure: --docs naming a file. Refused here so the\n // caller gets a clean envelope/message instead of mkdir's ENOTDIR throw.\n if (existsSync(docsDir) && !statSync(docsDir).isDirectory()) {\n fail(json, \"invalid-option\", `--docs points at a file, not a directory: ${docsDir}`);\n return;\n }\n\n // Joining an existing docs repo and creating a new one are different\n // intentions, and `init` used to perform the second one whenever the first\n // was misspelled: `--docs ../dcos` scaffolded a second, empty docs repo\n // beside the real one, and the fleet quietly split in two. Creation is now\n // something the caller asks for.\n const joining = isDocsRepo(docsDir);\n if (!joining && opts.create !== true) {\n fail(\n json,\n \"invalid-option\",\n docsRepoState(docsDir).kind === \"missing\"\n ? `--docs points at a directory that does not exist: ${docsDir}. ` +\n \"If that is a typo, fix it; to create a new docs repo there, pass --create.\"\n : `--docs points at ${docsDir}, which is not a docs repo ` +\n \"(a docs repo has services/ and AGENTS.md). \" +\n \"Point --docs at the shared docs repo, or pass --create to make a new one here.\",\n );\n return;\n }\n\n // --tools selects the files to write; --no-commands and --no-skills each\n // say write none of that kind. Together they contradict — refused, not\n // arbitrated, in either combination.\n const suppressed = [\n ...(opts.commands ? [] : [\"--no-commands\"]),\n ...(opts.skills ? [] : [\"--no-skills\"]),\n ];\n if (opts.tools !== undefined && suppressed.length > 0) {\n fail(\n json,\n \"invalid-option\",\n `--tools contradicts ${suppressed.join(\" and \")}: ` +\n \"one selects the files to generate, the other suppresses them.\",\n );\n return;\n }\n const delivery: Delivery[] = [\n ...(opts.commands ? ([\"commands\"] as const) : []),\n ...(opts.skills ? ([\"skills\"] as const) : []),\n ];\n\n // Without --tools the repo decides: whichever tools have left their own\n // dot-directories here. Falling back to claude keeps a bare repo behaving\n // exactly as it did before the scan existed. `detected` is reported either\n // way — what the scan saw is a fact about the repo, not about the flags.\n const detected = detectAgentTools(cwd);\n const scanned = detected.length > 0 ? detected : [\"claude\"];\n const tools = opts.tools === undefined ? scanned : resolveTools(opts.tools, json);\n if (tools === null) return;\n const profile = resolveAgentProfile(opts.agentProfile, existing?.agentProfile, json);\n if (profile === null) return;\n\n // Probe the pre-existing paths in planned order. Managed files refreshed\n // below are removed from `skipped`; custom and already-current files stay.\n const plannedAgentFiles = delivery.length > 0 ? plannedCommandFiles(cwd, tools, delivery, profile) : [];\n // `.mcp.json` joins the same list rather than being reported on the side:\n // it is a scaffolded path like any other, so it must appear in `created`\n // or in `skipped` by the one rule that governs both. Last, because it is\n // written last.\n const candidates = [\n ...plannedDocsFiles(docsDir),\n ...plannedAgentFiles.map((f) => f.path),\n ...(opts.mcp === true || opts.mcpAuthor === true ? [mcpConfigPath(cwd)] : []),\n ];\n let skipped = candidates.filter((p) => existsSync(p));\n\n // Joining touches the docs repo not at all: it is somebody's committed\n // repository, and \"point at it\" must never mean \"write to it\".\n const created = joining && opts.create !== true ? [] : (await scaffoldDocs(docsDir)).created;\n const generatedFor = delivery.length > 0 ? tools : [];\n let refreshed: string[] = [];\n let managedFiles: Record<string, string> = {};\n if (delivery.length > 0) {\n const synced = await syncAgentCommands({\n cwd,\n toolIds: tools,\n delivery,\n profile,\n known: existing?.agentFiles,\n });\n created.push(...synced.created);\n refreshed = synced.refreshed;\n managedFiles = synced.managed;\n skipped = skipped.filter((path) => !refreshed.includes(path));\n }\n\n // The third delivery, and the only machine-readable one. It refuses out\n // loud rather than being written best-effort: a `.mcp.json` half-landed\n // is a host that starts a broken server, and the caller asked for it by\n // name. Before saveConfig, so a run that cannot write this file has not\n // yet moved the committed pointer either.\n let mcpWritten = false;\n if (opts.mcp === true || opts.mcpAuthor === true) {\n const write = await writeMcpConfig(cwd, opts.mcpAuthor === true);\n if (write.kind === \"failed\") {\n fail(json, write.code, write.message);\n return;\n }\n mcpWritten = write.kind === \"created\";\n if (mcpWritten) created.push(mcpConfigPath(cwd));\n }\n\n const stored = storedDocsDir(docsOption);\n // Union, not replacement: this run's files join the ones earlier runs left\n // on disk, and `agentTools` is a record of what the repo HOLDS. A run that\n // generated nothing adds nothing and erases nothing.\n const recordedTools = [...new Set([...(existing?.agentTools ?? []), ...generatedFor])];\n const agentFileManifest = updatedAgentFileManifest(cwd, plannedAgentFiles, existing?.agentFiles, managedFiles);\n const config: StoredConfig = {\n ...existing,\n docsDir: stored,\n ...(opts.service ? { service: opts.service } : {}),\n ...(recordedTools.length > 0 ? { agentTools: recordedTools } : {}),\n agentProfile: profile,\n };\n if (Object.keys(agentFileManifest).length > 0) config.agentFiles = agentFileManifest;\n const configFile = await saveConfig(config, cwd);\n\n const serviceCount = (await listServices(docsDir)).length;\n\n if (json) {\n // `docsDir` stays the resolved absolute path — it is what a consumer\n // needs to find the repo. `docsDirStored` is what was written to\n // loam.json, which is the fact this command is now careful about.\n // `tools` names what files were generated FOR — empty when every\n // delivery was suppressed and none were. `detected` is the separate\n // question of what the scan SAW, which `--tools` overrides without\n // changing: an agent comparing the two learns whether its selection\n // matches the repo.\n emitJson({\n command: \"init\",\n docsDir,\n docsDirStored: stored,\n // Where the pointer came from: `flag` (a --docs somebody typed),\n // `config` (the one this repo already committed — a re-run cannot\n // move it), or `default` (nothing said, nothing committed).\n docsDirSource: docsSource,\n docsRepo: joining ? \"existing\" : \"created\",\n services: serviceCount,\n created,\n refreshed,\n skipped,\n tools: generatedFor,\n agentProfile: profile,\n detected,\n });\n return;\n }\n\n console.log(\"loam initialized.\");\n if (joining) {\n console.log(` docs repo: ${docsDir}`);\n console.log(` pointing at existing docs repo (${serviceCount} services)`);\n } else {\n console.log(` docs repo: ${docsDir} (created)`);\n }\n console.log(\n ` docsDir: ${stored} (as stored in ${CONFIG_FILENAME}`\n + (docsSource === \"config\" ? \" — kept; no --docs was passed\" : \"\") + \")\",\n );\n if (config.service) console.log(` service: ${config.service}`);\n console.log(` config: ${configFile}`);\n console.log(` commit ${CONFIG_FILENAME} — docsDir is resolved relative to it`);\n if (opts.tools === undefined && detected.length > 0) {\n console.log(` detected: ${detected.join(\", \")}`);\n }\n if (opts.commands) console.log(` commands: ${tools.join(\", \")}`);\n if (opts.skills) console.log(` skills: ${tools.join(\", \")}`);\n console.log(` profile: ${profile}`);\n if (created.length > 0) {\n console.log(\" scaffolded:\");\n for (const c of created) console.log(` + ${c}`);\n }\n if (refreshed.length > 0) {\n console.log(\" refreshed (unchanged since loam last wrote them):\");\n for (const path of refreshed) console.log(` ↻ ${path}`);\n }\n\n // The one skipped path that earns more than its line in `skipped`: loam\n // will not merge into a `.mcp.json` a human owns, and the caller asked\n // for this delivery by name — so they are handed the exact key to paste\n // into their own `mcpServers` object. Nothing was read out of their file\n // to produce it; the snippet is the same bytes a fresh write would have\n // used, which is also why it names the launch form this install shape\n // actually needs.\n if ((opts.mcp === true || opts.mcpAuthor === true) && !mcpWritten) {\n console.log(` mcp: ${MCP_CONFIG_FILENAME} is already here — left exactly as it is.`);\n console.log(` paste this into its \"mcpServers\" object:`);\n for (const line of mcpServerSnippet(cwd, opts.mcpAuthor === true)) console.log(` ${line}`);\n }\n\n // Printed ONLY for the single-repo trial composition: one run that both\n // created the docs repo and left a service bound (the binding may\n // predate the run — the config spread above carries an existing\n // loam.json's `service` forward). `loam status` is NOT mute here — its\n // first-hour ladder (core/status/fleet/next.ts) already answers\n // `next.adopt-bound` in exactly this state — but status has to be run\n // to say so, and no status rung ever names `loam vouch`, the one step\n // of this loop that is a person's; init's output is already on screen,\n // so it prints the whole hour once. A join stays silent on purpose: a\n // joined fleet has other repositories already through this loop, and\n // `loam status` in its docs repo is the working guide there. The --json\n // path returned above, so the envelope is byte-identical to before\n // this block existed.\n if (!joining && config.service !== undefined) {\n const steps = firstHour(config.service);\n const width = Math.max(...steps.map(([command]) => command.length));\n console.log(\" next — the first hour:\");\n for (const [command, why] of steps) {\n console.log(` ${command.padEnd(width)} # ${why}`);\n }\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init/init.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n CONFIG_FILENAME,\n findConfigPath,\n loadConfig,\n localConfigPath,\n saveConfig,\n type StoredConfig,\n} from \"../../core/envelope/config.js\";\nimport { docsDirOf } from \"../../core/kernel/ids/dirs.js\";\nimport { emitJson, fail } from \"../../core/envelope/json.js\";\nimport { plannedDocsFiles, scaffoldDocs } from \"../../core/docs.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { listServices } from \"../../core/repo/repo.js\";\nimport { InvalidIdError, assertServiceId } from \"../../core/kernel/ids/service.js\";\nimport {\n detectAgentTools,\n plannedCommandFiles,\n syncAgentCommands,\n updatedAgentFileManifest,\n type Delivery,\n} from \"../../core/agent/scaffold.js\";\nimport { AGENT_TOOLS } from \"../../core/agent/tools/registry.js\";\nimport { initExample } from \"./example.js\";\nimport { firstHourLines } from \"./first-hour.js\";\nimport { MCP_CONFIG_FILENAME, mcpConfigPath, mcpServerSnippet, writeMcpConfig } from \"./mcp.js\";\nimport { isDocsRepo, resolveAgentProfile, resolveTools, storedDocsDir } from \"./options.js\";\n\ninterface InitOptions {\n docs: string;\n service?: string;\n /** --create: make a new docs repo at --docs instead of joining an existing one. */\n create?: boolean;\n /** --force: write loam.json here even though one already governs a parent directory. */\n force?: boolean;\n /** commander's --no-commands: true unless the flag is passed. */\n commands: boolean;\n /** commander's --no-skills: true unless the flag is passed. */\n skills: boolean;\n /**\n * --mcp: also write the repo-root `.mcp.json`. Opt-in and default off — a\n * third delivery, independent of the two `--no-*` flags rather than a\n * variation on them, so it neither contradicts them nor is suppressed by\n * either. See ./mcp.ts for why the file is opt-in at all.\n */\n mcp?: boolean;\n mcpAuthor?: boolean;\n /** --tools: comma-separated AGENT_TOOLS ids, or \"all\". Absent = autodetect. */\n tools?: string;\n /** Workflow subset to scaffold. */\n agentProfile?: string;\n /** --example: copy the packaged example fleet to this directory and do nothing else. */\n example?: string;\n json?: boolean;\n}\n\nexport function registerInit(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize loam: create/point at the shared docs repo and write local config\")\n .option(\"--docs <dir>\", \"path to the shared docs repo (the source of truth)\", \".loam-docs\")\n .option(\"--service <id>\", \"canonical id of the service in this repo\")\n .option(\"--create\", \"create a new docs repo at --docs instead of joining an existing one\")\n .option(\"--force\", `write ${CONFIG_FILENAME} here even though one governs a parent directory`)\n .option(\n \"--tools <ids>\",\n \"agent tools to generate for, overriding the scan of this directory: \" +\n `comma-separated (${Object.keys(AGENT_TOOLS).join(\", \")}) or \"all\"`,\n )\n .option(\"--agent-profile <profile>\", \"workflow set: full | service | docs\")\n .option(\n \"--example <dir>\",\n \"copy the packaged example fleet to <dir> and stop: no repository is bound, nothing is scaffolded\",\n )\n .option(\"--no-commands\", \"skip the slash commands for this repo entirely\")\n .option(\"--no-skills\", \"skip the agent skills for this repo entirely\")\n .option(\n \"--mcp\",\n `also write ${MCP_CONFIG_FILENAME} here, so an MCP host launches \\`loam mcp\\` for this repo`,\n )\n .option(\"--mcp-author\", `write ${MCP_CONFIG_FILENAME} with the opt-in authoring profile`)\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: InitOptions, command: Command) => {\n const json = opts.json === true;\n const cwd = process.cwd();\n\n // FIRST, before every guard below including the governing-config one:\n // `--example` writes a fresh tree, it does not join a system. A\n // loam.json in an ancestor governs the directories a repository is bound\n // INTO, and this flag binds nothing — refusing here would be the tool\n // declining to show itself working because of a config that has no\n // bearing on the copy. Before the --service id check too, so a run\n // combining the two answers the contradiction rather than grading an id\n // it is about to refuse to use.\n if (opts.example !== undefined) {\n await initExample({\n dir: opts.example,\n conflicts: [\n // `--docs` carries a default, so only its SOURCE can say whether\n // anybody typed it — the same question the docsDir precedence\n // below asks, for the same reason.\n ...(command.getOptionValueSource(\"docs\") !== \"default\" ? [\"--docs\"] : []),\n ...(opts.service !== undefined ? [\"--service\"] : []),\n ...(opts.create === true ? [\"--create\"] : []),\n ...(opts.tools !== undefined ? [\"--tools\"] : []),\n ...(opts.agentProfile !== undefined ? [\"--agent-profile\"] : []),\n ...(opts.mcpAuthor === true ? [\"--mcp-author\"] : []),\n ],\n json,\n });\n return;\n }\n\n // The id becomes services/<id>/ in the shared repo, so it is validated\n // before anything is written — by the same rule adopt, vouch and gherkin\n // use, never by a guard invented here.\n if (opts.service !== undefined) {\n try {\n assertServiceId(opts.service);\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n }\n\n // A loam.json in an ancestor already governs this directory: every command\n // run from here resolves to it. Writing a second one below it would shadow\n // the first for this subtree only — the kind of split-brain nobody\n // discovers until two commands disagree about which docs repo is the fleet.\n const governing = findConfigPath(cwd);\n if (governing !== null && governing !== localConfigPath(cwd) && opts.force !== true) {\n fail(\n json,\n \"already-exists\",\n `${governing} already governs this directory. ` +\n `Run loam from ${resolve(governing, \"..\")}, or pass --force to give this ` +\n `subdirectory its own ${CONFIG_FILENAME}.`,\n );\n return;\n }\n\n // Only THIS directory's config is spread forward. Under --force there is\n // a config in an ancestor too, and inheriting its `service` would bind\n // this repo to a service somebody else's repo declared.\n // `kind: \"invalid\"` reads as null on purpose: init REPAIRS a corrupt\n // loam.json by rewriting it, so a config nobody can read is treated\n // exactly like no config at all. The old stderr line about it was\n // core's side effect, promised nowhere.\n const load = existsSync(localConfigPath(cwd)) ? await loadConfig(cwd) : null;\n const existing = load?.kind === \"loaded\" ? load.config : null;\n\n // A re-run must not move a committed pointer. `--docs` has a default\n // (`.loam-docs`), so an init invoked for any OTHER reason — `--service`,\n // a new `--tools`, the fix `doctor.agent-files-missing` prints — used to\n // arrive here carrying a docsDir nobody typed: the default then named no\n // docs repo, the refusal steered the caller to `--create`, and `--create`\n // rewrote `docsDir` in the committed loam.json to a freshly scaffolded\n // empty one. `validate --all` went green over an empty fleet, which is\n // the exact outcome `--create` exists to prevent. So the flag wins only\n // when it was actually passed; otherwise the file this repo already\n // committed does.\n const committedDocs = existing?.docsDirAsWritten;\n const docsTyped = command.getOptionValueSource(\"docs\") !== \"default\";\n const docsSource: \"flag\" | \"config\" | \"default\" =\n docsTyped ? \"flag\" : committedDocs === undefined ? \"default\" : \"config\";\n const docsOption = !docsTyped && committedDocs !== undefined ? committedDocs : opts.docs;\n // Branded at the resolution, because the guards below are what validate\n // it — the brand's provenance is \"an explicit --docs a command\n // validated\", and this command is the one that does. The STORED spelling\n // (`storedDocsDir` below) deliberately stays a plain string.\n const docsDir = docsDirOf(resolve(cwd, docsOption));\n\n // The one predictable failure: --docs naming a file. Refused here so the\n // caller gets a clean envelope/message instead of mkdir's ENOTDIR throw.\n if (existsSync(docsDir) && !statSync(docsDir).isDirectory()) {\n fail(json, \"invalid-option\", `--docs points at a file, not a directory: ${docsDir}`);\n return;\n }\n\n // Joining an existing docs repo and creating a new one are different\n // intentions, and `init` used to perform the second one whenever the first\n // was misspelled: `--docs ../dcos` scaffolded a second, empty docs repo\n // beside the real one, and the fleet quietly split in two. Creation is now\n // something the caller asks for.\n const joining = isDocsRepo(docsDir);\n if (!joining && opts.create !== true) {\n fail(\n json,\n \"invalid-option\",\n docsRepoState(docsDir).kind === \"missing\"\n ? `--docs points at a directory that does not exist: ${docsDir}. ` +\n \"If that is a typo, fix it; to create a new docs repo there, pass --create.\"\n : `--docs points at ${docsDir}, which is not a docs repo ` +\n \"(a docs repo has services/ and AGENTS.md). \" +\n \"Point --docs at the shared docs repo, or pass --create to make a new one here.\",\n );\n return;\n }\n\n // --tools selects the files to write; --no-commands and --no-skills each\n // say write none of that kind. Together they contradict — refused, not\n // arbitrated, in either combination.\n const suppressed = [\n ...(opts.commands ? [] : [\"--no-commands\"]),\n ...(opts.skills ? [] : [\"--no-skills\"]),\n ];\n if (opts.tools !== undefined && suppressed.length > 0) {\n fail(\n json,\n \"invalid-option\",\n `--tools contradicts ${suppressed.join(\" and \")}: ` +\n \"one selects the files to generate, the other suppresses them.\",\n );\n return;\n }\n const delivery: Delivery[] = [\n ...(opts.commands ? ([\"commands\"] as const) : []),\n ...(opts.skills ? ([\"skills\"] as const) : []),\n ];\n\n // Without --tools the repo decides: whichever tools have left their own\n // dot-directories here. Falling back to claude keeps a bare repo behaving\n // exactly as it did before the scan existed. `detected` is reported either\n // way — what the scan saw is a fact about the repo, not about the flags.\n const detected = detectAgentTools(cwd);\n const scanned = detected.length > 0 ? detected : [\"claude\"];\n const tools = opts.tools === undefined ? scanned : resolveTools(opts.tools, json);\n if (tools === null) return;\n const profile = resolveAgentProfile(opts.agentProfile, existing?.agentProfile, json);\n if (profile === null) return;\n\n // Probe the pre-existing paths in planned order. Managed files refreshed\n // below are removed from `skipped`; custom and already-current files stay.\n const plannedAgentFiles = delivery.length > 0 ? plannedCommandFiles(cwd, tools, delivery, profile) : [];\n // `.mcp.json` joins the same list rather than being reported on the side:\n // it is a scaffolded path like any other, so it must appear in `created`\n // or in `skipped` by the one rule that governs both. Last, because it is\n // written last.\n const candidates = [\n ...plannedDocsFiles(docsDir),\n ...plannedAgentFiles.map((f) => f.path),\n ...(opts.mcp === true || opts.mcpAuthor === true ? [mcpConfigPath(cwd)] : []),\n ];\n let skipped = candidates.filter((p) => existsSync(p));\n\n // Joining touches the docs repo not at all: it is somebody's committed\n // repository, and \"point at it\" must never mean \"write to it\".\n const created = joining && opts.create !== true ? [] : (await scaffoldDocs(docsDir)).created;\n const generatedFor = delivery.length > 0 ? tools : [];\n let refreshed: string[] = [];\n let managedFiles: Record<string, string> = {};\n if (delivery.length > 0) {\n const synced = await syncAgentCommands({\n cwd,\n toolIds: tools,\n delivery,\n profile,\n known: existing?.agentFiles,\n });\n created.push(...synced.created);\n refreshed = synced.refreshed;\n managedFiles = synced.managed;\n skipped = skipped.filter((path) => !refreshed.includes(path));\n }\n\n // The third delivery, and the only machine-readable one. It refuses out\n // loud rather than being written best-effort: a `.mcp.json` half-landed\n // is a host that starts a broken server, and the caller asked for it by\n // name. Before saveConfig, so a run that cannot write this file has not\n // yet moved the committed pointer either.\n let mcpWritten = false;\n if (opts.mcp === true || opts.mcpAuthor === true) {\n const write = await writeMcpConfig(cwd, opts.mcpAuthor === true);\n if (write.kind === \"failed\") {\n fail(json, write.code, write.message);\n return;\n }\n mcpWritten = write.kind === \"created\";\n if (mcpWritten) created.push(mcpConfigPath(cwd));\n }\n\n const stored = storedDocsDir(docsOption);\n // Union, not replacement: this run's files join the ones earlier runs left\n // on disk, and `agentTools` is a record of what the repo HOLDS. A run that\n // generated nothing adds nothing and erases nothing.\n const recordedTools = [...new Set([...(existing?.agentTools ?? []), ...generatedFor])];\n const agentFileManifest = updatedAgentFileManifest(cwd, plannedAgentFiles, existing?.agentFiles, managedFiles);\n const config: StoredConfig = {\n ...existing,\n docsDir: stored,\n ...(opts.service ? { service: opts.service } : {}),\n ...(recordedTools.length > 0 ? { agentTools: recordedTools } : {}),\n agentProfile: profile,\n };\n if (Object.keys(agentFileManifest).length > 0) config.agentFiles = agentFileManifest;\n const configFile = await saveConfig(config, cwd);\n\n const serviceCount = (await listServices(docsDir)).length;\n\n if (json) {\n // `docsDir` stays the resolved absolute path — it is what a consumer\n // needs to find the repo. `docsDirStored` is what was written to\n // loam.json, which is the fact this command is now careful about.\n // `tools` names what files were generated FOR — empty when every\n // delivery was suppressed and none were. `detected` is the separate\n // question of what the scan SAW, which `--tools` overrides without\n // changing: an agent comparing the two learns whether its selection\n // matches the repo.\n emitJson({\n command: \"init\",\n docsDir,\n docsDirStored: stored,\n // Where the pointer came from: `flag` (a --docs somebody typed),\n // `config` (the one this repo already committed — a re-run cannot\n // move it), or `default` (nothing said, nothing committed).\n docsDirSource: docsSource,\n docsRepo: joining ? \"existing\" : \"created\",\n services: serviceCount,\n created,\n refreshed,\n skipped,\n tools: generatedFor,\n agentProfile: profile,\n detected,\n });\n return;\n }\n\n console.log(\"loam initialized.\");\n if (joining) {\n console.log(` docs repo: ${docsDir}`);\n console.log(` pointing at existing docs repo (${serviceCount} services)`);\n } else {\n console.log(` docs repo: ${docsDir} (created)`);\n }\n console.log(\n ` docsDir: ${stored} (as stored in ${CONFIG_FILENAME}`\n + (docsSource === \"config\" ? \" — kept; no --docs was passed\" : \"\") + \")\",\n );\n if (config.service) console.log(` service: ${config.service}`);\n console.log(` config: ${configFile}`);\n console.log(` commit ${CONFIG_FILENAME} — docsDir is resolved relative to it`);\n if (opts.tools === undefined && detected.length > 0) {\n console.log(` detected: ${detected.join(\", \")}`);\n }\n if (opts.commands) console.log(` commands: ${tools.join(\", \")}`);\n if (opts.skills) console.log(` skills: ${tools.join(\", \")}`);\n console.log(` profile: ${profile}`);\n if (created.length > 0) {\n console.log(\" scaffolded:\");\n for (const c of created) console.log(` + ${c}`);\n }\n if (refreshed.length > 0) {\n console.log(\" refreshed (unchanged since loam last wrote them):\");\n for (const path of refreshed) console.log(` ↻ ${path}`);\n }\n\n // The one skipped path that earns more than its line in `skipped`: loam\n // will not merge into a `.mcp.json` a human owns, and the caller asked\n // for this delivery by name — so they are handed the exact key to paste\n // into their own `mcpServers` object. Nothing was read out of their file\n // to produce it; the snippet is the same bytes a fresh write would have\n // used, which is also why it names the launch form this install shape\n // actually needs.\n if ((opts.mcp === true || opts.mcpAuthor === true) && !mcpWritten) {\n console.log(` mcp: ${MCP_CONFIG_FILENAME} is already here — left exactly as it is.`);\n console.log(` paste this into its \"mcpServers\" object:`);\n for (const line of mcpServerSnippet(cwd, opts.mcpAuthor === true)) console.log(` ${line}`);\n }\n\n // Printed ONLY when this run created the docs repo. With a service bound\n // (the binding may predate the run — the config spread above carries an\n // existing loam.json's `service` forward) it is the single-repo trial's\n // hour. `loam status` is NOT mute here — its first-hour ladder\n // (core/status/fleet/next.ts) already answers `next.adopt-bound` in\n // exactly this state — but status has to be run to say so, and no status\n // rung ever names `loam vouch`, the one step of this loop that is a\n // person's; init's output is already on screen, so it prints the whole\n // hour once. With none bound it is the docs repo's own hour (map, bind,\n // adopt — first-hour.ts says why that used to be silence). A join stays\n // silent on purpose: a joined fleet has other repositories already\n // through this loop, and `loam status` in its docs repo is the working\n // guide there. The --json path returned above, so the envelope is\n // byte-identical to before this block existed: neither variant has a key.\n if (!joining) for (const line of firstHourLines(config.service)) console.log(line);\n });\n}\n"]}
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* already keeps.
|
|
9
9
|
*
|
|
10
10
|
* Resolution reuses the tree machinery verbatim (`findInTree`,
|
|
11
|
-
* `servicesUnder`, `
|
|
11
|
+
* `servicesUnder`, `closeNamesHint`), so `list --subsystem` and every
|
|
12
12
|
* `loam subsystem` verb read the flat namespace the same way — two spellings
|
|
13
13
|
* of "what does this name mean" would drift.
|
|
14
14
|
*/
|
|
15
15
|
import { fail } from "../../../core/envelope/json.js";
|
|
16
|
-
import {
|
|
16
|
+
import { closeNamesHint, findInTree, servicesUnder, subsystemsUnder } from "../../../core/repo/tree/find.js";
|
|
17
17
|
/**
|
|
18
18
|
* Resolve `--subsystem <name>` against the flat namespace, or report the
|
|
19
19
|
* refusal and return null. The reserved name `unfiled` selects the services
|
|
@@ -38,11 +38,9 @@ export function resolveSubsystemSlice(tree, name, json) {
|
|
|
38
38
|
if (hit === null) {
|
|
39
39
|
// The Set: a real subsystem named 'unfiled' would otherwise sit in the
|
|
40
40
|
// hint pool twice and could be offered twice in one hint.
|
|
41
|
-
const
|
|
41
|
+
const pool = [...new Set([...tree.subsystems.map((s) => s.name), "unfiled"])];
|
|
42
42
|
fail(json, "unknown-target", `No subsystem '${name}' in the tree.` +
|
|
43
|
-
(
|
|
44
|
-
? ` Close names: ${close.join(", ")}.`
|
|
45
|
-
: " `loam subsystem list` shows what exists; `unfiled` selects the services filed under none."));
|
|
43
|
+
closeNamesHint(name, pool, " `loam subsystem list` shows what exists; `unfiled` selects the services filed under none."));
|
|
46
44
|
return null;
|
|
47
45
|
}
|
|
48
46
|
if (hit.kind === "service") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"campaign.js","sourceRoot":"","sources":["../../../../src/commands/list/campaign/campaign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"campaign.js","sourceRoot":"","sources":["../../../../src/commands/list/campaign/campaign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAqB7G;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAe,EAAE,IAAY,EAAE,IAAa;IAChF,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO;YACL,OAAO,EAAE,IAAI,GAAG,CAAS,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACjG,UAAU,EAAE,EAAE;YACd,KAAK,EAAE,6CAA6C;SACrD,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,uEAAuE;QACvE,0DAA0D;QAC1D,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;QAC9E,IAAI,CACF,IAAI,EACJ,gBAAgB,EAChB,iBAAiB,IAAI,gBAAgB;YACnC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,4FAA4F,CAAC,CAC3H,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,yEAAyE;QACzE,wEAAwE;QACxE,0DAA0D;QAC1D,MAAM,QAAQ,GACZ,IAAI,KAAK,SAAS;YAChB,CAAC,CAAC,uHAAuH;YACzH,CAAC,CAAC,EAAE,CAAC;QACT,IAAI,CACF,IAAI,EACJ,gBAAgB,EAChB,kCAAkC,IAAI,+CAA+C,QAAQ,EAAE,CAChG,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC;IAC1B,OAAO;QACL,OAAO,EAAE,IAAI,GAAG,CAAS,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpE,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,KAAK,EAAE,cAAc,GAAG,CAAC,IAAI,eAAe,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI;KACnE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The `--subsystem` slice behind `loam list`: what the flag's name denotes,\n * and which services the filter keeps. A sub-package of list/ because the\n * list package proper stands at its five-file limit (review.ts records\n * that), and the adoption-campaign flag surface is its own subject beside\n * the section renderers. The dependency direction is list → list/campaign,\n * never back — this package imports core only, the same rule repo → repo/tree\n * already keeps.\n *\n * Resolution reuses the tree machinery verbatim (`findInTree`,\n * `servicesUnder`, `closeNamesHint`), so `list --subsystem` and every\n * `loam subsystem` verb read the flat namespace the same way — two spellings\n * of \"what does this name mean\" would drift.\n */\nimport { fail } from \"../../../core/envelope/json.js\";\nimport { closeNamesHint, findInTree, servicesUnder, subsystemsUnder } from \"../../../core/repo/tree/find.js\";\nimport type { FleetTree, SubsystemEntry } from \"../../../core/repo/tree/walk.js\";\n\nexport interface SubsystemSlice {\n /**\n * DIRECTORIES of the member services, at any depth beneath the filter\n * target — the directory, not the id, because the id is a leaf name the\n * fleet does not guarantee unique: a `subsystem.name-collision` tree still\n * enumerates both claimants on purpose, and an id-keyed slice would let\n * each of two disjoint slices claim the other's service.\n */\n readonly members: ReadonlySet<string>;\n /**\n * The subsystem rows the filtered payload shows — the named subsystem and\n * its descendants; `[]` for the unfiled slice, which sits under no group.\n */\n readonly subsystems: SubsystemEntry[];\n /** The human filter line's description of what the slice is. */\n readonly label: string;\n}\n\n/**\n * Resolve `--subsystem <name>` against the flat namespace, or report the\n * refusal and return null. The reserved name `unfiled` selects the services\n * filed under no subsystem — the tree's own concept, otherwise unreachable —\n * but only while nothing in the tree claims that name: a REAL subsystem or\n * service spelled `unfiled` wins, the same concrete-name-wins precedence\n * `findInTree` already applies to service/subsystem ties. An unknown name\n * refuses `unknown-target` with close-name hints rather than succeeding\n * emptily — an empty table over a typo would read as a finished campaign —\n * and a service name refuses `invalid-option` exactly as `loam adopt\n * --subsystem` does: a service never contains other services.\n */\nexport function resolveSubsystemSlice(tree: FleetTree, name: string, json: boolean): SubsystemSlice | null {\n const hit = findInTree(tree, name);\n if (hit === null && name === \"unfiled\") {\n return {\n members: new Set<string>(tree.services.filter((s) => s.subsystem.length === 0).map((s) => s.dir)),\n subsystems: [],\n label: \"unfiled services (directly under services/)\",\n };\n }\n if (hit === null) {\n // The Set: a real subsystem named 'unfiled' would otherwise sit in the\n // hint pool twice and could be offered twice in one hint.\n const pool = [...new Set([...tree.subsystems.map((s) => s.name), \"unfiled\"])];\n fail(\n json,\n \"unknown-target\",\n `No subsystem '${name}' in the tree.` +\n closeNamesHint(name, pool, \" `loam subsystem list` shows what exists; `unfiled` selects the services filed under none.\"),\n );\n return null;\n }\n if (hit.kind === \"service\") {\n // With a SERVICE claiming the name 'unfiled' the reserved reading has no\n // spelling at all, so the refusal must say what is shadowed — otherwise\n // the message reads as loam misunderstanding the request.\n const shadowed =\n name === \"unfiled\"\n ? \" While a service claims this name, the reserved unfiled-services reading is shadowed; rename the service to reach it.\"\n : \"\";\n fail(\n json,\n \"invalid-option\",\n `--subsystem names the service '${name}' — a service never contains other services.${shadowed}`,\n );\n return null;\n }\n const sub = hit.subsystem;\n return {\n members: new Set<string>(servicesUnder(tree, sub).map((s) => s.dir)),\n subsystems: [sub, ...subsystemsUnder(tree, sub)],\n label: `subsystem '${sub.name}' (services/${sub.path.join(\"/\")}/)`,\n };\n}\n"]}
|
|
@@ -137,10 +137,6 @@ export function featureJson(docsDir, f, verification) {
|
|
|
137
137
|
verification,
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
|
-
/* ------------------------------------------------------------------ */
|
|
141
|
-
/* Verification */
|
|
142
|
-
/* ------------------------------------------------------------------ */
|
|
143
|
-
/** What the features table says about verification without N `loam verify` runs. */
|
|
144
140
|
/**
|
|
145
141
|
* One `glossary[]` row — additive payload, explicit `loam list glossary` only.
|
|
146
142
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/commands/list/json.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAsB,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAqB,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D,OAAO,EAA2C,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,OAAe,EACf,IAAkF;IAElF,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1B,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;QACtC,UAAU,EAAE,CAAC,GAAG,CAAC;KAClB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,CAAc;IACzD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;IAClB,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC;QAC9B,yEAAyE;QACzE,wEAAwE;QACxE,gCAAgC;QAChC,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE;QACvC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,qEAAqE;QACrE,yEAAyE;QACzE,kDAAkD;QAClD,GAAG,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,wBAAwB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,uEAAuE;QACvE,0EAA0E;QAC1E,yEAAyE;QACzE,qEAAqE;QACrE,4BAA4B;QAC5B,GAAG,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAe;IAC7D,OAAO,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;SACxB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9D,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACb,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC;QAChC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI;QAC7B,WAAW,EAAE,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,MAAM;KAC7C,CAAC,CAAC,CAAC;AACR,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACtG,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;KACvF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,4EAA4E;QAC5E,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,6CAA6C;QAC7C,EAAE;QACF,uEAAuE;QACvE,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,0EAA0E;QAC1E,yEAAyE;QACzE,wEAAwE;QACxE,4EAA4E;QAC5E,GAAG,CAAC,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;KAC9E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAe,EACf,CAAe,EACf,YAAqC;IAErC,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC;QAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,YAAY;KACb,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,oFAAoF;AAEpF;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAkC,EAAE,QAAkB,EAAE,OAAe;IAClG,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;AACvE,CAAC","sourcesContent":["/**\n * The `--json` rows. Separate from `./print.ts` because only this side is a\n * contract: the payload keys are frozen, the columns beside them are not.\n */\nimport { repoPath } from \"../../core/envelope/json.js\";\nimport { type CapabilityRow } from \"../../core/capabilities/rollup.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { servicesUnder } from \"../../core/repo/tree/find.js\";\nimport type { FleetTree } from \"../../core/repo/tree/walk.js\";\nimport type { OwnersJoin } from \"./campaign/owners.js\";\nimport { type ServiceView, type VerificationCell } from \"./views.js\";\n\n/**\n * The `services[]` rows: ranked and decorated under `--review-order`, plain\n * otherwise — as a VARIANT, so a call carrying both a plain list and a ranked\n * queue is unrepresentable rather than resolved by precedence. `fanIn`/\n * `reviewRank` are ADDITIVE and appear ONLY under the flag — bare\n * `loam list --json` and `--needs-work --json` stay byte-identical and\n * cost-identical, the frozen-default doctrine the explicit-only capabilities\n * section also follows. The spread keeps serviceJson at its two-param\n * contract; `reviewRank` is 1-based and equals the row's position, so the\n * array order IS the queue.\n */\nexport function serviceRows(\n docsDir: string,\n rows: ServiceView[] | { queue: ServiceView[]; fanIn: ReadonlyMap<string, number> },\n): Record<string, unknown>[] {\n if (Array.isArray(rows)) return rows.map((v) => serviceJson(docsDir, v));\n return rows.queue.map((v, i) => ({\n ...serviceJson(docsDir, v),\n fanIn: rows.fanIn.get(v.entry.id) ?? 0,\n reviewRank: i + 1,\n }));\n}\n\nexport function serviceJson(docsDir: string, v: ServiceView): Record<string, unknown> {\n const s = v.entry;\n return {\n id: s.id,\n path: repoPath(docsDir, s.dir),\n // Names from services/ down, outermost first; [] = unfiled. `path` above\n // already truthfully reflects placement — this is the same fact as data\n // instead of a string to split.\n subsystem: s.subsystem,\n has: { ...s.has, archSpec: v.archSpec },\n adrs: s.adrs,\n status: s.status,\n maturity: v.maturity,\n missing: v.missing,\n apiExpected: v.apiExpected,\n // Never omitted when true: a consumer that filters this table into a\n // worklist has to be able to tell \"checked and fine\" from \"not checkable\n // from here\", and absence reads as the first one.\n ...(v.unverifiableFromHere ? { provenance: \"unverifiable-from-here\" } : {}),\n // Additive, and only when there IS a scope — omission is the fine case\n // here, exactly as it is for `provenance` above, and `maturity` stays the\n // frozen `vouched` beside it. A dashboard counting `maturity: \"vouched\"`\n // rows keeps counting them; one that cares whether a person read the\n // document subtracts these.\n ...(s.vouchScope === \"sampled\" ? { vouchScope: \"sampled\" } : {}),\n };\n}\n\n/**\n * The subsystem rows beside the service table, sorted by path. `memberCount`\n * is TRANSITIVE — every service beneath, at any depth — matching the\n * generated views file's membership, so the two numbers a consumer can\n * cross-check never disagree about what \"in this group\" means.\n */\nexport function subsystemsJson(docsDir: string, tree: FleetTree): Record<string, unknown>[] {\n return [...tree.subsystems]\n .sort((a, b) => (a.path.join(\"/\") < b.path.join(\"/\") ? -1 : 1))\n .map((sub) => ({\n name: sub.name,\n path: repoPath(docsDir, sub.dir),\n title: sub.meta.title ?? null,\n memberCount: servicesUnder(tree, sub).length,\n }));\n}\n\n/**\n * The additive `owners` payload key under `--owners` — the join result as a\n * contract: `path` (the file exactly as the user named it), `teams[]` (owner\n * plus that team's services, in the LISTING's own filtered-and-ordered row\n * order, so each array is that team's campaign worklist), `unowned[]` (rows\n * no rule matched — explicit, because absence would read as owned-somewhere),\n * and `skippedRules[]` (recognised rules outside the implemented CODEOWNERS\n * subset, each with its line — reported, never guessed at). Key spellings\n * frozen once shipped; the join itself lives in campaign/owners.ts.\n *\n * The service arrays carry IDS. On a `subsystem.name-collision` fleet —\n * already an error finding — two rows can share an id, so a consumer cannot\n * tell them apart here; if that ever needs disambiguating, the fix is a new\n * ADDITIVE key carrying paths, never a change to these.\n */\nexport function ownersJson(join: OwnersJoin): Record<string, unknown> {\n return {\n path: join.path,\n teams: join.teams.map((team) => ({ owner: team.owner, services: team.services.map((row) => row.id) })),\n unowned: join.unowned.map((row) => row.id),\n skippedRules: join.skipped.map((rule) => ({ line: rule.line, pattern: rule.pattern })),\n };\n}\n\n/**\n * One `capabilities[]` row — additive payload, explicit `loam list\n * capabilities` only, and diff-stable: the rollup already sorts rows,\n * realizedBy and statuses' keys deterministically, and this projection adds\n * nothing that could vary between machines. Optional keys are omitted rather\n * than nulled so a declaration that never carried a description does not grow\n * one in the diff.\n */\nexport function capabilityJson(row: CapabilityRow): Record<string, unknown> {\n return {\n id: row.id,\n ...(row.description !== undefined ? { description: row.description } : {}),\n ...(row.owner !== undefined ? { owner: row.owner } : {}),\n realizedBy: row.realizedBy,\n services: row.services,\n statuses: row.statuses,\n // Present only for a capability that HAS a document, and absent rather than\n // `[]` for one that does not — the same optional-key rule the two fields\n // above follow, and here it carries a distinction a reader needs: an empty\n // array means the document declares no requirements, while no key at all\n // means there is no document to declare any.\n //\n // Each row inside carries `realizedBy` (the service requirements whose\n // `Realizes:` line names it) and `keptBy` (the flows whose `#cap-`/`#req-`\n // tags resolve to it) — two independent corpora, NEITHER derived from the\n // other, because a cross-service promise can only be kept by a flow and a\n // per-service one is normally kept by a requirement. `keptBy` follows the\n // same three-state rule one level down and `rollup.ts` states it: absent\n // means nobody looked, `[]` means loam looked and no flow keeps it. The\n // payload's own `useCases.unreadable` says which of the two absences it is.\n ...(row.requirements === undefined ? {} : { requirements: row.requirements }),\n };\n}\n\nexport function featureJson(\n docsDir: string,\n f: FeatureEntry,\n verification: VerificationCell | null,\n): Record<string, unknown> {\n return {\n id: f.id,\n dirName: f.dirName,\n path: repoPath(docsDir, f.dir),\n archived: f.archived,\n services: f.services,\n has: f.has,\n verification,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Verification */\n/* ------------------------------------------------------------------ */\n\n/** What the features table says about verification without N `loam verify` runs. */\n\n/**\n * One `glossary[]` row — additive payload, explicit `loam list glossary` only.\n *\n * `linkedBy` is the whole reason the section exists: \"which documents use this\n * term\" is a grep today and a join here, and it is the same answer\n * `glossary.unlinked` grades. Repo-relative and sorted by the index itself, so\n * the payload is diff-stable across machines. Empty means loam read every\n * authored document and none of them cites the term — never \"nobody looked\",\n * which is what the sibling `links.unreadable` list is for.\n */\nexport function glossaryJson(term: { id: string; path: string }, linkedBy: string[], docsDir: string): Record<string, unknown> {\n return { id: term.id, path: repoPath(docsDir, term.path), linkedBy };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/commands/list/json.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `--json` rows. Separate from `./print.ts` because only this side is a\n * contract: the payload keys are frozen, the columns beside them are not.\n */\nimport { repoPath } from \"../../core/envelope/json.js\";\nimport { type CapabilityRow } from \"../../core/capabilities/rollup.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { servicesUnder } from \"../../core/repo/tree/find.js\";\nimport type { FleetTree } from \"../../core/repo/tree/walk.js\";\nimport type { OwnersJoin } from \"./campaign/owners.js\";\nimport { type ServiceView, type VerificationCell } from \"./views.js\";\n\n/**\n * The `services[]` rows: ranked and decorated under `--review-order`, plain\n * otherwise — as a VARIANT, so a call carrying both a plain list and a ranked\n * queue is unrepresentable rather than resolved by precedence. `fanIn`/\n * `reviewRank` are ADDITIVE and appear ONLY under the flag — bare\n * `loam list --json` and `--needs-work --json` stay byte-identical and\n * cost-identical, the frozen-default doctrine the explicit-only capabilities\n * section also follows. The spread keeps serviceJson at its two-param\n * contract; `reviewRank` is 1-based and equals the row's position, so the\n * array order IS the queue.\n */\nexport function serviceRows(\n docsDir: string,\n rows: ServiceView[] | { queue: ServiceView[]; fanIn: ReadonlyMap<string, number> },\n): Record<string, unknown>[] {\n if (Array.isArray(rows)) return rows.map((v) => serviceJson(docsDir, v));\n return rows.queue.map((v, i) => ({\n ...serviceJson(docsDir, v),\n fanIn: rows.fanIn.get(v.entry.id) ?? 0,\n reviewRank: i + 1,\n }));\n}\n\nexport function serviceJson(docsDir: string, v: ServiceView): Record<string, unknown> {\n const s = v.entry;\n return {\n id: s.id,\n path: repoPath(docsDir, s.dir),\n // Names from services/ down, outermost first; [] = unfiled. `path` above\n // already truthfully reflects placement — this is the same fact as data\n // instead of a string to split.\n subsystem: s.subsystem,\n has: { ...s.has, archSpec: v.archSpec },\n adrs: s.adrs,\n status: s.status,\n maturity: v.maturity,\n missing: v.missing,\n apiExpected: v.apiExpected,\n // Never omitted when true: a consumer that filters this table into a\n // worklist has to be able to tell \"checked and fine\" from \"not checkable\n // from here\", and absence reads as the first one.\n ...(v.unverifiableFromHere ? { provenance: \"unverifiable-from-here\" } : {}),\n // Additive, and only when there IS a scope — omission is the fine case\n // here, exactly as it is for `provenance` above, and `maturity` stays the\n // frozen `vouched` beside it. A dashboard counting `maturity: \"vouched\"`\n // rows keeps counting them; one that cares whether a person read the\n // document subtracts these.\n ...(s.vouchScope === \"sampled\" ? { vouchScope: \"sampled\" } : {}),\n };\n}\n\n/**\n * The subsystem rows beside the service table, sorted by path. `memberCount`\n * is TRANSITIVE — every service beneath, at any depth — matching the\n * generated views file's membership, so the two numbers a consumer can\n * cross-check never disagree about what \"in this group\" means.\n */\nexport function subsystemsJson(docsDir: string, tree: FleetTree): Record<string, unknown>[] {\n return [...tree.subsystems]\n .sort((a, b) => (a.path.join(\"/\") < b.path.join(\"/\") ? -1 : 1))\n .map((sub) => ({\n name: sub.name,\n path: repoPath(docsDir, sub.dir),\n title: sub.meta.title ?? null,\n memberCount: servicesUnder(tree, sub).length,\n }));\n}\n\n/**\n * The additive `owners` payload key under `--owners` — the join result as a\n * contract: `path` (the file exactly as the user named it), `teams[]` (owner\n * plus that team's services, in the LISTING's own filtered-and-ordered row\n * order, so each array is that team's campaign worklist), `unowned[]` (rows\n * no rule matched — explicit, because absence would read as owned-somewhere),\n * and `skippedRules[]` (recognised rules outside the implemented CODEOWNERS\n * subset, each with its line — reported, never guessed at). Key spellings\n * frozen once shipped; the join itself lives in campaign/owners.ts.\n *\n * The service arrays carry IDS. On a `subsystem.name-collision` fleet —\n * already an error finding — two rows can share an id, so a consumer cannot\n * tell them apart here; if that ever needs disambiguating, the fix is a new\n * ADDITIVE key carrying paths, never a change to these.\n */\nexport function ownersJson(join: OwnersJoin): Record<string, unknown> {\n return {\n path: join.path,\n teams: join.teams.map((team) => ({ owner: team.owner, services: team.services.map((row) => row.id) })),\n unowned: join.unowned.map((row) => row.id),\n skippedRules: join.skipped.map((rule) => ({ line: rule.line, pattern: rule.pattern })),\n };\n}\n\n/**\n * One `capabilities[]` row — additive payload, explicit `loam list\n * capabilities` only, and diff-stable: the rollup already sorts rows,\n * realizedBy and statuses' keys deterministically, and this projection adds\n * nothing that could vary between machines. Optional keys are omitted rather\n * than nulled so a declaration that never carried a description does not grow\n * one in the diff.\n */\nexport function capabilityJson(row: CapabilityRow): Record<string, unknown> {\n return {\n id: row.id,\n ...(row.description !== undefined ? { description: row.description } : {}),\n ...(row.owner !== undefined ? { owner: row.owner } : {}),\n realizedBy: row.realizedBy,\n services: row.services,\n statuses: row.statuses,\n // Present only for a capability that HAS a document, and absent rather than\n // `[]` for one that does not — the same optional-key rule the two fields\n // above follow, and here it carries a distinction a reader needs: an empty\n // array means the document declares no requirements, while no key at all\n // means there is no document to declare any.\n //\n // Each row inside carries `realizedBy` (the service requirements whose\n // `Realizes:` line names it) and `keptBy` (the flows whose `#cap-`/`#req-`\n // tags resolve to it) — two independent corpora, NEITHER derived from the\n // other, because a cross-service promise can only be kept by a flow and a\n // per-service one is normally kept by a requirement. `keptBy` follows the\n // same three-state rule one level down and `rollup.ts` states it: absent\n // means nobody looked, `[]` means loam looked and no flow keeps it. The\n // payload's own `useCases.unreadable` says which of the two absences it is.\n ...(row.requirements === undefined ? {} : { requirements: row.requirements }),\n };\n}\n\nexport function featureJson(\n docsDir: string,\n f: FeatureEntry,\n verification: VerificationCell | null,\n): Record<string, unknown> {\n return {\n id: f.id,\n dirName: f.dirName,\n path: repoPath(docsDir, f.dir),\n archived: f.archived,\n services: f.services,\n has: f.has,\n verification,\n };\n}\n\n/**\n * One `glossary[]` row — additive payload, explicit `loam list glossary` only.\n *\n * `linkedBy` is the whole reason the section exists: \"which documents use this\n * term\" is a grep today and a join here, and it is the same answer\n * `glossary.unlinked` grades. Repo-relative and sorted by the index itself, so\n * the payload is diff-stable across machines. Empty means loam read every\n * authored document and none of them cites the term — never \"nobody looked\",\n * which is what the sibling `links.unreadable` list is for.\n */\nexport function glossaryJson(term: { id: string; path: string }, linkedBy: string[], docsDir: string): Record<string, unknown> {\n return { id: term.id, path: repoPath(docsDir, term.path), linkedBy };\n}\n"]}
|