@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +37 -34
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +103 -23
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +106 -46
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +21 -107
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +36 -14
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +55 -52
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/protocol.js +1 -1
- package/dist/core/agent/protocol.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/support/report.js +46 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +147 -16
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/docs.js +31 -22
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/delta.js +10 -7
- package/dist/core/glossary/delta.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +40 -10
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +31 -7
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +70 -12
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/seed/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a seed run will write, decided in full before a byte lands. Split from\n * `./commit.ts` on the phase seam: that module owns the WINDOW — the lock, the\n * interrupted-commit recovery, the two refusals and the transaction — while\n * this one answers the single question \"given the tree as it is and the fleet\n * file as it reads, which files change and how\". Nothing here acquires\n * anything, and nothing here writes: it is called under the lock and hands\n * back a plan the transaction executes or rolls back whole.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Elem } from \"../../core/c4/likec4.js\";\nimport type { FleetSeed } from \"../../core/c4/seed/fleet-file.js\";\nimport { serviceDirOf, type DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport {\n landscapePath,\n servicePathsUnder,\n servicesDir,\n subsystemPathUnder,\n subsystemViewsPath,\n unfiledServicePaths,\n} from \"../../core/repo/paths.js\";\nimport { SUBSYSTEM_MARKER } from \"../../core/repo/tree/marker.js\";\nimport { renderSubsystemViews, viewsAgree } from \"../../core/repo/tree/views.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"../../core/repo/tree/walk.js\";\nimport { TEMP_FILE_RE } from \"../../core/staging/commit.js\";\nimport { planWrite, type PlannedWrite } from \"../../core/staging/writes.js\";\n\n/** Everything the plan reads. The commit request extends this and adds only `rerun`. */\nexport interface SeedPlanInput {\n docsDir: DocsDir;\n seed: FleetSeed;\n /** The sealed landscape text (stamp on line 1), already proven to parse. */\n landscape: string;\n /**\n * That same text's parsed elements — the loadSource self-check's output,\n * reused for the views join so the generated views file agrees byte for byte\n * with what `validate --all` will recompute from the committed landscape.\n */\n elements: Elem[];\n}\n\nexport interface SeedPlan {\n writes: PlannedWrite[];\n /** Service ids split by whether this run creates the directory. */\n services: { created: string[]; existing: string[] };\n}\n\n/**\n * Does this existing service directory hold nothing git would keep? An empty\n * directory is a service to the walk but nothing at all to git, so a landscape\n * declaring it clones into a repo where `validate --all` fails on a service\n * that exists only on the author's disk. Seed's own crash window produces\n * exactly this tree — `stageWrites` creates directories before the journal is\n * written, so a kill in between leaves directories no recovery knows about —\n * and so does a plain `mkdir`. Staging temps do not count as content: they are\n * that same crash's litter, not somebody's file.\n */\nasync function keepsNothing(dir: string): Promise<boolean> {\n try {\n return (await readdir(dir)).every((entry) => TEMP_FILE_RE.test(entry));\n } catch {\n // Unreadable is not empty: the walk found this directory a moment ago, and\n // inventing a write into a directory we cannot list is the wrong guess.\n return false;\n }\n}\n\n/**\n * The complete plan, in memory, before a byte lands: the sealed landscape, one\n * marker per NEW subsystem, one `.gitkeep` per service directory this commit\n * creates — and per existing one holding nothing git would keep (see\n * `keepsNothing`) — and the generated views file rendered from the tree AS\n * THIS COMMIT LEAVES IT, never from a re-walk between two states\n * (commands/subsystem/txn/views.ts's `movedTree` doctrine). That last one is\n * the only write that can be a DELETE: a tree with no subsystems owes no views\n * file, and a leftover is a finding.\n *\n * Existing directories are never moved: fleet.yaml's placement is ignored for\n * a service that already exists — moving is `loam subsystem move <id> --into\n * <name>`'s job, under its own guards — and nothing anywhere is deleted except\n * that one generated file.\n */\nexport async function planSeedWrites(req: SeedPlanInput, tree: FleetTree): Promise<SeedPlan> {\n const { docsDir, seed } = req;\n const writes: PlannedWrite[] = [planWrite(landscapePath(docsDir), req.landscape)];\n\n const existingSubs = new Map(tree.subsystems.map((s) => [s.name, s]));\n const newSubs: SubsystemEntry[] = [];\n for (const sub of seed.subsystems) {\n if (existingSubs.has(sub as string)) continue;\n const dir = subsystemPathUnder(servicesDir(docsDir), sub);\n // A comment line parses to null — a valid empty marker (tree/marker.ts).\n writes.push(planWrite(join(dir, SUBSYSTEM_MARKER), \"# subsystem marker\\n\"));\n newSubs.push({ name: sub as string, path: [sub as string], dir, meta: {} });\n }\n\n const treeDirs = new Map(tree.services.map((s) => [s.id as string, s.dir]));\n // Every existing directory inspected at once — independent reads, so the\n // loop below stays synchronous.\n const invisible = new Map(\n await Promise.all([...treeDirs].map(async ([id, dir]) => [id, await keepsNothing(dir)] as const)),\n );\n const created: string[] = [];\n const existing: string[] = [];\n const newWalked: WalkedService[] = [];\n for (const s of seed.services) {\n const already = treeDirs.get(s.id as string);\n if (already !== undefined) {\n existing.push(s.id);\n // Existing, but git-invisible: give it the `.gitkeep` a created service\n // gets, or the landscape about to be written declares a directory the\n // next clone will not have. `planWrite` makes this a no-op when the file\n // is already there, so a real service is never touched.\n if (invisible.get(s.id as string) === true) writes.push(planWrite(join(already, \".gitkeep\"), \"\"));\n continue;\n }\n created.push(s.id);\n const parent = s.subsystem === null ? null : existingSubs.get(s.subsystem as string) ?? null;\n const dir =\n s.subsystem === null\n ? unfiledServicePaths(docsDir, s.id).dir\n : parent !== null\n ? servicePathsUnder(parent.dir, s.id).dir\n : // A subsystem this same commit creates: the creation spelling,\n // through the same branded joins the subsystem verbs use.\n serviceDirOf(join(subsystemPathUnder(servicesDir(docsDir), s.subsystem), s.id));\n writes.push(planWrite(join(dir, \".gitkeep\"), \"\"));\n newWalked.push({\n // A ServiceId IS a RawServiceId (the checked subtype), so no re-cast.\n id: s.id,\n dir,\n subsystem: s.subsystem === null ? [] : parent !== null ? parent.path : [s.subsystem as string],\n });\n }\n\n const plannedTree: FleetTree = {\n findings: [],\n services: [...tree.services, ...newWalked],\n subsystems: [...tree.subsystems, ...newSubs],\n };\n // The generated views file, including its ABSENCE. `renderSubsystemViews`\n // returning null means the tree has no subsystems, and its contract is that\n // the file must then not exist — `validate` grades a leftover as\n // `subsystem.views-stale`, so a seed that ignored the file would exit 0 and\n // send the caller, via its own `next` list, into a failing gate over a file\n // it had in hand. `content: null` is the delete the transaction understands\n // (commands/subsystem/txn/txn.ts's `\"removed\"`), and a file that already\n // SAYS this is left out of the journal entirely rather than written back\n // over itself.\n //\n // \"Already says this\" is content, not bytes: `viewsAgree` performs the\n // comparison and records the Windows reason for it, beside the generator\n // that mints the bytes. What gets WRITTEN is unchanged — the LF render, as a\n // Buffer — so this decode is a comparison only, never the string round trip\n // to disk that writes.ts forbids; a views file whose bytes are not UTF-8\n // therefore fails the compare and is regenerated, which is the right answer\n // for it anyway.\n const viewsPath = subsystemViewsPath(docsDir);\n const views = renderSubsystemViews(plannedTree, req.elements);\n const wanted = views === null ? null : Buffer.from(views, \"utf8\");\n const onDisk = existsSync(viewsPath) ? await readFile(viewsPath, \"utf8\") : null;\n if (!viewsAgree(onDisk, views)) writes.push({ path: viewsPath, content: wanted });\n\n return { writes, services: { created: created.sort(), existing: existing.sort() } };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/seed/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a seed run will write, decided in full before a byte lands. Split from\n * `./commit.ts` on the phase seam: that module owns the WINDOW — the lock, the\n * interrupted-commit recovery, the refusals and the transaction — while\n * this one answers the single question \"given the tree as it is and the fleet\n * file as it reads, which files change and how\". Nothing here acquires\n * anything, and nothing here writes: it is called under the lock and hands\n * back a plan the transaction executes or rolls back whole.\n */\nimport { copyFile, mkdir, mkdtemp, readdir, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { architectureDir, architectureProjectDocuments } from \"../../core/c4/project/architecture.js\";\nimport { loadProject } from \"../../core/c4/project/load.js\";\nimport type { FleetSeed } from \"../../core/c4/seed/fleet-file.js\";\nimport { repoPath } from \"../../core/envelope/json.js\";\nimport { serviceDirOf, type DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport {\n landscapePath,\n servicePathsUnder,\n servicesDir,\n serviceViewsPath,\n subsystemPathUnder,\n subsystemViewsPath,\n unfiledServicePaths,\n} from \"../../core/repo/paths.js\";\nimport { SUBSYSTEM_MARKER } from \"../../core/repo/tree/marker.js\";\nimport { readGeneratedFile } from \"../../core/repo/tree/render/service/header.js\";\nimport {\n type MapFacts,\n renderServiceOverviews,\n renderSubsystemViews,\n viewsAgree,\n} from \"../../core/repo/tree/render/views.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"../../core/repo/tree/walk.js\";\nimport { TEMP_FILE_RE } from \"../../core/staging/commit.js\";\nimport { planWrite, type PlannedWrite } from \"../../core/staging/writes.js\";\n\n/** Everything the plan reads. The commit request extends this and adds only `rerun`. */\nexport interface SeedPlanInput {\n docsDir: DocsDir;\n seed: FleetSeed;\n /** The sealed landscape text (stamp on line 1), already proven to parse. */\n landscape: string;\n /**\n * That same text's parsed facts — the loadSource self-check's output. They\n * were first reused for the views join on their own, on the reasoning that\n * the seed template declares no `global` block (`core/c4/seed/template.ts`)\n * and so the render's style question had the answer \"none\"; true of the\n * template and beside the point, because `validate --all` recomputes the\n * expected bytes from the whole `architecture/` PROJECT, and a palette in a\n * sibling document is in that census (`projectFacts` below carries the\n * defect). They are now the render's input only when the landscape IS the\n * whole project — no sibling document exists — and the fallback when the\n * project does not parse.\n */\n map: MapFacts;\n}\n\n/**\n * The facts the generated views are rendered from: the `architecture/` PROJECT\n * as this commit leaves it — the sealed landscape standing in for the file it is\n * about to become, beside every other `.likec4` the directory already holds —\n * and never the sealed landscape alone.\n *\n * WHY THE PROJECT. `subsystem.views-stale` (`validate --all`) and `loam\n * subsystem sync` both recompute the expected bytes from `loadArchitecture` —\n * the landscape merged with every other `architecture/**` document, the\n * generated file excluded — and the render asks that document set which global\n * style ids it declares. A repository's `global { styleGroup subsystems { … } }`\n * naturally lives in a sibling such as `architecture/usecases/palette.likec4`,\n * since seed regenerates the landscape wholesale, so it was in the grader's\n * census and not in seed's: a seed into such a repo wrote a views file the very\n * next `validate --all` — seed's own first `next` command — graded stale, and\n * re-seeding wrote the same bytes again, the one loop no `next` could leave.\n * Reading the same documents the grader reads is what makes the shared render\n * shared in fact rather than in name (commands/subsystem/txn/views.ts's banner).\n *\n * NO SIBLING, NO WORKSPACE: the landscape's own facts, already in hand from the\n * self-check, ARE the project's, and the common case pays one readdir.\n *\n * STAGED, for `core/c4/project/staged.ts`'s reason: `loadProject` copies\n * documents from disk keyed on their path relative to a base, and the landscape\n * here exists only in memory. That module's staging is bound to a feature's\n * merge preview, so the same handful of copies is made here once more — the\n * temp tree is gone before this returns, and nothing under the docs repo is\n * touched.\n *\n * FAIL CLOSED to the landscape's own facts. A project that does not parse (a\n * sibling carrying an error of its own, or a copy the temp directory refused)\n * declares nothing loam can rely on, so the render carries exactly what it\n * carried before the project was asked, and the seed still lands: that sibling\n * is `landscape.invalid`'s to name on the next `validate --all`, and a seed\n * refusing over it would make seed the first command to grade a file it does\n * not own. Nothing is hidden by the fallback: the grader compares the views\n * file only once the project has parsed (`validate/fleet/landscape.ts`), so\n * the repo sees the one finding that names the sibling, never a stale-views\n * finding over a file no command could have rendered differently.\n */\nasync function projectFacts(req: SeedPlanInput): Promise<MapFacts> {\n const arch = architectureDir(req.docsDir);\n const landscape = resolve(landscapePath(req.docsDir));\n // The PROJECT's documents, root `exclude` applied — `loadArchitecture`'s own\n // list minus the landscape, which is staged from memory just below. Walking\n // `architecture/` directly skipped that filter, and here that is a WRONG\n // answer rather than an over-scan: a `palette.likec4` under an excluded path\n // is a document the renderer never loads, so its `global styleGroup` id put\n // `global style subsystems` into the generated views file — a name the fleet\n // project cannot resolve — and the very next `validate --all` (seed's own\n // `next` command) graded that file against the same set THIS one now reads.\n // The W5b defect, one command over.\n const siblings = (await architectureProjectDocuments(req.docsDir)).filter((p) => resolve(p) !== landscape);\n if (siblings.length === 0) return req.map;\n // The temp directory is made INSIDE the try, not one line above it: a full\n // disk, a read-only temp root, or a TMPDIR naming a directory that no longer\n // exists rejects `mkdtemp`, and outside the try that rejection left `seed` as\n // the generic `internal` refusal on a repository where nothing is wrong — the\n // defect docs/CODE-STYLE.md's \"read inside the try that handles the read\" rule\n // was written from.\n let root: string | null = null;\n try {\n root = await mkdtemp(join(tmpdir(), \"loam-seed-\"));\n const staged: string[] = [];\n for (const path of [landscape, ...siblings]) {\n const dest = join(root, relative(arch, path));\n await mkdir(dirname(dest), { recursive: true });\n // The landscape is staged from the bytes this commit writes, never from\n // the file on disk — which may be the scaffold's stub, a stamped\n // predecessor, or absent — so the project parsed is the one the grader\n // will read after the commit.\n if (path === landscape) await writeFile(dest, req.landscape, \"utf8\");\n else await copyFile(path, dest);\n staged.push(dest);\n }\n const project = await loadProject(root, staged);\n // RELATIONSHIPS travel with the elements, and dropping them is silent: they\n // are what each per-service overview computes its peer set from, so a facts\n // record without them renders every service view peerless — the service and\n // its own interior, and nothing it touches — with no error anywhere\n // (`core/repo/tree/render/views.ts` states the consequence beside the\n // optional field). The fallback `req.map` is the whole self-check document\n // and already carries them.\n return project.clean\n ? { elements: project.elements, globalStyles: project.globalStyles, relationships: project.relationships }\n : req.map;\n } catch {\n // `loadProject` throws on an error it cannot attribute to a document, and\n // a full disk fails the staging above; both are the fail-closed arm.\n return req.map;\n } finally {\n if (root !== null) await rm(root, { recursive: true, force: true });\n }\n}\n\nexport interface SeedPlan {\n writes: PlannedWrite[];\n /** Service ids split by whether this run creates the directory. */\n services: { created: string[]; existing: string[] };\n /**\n * Repo-relative paths of the generated views files that hold a document loam\n * did NOT write — the plan's own refusal, handed to `./commit.ts` rather than\n * spelled here, because this module decides and never announces.\n *\n * Empty on every ordinary run. Non-empty means the whole seed must be refused\n * before a byte lands: see `stageGenerated`.\n */\n authored: string[];\n}\n\n/**\n * Does this existing service directory hold nothing git would keep? An empty\n * directory is a service to the walk but nothing at all to git, so a landscape\n * declaring it clones into a repo where `validate --all` fails on a service\n * that exists only on the author's disk. Seed's own crash window produces\n * exactly this tree — `stageWrites` creates directories before the journal is\n * written, so a kill in between leaves directories no recovery knows about —\n * and so does a plain `mkdir`. Staging temps do not count as content: they are\n * that same crash's litter, not somebody's file.\n */\nasync function keepsNothing(dir: string): Promise<boolean> {\n try {\n return (await readdir(dir)).every((entry) => TEMP_FILE_RE.test(entry));\n } catch {\n // Unreadable is not empty: the walk found this directory a moment ago, and\n // inventing a write into a directory we cannot list is the wrong guess.\n return false;\n }\n}\n\n/**\n * The complete plan, in memory, before a byte lands: the sealed landscape, one\n * marker per NEW subsystem, one `.gitkeep` per service directory this commit\n * creates — and per existing one holding nothing git would keep (see\n * `keepsNothing`) — and the generated views file rendered from the tree AS\n * THIS COMMIT LEAVES IT, never from a re-walk between two states\n * (commands/subsystem/txn/views.ts's `movedTree` doctrine). That last one is\n * the only write that can be a DELETE: a tree with no subsystems owes no views\n * file, and a leftover is a finding.\n *\n * Existing directories are never moved: fleet.yaml's placement is ignored for\n * a service that already exists — moving is `loam subsystem move <id> --into\n * <name>`'s job, under its own guards — and nothing anywhere is deleted except\n * that one generated file.\n */\nexport async function planSeedWrites(req: SeedPlanInput, tree: FleetTree): Promise<SeedPlan> {\n const { docsDir, seed } = req;\n const writes: PlannedWrite[] = [planWrite(landscapePath(docsDir), req.landscape)];\n\n const existingSubs = new Map(tree.subsystems.map((s) => [s.name, s]));\n const newSubs: SubsystemEntry[] = [];\n for (const sub of seed.subsystems) {\n if (existingSubs.has(sub as string)) continue;\n const dir = subsystemPathUnder(servicesDir(docsDir), sub);\n // A comment line parses to null — a valid empty marker (tree/marker.ts).\n writes.push(planWrite(join(dir, SUBSYSTEM_MARKER), \"# subsystem marker\\n\"));\n newSubs.push({ name: sub as string, path: [sub as string], dir, meta: {} });\n }\n\n const treeDirs = new Map(tree.services.map((s) => [s.id as string, s.dir]));\n // Every existing directory inspected at once — independent reads, so the\n // loop below stays synchronous.\n const invisible = new Map(\n await Promise.all([...treeDirs].map(async ([id, dir]) => [id, await keepsNothing(dir)] as const)),\n );\n const created: string[] = [];\n const existing: string[] = [];\n const newWalked: WalkedService[] = [];\n for (const s of seed.services) {\n const already = treeDirs.get(s.id as string);\n if (already !== undefined) {\n existing.push(s.id);\n // Existing, but git-invisible: give it the `.gitkeep` a created service\n // gets, or the landscape about to be written declares a directory the\n // next clone will not have. `planWrite` makes this a no-op when the file\n // is already there, so a real service is never touched.\n if (invisible.get(s.id as string) === true) writes.push(planWrite(join(already, \".gitkeep\"), \"\"));\n continue;\n }\n created.push(s.id);\n const parent = s.subsystem === null ? null : existingSubs.get(s.subsystem as string) ?? null;\n const dir =\n s.subsystem === null\n ? unfiledServicePaths(docsDir, s.id).dir\n : parent !== null\n ? servicePathsUnder(parent.dir, s.id).dir\n : // A subsystem this same commit creates: the creation spelling,\n // through the same branded joins the subsystem verbs use.\n serviceDirOf(join(subsystemPathUnder(servicesDir(docsDir), s.subsystem), s.id));\n writes.push(planWrite(join(dir, \".gitkeep\"), \"\"));\n newWalked.push({\n // A ServiceId IS a RawServiceId (the checked subtype), so no re-cast.\n id: s.id,\n dir,\n subsystem: s.subsystem === null ? [] : parent !== null ? parent.path : [s.subsystem as string],\n });\n }\n\n const plannedTree: FleetTree = {\n findings: [],\n services: [...tree.services, ...newWalked],\n subsystems: [...tree.subsystems, ...newSubs],\n };\n // The generated views file, including its ABSENCE. `renderSubsystemViews`\n // returning null means the tree has no subsystems, and its contract is that\n // the file must then not exist — `validate` grades a leftover as\n // `subsystem.views-stale`, so a seed that ignored the file would exit 0 and\n // send the caller, via its own `next` list, into a failing gate over a file\n // it had in hand. `content: null` is the delete the transaction understands\n // (commands/subsystem/txn/txn.ts's `\"removed\"`), and a file that already\n // SAYS this is left out of the journal entirely rather than written back\n // over itself.\n //\n // \"Already says this\" is content, not bytes: `viewsAgree` performs the\n // comparison and records the Windows reason for it, beside the generator\n // that mints the bytes. What gets WRITTEN is unchanged — the LF render, as a\n // Buffer — so this decode is a comparison only, never the string round trip\n // to disk that writes.ts forbids; a views file whose bytes are not UTF-8\n // therefore fails the compare and is regenerated, which is the right answer\n // for it anyway.\n //\n // BOTH generated files, from ONE read of the project: `architecture/services.likec4`\n // is the same contract one file over — one overview view per service the map\n // draws, absent when it draws none — and a seed that landed only the subsystem\n // views would send its caller, through its own `next` list, into a\n // `subsystem.views-stale` no `seed` could clear. This is where the seeded\n // services first exist AND the sealed landscape first says what they are, so\n // it is the only moment the pair can be rendered together.\n const facts = await projectFacts(req);\n const authored = [\n await stageGenerated(writes, subsystemViewsPath(docsDir), renderSubsystemViews(plannedTree, facts)),\n await stageGenerated(writes, serviceViewsPath(docsDir), renderServiceOverviews(plannedTree, facts)),\n ].filter((path): path is string => path !== null);\n\n return {\n writes,\n services: { created: created.sort(), existing: existing.sort() },\n authored: authored.map((path) => repoPath(docsDir, path)),\n };\n}\n\n/**\n * Append the write for ONE generated file, or nothing when the file already\n * says it — the comparison the block above argues for, spelled once so the two\n * generated files cannot drift into two rules. Answers the path when the file\n * standing there is SOMEBODY ELSE'S, and stages nothing for it.\n *\n * OWNERSHIP FIRST, before the compare, exactly as `commands/subsystem/sync.ts`\n * asks it — through the same reader, from beneath every writer\n * (`core/repo/tree/render/service/header.ts`). Both of these are filenames loam\n * RESERVES inside `architecture/`, a directory the team owns, and seed rewrites\n * them whole; until this line existed a `loam seed` into a repository that kept\n * a hand-authored diagram at one of those paths destroyed it without a word.\n * That is the same promise `seed-landscape-edited` makes one file over — seed\n * never overwrites human work — and it was missing for the two files seed\n * reserves rather than shares.\n *\n * It is checked before the compare and not after, so that \"the file already says\n * this\" can never vouch for a file loam did not write. An authored file cannot\n * in fact agree with a render that opens with loam's mark, but a reader must not\n * have to prove that to know the guard holds.\n */\nasync function stageGenerated(writes: PlannedWrite[], path: string, content: string | null): Promise<string | null> {\n const { text: onDisk, authored } = await readGeneratedFile(path);\n if (authored) return path;\n if (viewsAgree(onDisk, content)) return null;\n writes.push({ path, content: content === null ? null : Buffer.from(content, \"utf8\") });\n return null;\n}\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { loadSource } from "../../core/c4/likec4.js";
|
|
2
|
+
import { errorText, loadSource } from "../../core/c4/likec4.js";
|
|
3
3
|
import { readFleetFile } from "../../core/c4/seed/fleet-file.js";
|
|
4
4
|
import { sealLandscape } from "../../core/c4/seed/stamp.js";
|
|
5
5
|
import { renderLandscape } from "../../core/c4/seed/template.js";
|
|
6
|
-
import {
|
|
7
|
-
import { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from "../../core/envelope/json.js";
|
|
6
|
+
import { configOrRefuse, emitJson, emitJsonError, fail, repoPath } from "../../core/envelope/json.js";
|
|
8
7
|
import { NotUtf8Error, readUtf8 } from "../../core/staging/writes.js";
|
|
9
8
|
import { sayRecovered } from "../policy/format.js";
|
|
10
9
|
import { docsRepoReady, reportDocsRepoError } from "../policy/gate.js";
|
|
@@ -19,12 +18,10 @@ export function registerSeed(program) {
|
|
|
19
18
|
.action(async (opts) => {
|
|
20
19
|
const json = opts.json === true;
|
|
21
20
|
const fromArg = opts.from ?? "fleet.yaml";
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
reportNoConfig(json, loaded);
|
|
21
|
+
const config = await configOrRefuse(json);
|
|
22
|
+
if (config === null)
|
|
25
23
|
return;
|
|
26
|
-
}
|
|
27
|
-
const { docsDir } = loaded.config;
|
|
24
|
+
const { docsDir } = config;
|
|
28
25
|
// `services`, not `docs`: every id in fleet.yaml becomes a
|
|
29
26
|
// services/<id>/ directory, and the under-lock preflight enumerates the
|
|
30
27
|
// tree — a run without the directory that IS the fleet is guessing.
|
|
@@ -59,19 +56,24 @@ export function registerSeed(program) {
|
|
|
59
56
|
const seed = read.seed;
|
|
60
57
|
// The self-check: the emitted DSL is parsed in memory BEFORE anything
|
|
61
58
|
// is written, so a templater bug can never land as a broken landscape.
|
|
62
|
-
// Its parsed
|
|
59
|
+
// Its parsed document travels to the plan as the landscape's OWN facts —
|
|
60
|
+
// the render's input when no other `architecture/` document exists, and
|
|
61
|
+
// the fallback otherwise. It is not the render's whole input: the global
|
|
62
|
+
// style ids the render asks for are the PROJECT's (the template declares
|
|
63
|
+
// no `global`, but a sibling document may), which `plan.ts`'s
|
|
64
|
+
// `projectFacts` reads the way `validate --all` will.
|
|
63
65
|
const sealed = sealLandscape(renderLandscape(seed));
|
|
64
66
|
const doc = await loadSource(sealed);
|
|
65
67
|
const firstError = doc.errors[0];
|
|
66
68
|
if (firstError !== undefined) {
|
|
67
69
|
return fail(json, "internal", `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +
|
|
68
|
-
`in ${fromArg}; nothing was written. First error: ${firstError
|
|
70
|
+
`in ${fromArg}; nothing was written. First error: ${errorText(firstError)}`);
|
|
69
71
|
}
|
|
70
72
|
const committed = await commitSeed({
|
|
71
73
|
docsDir,
|
|
72
74
|
seed,
|
|
73
75
|
landscape: sealed,
|
|
74
|
-
|
|
76
|
+
map: doc,
|
|
75
77
|
rerun: `loam seed --from ${fromArg}`,
|
|
76
78
|
});
|
|
77
79
|
if (!committed.ok)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/commands/seed/seed.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAwB,MAAM,kCAAkC,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACtG,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,UAAU,EAA8C,MAAM,aAAa,CAAC;AAOrF,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CACV,sFAAsF,CACvF;SACA,MAAM,CAAC,eAAe,EAAE,uEAAuE,CAAC;SAChG,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,KAAK,EAAE,IAAiB,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAClC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;QAClC,2DAA2D;QAC3D,wEAAwE;QACxE,oEAAoE;QACpE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC;YAAE,OAAO;QAEtD,qEAAqE;QACrE,uEAAuE;QACvE,uEAAuE;QACvE,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,YAAY;gBAAE,OAAO,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YACrF,MAAM,CAAC,GAAG,GAA4B,CAAC;YACvC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxB,OAAO,IAAI,CACT,IAAI,EACJ,mBAAmB,EACnB,GAAG,OAAO,wEAAwE;oBAChF,+EAA+E;oBAC/E,sDAAsD,CACzD,CAAC;YACJ,CAAC;YACD,sEAAsE;YACtE,iEAAiE;YACjE,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACxD,OAAO,IAAI,CACT,IAAI,EACJ,mBAAmB,EACnB,GAAG,OAAO,0CAA0C,CAAC,CAAC,OAAO,wBAAwB,CACtF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAEvB,sEAAsE;QACtE,uEAAuE;QACvE,+DAA+D;QAC/D,MAAM,MAAM,GAAG,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,IAAI,CACT,IAAI,EACJ,UAAU,EACV,qFAAqF;gBACnF,MAAM,OAAO,uCAAuC,UAAU,CAAC,OAAO,EAAE,CAC3E,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC;YACjC,OAAO;YACP,IAAI;YACJ,SAAS,EAAE,MAAM;YACjB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,KAAK,EAAE,oBAAoB,OAAO,EAAE;SACrC,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,EAAE;YAAE,OAAO,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAExD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG;YACX,qBAAqB;YACrB,wBAAwB;YACxB,kEAAkE;YAClE,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,wBAAwB,YAAY,EAAE,CAAC,CAAC;SAChF,CAAC;QAEF,IAAI,IAAI,EAAE,CAAC;YACT,QAAQ,CAAC;gBACP,OAAO,EAAE,MAAM;gBACf,SAAS,EAAE,OAAO;gBAClB,SAAS,EAAE,SAAS,CAAC,SAAS;gBAC9B,OAAO,EAAE,OAAO;gBAChB,OAAO;gBACP,QAAQ,EAAE,SAAS,CAAC,QAAQ;gBAC5B,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE;gBACvC,SAAS,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE;gBACrC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;gBACxB,IAAI;gBACJ,GAAG,CAAC,SAAS,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;aAC5E,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,SAAS,CAAC,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,SAAS,CAAC,OAAO,KAAK,YAAY,EAAE,CAAC;YACjF,OAAO,CAAC,GAAG,CAAC,SAAS,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,CAAC,GAAG,CACT,iCAAiC,kBAAkB,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK;YAC3E,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,gBAAgB,IAAI,CAAC,KAAK,CAAC,MAAM,kBAAkB,OAAO,EAAE,CACtF,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjD,uEAAuE;QACvE,yEAAyE;QACzE,4DAA4D;QAC5D,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC;QAC9F,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC7C,0EAA0E;YAC1E,uEAAuE;YACvE,wEAAwE;YACxE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CACT,OAAO,EAAE,4DAA4D;gBACnE,uEAAuE,EAAE,GAAG;gBAC5E,mCAAmC,CACtC,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvB,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,kBAAkB,GAAyC;IAC/D,OAAO,EAAE,SAAS;IAClB,eAAe,EAAE,kDAAkD;IACnE,WAAW,EAAE,qDAAqD;CACnE,CAAC;AAEF,sFAAsF;AACtF,SAAS,eAAe,CAAC,IAAa,EAAE,OAAwB;IAC9D,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC1D,KAAK,WAAW;YACd,OAAO,IAAI,CAAC,IAAI,EAAE,wBAAwB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/D,KAAK,mBAAmB;YACtB,OAAO,IAAI,CAAC,IAAI,EAAE,wBAAwB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/D,KAAK,kBAAkB;YACrB,qEAAqE;YACrE,kEAAkE;YAClE,OAAO,IAAI,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,yEAAyE;IACzE,yEAAyE;IACzE,2EAA2E;IAC3E,iCAAiC;IACjC,MAAM,WAAW,GAAU,OAAO,CAAC;IACnC,MAAM,IAAI,KAAK,CAAC,oDAAoD,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;AACtG,CAAC;AAED,SAAS,YAAY,CAAC,IAAa,EAAE,SAA4C;IAC/E,IAAI,UAAU,IAAI,SAAS;QAAE,OAAO,mBAAmB,CAAC,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClF,IAAI,SAAS,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QAC3C,wEAAwE;QACxE,gDAAgD;QAChD,IAAI,IAAI,EAAE,CAAC;YACT,aAAa,CAAC,mBAAmB,EAAE,SAAS,CAAC,OAAO,EAAE;gBACpD,eAAe,EAAE,SAAS,CAAC,eAAe;aAC3C,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;AACvD,CAAC","sourcesContent":["/**\n * `loam seed` — the fleet map's first hour, mechanically: a tiny\n * human-authored fleet.yaml (service ids, optional subsystems, externals and\n * `a -> b` calls) templated into architecture/landscape.likec4 plus one\n * services/<id>/ directory per service, in one journaled transaction.\n *\n * NOT an extractor, deliberately: a human stated every fact in the file —\n * which services exist and who calls whom is the one thing no generator can\n * read off a repository (core/docs.ts's landscape doctrine) — and loam does\n * mechanical templating over those statements, the same category as\n * `loam new`. Seed never overwrites human work: it writes the landscape only\n * when the file is absent, is the scaffold's untouched stub, or carries its\n * own verified line-1 stamp (`core/c4/seed/stamp.ts`); everything else\n * refuses `seed-landscape-edited`.\n */\nimport type { Command } from \"commander\";\nimport { resolve } from \"node:path\";\nimport { loadSource } from \"../../core/c4/likec4.js\";\nimport { readFleetFile, type SeedFileProblem } from \"../../core/c4/seed/fleet-file.js\";\nimport { sealLandscape } from \"../../core/c4/seed/stamp.js\";\nimport { renderLandscape } from \"../../core/c4/seed/template.js\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { NotUtf8Error, readUtf8 } from \"../../core/staging/writes.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { serviceTreePathOf } from \"../../core/repo/service-target.js\";\nimport { commitSeed, type LandscapeDisposition, type SeedCommit } from \"./commit.js\";\n\ninterface SeedOptions {\n from?: string;\n json?: boolean;\n}\n\nexport function registerSeed(program: Command): void {\n program\n .command(\"seed\")\n .description(\n \"Template the fleet map and service directories from a tiny human-authored fleet.yaml\",\n )\n .option(\"--from <file>\", \"the fleet file to read (default: fleet.yaml in the current directory)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: SeedOptions) => {\n const json = opts.json === true;\n const fromArg = opts.from ?? \"fleet.yaml\";\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const { docsDir } = loaded.config;\n // `services`, not `docs`: every id in fleet.yaml becomes a\n // services/<id>/ directory, and the under-lock preflight enumerates the\n // tree — a run without the directory that IS the fleet is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // The fleet file is INPUT only — read once, from wherever the caller\n // keeps it (it may legitimately live outside both repos), never stored\n // and never written back. Refusals name the spelling the caller typed.\n let text: string;\n try {\n text = await readUtf8(resolve(fromArg));\n } catch (err) {\n if (err instanceof NotUtf8Error) return fail(json, \"seed-file-invalid\", err.message);\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} does not exist — write the fleet file (a services: list of ids, plus ` +\n `optional subsystems:, externals: and calls: like 'checkout -> payments'), or ` +\n `point --from at where it lives. Nothing was written.`,\n );\n }\n // An errno is a filesystem answer (EISDIR, EACCES, …) about the named\n // file; anything without one is a real bug and stays `internal`.\n if (e.path !== undefined || typeof e.errno === \"number\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} could not be read as the fleet file — ${e.message}. Nothing was written.`,\n );\n }\n throw err;\n }\n\n const read = readFleetFile(text, fromArg);\n if (!read.ok) return refuseFleetFile(json, read.problem);\n const seed = read.seed;\n\n // The self-check: the emitted DSL is parsed in memory BEFORE anything\n // is written, so a templater bug can never land as a broken landscape.\n // Its parsed elements are reused for the generated views join.\n const sealed = sealLandscape(renderLandscape(seed));\n const doc = await loadSource(sealed);\n const firstError = doc.errors[0];\n if (firstError !== undefined) {\n return fail(\n json,\n \"internal\",\n `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +\n `in ${fromArg}; nothing was written. First error: ${firstError.message}`,\n );\n }\n\n const committed = await commitSeed({\n docsDir,\n seed,\n landscape: sealed,\n elements: doc.elements,\n rerun: `loam seed --from ${fromArg}`,\n });\n if (!committed.ok) return refuseCommit(json, committed);\n\n const written = committed.written.map((w) => repoPath(docsDir, w));\n const removed = committed.removed.map((w) => repoPath(docsDir, w));\n const firstCreated = committed.services.created[0];\n const next = [\n \"loam validate --all\",\n \"loam list --needs-work\",\n // Interpolation is agent-safe: the id already passed the grammar.\n ...(firstCreated === undefined ? [] : [`loam adopt --service ${firstCreated}`]),\n ];\n\n if (json) {\n emitJson({\n command: \"seed\",\n fleetFile: fromArg,\n landscape: committed.landscape,\n created: written,\n removed,\n services: committed.services,\n subsystems: [...seed.subsystems].sort(),\n externals: [...seed.externals].sort(),\n calls: seed.calls.length,\n next,\n ...(committed.recovered === null ? {} : { recovered: committed.recovered }),\n });\n return;\n }\n if (committed.recovered !== null && committed.recovered.outcome !== \"consistent\") {\n console.log(`note: ${sayRecovered(committed.recovered)}`);\n }\n console.log(\n `architecture/landscape.likec4 ${LANDSCAPE_SENTENCE[committed.landscape]} — ` +\n `${seed.services.length} service(s), ${seed.calls.length} call(s), from ${fromArg}`,\n );\n for (const w of written) console.log(` + ${w}`);\n // Only ever the generated views file, and only when the resulting tree\n // has no subsystems left to view — but a removal printed as `+` would be\n // a lie, and an unmentioned one a surprise in `git status`.\n for (const w of removed) console.log(` - ${w} (generated, and this tree has no subsystems)`);\n for (const id of committed.services.existing) {\n // Spelled from the enumeration, and this line is the one place it matters\n // most: the sentence is ABOUT placement, so naming the root form would\n // say \"left exactly where it is\" while pointing at somewhere it is not.\n const at = await serviceTreePathOf(docsDir, id);\n console.log(\n ` = ${at}/ already exists — left exactly where it is (fleet.yaml's ` +\n `placement is ignored for an existing service; \\`loam subsystem move ${id} ` +\n `--into <name>\\` is how one moves)`,\n );\n }\n console.log(\"\\nNext:\");\n for (const n of next) console.log(` ${n}`);\n });\n}\n\nconst LANDSCAPE_SENTENCE: Record<LandscapeDisposition, string> = {\n created: \"created\",\n \"replaced-stub\": \"written (replaced the scaffold's untouched stub)\",\n regenerated: \"regenerated (the line-1 stamp proved no hand edits)\",\n};\n\n/** One stable code per problem kind — the mapping the fleet-file union exists for. */\nfunction refuseFleetFile(json: boolean, problem: SeedFileProblem): void {\n switch (problem.kind) {\n case \"invalid\":\n return fail(json, \"seed-file-invalid\", problem.message);\n case \"duplicate\":\n return fail(json, \"seed-duplicate-service\", problem.message);\n case \"unknown-subsystem\":\n return fail(json, \"seed-unknown-subsystem\", problem.message);\n case \"unknown-endpoint\":\n // The existing code, not a fifth new one: the caller's action is the\n // same as for every other unknown service — fix the name, re-run.\n return fail(json, \"unknown-service\", problem.message);\n }\n // Exhaustiveness checked, not assumed, and the branch is genuinely dead:\n // without it a fifth `kind` falls out of the switch printing nothing and\n // leaving `process.exitCode` unset — a refused fleet file exiting 0, which\n // is the worst answer available.\n const unreachable: never = problem;\n throw new Error(`refuseFleetFile: no code for fleet-file problem '${JSON.stringify(unreachable)}'`);\n}\n\nfunction refuseCommit(json: boolean, committed: Exclude<SeedCommit, { ok: true }>): void {\n if (\"repoGone\" in committed) return reportDocsRepoError(json, committed.repoGone);\n if (committed.code === \"seed-file-invalid\") {\n // The exact ids to paste ride as data beside the prose, so an agent can\n // repair fleet.yaml without parsing a sentence.\n if (json) {\n emitJsonError(\"seed-file-invalid\", committed.message, {\n missingServices: committed.missingServices,\n });\n return;\n }\n return fail(json, \"seed-file-invalid\", committed.message);\n }\n return fail(json, committed.code, committed.message);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/commands/seed/seed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam seed` — the fleet map's first hour, mechanically: a tiny\n * human-authored fleet.yaml (service ids, optional subsystems, externals and\n * `a -> b` calls) templated into architecture/landscape.likec4 plus one\n * services/<id>/ directory per service, in one journaled transaction.\n *\n * NOT an extractor, deliberately: a human stated every fact in the file —\n * which services exist and who calls whom is the one thing no generator can\n * read off a repository (core/docs.ts's landscape doctrine) — and loam does\n * mechanical templating over those statements, the same category as\n * `loam new`. Seed never overwrites human work: it writes the landscape only\n * when the file is absent, is the scaffold's untouched stub, or carries its\n * own verified line-1 stamp (`core/c4/seed/stamp.ts`); everything else\n * refuses `seed-landscape-edited`.\n */\nimport type { Command } from \"commander\";\nimport { resolve } from \"node:path\";\nimport { errorText, loadSource } from \"../../core/c4/likec4.js\";\nimport { readFleetFile, type SeedFileProblem } from \"../../core/c4/seed/fleet-file.js\";\nimport { sealLandscape } from \"../../core/c4/seed/stamp.js\";\nimport { renderLandscape } from \"../../core/c4/seed/template.js\";\nimport { configOrRefuse, emitJson, emitJsonError, fail, repoPath } from \"../../core/envelope/json.js\";\nimport { NotUtf8Error, readUtf8 } from \"../../core/staging/writes.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { serviceTreePathOf } from \"../../core/repo/service-target.js\";\nimport { commitSeed, type LandscapeDisposition, type SeedCommit } from \"./commit.js\";\n\ninterface SeedOptions {\n from?: string;\n json?: boolean;\n}\n\nexport function registerSeed(program: Command): void {\n program\n .command(\"seed\")\n .description(\n \"Template the fleet map and service directories from a tiny human-authored fleet.yaml\",\n )\n .option(\"--from <file>\", \"the fleet file to read (default: fleet.yaml in the current directory)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: SeedOptions) => {\n const json = opts.json === true;\n const fromArg = opts.from ?? \"fleet.yaml\";\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // `services`, not `docs`: every id in fleet.yaml becomes a\n // services/<id>/ directory, and the under-lock preflight enumerates the\n // tree — a run without the directory that IS the fleet is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // The fleet file is INPUT only — read once, from wherever the caller\n // keeps it (it may legitimately live outside both repos), never stored\n // and never written back. Refusals name the spelling the caller typed.\n let text: string;\n try {\n text = await readUtf8(resolve(fromArg));\n } catch (err) {\n if (err instanceof NotUtf8Error) return fail(json, \"seed-file-invalid\", err.message);\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} does not exist — write the fleet file (a services: list of ids, plus ` +\n `optional subsystems:, externals: and calls: like 'checkout -> payments'), or ` +\n `point --from at where it lives. Nothing was written.`,\n );\n }\n // An errno is a filesystem answer (EISDIR, EACCES, …) about the named\n // file; anything without one is a real bug and stays `internal`.\n if (e.path !== undefined || typeof e.errno === \"number\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} could not be read as the fleet file — ${e.message}. Nothing was written.`,\n );\n }\n throw err;\n }\n\n const read = readFleetFile(text, fromArg);\n if (!read.ok) return refuseFleetFile(json, read.problem);\n const seed = read.seed;\n\n // The self-check: the emitted DSL is parsed in memory BEFORE anything\n // is written, so a templater bug can never land as a broken landscape.\n // Its parsed document travels to the plan as the landscape's OWN facts —\n // the render's input when no other `architecture/` document exists, and\n // the fallback otherwise. It is not the render's whole input: the global\n // style ids the render asks for are the PROJECT's (the template declares\n // no `global`, but a sibling document may), which `plan.ts`'s\n // `projectFacts` reads the way `validate --all` will.\n const sealed = sealLandscape(renderLandscape(seed));\n const doc = await loadSource(sealed);\n const firstError = doc.errors[0];\n if (firstError !== undefined) {\n return fail(\n json,\n \"internal\",\n `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +\n `in ${fromArg}; nothing was written. First error: ${errorText(firstError)}`,\n );\n }\n\n const committed = await commitSeed({\n docsDir,\n seed,\n landscape: sealed,\n map: doc,\n rerun: `loam seed --from ${fromArg}`,\n });\n if (!committed.ok) return refuseCommit(json, committed);\n\n const written = committed.written.map((w) => repoPath(docsDir, w));\n const removed = committed.removed.map((w) => repoPath(docsDir, w));\n const firstCreated = committed.services.created[0];\n const next = [\n \"loam validate --all\",\n \"loam list --needs-work\",\n // Interpolation is agent-safe: the id already passed the grammar.\n ...(firstCreated === undefined ? [] : [`loam adopt --service ${firstCreated}`]),\n ];\n\n if (json) {\n emitJson({\n command: \"seed\",\n fleetFile: fromArg,\n landscape: committed.landscape,\n created: written,\n removed,\n services: committed.services,\n subsystems: [...seed.subsystems].sort(),\n externals: [...seed.externals].sort(),\n calls: seed.calls.length,\n next,\n ...(committed.recovered === null ? {} : { recovered: committed.recovered }),\n });\n return;\n }\n if (committed.recovered !== null && committed.recovered.outcome !== \"consistent\") {\n console.log(`note: ${sayRecovered(committed.recovered)}`);\n }\n console.log(\n `architecture/landscape.likec4 ${LANDSCAPE_SENTENCE[committed.landscape]} — ` +\n `${seed.services.length} service(s), ${seed.calls.length} call(s), from ${fromArg}`,\n );\n for (const w of written) console.log(` + ${w}`);\n // Only ever the generated views file, and only when the resulting tree\n // has no subsystems left to view — but a removal printed as `+` would be\n // a lie, and an unmentioned one a surprise in `git status`.\n for (const w of removed) console.log(` - ${w} (generated, and this tree has no subsystems)`);\n for (const id of committed.services.existing) {\n // Spelled from the enumeration, and this line is the one place it matters\n // most: the sentence is ABOUT placement, so naming the root form would\n // say \"left exactly where it is\" while pointing at somewhere it is not.\n const at = await serviceTreePathOf(docsDir, id);\n console.log(\n ` = ${at}/ already exists — left exactly where it is (fleet.yaml's ` +\n `placement is ignored for an existing service; \\`loam subsystem move ${id} ` +\n `--into <name>\\` is how one moves)`,\n );\n }\n console.log(\"\\nNext:\");\n for (const n of next) console.log(` ${n}`);\n });\n}\n\nconst LANDSCAPE_SENTENCE: Record<LandscapeDisposition, string> = {\n created: \"created\",\n \"replaced-stub\": \"written (replaced the scaffold's untouched stub)\",\n regenerated: \"regenerated (the line-1 stamp proved no hand edits)\",\n};\n\n/** One stable code per problem kind — the mapping the fleet-file union exists for. */\nfunction refuseFleetFile(json: boolean, problem: SeedFileProblem): void {\n switch (problem.kind) {\n case \"invalid\":\n return fail(json, \"seed-file-invalid\", problem.message);\n case \"duplicate\":\n return fail(json, \"seed-duplicate-service\", problem.message);\n case \"unknown-subsystem\":\n return fail(json, \"seed-unknown-subsystem\", problem.message);\n case \"unknown-endpoint\":\n // The existing code, not a fifth new one: the caller's action is the\n // same as for every other unknown service — fix the name, re-run.\n return fail(json, \"unknown-service\", problem.message);\n }\n // Exhaustiveness checked, not assumed, and the branch is genuinely dead:\n // without it a fifth `kind` falls out of the switch printing nothing and\n // leaving `process.exitCode` unset — a refused fleet file exiting 0, which\n // is the worst answer available.\n const unreachable: never = problem;\n throw new Error(`refuseFleetFile: no code for fleet-file problem '${JSON.stringify(unreachable)}'`);\n}\n\nfunction refuseCommit(json: boolean, committed: Exclude<SeedCommit, { ok: true }>): void {\n if (\"repoGone\" in committed) return reportDocsRepoError(json, committed.repoGone);\n if (committed.code === \"seed-file-invalid\") {\n // The exact ids to paste ride as data beside the prose, so an agent can\n // repair fleet.yaml without parsing a sentence.\n if (json) {\n emitJsonError(\"seed-file-invalid\", committed.message, {\n missingServices: committed.missingServices,\n });\n return;\n }\n return fail(json, \"seed-file-invalid\", committed.message);\n }\n return fail(json, committed.code, committed.message);\n}\n"]}
|
|
@@ -22,6 +22,7 @@ import {} from "../../core/c4/likec4.js";
|
|
|
22
22
|
import { FleetContext } from "../../core/fleet-context.js";
|
|
23
23
|
import {} from "../../core/repo/entries.js";
|
|
24
24
|
import { featurePaths, featureSpecPaths } from "../../core/repo/paths.js";
|
|
25
|
+
import { featureTitle } from "../../core/repo/repo.js";
|
|
25
26
|
import { parseRequirements } from "../../core/document/parse.js";
|
|
26
27
|
import { featureCapabilityDeltas } from "../../core/capabilities/delta/tree.js";
|
|
27
28
|
import { capabilityDeltaSummaries } from "../../core/capabilities/delta/summary.js";
|
|
@@ -31,7 +32,8 @@ import { featureStatus } from "../../core/status/feature/feature.js";
|
|
|
31
32
|
import { executableNext } from "../../core/status/actions/execution.js";
|
|
32
33
|
import { findingJson } from "../../core/vocabulary/report.js";
|
|
33
34
|
import { plural } from "../policy/format.js";
|
|
34
|
-
import {
|
|
35
|
+
import { kindCounts, mark } from "./marks.js";
|
|
36
|
+
import { errorText } from "../../core/c4/likec4.js";
|
|
35
37
|
const REVIEW_INTENT_LIMIT = 1_200;
|
|
36
38
|
function intentSummary(text) {
|
|
37
39
|
const withoutFrontmatter = text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
|
|
@@ -59,9 +61,7 @@ export async function showFeature(docsDir, feature, json, context = new FleetCon
|
|
|
59
61
|
const reqs = existsSync(specPath) ? parseRequirements(await readFile(specPath, "utf8")) : [];
|
|
60
62
|
services.push({
|
|
61
63
|
id: svc,
|
|
62
|
-
|
|
63
|
-
modified: reqs.filter((r) => r.kind === "MODIFIED").length,
|
|
64
|
-
removed: reqs.filter((r) => r.kind === "REMOVED").length,
|
|
64
|
+
...kindCounts(reqs),
|
|
65
65
|
operations: [...new Set(reqs.flatMap((r) => r.operations))],
|
|
66
66
|
api: await apiChanges(servicePaths.openapi),
|
|
67
67
|
events: await eventChanges(servicePaths.asyncapi),
|
|
@@ -71,6 +71,8 @@ export async function showFeature(docsDir, feature, json, context = new FleetCon
|
|
|
71
71
|
// feature in a fleet that has not adopted the business axis — the walk's own
|
|
72
72
|
// short-circuit, and the whole cost such a fleet pays for this section.
|
|
73
73
|
const capabilities = await capabilityDeltaSummaries((await featureCapabilityDeltas(feature.dir)).docs, async (path) => parseRequirements(await readFile(path, "utf8")));
|
|
74
|
+
// intent.md's title, so the header names the change and not only its key.
|
|
75
|
+
const title = await featureTitle(feature);
|
|
74
76
|
if (json) {
|
|
75
77
|
const status = await featureStatus(docsDir, feature, { context });
|
|
76
78
|
const issues = status.checks.issues.map((finding) => findingJson(finding, { path: status.feature.path, role: "scope" }));
|
|
@@ -80,6 +82,8 @@ export async function showFeature(docsDir, feature, json, context = new FleetCon
|
|
|
80
82
|
command: "show",
|
|
81
83
|
type: "feature",
|
|
82
84
|
id: feature.id,
|
|
85
|
+
// Additive (core/envelope/json.ts).
|
|
86
|
+
title,
|
|
83
87
|
dirName: feature.dirName,
|
|
84
88
|
path: repoPath(docsDir, feature.dir),
|
|
85
89
|
archived: feature.archived,
|
|
@@ -136,7 +140,7 @@ export async function showFeature(docsDir, feature, json, context = new FleetCon
|
|
|
136
140
|
return;
|
|
137
141
|
}
|
|
138
142
|
const state = feature.archived ? "archived" : "active";
|
|
139
|
-
console.log(`${feature.id} ${repoPath(docsDir, feature.dir)} ${state}\n`);
|
|
143
|
+
console.log(`${feature.id}${title === null ? "" : ` · ${title}`} ${repoPath(docsDir, feature.dir)} ${state}\n`);
|
|
140
144
|
console.log(" artifacts");
|
|
141
145
|
console.log(` ${mark(feature.has.intent)} intent.md`);
|
|
142
146
|
const deltaNote = delta.errors.length > 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../src/commands/show/feature.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One feature as `loam show` presents it: its intent, its delta, the\n * per-service specs under it, and the business promises it changes.\n *\n * Split from `./service.ts` because a service and a feature answer different\n * questions — what a repository holds now, against what a change proposes —\n * and only the service view has to reach the landscape for its edges. The\n * one-line shapes both write in are in `./marks.ts`.\n *\n * THE CAPABILITY SECTION IS NOT DECORATION. Without it a feature carrying only\n * a `features/<FEAT>/capabilities/<id>/spec.md` — a business change with no\n * service touched yet, which is the first thing an analyst writes and what\n * `loam new --capability` scaffolds — displayed as a feature that carries\n * nothing at all, from the command a person runs to find out what a feature\n * carries. The archive would then refuse it (`capability.uncovered`,\n * `scaffold.placeholder`) over documents this view said were not there.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { emitJson, repoPath } from \"../../core/envelope/json.js\";\nimport { type Elem } from \"../../core/c4/likec4.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { featurePaths, featureSpecPaths } from \"../../core/repo/paths.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { featureCapabilityDeltas } from \"../../core/capabilities/delta/tree.js\";\nimport { capabilityDeltaSummaries } from \"../../core/capabilities/delta/summary.js\";\nimport { apiChanges } from \"../../core/projection/api.js\";\nimport { eventChanges } from \"../../core/projection/events.js\";\nimport { featureStatus } from \"../../core/status/feature/feature.js\";\nimport { executableNext } from \"../../core/status/actions/execution.js\";\nimport { findingJson } from \"../../core/vocabulary/report.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { plural } from \"../policy/format.js\";\nimport { errorText, mark } from \"./marks.js\";\n\nconst REVIEW_INTENT_LIMIT = 1_200;\n\nfunction intentSummary(text: string): string {\n const withoutFrontmatter = text.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/, \"\");\n const prose = withoutFrontmatter\n .replace(/<!--[\\s\\S]*?-->/g, \"\")\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"))\n .join(\" \");\n return prose.length <= REVIEW_INTENT_LIMIT\n ? prose\n : `${prose.slice(0, REVIEW_INTENT_LIMIT - 1).trimEnd()}…`;\n}\n\nexport async function showFeature(\n docsDir: DocsDir,\n feature: FeatureEntry,\n json: boolean,\n context = new FleetContext(),\n): Promise<void> {\n const paths = featurePaths(feature.dir);\n const delta = feature.has.delta\n ? await context.loadLikeC4(paths.delta)\n : { errors: [], elements: [] as Elem[], relationships: [] };\n const taggedEls = delta.elements.filter((e) => e.tags.includes(feature.id));\n const taggedRels = delta.relationships.filter((r) => r.tags.includes(feature.id));\n\n const services = [];\n for (const svc of feature.services) {\n const servicePaths = featureSpecPaths(feature.dir, svc);\n const specPath = servicePaths.spec;\n const reqs = existsSync(specPath) ? parseRequirements(await readFile(specPath, \"utf8\")) : [];\n services.push({\n id: svc,\n added: reqs.filter((r) => r.kind === \"ADDED\").length,\n modified: reqs.filter((r) => r.kind === \"MODIFIED\").length,\n removed: reqs.filter((r) => r.kind === \"REMOVED\").length,\n operations: [...new Set(reqs.flatMap((r) => r.operations))],\n api: await apiChanges(servicePaths.openapi),\n events: await eventChanges(servicePaths.asyncapi),\n });\n }\n\n // One `existsSync` for a feature with no capability delta, which is every\n // feature in a fleet that has not adopted the business axis — the walk's own\n // short-circuit, and the whole cost such a fleet pays for this section.\n const capabilities = await capabilityDeltaSummaries(\n (await featureCapabilityDeltas(feature.dir)).docs,\n async (path) => parseRequirements(await readFile(path, \"utf8\")),\n );\n\n if (json) {\n const status = await featureStatus(docsDir, feature, { context });\n const issues = status.checks.issues.map((finding) =>\n findingJson(finding, { path: status.feature.path, role: \"scope\" }),\n );\n const blockers = issues.filter(\n (finding) => finding[\"severity\"] === \"error\" || finding[\"gates\"] === true,\n );\n const intent = existsSync(paths.intent) ? intentSummary(await readFile(paths.intent, \"utf8\")) : \"\";\n emitJson({\n command: \"show\",\n type: \"feature\",\n id: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n archived: feature.archived,\n has: feature.has,\n delta: {\n elements: taggedEls.length,\n relationships: taggedRels.length,\n errors: delta.errors.map(errorText),\n },\n services,\n // Additive, which the envelope permits (core/envelope/json.ts), and an\n // EMPTY ARRAY rather than an omitted key for a feature that carries none:\n // a consumer indexing it must not have to tell \"no capability delta\" from\n // \"this loam does not report them\", and the two would be the same absence.\n capabilities: capabilities.map((c) => ({\n id: c.id,\n path: repoPath(docsDir, c.spec),\n added: c.added,\n modified: c.modified,\n removed: c.removed,\n promises: c.promises,\n })),\n review: {\n readyToArchive: !feature.archived && status.feature.stage === \"done\" && blockers.length === 0,\n intent: {\n path: repoPath(docsDir, paths.intent),\n summary: intent,\n },\n architecture: {\n path: repoPath(docsDir, paths.delta),\n elements: taggedEls.map((element) => ({\n id: element.id,\n kind: element.kind,\n title: element.title,\n })),\n relationships: taggedRels.map((relationship) => ({\n source: relationship.source,\n target: relationship.target,\n title: relationship.title ?? null,\n operation: relationship.op ?? null,\n })),\n errors: delta.errors.map(errorText),\n },\n services,\n dependencies: { blockedBy: status.feature.blockedBy },\n artifacts: status.artifacts,\n verification: status.verification,\n blockers,\n advisories: issues.filter(\n (finding) => finding[\"severity\"] === \"warn\" && finding[\"gates\"] !== true,\n ),\n useCases: status.useCases,\n next: status.next.map(executableNext),\n },\n });\n return;\n }\n\n const state = feature.archived ? \"archived\" : \"active\";\n console.log(`${feature.id} ${repoPath(docsDir, feature.dir)} ${state}\\n`);\n\n console.log(\" artifacts\");\n console.log(` ${mark(feature.has.intent)} intent.md`);\n const deltaNote =\n delta.errors.length > 0\n ? `${delta.errors.length} error(s)`\n : `${plural(taggedEls.length, \"element\")} · ${plural(taggedRels.length, \"relationship\")} tagged ${feature.id}`;\n console.log(` ${mark(feature.has.delta)} delta.likec4 ${feature.has.delta ? deltaNote : \"\"}`.trimEnd());\n\n if (services.length > 0) {\n console.log(\"\\n services\");\n const width = Math.max(...services.map((s) => s.id.length));\n for (const s of services) {\n const ops = s.operations.length > 0 ? ` · ${s.operations.join(\", \")}` : \"\";\n console.log(\n ` ${s.id.padEnd(width)} +${s.added} ~${s.modified} -${s.removed} requirements${ops}`,\n );\n }\n }\n\n // Printed only when the feature carries one: a fleet that has not adopted the\n // business axis must see nothing, and an empty \"capabilities\" heading over\n // every feature in every such fleet is exactly the noise that teaches people\n // to stop reading the output.\n if (capabilities.length > 0) {\n console.log(\"\\n capabilities\");\n const width = Math.max(...capabilities.map((c) => c.id.length));\n for (const c of capabilities) {\n // The promise ids, not just the counts: `Realizes: <capability>#<id>` is\n // the line somebody has to write next, and the id is the half of it a\n // reader cannot guess.\n const ids = c.promises.flatMap((p) => (p.id === null ? [] : [p.id]));\n console.log(\n ` ${c.id.padEnd(width)} +${c.added} ~${c.modified} -${c.removed} promises` +\n (ids.length === 0 ? \"\" : ` · ${ids.join(\", \")}`),\n );\n }\n }\n\n if (delta.errors.length > 0) {\n console.log(\"\\n delta errors\");\n for (const e of delta.errors) console.log(` ✗ ${errorText(e)}`);\n }\n}\n\n/* ------------------------------------------------------------------ */\n\n/** \"-\" for absent, matching `list`: ✗ is the error glyph here, and a missing runbook is not an error. */\n"]}
|
|
1
|
+
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../src/commands/show/feature.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One feature as `loam show` presents it: its intent, its delta, the\n * per-service specs under it, and the business promises it changes.\n *\n * Split from `./service.ts` because a service and a feature answer different\n * questions — what a repository holds now, against what a change proposes —\n * and only the service view has to reach the landscape for its edges. The\n * one-line shapes both write in are in `./marks.ts`.\n *\n * THE CAPABILITY SECTION IS NOT DECORATION. Without it a feature carrying only\n * a `features/<FEAT>/capabilities/<id>/spec.md` — a business change with no\n * service touched yet, which is the first thing an analyst writes and what\n * `loam new --capability` scaffolds — displayed as a feature that carries\n * nothing at all, from the command a person runs to find out what a feature\n * carries. The archive would then refuse it (`capability.uncovered`,\n * `scaffold.placeholder`) over documents this view said were not there.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { emitJson, repoPath } from \"../../core/envelope/json.js\";\nimport { type Elem } from \"../../core/c4/likec4.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { featurePaths, featureSpecPaths } from \"../../core/repo/paths.js\";\nimport { featureTitle } from \"../../core/repo/repo.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { featureCapabilityDeltas } from \"../../core/capabilities/delta/tree.js\";\nimport { capabilityDeltaSummaries } from \"../../core/capabilities/delta/summary.js\";\nimport { apiChanges } from \"../../core/projection/api.js\";\nimport { eventChanges } from \"../../core/projection/events.js\";\nimport { featureStatus } from \"../../core/status/feature/feature.js\";\nimport { executableNext } from \"../../core/status/actions/execution.js\";\nimport { findingJson } from \"../../core/vocabulary/report.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { plural } from \"../policy/format.js\";\nimport { kindCounts, mark } from \"./marks.js\";\nimport { errorText } from \"../../core/c4/likec4.js\";\n\nconst REVIEW_INTENT_LIMIT = 1_200;\n\nfunction intentSummary(text: string): string {\n const withoutFrontmatter = text.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/, \"\");\n const prose = withoutFrontmatter\n .replace(/<!--[\\s\\S]*?-->/g, \"\")\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"))\n .join(\" \");\n return prose.length <= REVIEW_INTENT_LIMIT\n ? prose\n : `${prose.slice(0, REVIEW_INTENT_LIMIT - 1).trimEnd()}…`;\n}\n\nexport async function showFeature(\n docsDir: DocsDir,\n feature: FeatureEntry,\n json: boolean,\n context = new FleetContext(),\n): Promise<void> {\n const paths = featurePaths(feature.dir);\n const delta = feature.has.delta\n ? await context.loadLikeC4(paths.delta)\n : { errors: [], elements: [] as Elem[], relationships: [] };\n const taggedEls = delta.elements.filter((e) => e.tags.includes(feature.id));\n const taggedRels = delta.relationships.filter((r) => r.tags.includes(feature.id));\n\n const services = [];\n for (const svc of feature.services) {\n const servicePaths = featureSpecPaths(feature.dir, svc);\n const specPath = servicePaths.spec;\n const reqs = existsSync(specPath) ? parseRequirements(await readFile(specPath, \"utf8\")) : [];\n services.push({\n id: svc,\n ...kindCounts(reqs),\n operations: [...new Set(reqs.flatMap((r) => r.operations))],\n api: await apiChanges(servicePaths.openapi),\n events: await eventChanges(servicePaths.asyncapi),\n });\n }\n\n // One `existsSync` for a feature with no capability delta, which is every\n // feature in a fleet that has not adopted the business axis — the walk's own\n // short-circuit, and the whole cost such a fleet pays for this section.\n const capabilities = await capabilityDeltaSummaries(\n (await featureCapabilityDeltas(feature.dir)).docs,\n async (path) => parseRequirements(await readFile(path, \"utf8\")),\n );\n\n // intent.md's title, so the header names the change and not only its key.\n const title = await featureTitle(feature);\n\n if (json) {\n const status = await featureStatus(docsDir, feature, { context });\n const issues = status.checks.issues.map((finding) =>\n findingJson(finding, { path: status.feature.path, role: \"scope\" }),\n );\n const blockers = issues.filter(\n (finding) => finding[\"severity\"] === \"error\" || finding[\"gates\"] === true,\n );\n const intent = existsSync(paths.intent) ? intentSummary(await readFile(paths.intent, \"utf8\")) : \"\";\n emitJson({\n command: \"show\",\n type: \"feature\",\n id: feature.id,\n // Additive (core/envelope/json.ts).\n title,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n archived: feature.archived,\n has: feature.has,\n delta: {\n elements: taggedEls.length,\n relationships: taggedRels.length,\n errors: delta.errors.map(errorText),\n },\n services,\n // Additive, which the envelope permits (core/envelope/json.ts), and an\n // EMPTY ARRAY rather than an omitted key for a feature that carries none:\n // a consumer indexing it must not have to tell \"no capability delta\" from\n // \"this loam does not report them\", and the two would be the same absence.\n capabilities: capabilities.map((c) => ({\n id: c.id,\n path: repoPath(docsDir, c.spec),\n added: c.added,\n modified: c.modified,\n removed: c.removed,\n promises: c.promises,\n })),\n review: {\n readyToArchive: !feature.archived && status.feature.stage === \"done\" && blockers.length === 0,\n intent: {\n path: repoPath(docsDir, paths.intent),\n summary: intent,\n },\n architecture: {\n path: repoPath(docsDir, paths.delta),\n elements: taggedEls.map((element) => ({\n id: element.id,\n kind: element.kind,\n title: element.title,\n })),\n relationships: taggedRels.map((relationship) => ({\n source: relationship.source,\n target: relationship.target,\n title: relationship.title ?? null,\n operation: relationship.op ?? null,\n })),\n errors: delta.errors.map(errorText),\n },\n services,\n dependencies: { blockedBy: status.feature.blockedBy },\n artifacts: status.artifacts,\n verification: status.verification,\n blockers,\n advisories: issues.filter(\n (finding) => finding[\"severity\"] === \"warn\" && finding[\"gates\"] !== true,\n ),\n useCases: status.useCases,\n next: status.next.map(executableNext),\n },\n });\n return;\n }\n\n const state = feature.archived ? \"archived\" : \"active\";\n console.log(`${feature.id}${title === null ? \"\" : ` · ${title}`} ${repoPath(docsDir, feature.dir)} ${state}\\n`);\n\n console.log(\" artifacts\");\n console.log(` ${mark(feature.has.intent)} intent.md`);\n const deltaNote =\n delta.errors.length > 0\n ? `${delta.errors.length} error(s)`\n : `${plural(taggedEls.length, \"element\")} · ${plural(taggedRels.length, \"relationship\")} tagged ${feature.id}`;\n console.log(` ${mark(feature.has.delta)} delta.likec4 ${feature.has.delta ? deltaNote : \"\"}`.trimEnd());\n\n if (services.length > 0) {\n console.log(\"\\n services\");\n const width = Math.max(...services.map((s) => s.id.length));\n for (const s of services) {\n const ops = s.operations.length > 0 ? ` · ${s.operations.join(\", \")}` : \"\";\n console.log(\n ` ${s.id.padEnd(width)} +${s.added} ~${s.modified} -${s.removed} requirements${ops}`,\n );\n }\n }\n\n // Printed only when the feature carries one: a fleet that has not adopted the\n // business axis must see nothing, and an empty \"capabilities\" heading over\n // every feature in every such fleet is exactly the noise that teaches people\n // to stop reading the output.\n if (capabilities.length > 0) {\n console.log(\"\\n capabilities\");\n const width = Math.max(...capabilities.map((c) => c.id.length));\n for (const c of capabilities) {\n // The promise ids, not just the counts: `Realizes: <capability>#<id>` is\n // the line somebody has to write next, and the id is the half of it a\n // reader cannot guess.\n const ids = c.promises.flatMap((p) => (p.id === null ? [] : [p.id]));\n console.log(\n ` ${c.id.padEnd(width)} +${c.added} ~${c.modified} -${c.removed} promises` +\n (ids.length === 0 ? \"\" : ` · ${ids.join(\", \")}`),\n );\n }\n }\n\n if (delta.errors.length > 0) {\n console.log(\"\\n delta errors\");\n for (const e of delta.errors) console.log(` ✗ ${errorText(e)}`);\n }\n}\n\n/* ------------------------------------------------------------------ */\n\n/** \"-\" for absent, matching `list`: ✗ is the error glyph here, and a missing runbook is not an error. */\n"]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A service an active feature INTRODUCES, as `loam show` presents it: which
|
|
3
|
+
* feature carries it, the delta documents under that feature's
|
|
4
|
+
* `specs/<service>/`, what they add, and where a reader goes next.
|
|
5
|
+
*
|
|
6
|
+
* Its own view rather than a branch of `./service.ts`, because every line that
|
|
7
|
+
* view prints is a statement about the LIVING documents — artifact ticks under
|
|
8
|
+
* `services/<id>/`, a vouch, the fleet map's edges — and for this service each
|
|
9
|
+
* one would be a false "absent": the directory does not exist yet, by design,
|
|
10
|
+
* until the feature archives. Until this view existed `loam show` refused the
|
|
11
|
+
* name outright ("No service or feature"), on the same tree where `loam tasks`
|
|
12
|
+
* listed work for it, so the newest service in the fleet was the one a reader
|
|
13
|
+
* could not look up.
|
|
14
|
+
*
|
|
15
|
+
* The per-feature counts are `loam show <FEAT>`'s per-service row, read through
|
|
16
|
+
* the same core projections (`featureSpecPaths`, `apiChanges`, `eventChanges`),
|
|
17
|
+
* so the two views of one delta cannot disagree about what it adds.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { emitJson, repoPath } from "../../core/envelope/json.js";
|
|
21
|
+
import { FleetContext } from "../../core/fleet-context.js";
|
|
22
|
+
import {} from "../../core/repo/entries.js";
|
|
23
|
+
import { featureSpecPaths } from "../../core/repo/paths.js";
|
|
24
|
+
import { apiChanges } from "../../core/projection/api.js";
|
|
25
|
+
import { eventChanges } from "../../core/projection/events.js";
|
|
26
|
+
import {} from "../../core/document/spec.js";
|
|
27
|
+
import { plural } from "../policy/format.js";
|
|
28
|
+
import { kindCounts, mark, requirementJson, requirementLine, scenarioCount } from "./marks.js";
|
|
29
|
+
export async function showIntroduced(docsDir, introduced, json, context) {
|
|
30
|
+
const { id } = introduced;
|
|
31
|
+
const rows = await Promise.all(introduced.features.map((f) => readIntroduction(f, id, context)));
|
|
32
|
+
const by = introduced.features.map((f) => f.id).join(", ");
|
|
33
|
+
if (json) {
|
|
34
|
+
emitJson({
|
|
35
|
+
command: "show",
|
|
36
|
+
type: "service",
|
|
37
|
+
id,
|
|
38
|
+
// The marker a consumer branches on, beside the id. It is absent from a
|
|
39
|
+
// living service's payload rather than `true` there, because that
|
|
40
|
+
// payload is published and stays byte-identical.
|
|
41
|
+
living: false,
|
|
42
|
+
introducedBy: introduced.features.map((f) => f.id),
|
|
43
|
+
introductions: rows.map((row) => ({
|
|
44
|
+
feature: row.feature.id,
|
|
45
|
+
path: repoPath(docsDir, row.paths.dir),
|
|
46
|
+
has: row.has,
|
|
47
|
+
// `show <FEAT> --json`'s per-service row, key for key, so a consumer
|
|
48
|
+
// that reads one reads the other.
|
|
49
|
+
...kindCounts(row.reqs),
|
|
50
|
+
operations: [...new Set(row.reqs.flatMap((r) => r.operations))],
|
|
51
|
+
api: row.api,
|
|
52
|
+
events: row.events,
|
|
53
|
+
// And the living payload's requirement entries, so the delta is
|
|
54
|
+
// listed exactly as the service will list it once it merges.
|
|
55
|
+
requirements: row.reqs.map(requirementJson),
|
|
56
|
+
archSpec: {
|
|
57
|
+
requirements: row.archReqs.length,
|
|
58
|
+
scenarios: scenarioCount(row.archReqs),
|
|
59
|
+
entries: row.archReqs.map(requirementJson),
|
|
60
|
+
},
|
|
61
|
+
next: nextCommands(row.feature, id),
|
|
62
|
+
})),
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(`${id} introduced by ${by} (in flight) — not in the living docs until it archives\n`);
|
|
67
|
+
rows.forEach((row, i) => {
|
|
68
|
+
if (i > 0)
|
|
69
|
+
console.log("");
|
|
70
|
+
printIntroduction(docsDir, row, id);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async function readIntroduction(feature, id, context) {
|
|
74
|
+
const paths = featureSpecPaths(feature.dir, id);
|
|
75
|
+
const has = {
|
|
76
|
+
spec: existsSync(paths.spec),
|
|
77
|
+
archSpec: existsSync(paths.archSpec),
|
|
78
|
+
openapi: existsSync(paths.openapi),
|
|
79
|
+
asyncapi: existsSync(paths.asyncapi),
|
|
80
|
+
};
|
|
81
|
+
// The contract projections carry their own readability, so a delta document
|
|
82
|
+
// that does not parse is said below rather than shown as "no operations".
|
|
83
|
+
const [reqs, archReqs, api, events] = await Promise.all([
|
|
84
|
+
has.spec ? context.readRequirements(paths.spec) : [],
|
|
85
|
+
has.archSpec ? context.readRequirements(paths.archSpec) : [],
|
|
86
|
+
apiChanges(paths.openapi),
|
|
87
|
+
eventChanges(paths.asyncapi),
|
|
88
|
+
]);
|
|
89
|
+
return { feature, paths, has, reqs, archReqs, api, events };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Where a reader goes next: the feature that carries the service, its delta
|
|
93
|
+
* over it, and the open work on it. Both interpolants are enumeration
|
|
94
|
+
* spellings — the canonical feature id and the feature's own directory name for
|
|
95
|
+
* the service — so the printed commands parse.
|
|
96
|
+
*/
|
|
97
|
+
function nextCommands(feature, id) {
|
|
98
|
+
return [
|
|
99
|
+
`loam show ${feature.id}`,
|
|
100
|
+
`loam delta ${feature.id} --service ${id}`,
|
|
101
|
+
`loam tasks ${feature.id} --service ${id}`,
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
function printIntroduction(docsDir, row, id) {
|
|
105
|
+
console.log(` ${row.feature.id} ${repoPath(docsDir, row.paths.dir)}`);
|
|
106
|
+
const specNote = (rs) => {
|
|
107
|
+
const k = kindCounts(rs);
|
|
108
|
+
return `+${k.added} ~${k.modified} -${k.removed} requirements · ${plural(scenarioCount(rs), "scenario")}`;
|
|
109
|
+
};
|
|
110
|
+
console.log(` ${mark(row.has.spec)} spec.md ${row.has.spec ? specNote(row.reqs) : ""}`.trimEnd());
|
|
111
|
+
console.log(` ${mark(row.has.archSpec)} arch.spec.md ${row.has.archSpec ? specNote(row.archReqs) : ""}`.trimEnd());
|
|
112
|
+
const apiNote = row.api.unreadable ? "does not parse" : plural(row.api.changes.length, "operation");
|
|
113
|
+
console.log(` ${mark(row.has.openapi)} openapi.yaml ${row.has.openapi ? apiNote : ""}`.trimEnd());
|
|
114
|
+
const eventNote = row.events.unreadable ? "does not read as AsyncAPI 3.0" : plural(row.events.changes.length, "message");
|
|
115
|
+
console.log(` ${mark(row.has.asyncapi)} asyncapi.yaml ${row.has.asyncapi ? eventNote : ""}`.trimEnd());
|
|
116
|
+
// The kind leads each line: in a delta it is the fact a reviewer reads first,
|
|
117
|
+
// and a requirement listed here without it would read as one already living.
|
|
118
|
+
if (row.reqs.length > 0) {
|
|
119
|
+
console.log("\n requirements");
|
|
120
|
+
for (const r of row.reqs)
|
|
121
|
+
console.log(` [${r.kind}] ${requirementLine(r)}`);
|
|
122
|
+
}
|
|
123
|
+
if (row.archReqs.length > 0) {
|
|
124
|
+
console.log("\n arch requirements");
|
|
125
|
+
for (const r of row.archReqs)
|
|
126
|
+
console.log(` [${r.kind}] ${requirementLine(r)}`);
|
|
127
|
+
}
|
|
128
|
+
if (row.api.unreadable) {
|
|
129
|
+
console.log("\n operations");
|
|
130
|
+
console.log(` ✗ openapi.yaml does not parse — nothing here lists what ${row.feature.id} adds to ${id}`);
|
|
131
|
+
if (row.api.error !== undefined)
|
|
132
|
+
console.log(` ${row.api.error}`);
|
|
133
|
+
}
|
|
134
|
+
else if (row.api.changes.length > 0) {
|
|
135
|
+
console.log("\n operations");
|
|
136
|
+
for (const op of row.api.changes) {
|
|
137
|
+
console.log(` ${op.remove ? "REMOVE " : ""}${op.method} ${op.path} ${op.operationId}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (row.events.unreadable) {
|
|
141
|
+
console.log("\n events");
|
|
142
|
+
console.log(` ✗ asyncapi.yaml does not read as AsyncAPI 3.0 — nothing here lists what ${row.feature.id} adds to ${id}`);
|
|
143
|
+
if (row.events.error !== undefined)
|
|
144
|
+
console.log(` ${row.events.error}`);
|
|
145
|
+
}
|
|
146
|
+
else if (row.events.changes.length > 0) {
|
|
147
|
+
console.log("\n events");
|
|
148
|
+
for (const m of row.events.changes) {
|
|
149
|
+
const direction = m.direction === null ? "" : `${m.direction.toUpperCase()} `;
|
|
150
|
+
console.log(` ${m.remove ? "REMOVE " : ""}${direction}${m.message} (${m.slot})`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.log("\n next");
|
|
154
|
+
for (const command of nextCommands(row.feature, id))
|
|
155
|
+
console.log(` ${command}`);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=introduced.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introduced.js","sourceRoot":"","sources":["../../../src/commands/show/introduced.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A service an active feature INTRODUCES, as `loam show` presents it: which\n * feature carries it, the delta documents under that feature's\n * `specs/<service>/`, what they add, and where a reader goes next.\n *\n * Its own view rather than a branch of `./service.ts`, because every line that\n * view prints is a statement about the LIVING documents — artifact ticks under\n * `services/<id>/`, a vouch, the fleet map's edges — and for this service each\n * one would be a false \"absent\": the directory does not exist yet, by design,\n * until the feature archives. Until this view existed `loam show` refused the\n * name outright (\"No service or feature\"), on the same tree where `loam tasks`\n * listed work for it, so the newest service in the fleet was the one a reader\n * could not look up.\n *\n * The per-feature counts are `loam show <FEAT>`'s per-service row, read through\n * the same core projections (`featureSpecPaths`, `apiChanges`, `eventChanges`),\n * so the two views of one delta cannot disagree about what it adds.\n */\nimport { existsSync } from \"node:fs\";\nimport { emitJson, repoPath } from \"../../core/envelope/json.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { featureSpecPaths, type FeatureSpecPaths } from \"../../core/repo/paths.js\";\nimport { apiChanges, type ApiSlice } from \"../../core/projection/api.js\";\nimport { eventChanges, type EventSlice } from \"../../core/projection/events.js\";\nimport { type Requirement } from \"../../core/document/spec.js\";\nimport type { RawServiceId } from \"../../core/kernel/ids/service.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { plural } from \"../policy/format.js\";\nimport { kindCounts, mark, requirementJson, requirementLine, scenarioCount } from \"./marks.js\";\n\nexport interface Introduced {\n /** The service, as the introducing feature's own `specs/` readdir spelled it. */\n id: RawServiceId;\n /** `introducingFeatures`' answer — never empty when this view is reached. */\n features: FeatureEntry[];\n}\n\n/** One introducing feature's delta over the service, read once for both renderings. */\ninterface Introduction {\n feature: FeatureEntry;\n paths: FeatureSpecPaths;\n has: { spec: boolean; archSpec: boolean; openapi: boolean; asyncapi: boolean };\n reqs: Requirement[];\n archReqs: Requirement[];\n api: ApiSlice;\n events: EventSlice;\n}\n\nexport async function showIntroduced(\n docsDir: DocsDir,\n introduced: Introduced,\n json: boolean,\n context: FleetContext,\n): Promise<void> {\n const { id } = introduced;\n const rows = await Promise.all(introduced.features.map((f) => readIntroduction(f, id, context)));\n const by = introduced.features.map((f) => f.id).join(\", \");\n\n if (json) {\n emitJson({\n command: \"show\",\n type: \"service\",\n id,\n // The marker a consumer branches on, beside the id. It is absent from a\n // living service's payload rather than `true` there, because that\n // payload is published and stays byte-identical.\n living: false,\n introducedBy: introduced.features.map((f) => f.id),\n introductions: rows.map((row) => ({\n feature: row.feature.id,\n path: repoPath(docsDir, row.paths.dir),\n has: row.has,\n // `show <FEAT> --json`'s per-service row, key for key, so a consumer\n // that reads one reads the other.\n ...kindCounts(row.reqs),\n operations: [...new Set(row.reqs.flatMap((r) => r.operations))],\n api: row.api,\n events: row.events,\n // And the living payload's requirement entries, so the delta is\n // listed exactly as the service will list it once it merges.\n requirements: row.reqs.map(requirementJson),\n archSpec: {\n requirements: row.archReqs.length,\n scenarios: scenarioCount(row.archReqs),\n entries: row.archReqs.map(requirementJson),\n },\n next: nextCommands(row.feature, id),\n })),\n });\n return;\n }\n\n console.log(`${id} introduced by ${by} (in flight) — not in the living docs until it archives\\n`);\n rows.forEach((row, i) => {\n if (i > 0) console.log(\"\");\n printIntroduction(docsDir, row, id);\n });\n}\n\nasync function readIntroduction(feature: FeatureEntry, id: RawServiceId, context: FleetContext): Promise<Introduction> {\n const paths = featureSpecPaths(feature.dir, id);\n const has = {\n spec: existsSync(paths.spec),\n archSpec: existsSync(paths.archSpec),\n openapi: existsSync(paths.openapi),\n asyncapi: existsSync(paths.asyncapi),\n };\n // The contract projections carry their own readability, so a delta document\n // that does not parse is said below rather than shown as \"no operations\".\n const [reqs, archReqs, api, events] = await Promise.all([\n has.spec ? context.readRequirements(paths.spec) : [],\n has.archSpec ? context.readRequirements(paths.archSpec) : [],\n apiChanges(paths.openapi),\n eventChanges(paths.asyncapi),\n ]);\n return { feature, paths, has, reqs, archReqs, api, events };\n}\n\n/**\n * Where a reader goes next: the feature that carries the service, its delta\n * over it, and the open work on it. Both interpolants are enumeration\n * spellings — the canonical feature id and the feature's own directory name for\n * the service — so the printed commands parse.\n */\nfunction nextCommands(feature: FeatureEntry, id: RawServiceId): string[] {\n return [\n `loam show ${feature.id}`,\n `loam delta ${feature.id} --service ${id}`,\n `loam tasks ${feature.id} --service ${id}`,\n ];\n}\n\nfunction printIntroduction(docsDir: DocsDir, row: Introduction, id: RawServiceId): void {\n console.log(` ${row.feature.id} ${repoPath(docsDir, row.paths.dir)}`);\n const specNote = (rs: Requirement[]): string => {\n const k = kindCounts(rs);\n return `+${k.added} ~${k.modified} -${k.removed} requirements · ${plural(scenarioCount(rs), \"scenario\")}`;\n };\n console.log(` ${mark(row.has.spec)} spec.md ${row.has.spec ? specNote(row.reqs) : \"\"}`.trimEnd());\n console.log(` ${mark(row.has.archSpec)} arch.spec.md ${row.has.archSpec ? specNote(row.archReqs) : \"\"}`.trimEnd());\n const apiNote = row.api.unreadable ? \"does not parse\" : plural(row.api.changes.length, \"operation\");\n console.log(` ${mark(row.has.openapi)} openapi.yaml ${row.has.openapi ? apiNote : \"\"}`.trimEnd());\n const eventNote = row.events.unreadable ? \"does not read as AsyncAPI 3.0\" : plural(row.events.changes.length, \"message\");\n console.log(` ${mark(row.has.asyncapi)} asyncapi.yaml ${row.has.asyncapi ? eventNote : \"\"}`.trimEnd());\n\n // The kind leads each line: in a delta it is the fact a reviewer reads first,\n // and a requirement listed here without it would read as one already living.\n if (row.reqs.length > 0) {\n console.log(\"\\n requirements\");\n for (const r of row.reqs) console.log(` [${r.kind}] ${requirementLine(r)}`);\n }\n if (row.archReqs.length > 0) {\n console.log(\"\\n arch requirements\");\n for (const r of row.archReqs) console.log(` [${r.kind}] ${requirementLine(r)}`);\n }\n\n if (row.api.unreadable) {\n console.log(\"\\n operations\");\n console.log(` ✗ openapi.yaml does not parse — nothing here lists what ${row.feature.id} adds to ${id}`);\n if (row.api.error !== undefined) console.log(` ${row.api.error}`);\n } else if (row.api.changes.length > 0) {\n console.log(\"\\n operations\");\n for (const op of row.api.changes) {\n console.log(` ${op.remove ? \"REMOVE \" : \"\"}${op.method} ${op.path} ${op.operationId}`);\n }\n }\n\n if (row.events.unreadable) {\n console.log(\"\\n events\");\n console.log(` ✗ asyncapi.yaml does not read as AsyncAPI 3.0 — nothing here lists what ${row.feature.id} adds to ${id}`);\n if (row.events.error !== undefined) console.log(` ${row.events.error}`);\n } else if (row.events.changes.length > 0) {\n console.log(\"\\n events\");\n for (const m of row.events.changes) {\n const direction = m.direction === null ? \"\" : `${m.direction.toUpperCase()} `;\n console.log(` ${m.remove ? \"REMOVE \" : \"\"}${direction}${m.message} (${m.slot})`);\n }\n }\n\n console.log(\"\\n next\");\n for (const command of nextCommands(row.feature, id)) console.log(` ${command}`);\n}\n"]}
|