@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
package/dist/core/repo/repo.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* a repo to walk at all in `state.ts`.
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
|
-
import { readdir } from "node:fs/promises";
|
|
10
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import { listField, readFrontmatter, stringField } from "../document/frontmatter.js";
|
|
12
|
+
import { contentStamp, listField, movedUnderVouch, readFrontmatter, stringField, } from "../document/frontmatter.js";
|
|
13
13
|
import { featureDirOf } from "../kernel/ids/dirs.js";
|
|
14
14
|
import { rawServiceId, serviceIdProblem } from "../kernel/ids/service.js";
|
|
15
15
|
import { readVouchScope } from "../provenance/sample/scope.js";
|
|
16
16
|
import { compareIds, featureIdFromDirName } from "./entries.js";
|
|
17
|
-
import { ARCHIVE_DIR, featurePaths, featuresDir, fleetAdrsDir, isServiceArtifactName, servicePathsAt } from "./paths.js";
|
|
17
|
+
import { ARCHIVE_DIR, featurePaths, featuresDir, fleetAdrsDir, isServiceArtifactName, servicePathsAt, SPEC_AXES, } from "./paths.js";
|
|
18
18
|
import { requireDocsRepo } from "./state.js";
|
|
19
19
|
import { countMarkdown, entryIs } from "./tree/fs.js";
|
|
20
20
|
import { walkServicesTree } from "./tree/walk.js";
|
|
@@ -93,6 +93,33 @@ async function archHeader(path) {
|
|
|
93
93
|
return { present: false, malformed: false, data: {}, body: "" };
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* The spec files whose body moved under their vouch — `content.stale`'s
|
|
98
|
+
* question, asked through the one predicate `loam validate` grades with
|
|
99
|
+
* (`movedUnderVouch`) so the fleet listing and the finding cannot disagree
|
|
100
|
+
* about one file. Only a header that carries a stamp (`contentStamp`) costs the
|
|
101
|
+
* second read: the frontmatter read already answered whether there is anything
|
|
102
|
+
* to compare, and on a fleet mid-adoption most headers do not. The body is
|
|
103
|
+
* read exactly as the finding reads it, as UTF-8 text, because the stamp is
|
|
104
|
+
* byte-exact.
|
|
105
|
+
*/
|
|
106
|
+
async function filesMovedUnderVouch(axes) {
|
|
107
|
+
const moved = await Promise.all(axes.map(async ({ file, path, header }) => contentStamp(header) !== undefined && movedUnderVouch(header, await readFile(path, "utf8")) ? [file] : []));
|
|
108
|
+
return moved.flat();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `ServiceEntry.adoption`, read off one living spec.md's frontmatter — the one
|
|
112
|
+
* spelling of the marker's test, for the enumeration below and for the adopt
|
|
113
|
+
* brief, which must not call a partial adoption finished. Any value but
|
|
114
|
+
* `partial` reads as absent (SCHEMA.md).
|
|
115
|
+
*/
|
|
116
|
+
export function adoptionOf(fm) {
|
|
117
|
+
return stringField(fm, "adoption") === "partial" ? "partial" : null;
|
|
118
|
+
}
|
|
119
|
+
/** `adoptionOf` for a spec.md by path; an absent file is no marker. */
|
|
120
|
+
export async function specAdoption(specPath) {
|
|
121
|
+
return adoptionOf(await readFrontmatter(specPath));
|
|
122
|
+
}
|
|
96
123
|
export async function serviceEntries(tree) {
|
|
97
124
|
const entries = await Promise.all(tree.services.map(async (svc) => {
|
|
98
125
|
const p = servicePathsAt(svc.dir);
|
|
@@ -103,6 +130,7 @@ export async function serviceEntries(tree) {
|
|
|
103
130
|
// sampled. `readFrontmatter` answers "absent" for a file that is not
|
|
104
131
|
// there, so a service without the second axis pays one existsSync.
|
|
105
132
|
const [fm, archFm] = await Promise.all([readFrontmatter(p.spec), archHeader(p.archSpec)]);
|
|
133
|
+
const headers = { spec: fm, archSpec: archFm };
|
|
106
134
|
const idProblem = serviceIdProblem(svc.id, "directory name");
|
|
107
135
|
return {
|
|
108
136
|
id: svc.id,
|
|
@@ -119,6 +147,7 @@ export async function serviceEntries(tree) {
|
|
|
119
147
|
},
|
|
120
148
|
adrs: await countMarkdown(p.adrsDir),
|
|
121
149
|
status: stringField(fm, "status") ?? null,
|
|
150
|
+
adoption: adoptionOf(fm),
|
|
122
151
|
sources: {
|
|
123
152
|
declared: listField(fm, "sources").length > 0,
|
|
124
153
|
stamped: stringField(fm, "sources_digest") !== undefined,
|
|
@@ -129,6 +158,10 @@ export async function serviceEntries(tree) {
|
|
|
129
158
|
// listing is the last place a mangled field should be able to buy
|
|
130
159
|
// itself a full-trust row.
|
|
131
160
|
vouchScope: [fm, archFm].some((header) => readVouchScope(header).kind !== "none") ? "sampled" : null,
|
|
161
|
+
// Both axes again, for the scope's reason: a stamp is per FILE, and an
|
|
162
|
+
// arch.spec.md edited under its vouch is a service part of whose
|
|
163
|
+
// documentation nobody has re-read.
|
|
164
|
+
contentStale: await filesMovedUnderVouch(SPEC_AXES.map((axis) => ({ file: axis.file, path: p[axis.key], header: headers[axis.key] }))),
|
|
132
165
|
};
|
|
133
166
|
}));
|
|
134
167
|
return entries.sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dir, b.dir));
|
|
@@ -148,6 +181,42 @@ export async function serviceEntries(tree) {
|
|
|
148
181
|
export async function fleetAdrCount(docsDir) {
|
|
149
182
|
return countMarkdown(fleetAdrsDir(docsDir));
|
|
150
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* What a feature is CALLED, for a person: intent.md's `title:`, else its first
|
|
186
|
+
* `# ` heading with the feature id peeled off the front, else null.
|
|
187
|
+
*
|
|
188
|
+
* Every row that names a feature used to print the id alone, and an id is a
|
|
189
|
+
* key, not a name — a newcomer looking at `ready FEAT-101` could not tell
|
|
190
|
+
* which change it was without opening the directory. Read on demand rather
|
|
191
|
+
* than carried on `FeatureEntry`, because the enumeration runs under every
|
|
192
|
+
* command and only the views a person reads need the words. A missing or
|
|
193
|
+
* unreadable intent.md is simply no title: the row still prints its id, and
|
|
194
|
+
* whether intent.md is owed is `status`'s question, not this one's.
|
|
195
|
+
*/
|
|
196
|
+
export async function featureTitle(feature) {
|
|
197
|
+
let fm;
|
|
198
|
+
try {
|
|
199
|
+
fm = await readFrontmatter(featurePaths(feature.dir).intent);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// A directory in intent.md's place, a permission: unreadable is no title.
|
|
203
|
+
// `readFrontmatter` folds only the encoding failure into a flag, and before
|
|
204
|
+
// this read the views that print a title never opened intent.md at all —
|
|
205
|
+
// so letting the error escape would hand `loam status` and `loam list`
|
|
206
|
+
// whole to `repository-unavailable` over a label. Whether intent.md is
|
|
207
|
+
// fine is the intent row's and `validate`'s question.
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const declared = stringField(fm, "title")?.trim();
|
|
211
|
+
if (declared !== undefined && declared !== "")
|
|
212
|
+
return declared;
|
|
213
|
+
const heading = fm.body.split(/\r?\n/).find((l) => l.startsWith("# "));
|
|
214
|
+
if (heading === undefined)
|
|
215
|
+
return null;
|
|
216
|
+
const words = heading.slice(2).trim();
|
|
217
|
+
const bare = words.startsWith(feature.id) ? words.slice(feature.id.length).replace(/^[\s:—–-]+/, "") : words;
|
|
218
|
+
return bare === "" ? null : bare;
|
|
219
|
+
}
|
|
151
220
|
async function readFeature(dir, dirName, archived) {
|
|
152
221
|
const p = featurePaths(dir);
|
|
153
222
|
return {
|
|
@@ -201,7 +270,8 @@ export async function listFeatures(docsDir, opts = {}, context) {
|
|
|
201
270
|
* directory over `FEAT-5-slug`, whose id is also `FEAT-5`), and under "include"
|
|
202
271
|
* an active feature wins over an archived one. Ties among equally-exact
|
|
203
272
|
* candidates go to the first by name — deterministic, unlike the raw readdir
|
|
204
|
-
* order this replaces; `
|
|
273
|
+
* order this replaces; `featureCandidates` returns every one of them, for a
|
|
274
|
+
* caller that must see the tie (`loam status` names the colliding directories).
|
|
205
275
|
*/
|
|
206
276
|
export async function resolveFeature(docsDir, arg, archived, context) {
|
|
207
277
|
return (await featureCandidates(docsDir, arg, archived, context))[0] ?? null;
|
|
@@ -220,16 +290,6 @@ export async function featureCandidates(docsDir, arg, archived, context) {
|
|
|
220
290
|
Number(a.archived) - Number(b.archived) ||
|
|
221
291
|
compareIds(a.dirName, b.dirName));
|
|
222
292
|
}
|
|
223
|
-
/**
|
|
224
|
-
* The message for an argument that names more than one feature — two directories
|
|
225
|
-
* carrying the same id (`FEAT-6-aaa` and `FEAT-6-zzz`). `resolveFeature` still
|
|
226
|
-
* picks one deterministically, but a command that is about to WRITE should say
|
|
227
|
-
* which directories collided and make the caller spell one out.
|
|
228
|
-
*/
|
|
229
|
-
export function ambiguousFeatureMessage(arg, candidates) {
|
|
230
|
-
return (`'${arg}' names ${candidates.length} features: ${candidates.map((c) => c.dirName).join(", ")}. ` +
|
|
231
|
-
"Pass the directory name you mean.");
|
|
232
|
-
}
|
|
233
293
|
/**
|
|
234
294
|
* The message for an exclude-mode miss. When the feature exists under
|
|
235
295
|
* `features/archive/`, "no feature" is a lie the caller would have to debug —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../../src/core/repo/repo.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read model of the docs repo — enumeration, and only by the filesystem.\n *\n * Files are the source of truth, so `services/` IS the list of services and\n * there is no manifest that can disagree with it. The shapes this hands back\n * live in `entries.ts`, the layout it walks in `paths.ts`, and whether there is\n * a repo to walk at all in `state.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { listField, readFrontmatter, stringField, type Frontmatter } from \"../document/frontmatter.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { featureDirOf, type DocsDir, type FeatureDir } from \"../kernel/ids/dirs.js\";\nimport { rawServiceId, serviceIdProblem, type RawServiceId } from \"../kernel/ids/service.js\";\nimport { readVouchScope } from \"../provenance/sample/scope.js\";\nimport { compareIds, featureIdFromDirName, type FeatureEntry, type ServiceEntry } from \"./entries.js\";\nimport { ARCHIVE_DIR, featurePaths, featuresDir, fleetAdrsDir, isServiceArtifactName, servicePathsAt } from \"./paths.js\";\nimport { requireDocsRepo } from \"./state.js\";\nimport { countMarkdown, entryIs } from \"./tree/fs.js\";\nimport { walkServicesTree, type FleetTree } from \"./tree/walk.js\";\n\n/* ------------------------------------------------------------------ */\n/* Enumeration */\n/* ------------------------------------------------------------------ */\n\n/**\n * Sorted names of the real subdirectories of `dir` (dot-dirs and files skipped).\n * A missing `dir` is an empty list — the swallow is deliberate but narrow: it\n * only ever runs INSIDE a docs repo that `requireDocsRepo` has already\n * confirmed, where an absent `features/` or `specs/` means \"none yet\" and\n * nothing else.\n */\nasync function subdirs(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => !e.name.startsWith(\".\") && entryIs(dir, e, \"dir\"))\n .map((e) => e.name)\n .sort(compareIds);\n}\n\n/** Services a feature carries a delta for, ordered — the subdirs of its specs/. */\nexport async function featureSpecServices(\n featureDir: FeatureDir,\n context?: FleetContext,\n): Promise<RawServiceId[]> {\n if (context !== undefined) return context.featureSpecServices(featureDir);\n return (await subdirs(featurePaths(featureDir).specsDir)).map(rawServiceId);\n}\n\n/**\n * The classified `services/` tree — services at any depth, the subsystems\n * grouping them, and the walk's own findings (`subsystem.*`). The walk itself\n * lives in `./tree/walk.ts` and never imports back into this package; this\n * function is where its two injected dependencies are built — the docs-repo\n * precondition and the artifact-name table `paths.ts` owns.\n */\nexport async function listFleetTree(docsDir: DocsDir, context?: FleetContext): Promise<FleetTree> {\n if (context !== undefined) return context.fleetTree(docsDir);\n requireDocsRepo(docsDir, [\"ok\"]);\n return walkServicesTree({ docsDir, isServiceArtifact: isServiceArtifactName });\n}\n\n/** Every service in the docs repo, at any depth of the tree, ordered by id. */\nexport async function listServices(docsDir: DocsDir, context?: FleetContext): Promise<ServiceEntry[]> {\n if (context !== undefined) return context.listServices(docsDir);\n return serviceEntries(await listFleetTree(docsDir));\n}\n\n/**\n * The full entries for a walked tree — frontmatter, artifact presence, ADR\n * count. Split from `listServices` so `FleetContext` can derive its services\n * memo from its tree memo: one walk per invocation answers both questions.\n * Ordering is `compareIds` over the id wherever the service sits — placement\n * is never part of any identity, and `--json` consumers diff this order — with\n * the directory as the tiebreak so a duplicated id (its own error finding)\n * still lists deterministically.\n */\n/**\n * The second spec axis's header, or an empty one when nobody could read it.\n *\n * `readFrontmatter` folds a decode failure into a flag but still THROWS when\n * the path exists and cannot be read as a file at all — a directory named\n * `arch.spec.md`, which test/gate-command.test.ts builds on purpose. This is\n * the fleet ENUMERATION, where one service's bad byte must cost one subject\n * and not the listing of the other 119, so the throw stops here.\n *\n * The distinction between \"no scope\" and \"nobody could look\" is not lost by\n * swallowing it: a path that exists and cannot be read is `service.unreadable`\n * — an ERROR that fails `loam validate` and `loam gate` — which is a louder\n * signal than any row of a listing, and it names the file this cannot.\n */\nasync function archHeader(path: string): Promise<Frontmatter> {\n try {\n return await readFrontmatter(path);\n } catch {\n return { present: false, malformed: false, data: {}, body: \"\" };\n }\n}\n\nexport async function serviceEntries(tree: FleetTree): Promise<ServiceEntry[]> {\n const entries = await Promise.all(\n tree.services.map(async (svc): Promise<ServiceEntry> => {\n const p = servicePathsAt(svc.dir);\n // Both spec axes, because a sample is per FILE: one `--sample 3` run can\n // read a short spec.md in full and stamp a scope on a long arch.spec.md\n // beside it, and a fleet row that reported only spec.md's header would\n // print that service as fully vouched while `loam validate` reported it\n // sampled. `readFrontmatter` answers \"absent\" for a file that is not\n // there, so a service without the second axis pays one existsSync.\n const [fm, archFm] = await Promise.all([readFrontmatter(p.spec), archHeader(p.archSpec)]);\n const idProblem = serviceIdProblem(svc.id, \"directory name\");\n return {\n id: svc.id,\n dir: svc.dir,\n subsystem: svc.subsystem,\n ...(idProblem === null ? {} : { idProblem }),\n has: {\n model: existsSync(p.model),\n spec: existsSync(p.spec),\n openapi: existsSync(p.openapi),\n asyncapi: existsSync(p.asyncapi),\n runbook: existsSync(p.runbook),\n health: existsSync(p.health),\n },\n adrs: await countMarkdown(p.adrsDir),\n status: stringField(fm, \"status\") ?? null,\n sources: {\n declared: listField(fm, \"sources\").length > 0,\n stamped: stringField(fm, \"sources_digest\") !== undefined,\n },\n // Either axis makes the SERVICE sampled, and presence is the test, not\n // decodability: an unreadable scope is still a stamped partial read\n // (core/provenance/sample/scope.ts's `readVouchScope`), and the fleet\n // listing is the last place a mangled field should be able to buy\n // itself a full-trust row.\n vouchScope: [fm, archFm].some((header) => readVouchScope(header).kind !== \"none\") ? \"sampled\" : null,\n };\n }),\n );\n return entries.sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dir, b.dir));\n}\n\n/**\n * How many fleet-level ADRs `architecture/adrs/` holds — the same number, by the\n * same rule (`countMarkdown`), that `ServiceEntry.adrs` carries one altitude\n * down. One shape for one question: a second spelling of \"count the decisions\"\n * is how `list` and `show` once reported different ADR counts for one service.\n *\n * A count and never a finding. The directory is optional, an absent one is 0,\n * and nothing in loam grades either — see `fleetAdrsDir` for why that is the\n * whole design rather than an unfinished half of it. No `FleetContext` overload:\n * one caller asks this once per invocation, and a memo whose key is the docsDir\n * would be a cache nobody reads twice.\n */\nexport async function fleetAdrCount(docsDir: DocsDir): Promise<number> {\n return countMarkdown(fleetAdrsDir(docsDir));\n}\n\nasync function readFeature(dir: FeatureDir, dirName: string, archived: boolean): Promise<FeatureEntry> {\n const p = featurePaths(dir);\n return {\n id: featureIdFromDirName(dirName),\n dirName,\n dir,\n archived,\n services: (await subdirs(p.specsDir)).map(rawServiceId),\n has: { intent: existsSync(p.intent), delta: existsSync(p.delta) },\n };\n}\n\n/**\n * Features in the docs repo, ordered by id (active and archived interleaved when\n * both are asked for). `features/archive/` is the archive, never a feature.\n */\nexport async function listFeatures(\n docsDir: DocsDir,\n opts: { includeArchived?: boolean } = {},\n context?: FleetContext,\n): Promise<FeatureEntry[]> {\n if (context !== undefined) return context.listFeatures(docsDir, opts);\n // \"no-services\" is tolerated here on purpose: a docs repo whose services/ is\n // gone is broken, but it is `listServices` (and `loam doctor`) that says so.\n // Refusing here too would turn one diagnosis into two contradictory ones.\n requireDocsRepo(docsDir, [\"ok\", \"no-services\"]);\n const root = featuresDir(docsDir);\n const active = (await subdirs(root)).filter((n) => n !== ARCHIVE_DIR);\n // Branded at the join: the name came off this readdir, onto the features\n // root it was listed under — the provenance `FeatureDir` records.\n const out = await Promise.all(active.map((n) => readFeature(featureDirOf(join(root, n)), n, false)));\n\n if (opts.includeArchived) {\n const archiveRoot = join(root, ARCHIVE_DIR);\n const archived = await subdirs(archiveRoot);\n out.push(...(await Promise.all(archived.map((n) => readFeature(featureDirOf(join(archiveRoot, n)), n, true)))));\n }\n\n return out.sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dirName, b.dirName));\n}\n\n/**\n * Which side of `features/archive/` a lookup may land on. The policy is an\n * argument, not a default, so every command's stance on shipped features is\n * visible at its call site: delta/validate/archive work on in-flight features\n * (\"exclude\"), show/verify/new read shipped ones too (\"include\"), unarchive\n * takes back nothing else (\"only\").\n */\nexport type ArchivedPolicy = \"exclude\" | \"include\" | \"only\";\n\n/**\n * Resolve a feature argument — a canonical id (`FEAT-5`) or an exact directory\n * name (`FEAT-5-slug`) — to its entry. The entry's `id` is the canonical\n * spelling, and it is the only name a caller may use from here on: tag filters,\n * coherence, and the cross-feature self-exclusion scans all compare against\n * `feature.id`, never against the raw argument.\n *\n * Matching is EXACT, on one of two spellings: the directory name as it is on\n * disk, or the canonical id derived from it. It used to also match any dirName\n * starting with `arg + \"-\"`, which made the argument a prefix query in\n * disguise: with FEAT-401 and FEAT-402 in flight, `loam archive FEAT` archived\n * whichever sorted first, and `loam delta billing` reached into\n * `billing-7-rewrite`. A destructive command must never pick a target the\n * caller did not name, so a prefix now resolves to nothing at all.\n *\n * An exact directory name still wins over the id spelling (`FEAT-5` the\n * directory over `FEAT-5-slug`, whose id is also `FEAT-5`), and under \"include\"\n * an active feature wins over an archived one. Ties among equally-exact\n * candidates go to the first by name — deterministic, unlike the raw readdir\n * order this replaces; `ambiguousFeatureMessage` lists them for the caller.\n */\nexport async function resolveFeature(\n docsDir: DocsDir,\n arg: string,\n archived: ArchivedPolicy,\n context?: FleetContext,\n): Promise<FeatureEntry | null> {\n return (await featureCandidates(docsDir, arg, archived, context))[0] ?? null;\n}\n\n/**\n * Every feature the argument names exactly, best first. Split out of\n * `resolveFeature` so a caller that wants to REFUSE an ambiguous argument can\n * see the tie instead of inheriting the winner.\n */\nexport async function featureCandidates(\n docsDir: DocsDir,\n arg: string,\n archived: ArchivedPolicy,\n context?: FleetContext,\n): Promise<FeatureEntry[]> {\n const all = await listFeatures(docsDir, { includeArchived: archived !== \"exclude\" }, context);\n return all\n .filter((f) => archived === \"include\" || f.archived === (archived === \"only\"))\n .filter((f) => f.dirName === arg || f.id === arg)\n .sort(\n (a, b) =>\n Number(b.dirName === arg) - Number(a.dirName === arg) ||\n Number(a.archived) - Number(b.archived) ||\n compareIds(a.dirName, b.dirName),\n );\n}\n\n/**\n * The message for an argument that names more than one feature — two directories\n * carrying the same id (`FEAT-6-aaa` and `FEAT-6-zzz`). `resolveFeature` still\n * picks one deterministically, but a command that is about to WRITE should say\n * which directories collided and make the caller spell one out.\n */\nexport function ambiguousFeatureMessage(arg: string, candidates: FeatureEntry[]): string {\n return (\n `'${arg}' names ${candidates.length} features: ${candidates.map((c) => c.dirName).join(\", \")}. ` +\n \"Pass the directory name you mean.\"\n );\n}\n\n/**\n * The message for an exclude-mode miss. When the feature exists under\n * `features/archive/`, \"no feature\" is a lie the caller would have to debug —\n * the honest answer is \"already archived\", plus how to look at it. The `--json`\n * code stays `unknown-target` either way: the target is unknown to the command\n * that asked, and the prose carries the diagnosis.\n */\nexport async function missingFeatureMessage(\n docsDir: DocsDir,\n arg: string,\n context?: FleetContext,\n): Promise<string> {\n const shipped = await resolveFeature(docsDir, arg, \"only\", context);\n if (shipped !== null) {\n return (\n `Feature '${shipped.id}' is already archived (features/archive/${shipped.dirName}) — ` +\n `\\`loam show ${shipped.id}\\` reads it, \\`loam unarchive ${shipped.id}\\` re-opens it.`\n );\n }\n return `No feature '${arg}' under ${featuresDir(docsDir)}.`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../../src/core/repo/repo.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read model of the docs repo — enumeration, and only by the filesystem.\n *\n * Files are the source of truth, so `services/` IS the list of services and\n * there is no manifest that can disagree with it. The shapes this hands back\n * live in `entries.ts`, the layout it walks in `paths.ts`, and whether there is\n * a repo to walk at all in `state.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n contentStamp,\n listField,\n movedUnderVouch,\n readFrontmatter,\n stringField,\n type Frontmatter,\n} from \"../document/frontmatter.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { featureDirOf, type DocsDir, type FeatureDir } from \"../kernel/ids/dirs.js\";\nimport { rawServiceId, serviceIdProblem, type RawServiceId } from \"../kernel/ids/service.js\";\nimport { readVouchScope } from \"../provenance/sample/scope.js\";\nimport { compareIds, featureIdFromDirName, type FeatureEntry, type ServiceEntry } from \"./entries.js\";\nimport {\n ARCHIVE_DIR,\n featurePaths,\n featuresDir,\n fleetAdrsDir,\n isServiceArtifactName,\n servicePathsAt,\n SPEC_AXES,\n} from \"./paths.js\";\nimport { requireDocsRepo } from \"./state.js\";\nimport { countMarkdown, entryIs } from \"./tree/fs.js\";\nimport { walkServicesTree, type FleetTree } from \"./tree/walk.js\";\n\n/* ------------------------------------------------------------------ */\n/* Enumeration */\n/* ------------------------------------------------------------------ */\n\n/**\n * Sorted names of the real subdirectories of `dir` (dot-dirs and files skipped).\n * A missing `dir` is an empty list — the swallow is deliberate but narrow: it\n * only ever runs INSIDE a docs repo that `requireDocsRepo` has already\n * confirmed, where an absent `features/` or `specs/` means \"none yet\" and\n * nothing else.\n */\nasync function subdirs(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => !e.name.startsWith(\".\") && entryIs(dir, e, \"dir\"))\n .map((e) => e.name)\n .sort(compareIds);\n}\n\n/** Services a feature carries a delta for, ordered — the subdirs of its specs/. */\nexport async function featureSpecServices(\n featureDir: FeatureDir,\n context?: FleetContext,\n): Promise<RawServiceId[]> {\n if (context !== undefined) return context.featureSpecServices(featureDir);\n return (await subdirs(featurePaths(featureDir).specsDir)).map(rawServiceId);\n}\n\n/**\n * The classified `services/` tree — services at any depth, the subsystems\n * grouping them, and the walk's own findings (`subsystem.*`). The walk itself\n * lives in `./tree/walk.ts` and never imports back into this package; this\n * function is where its two injected dependencies are built — the docs-repo\n * precondition and the artifact-name table `paths.ts` owns.\n */\nexport async function listFleetTree(docsDir: DocsDir, context?: FleetContext): Promise<FleetTree> {\n if (context !== undefined) return context.fleetTree(docsDir);\n requireDocsRepo(docsDir, [\"ok\"]);\n return walkServicesTree({ docsDir, isServiceArtifact: isServiceArtifactName });\n}\n\n/** Every service in the docs repo, at any depth of the tree, ordered by id. */\nexport async function listServices(docsDir: DocsDir, context?: FleetContext): Promise<ServiceEntry[]> {\n if (context !== undefined) return context.listServices(docsDir);\n return serviceEntries(await listFleetTree(docsDir));\n}\n\n/**\n * The full entries for a walked tree — frontmatter, artifact presence, ADR\n * count. Split from `listServices` so `FleetContext` can derive its services\n * memo from its tree memo: one walk per invocation answers both questions.\n * Ordering is `compareIds` over the id wherever the service sits — placement\n * is never part of any identity, and `--json` consumers diff this order — with\n * the directory as the tiebreak so a duplicated id (its own error finding)\n * still lists deterministically.\n */\n/**\n * The second spec axis's header, or an empty one when nobody could read it.\n *\n * `readFrontmatter` folds a decode failure into a flag but still THROWS when\n * the path exists and cannot be read as a file at all — a directory named\n * `arch.spec.md`, which test/gate-command.test.ts builds on purpose. This is\n * the fleet ENUMERATION, where one service's bad byte must cost one subject\n * and not the listing of the other 119, so the throw stops here.\n *\n * The distinction between \"no scope\" and \"nobody could look\" is not lost by\n * swallowing it: a path that exists and cannot be read is `service.unreadable`\n * — an ERROR that fails `loam validate` and `loam gate` — which is a louder\n * signal than any row of a listing, and it names the file this cannot.\n */\nasync function archHeader(path: string): Promise<Frontmatter> {\n try {\n return await readFrontmatter(path);\n } catch {\n return { present: false, malformed: false, data: {}, body: \"\" };\n }\n}\n\n/**\n * The spec files whose body moved under their vouch — `content.stale`'s\n * question, asked through the one predicate `loam validate` grades with\n * (`movedUnderVouch`) so the fleet listing and the finding cannot disagree\n * about one file. Only a header that carries a stamp (`contentStamp`) costs the\n * second read: the frontmatter read already answered whether there is anything\n * to compare, and on a fleet mid-adoption most headers do not. The body is\n * read exactly as the finding reads it, as UTF-8 text, because the stamp is\n * byte-exact.\n */\nasync function filesMovedUnderVouch(\n axes: readonly { file: string; path: string; header: Frontmatter }[],\n): Promise<string[]> {\n const moved = await Promise.all(\n axes.map(async ({ file, path, header }) =>\n contentStamp(header) !== undefined && movedUnderVouch(header, await readFile(path, \"utf8\")) ? [file] : [],\n ),\n );\n return moved.flat();\n}\n\n/**\n * `ServiceEntry.adoption`, read off one living spec.md's frontmatter — the one\n * spelling of the marker's test, for the enumeration below and for the adopt\n * brief, which must not call a partial adoption finished. Any value but\n * `partial` reads as absent (SCHEMA.md).\n */\nexport function adoptionOf(fm: Frontmatter): \"partial\" | null {\n return stringField(fm, \"adoption\") === \"partial\" ? \"partial\" : null;\n}\n\n/** `adoptionOf` for a spec.md by path; an absent file is no marker. */\nexport async function specAdoption(specPath: string): Promise<\"partial\" | null> {\n return adoptionOf(await readFrontmatter(specPath));\n}\n\nexport async function serviceEntries(tree: FleetTree): Promise<ServiceEntry[]> {\n const entries = await Promise.all(\n tree.services.map(async (svc): Promise<ServiceEntry> => {\n const p = servicePathsAt(svc.dir);\n // Both spec axes, because a sample is per FILE: one `--sample 3` run can\n // read a short spec.md in full and stamp a scope on a long arch.spec.md\n // beside it, and a fleet row that reported only spec.md's header would\n // print that service as fully vouched while `loam validate` reported it\n // sampled. `readFrontmatter` answers \"absent\" for a file that is not\n // there, so a service without the second axis pays one existsSync.\n const [fm, archFm] = await Promise.all([readFrontmatter(p.spec), archHeader(p.archSpec)]);\n const headers = { spec: fm, archSpec: archFm };\n const idProblem = serviceIdProblem(svc.id, \"directory name\");\n return {\n id: svc.id,\n dir: svc.dir,\n subsystem: svc.subsystem,\n ...(idProblem === null ? {} : { idProblem }),\n has: {\n model: existsSync(p.model),\n spec: existsSync(p.spec),\n openapi: existsSync(p.openapi),\n asyncapi: existsSync(p.asyncapi),\n runbook: existsSync(p.runbook),\n health: existsSync(p.health),\n },\n adrs: await countMarkdown(p.adrsDir),\n status: stringField(fm, \"status\") ?? null,\n adoption: adoptionOf(fm),\n sources: {\n declared: listField(fm, \"sources\").length > 0,\n stamped: stringField(fm, \"sources_digest\") !== undefined,\n },\n // Either axis makes the SERVICE sampled, and presence is the test, not\n // decodability: an unreadable scope is still a stamped partial read\n // (core/provenance/sample/scope.ts's `readVouchScope`), and the fleet\n // listing is the last place a mangled field should be able to buy\n // itself a full-trust row.\n vouchScope: [fm, archFm].some((header) => readVouchScope(header).kind !== \"none\") ? \"sampled\" : null,\n // Both axes again, for the scope's reason: a stamp is per FILE, and an\n // arch.spec.md edited under its vouch is a service part of whose\n // documentation nobody has re-read.\n contentStale: await filesMovedUnderVouch(\n SPEC_AXES.map((axis) => ({ file: axis.file, path: p[axis.key], header: headers[axis.key] })),\n ),\n };\n }),\n );\n return entries.sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dir, b.dir));\n}\n\n/**\n * How many fleet-level ADRs `architecture/adrs/` holds — the same number, by the\n * same rule (`countMarkdown`), that `ServiceEntry.adrs` carries one altitude\n * down. One shape for one question: a second spelling of \"count the decisions\"\n * is how `list` and `show` once reported different ADR counts for one service.\n *\n * A count and never a finding. The directory is optional, an absent one is 0,\n * and nothing in loam grades either — see `fleetAdrsDir` for why that is the\n * whole design rather than an unfinished half of it. No `FleetContext` overload:\n * one caller asks this once per invocation, and a memo whose key is the docsDir\n * would be a cache nobody reads twice.\n */\nexport async function fleetAdrCount(docsDir: DocsDir): Promise<number> {\n return countMarkdown(fleetAdrsDir(docsDir));\n}\n\n/**\n * What a feature is CALLED, for a person: intent.md's `title:`, else its first\n * `# ` heading with the feature id peeled off the front, else null.\n *\n * Every row that names a feature used to print the id alone, and an id is a\n * key, not a name — a newcomer looking at `ready FEAT-101` could not tell\n * which change it was without opening the directory. Read on demand rather\n * than carried on `FeatureEntry`, because the enumeration runs under every\n * command and only the views a person reads need the words. A missing or\n * unreadable intent.md is simply no title: the row still prints its id, and\n * whether intent.md is owed is `status`'s question, not this one's.\n */\nexport async function featureTitle(feature: FeatureEntry): Promise<string | null> {\n let fm: Frontmatter;\n try {\n fm = await readFrontmatter(featurePaths(feature.dir).intent);\n } catch {\n // A directory in intent.md's place, a permission: unreadable is no title.\n // `readFrontmatter` folds only the encoding failure into a flag, and before\n // this read the views that print a title never opened intent.md at all —\n // so letting the error escape would hand `loam status` and `loam list`\n // whole to `repository-unavailable` over a label. Whether intent.md is\n // fine is the intent row's and `validate`'s question.\n return null;\n }\n const declared = stringField(fm, \"title\")?.trim();\n if (declared !== undefined && declared !== \"\") return declared;\n const heading = fm.body.split(/\\r?\\n/).find((l) => l.startsWith(\"# \"));\n if (heading === undefined) return null;\n const words = heading.slice(2).trim();\n const bare = words.startsWith(feature.id) ? words.slice(feature.id.length).replace(/^[\\s:—–-]+/, \"\") : words;\n return bare === \"\" ? null : bare;\n}\n\nasync function readFeature(dir: FeatureDir, dirName: string, archived: boolean): Promise<FeatureEntry> {\n const p = featurePaths(dir);\n return {\n id: featureIdFromDirName(dirName),\n dirName,\n dir,\n archived,\n services: (await subdirs(p.specsDir)).map(rawServiceId),\n has: { intent: existsSync(p.intent), delta: existsSync(p.delta) },\n };\n}\n\n/**\n * Features in the docs repo, ordered by id (active and archived interleaved when\n * both are asked for). `features/archive/` is the archive, never a feature.\n */\nexport async function listFeatures(\n docsDir: DocsDir,\n opts: { includeArchived?: boolean } = {},\n context?: FleetContext,\n): Promise<FeatureEntry[]> {\n if (context !== undefined) return context.listFeatures(docsDir, opts);\n // \"no-services\" is tolerated here on purpose: a docs repo whose services/ is\n // gone is broken, but it is `listServices` (and `loam doctor`) that says so.\n // Refusing here too would turn one diagnosis into two contradictory ones.\n requireDocsRepo(docsDir, [\"ok\", \"no-services\"]);\n const root = featuresDir(docsDir);\n const active = (await subdirs(root)).filter((n) => n !== ARCHIVE_DIR);\n // Branded at the join: the name came off this readdir, onto the features\n // root it was listed under — the provenance `FeatureDir` records.\n const out = await Promise.all(active.map((n) => readFeature(featureDirOf(join(root, n)), n, false)));\n\n if (opts.includeArchived) {\n const archiveRoot = join(root, ARCHIVE_DIR);\n const archived = await subdirs(archiveRoot);\n out.push(...(await Promise.all(archived.map((n) => readFeature(featureDirOf(join(archiveRoot, n)), n, true)))));\n }\n\n return out.sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dirName, b.dirName));\n}\n\n/**\n * Which side of `features/archive/` a lookup may land on. The policy is an\n * argument, not a default, so every command's stance on shipped features is\n * visible at its call site: delta/validate/archive work on in-flight features\n * (\"exclude\"), show/verify/new read shipped ones too (\"include\"), unarchive\n * takes back nothing else (\"only\").\n */\nexport type ArchivedPolicy = \"exclude\" | \"include\" | \"only\";\n\n/**\n * Resolve a feature argument — a canonical id (`FEAT-5`) or an exact directory\n * name (`FEAT-5-slug`) — to its entry. The entry's `id` is the canonical\n * spelling, and it is the only name a caller may use from here on: tag filters,\n * coherence, and the cross-feature self-exclusion scans all compare against\n * `feature.id`, never against the raw argument.\n *\n * Matching is EXACT, on one of two spellings: the directory name as it is on\n * disk, or the canonical id derived from it. It used to also match any dirName\n * starting with `arg + \"-\"`, which made the argument a prefix query in\n * disguise: with FEAT-401 and FEAT-402 in flight, `loam archive FEAT` archived\n * whichever sorted first, and `loam delta billing` reached into\n * `billing-7-rewrite`. A destructive command must never pick a target the\n * caller did not name, so a prefix now resolves to nothing at all.\n *\n * An exact directory name still wins over the id spelling (`FEAT-5` the\n * directory over `FEAT-5-slug`, whose id is also `FEAT-5`), and under \"include\"\n * an active feature wins over an archived one. Ties among equally-exact\n * candidates go to the first by name — deterministic, unlike the raw readdir\n * order this replaces; `featureCandidates` returns every one of them, for a\n * caller that must see the tie (`loam status` names the colliding directories).\n */\nexport async function resolveFeature(\n docsDir: DocsDir,\n arg: string,\n archived: ArchivedPolicy,\n context?: FleetContext,\n): Promise<FeatureEntry | null> {\n return (await featureCandidates(docsDir, arg, archived, context))[0] ?? null;\n}\n\n/**\n * Every feature the argument names exactly, best first. Split out of\n * `resolveFeature` so a caller that wants to REFUSE an ambiguous argument can\n * see the tie instead of inheriting the winner.\n */\nexport async function featureCandidates(\n docsDir: DocsDir,\n arg: string,\n archived: ArchivedPolicy,\n context?: FleetContext,\n): Promise<FeatureEntry[]> {\n const all = await listFeatures(docsDir, { includeArchived: archived !== \"exclude\" }, context);\n return all\n .filter((f) => archived === \"include\" || f.archived === (archived === \"only\"))\n .filter((f) => f.dirName === arg || f.id === arg)\n .sort(\n (a, b) =>\n Number(b.dirName === arg) - Number(a.dirName === arg) ||\n Number(a.archived) - Number(b.archived) ||\n compareIds(a.dirName, b.dirName),\n );\n}\n\n/**\n * The message for an exclude-mode miss. When the feature exists under\n * `features/archive/`, \"no feature\" is a lie the caller would have to debug —\n * the honest answer is \"already archived\", plus how to look at it. The `--json`\n * code stays `unknown-target` either way: the target is unknown to the command\n * that asked, and the prose carries the diagnosis.\n */\nexport async function missingFeatureMessage(\n docsDir: DocsDir,\n arg: string,\n context?: FleetContext,\n): Promise<string> {\n const shipped = await resolveFeature(docsDir, arg, \"only\", context);\n if (shipped !== null) {\n return (\n `Feature '${shipped.id}' is already archived (features/archive/${shipped.dirName}) — ` +\n `\\`loam show ${shipped.id}\\` reads it, \\`loam unarchive ${shipped.id}\\` re-opens it.`\n );\n }\n return `No feature '${arg}' under ${featuresDir(docsDir)}.`;\n}\n"]}
|
|
@@ -26,9 +26,18 @@ export function findInTree(tree, name) {
|
|
|
26
26
|
export function treeNames(tree) {
|
|
27
27
|
return [...tree.services.map((s) => s.id), ...tree.subsystems.map((s) => s.name)];
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The near-miss tail a refusal appends to its "no such name" sentence, over
|
|
31
|
+
* whichever pool the verb resolves against: the close names when there are
|
|
32
|
+
* any, else `otherwise` — the verb's own pointer at what exists, or "" for
|
|
33
|
+
* none. Eight refusals across `subsystem`, `adopt --subsystem` and
|
|
34
|
+
* `list --subsystem` end this way, and each spelled the conditional itself;
|
|
35
|
+
* the lead sentence and the fallback stay theirs, because those are what
|
|
36
|
+
* differ.
|
|
37
|
+
*/
|
|
38
|
+
export function closeNamesHint(name, pool, otherwise) {
|
|
39
|
+
const close = closeIds(name, pool);
|
|
40
|
+
return close.length > 0 ? ` Close names: ${close.join(", ")}.` : otherwise;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* Does the subsystem at `subPath` contain `path` — itself, or anywhere beneath
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/find.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAS5C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe,EAAE,IAAY;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IACrE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpF,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"find.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/find.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAS5C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe,EAAE,IAAY;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IACrE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAc,EAAE,SAAiB;IAC5E,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,OAAiB,EAAE,IAAc;IAC/D,OAAO,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AACvF,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,IAAe,EAAE,GAAmB;IAChE,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,IAAe,EAAE,GAAmB;IAClE,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACvF,CAAC","sourcesContent":["/**\n * Name resolution over a walked tree — the questions every `loam subsystem`\n * verb asks before it acts: what does this name denote, what sits beneath a\n * subsystem, and which names come close to a miss. One module so `move`,\n * `rm`, `new` and `history` cannot grow four private spellings of \"the flat\n * namespace\" — the walk already enforces one namespace, and resolution must\n * read it the same way.\n */\nimport { closeIds } from \"../../c4/arch.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"./walk.js\";\n\n/** What a name denotes in the tree — a service, a subsystem, or nothing. */\nexport type TreeHit =\n | { kind: \"service\"; service: WalkedService }\n | { kind: \"subsystem\"; subsystem: SubsystemEntry }\n | null;\n\n/**\n * Resolve one name against the flat namespace. Services win a tie on\n * purpose: a tie IS `subsystem.name-collision`, already an error finding,\n * and while it stands the service reading keeps every id-addressed command\n * coherent.\n */\nexport function findInTree(tree: FleetTree, name: string): TreeHit {\n const service = tree.services.find((s) => s.id === name);\n if (service !== undefined) return { kind: \"service\", service };\n const subsystem = tree.subsystems.find((s) => s.name === name);\n if (subsystem !== undefined) return { kind: \"subsystem\", subsystem };\n return null;\n}\n\n/** Every name the flat namespace holds — service ids and subsystem names together. */\nexport function treeNames(tree: FleetTree): string[] {\n return [...tree.services.map((s) => s.id), ...tree.subsystems.map((s) => s.name)];\n}\n\n/**\n * The near-miss tail a refusal appends to its \"no such name\" sentence, over\n * whichever pool the verb resolves against: the close names when there are\n * any, else `otherwise` — the verb's own pointer at what exists, or \"\" for\n * none. Eight refusals across `subsystem`, `adopt --subsystem` and\n * `list --subsystem` end this way, and each spelled the conditional itself;\n * the lead sentence and the fallback stay theirs, because those are what\n * differ.\n */\nexport function closeNamesHint(name: string, pool: string[], otherwise: string): string {\n const close = closeIds(name, pool);\n return close.length > 0 ? ` Close names: ${close.join(\", \")}.` : otherwise;\n}\n\n/**\n * Does the subsystem at `subPath` contain `path` — itself, or anywhere beneath\n * it? The move-into-own-subtree refusal. The parameter is the name chain, not\n * a `SubsystemEntry`, because a chain is all this comparison reads — the one\n * caller outside this module (`move`) has a chain it just composed and no\n * entry to hand, and a structural parameter it never reads would force a\n * placeholder there.\n */\nexport function withinSubsystem(subPath: string[], path: string[]): boolean {\n return subPath.length <= path.length && subPath.every((name, i) => path[i] === name);\n}\n\n/** Services anywhere beneath a subsystem — the transitive membership the views and counts share. */\nexport function servicesUnder(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return tree.services.filter((s) => sub.path.every((name, i) => s.subsystem[i] === name));\n}\n\n/** Child subsystems anywhere beneath one — `rm`'s other half of \"empty\". */\nexport function subsystemsUnder(tree: FleetTree, sub: SubsystemEntry): SubsystemEntry[] {\n return tree.subsystems.filter((c) => c !== sub && withinSubsystem(sub.path, c.path));\n}\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* package direction is `repo → repo/tree` (never back — `walk.ts` says why),
|
|
7
7
|
* so the shared spelling lives on this side of the edge.
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync, statSync } from "node:fs";
|
|
9
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
10
10
|
import { readdir } from "node:fs/promises";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
/**
|
|
@@ -61,4 +61,30 @@ export async function markdownFiles(dir) {
|
|
|
61
61
|
export async function countMarkdown(dir) {
|
|
62
62
|
return (await markdownFiles(dir)).length;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Mark a directory as descended-into; false when it (or another name for it)
|
|
66
|
+
* already was. The symlink-cycle guard every walk that follows links through
|
|
67
|
+
* `entryIs` carries — the service tree (`./walk.ts`), the capability tree, the
|
|
68
|
+
* glossary, the generated-gherkin scan — and for one reason: a followed cycle
|
|
69
|
+
* without a visited set recurses until the stack ends. A path realpath cannot
|
|
70
|
+
* resolve is one whose children cannot be read either, so it is never entered.
|
|
71
|
+
*/
|
|
72
|
+
export function markDescended(dir, visited) {
|
|
73
|
+
let real;
|
|
74
|
+
try {
|
|
75
|
+
real = realpathSync(dir);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (visited.has(real))
|
|
81
|
+
return false;
|
|
82
|
+
visited.add(real);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/** Sorted non-dot subdirectory names, links followed — a tree walk's deterministic child order. */
|
|
86
|
+
export async function subdirNames(dir) {
|
|
87
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
88
|
+
return entries.filter((e) => !e.name.startsWith(".") && entryIs(dir, e, "dir")).map((e) => e.name).sort();
|
|
89
|
+
}
|
|
64
90
|
//# sourceMappingURL=fs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,CAAS,EAAE,IAAoB;IAClE,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE;QAAE,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9E,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;SAChE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;SAC7B,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,OAAO,CAAC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,OAAoB;IAC7D,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5G,CAAC","sourcesContent":["/**\n * Directory-entry questions the enumerations share, symlinks followed.\n *\n * Moved here from `repo/repo.ts` when the tree walk arrived: the walk and the\n * feature enumeration must answer \"what IS this entry\" identically, and the\n * package direction is `repo → repo/tree` (never back — `walk.ts` says why),\n * so the shared spelling lives on this side of the edge.\n */\nimport { existsSync, realpathSync, statSync, type Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * What a directory entry IS, following a symlink to find out.\n *\n * `readdir(withFileTypes)` does not follow symlinks: for a symlink `Dirent`\n * every `isDirectory()`/`isFile()` is false, whatever it points at. That made a\n * symlinked `services/<id>/` or `features/<id>/` vanish from every enumeration\n * at once — `list`, `validate --all`, the fleet gate — with no diagnostic\n * anywhere, which is the one outcome a shared docs repo cannot survive: the\n * fleet gate reported a service that is not there as fine because it never saw\n * it. Composing a docs repo out of symlinks (a worktree, a submodule, one\n * service's directory shared between two checkouts) is legitimate, so they are\n * FOLLOWED rather than refused. A dangling link stats as nothing and is skipped\n * exactly as an absent directory is — it is not a service, and saying so would\n * mean inventing an entry to hang the complaint on.\n */\nexport function entryIs(dir: string, e: Dirent, want: \"dir\" | \"file\"): boolean {\n if (!e.isSymbolicLink()) return want === \"dir\" ? e.isDirectory() : e.isFile();\n try {\n const s = statSync(join(dir, e.name));\n return want === \"dir\" ? s.isDirectory() : s.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Markdown files sitting directly in `dir`, sorted, as full paths — the ADR\n * corpus, and the only place that rule is spelled. Symlinks are resolved\n * through `entryIs`, which is the whole reason this is shared: `show` once kept\n * a second copy that tested `Dirent.isFile()` itself — always false for a\n * symlink, whatever it points at — so a docs repo composed of symlinks (the\n * layout `entryIs` above exists to support) had `loam list` and `loam show`\n * reporting different ADR counts for the same service. One rule, one answer.\n *\n * The list, not the count, is the primitive: the link check reads these same\n * files, and a corpus that disagreed with the count would grade documents the\n * fleet listing says are not there.\n */\nexport async function markdownFiles(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.name.endsWith(\".md\") && entryIs(dir, e, \"file\"))\n .map((e) => join(dir, e.name))\n .sort();\n}\n\n/** How many markdown files sit directly in `dir` — the ADR count, over the corpus above. */\nexport async function countMarkdown(dir: string): Promise<number> {\n return (await markdownFiles(dir)).length;\n}\n\n/**\n * Mark a directory as descended-into; false when it (or another name for it)\n * already was. The symlink-cycle guard every walk that follows links through\n * `entryIs` carries — the service tree (`./walk.ts`), the capability tree, the\n * glossary, the generated-gherkin scan — and for one reason: a followed cycle\n * without a visited set recurses until the stack ends. A path realpath cannot\n * resolve is one whose children cannot be read either, so it is never entered.\n */\nexport function markDescended(dir: string, visited: Set<string>): boolean {\n let real: string;\n try {\n real = realpathSync(dir);\n } catch {\n return false;\n }\n if (visited.has(real)) return false;\n visited.add(real);\n return true;\n}\n\n/** Sorted non-dot subdirectory names, links followed — a tree walk's deterministic child order. */\nexport async function subdirNames(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => !e.name.startsWith(\".\") && entryIs(dir, e, \"dir\")).map((e) => e.name).sort();\n}\n"]}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one subsystem's generated view SHOWS: which boxes it draws around the
|
|
3
|
+
* services beneath a directory, and the one thing it may say about the map.
|
|
4
|
+
*
|
|
5
|
+
* Split from `../views.ts` when that file crossed the 400-line limit: the file
|
|
6
|
+
* render (header, view ids, staleness comparison) and the body render (the
|
|
7
|
+
* boxes) are two subjects, and the seam is exactly where the second one
|
|
8
|
+
* consults the LANDSCAPE. Everything here reads element ids and nothing else —
|
|
9
|
+
* no view is ever parsed (docs/DESIGN.md rule 26), and the generated file
|
|
10
|
+
* stays a pure function of the tree plus the committed map.
|
|
11
|
+
*/
|
|
12
|
+
import {} from "../../../c4/likec4.js";
|
|
13
|
+
import { serviceResolver } from "../../../c4/resolve/service.js";
|
|
14
|
+
import { servicesUnder, withinSubsystem } from "../find.js";
|
|
15
|
+
import { subsystemLabel } from "./text.js";
|
|
16
|
+
export function renderContext(tree, elements) {
|
|
17
|
+
const known = new Set(tree.services.map((s) => s.id));
|
|
18
|
+
const resolve = serviceResolver(elements, known);
|
|
19
|
+
return {
|
|
20
|
+
tree,
|
|
21
|
+
elementOf: elementByService(tree, elements, resolve),
|
|
22
|
+
elementIds: new Set(elements.map((e) => e.id)),
|
|
23
|
+
isServiceElement: (id) => known.has(resolve(id)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Three shapes, and which one applies is a fact about the landscape rather
|
|
28
|
+
* than a setting:
|
|
29
|
+
*
|
|
30
|
+
* 1. **The model draws this group.** Some ancestor element holds exactly this
|
|
31
|
+
* subsystem's members and nothing else, so the view includes it beside the
|
|
32
|
+
* leaves and the renderer nests them inside the box the AUTHOR drew, with
|
|
33
|
+
* the author's own title. Measured: including the ancestor alone renders
|
|
34
|
+
* one empty box and the leaves alone render flat — both lines are needed.
|
|
35
|
+
* 2. **The model is flat.** No member element is nested at all, so there is
|
|
36
|
+
* no authored box to agree with, and the view wraps the members in a
|
|
37
|
+
* `group '<title>'` of its own — a real named box in the renderer, derived
|
|
38
|
+
* from the DIRECTORY the services already sit in, mirroring the subtree so
|
|
39
|
+
* a parent subsystem shows its children as boxes rather than as one pile.
|
|
40
|
+
* Nothing is invented: the grouping is the tree, and the tree is what this
|
|
41
|
+
* file has always mirrored.
|
|
42
|
+
* 3. **The model nests them, differently.** The members ARE nested but no
|
|
43
|
+
* ancestor owns exactly them — the directory tree and the map disagree
|
|
44
|
+
* about this group. loam draws no box (a synthetic one would silently
|
|
45
|
+
* replace the author's boundary with the folder's, and the picture would
|
|
46
|
+
* look the same whether or not the two agree) and writes ONE comment line
|
|
47
|
+
* saying so, outside the view body. That comment is the whole of loam's
|
|
48
|
+
* answer to the drift: it grades nothing, it fails nothing, and it appears
|
|
49
|
+
* in the diff of the very commit that caused it — placement stays a fact
|
|
50
|
+
* nothing branches on.
|
|
51
|
+
*/
|
|
52
|
+
export function viewBody(ctx, sub) {
|
|
53
|
+
const members = memberElements(ctx, sub);
|
|
54
|
+
if (members.length === 0)
|
|
55
|
+
return { lines: [], disclosure: null, of: null };
|
|
56
|
+
if (members.every((id) => !id.includes("."))) {
|
|
57
|
+
return { lines: [...groupedBody(ctx, sub, " "), ...boundaryLines(members)], disclosure: null, of: null };
|
|
58
|
+
}
|
|
59
|
+
const includes = [...ownedAncestors(ctx, sub), ...members].map((id) => ` include ${id}`);
|
|
60
|
+
const lines = [...includes, ...boundaryLines(members)];
|
|
61
|
+
const owner = owningAncestor(ctx, sub);
|
|
62
|
+
if (owner !== null)
|
|
63
|
+
return { lines, disclosure: null, of: owner };
|
|
64
|
+
const shared = sharedAncestor(ctx, members);
|
|
65
|
+
return {
|
|
66
|
+
lines,
|
|
67
|
+
of: null,
|
|
68
|
+
disclosure: shared === null
|
|
69
|
+
? "model: no boundary — these members do not share a containing element."
|
|
70
|
+
: `model: no boundary — ${shared} also holds services outside this subsystem.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* What this group TOUCHES: for each member, every edge it has in either
|
|
75
|
+
* direction, so the view answers "what does payments talk to" and not only
|
|
76
|
+
* "who is in payments".
|
|
77
|
+
*
|
|
78
|
+
* **The measured trap, and it is why these lines are appended by the caller of
|
|
79
|
+
* `groupedBody` rather than inside it:** placed INSIDE the `group { }` block of
|
|
80
|
+
* the flat-map body shape, this exact predicate parses and validates with ZERO
|
|
81
|
+
* errors and draws a false picture. Measured at likec4 1.59.2 on a flat map of
|
|
82
|
+
* `alpha, beta` grouped and `outsider, store` outside it: at view level the two
|
|
83
|
+
* neighbours are drawn with no parent, correctly outside the box; moved inside
|
|
84
|
+
* the `group { }`, both are drawn as CHILDREN of that group — the diagram then
|
|
85
|
+
* claims `outsider` and `store` are part of the subsystem. A maintainer tidying
|
|
86
|
+
* the body by moving these lines next to the includes they refer to would ship
|
|
87
|
+
* that, and nothing anywhere would report it. They belong at view level, always.
|
|
88
|
+
*
|
|
89
|
+
* The WILDCARD is correct here, and that is not an inconsistency with the
|
|
90
|
+
* per-service views, which measure it as a defect (`./service/peers.ts`): at
|
|
91
|
+
* subsystem altitude a counterpart collapsing to its own group box is exactly
|
|
92
|
+
* the sentence "payments touches identity", and — once those group boxes are
|
|
93
|
+
* themselves views' subjects (`of <ancestor>`) — it is a click-through into
|
|
94
|
+
* that subsystem. At service altitude the same collapse is a lie about who
|
|
95
|
+
* calls whom.
|
|
96
|
+
*
|
|
97
|
+
* MEMBERS only, never `ownedAncestors`: `-> *` on an authored container pulls
|
|
98
|
+
* in every edge any descendant of it has, and in the `// model: no boundary`
|
|
99
|
+
* shape that container also holds services outside this subsystem — the view
|
|
100
|
+
* would draw traffic that is nobody-here's. Measured at 58 services in 11
|
|
101
|
+
* groups: 13-15 nodes and 31-39 edges per subsystem view, of which 11-12 nodes
|
|
102
|
+
* carry drill-down — the members into their own service views, the neighbouring
|
|
103
|
+
* group boxes into THEIR subsystem views.
|
|
104
|
+
*/
|
|
105
|
+
function boundaryLines(members) {
|
|
106
|
+
return ["", ...members.map((id) => ` include ${id} -> *, * -> ${id}`)];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The flat-model body: this subsystem as a box, its child subsystems as boxes
|
|
110
|
+
* inside it, its own services as leaves. Recursive, and the recursion is the
|
|
111
|
+
* point — a parent subsystem's view is otherwise one undifferentiated pile of
|
|
112
|
+
* every service beneath it.
|
|
113
|
+
*
|
|
114
|
+
* An empty child renders an empty named box, which is what the tree says: the
|
|
115
|
+
* group exists and holds nothing yet.
|
|
116
|
+
*/
|
|
117
|
+
function groupedBody(ctx, sub, indent) {
|
|
118
|
+
const inner = `${indent} `;
|
|
119
|
+
const out = [`${indent}group '${subsystemLabel(sub)}' {`];
|
|
120
|
+
for (const child of childSubsystems(ctx, sub))
|
|
121
|
+
out.push(...groupedBody(ctx, child, inner));
|
|
122
|
+
for (const id of directMemberElements(ctx, sub))
|
|
123
|
+
out.push(`${inner}include ${id}`);
|
|
124
|
+
out.push(`${indent}}`);
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
/** The child subsystems one level down, ordered by name. */
|
|
128
|
+
function childSubsystems(ctx, sub) {
|
|
129
|
+
return ctx.tree.subsystems
|
|
130
|
+
.filter((c) => c.path.length === sub.path.length + 1 && withinSubsystem(sub.path, c.path))
|
|
131
|
+
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
132
|
+
}
|
|
133
|
+
/** The drawn elements of the services filed in THIS directory, not in a child. */
|
|
134
|
+
function directMemberElements(ctx, sub) {
|
|
135
|
+
return drawnElements(ctx, membersOf(ctx.tree, sub).filter((s) => s.subsystem.length === sub.path.length));
|
|
136
|
+
}
|
|
137
|
+
/** The drawn elements of every service beneath this subsystem, ordered by service id. */
|
|
138
|
+
function memberElements(ctx, sub) {
|
|
139
|
+
return drawnElements(ctx, membersOf(ctx.tree, sub));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The element ids these services resolve to. A service the map does not draw
|
|
143
|
+
* is omitted rather than invented: the omission is already an error
|
|
144
|
+
* (`landscape.service-unmodelled`), and rendering only what the committed
|
|
145
|
+
* documents resolve keeps the output a function of committed bytes alone.
|
|
146
|
+
*/
|
|
147
|
+
function drawnElements(ctx, services) {
|
|
148
|
+
return services.map((s) => ctx.elementOf.get(s.id)).filter((id) => id !== undefined);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The boxes a nested-model view draws: this subsystem's own owning ancestor
|
|
152
|
+
* when it has one, plus the owning ancestor of every subsystem beneath it — so
|
|
153
|
+
* a parent view keeps its children's boundaries instead of flattening them
|
|
154
|
+
* into one box. Sorted and de-duplicated, because two subsystems resolve to
|
|
155
|
+
* one element only in a tree that already reports a collision.
|
|
156
|
+
*/
|
|
157
|
+
function ownedAncestors(ctx, sub) {
|
|
158
|
+
const found = new Set();
|
|
159
|
+
for (const candidate of ctx.tree.subsystems) {
|
|
160
|
+
if (!withinSubsystem(sub.path, candidate.path))
|
|
161
|
+
continue;
|
|
162
|
+
const owner = owningAncestor(ctx, candidate);
|
|
163
|
+
if (owner !== null)
|
|
164
|
+
found.add(owner);
|
|
165
|
+
}
|
|
166
|
+
return [...found].sort();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The deepest ancestor element that holds exactly this subsystem's members —
|
|
170
|
+
* or null when no element does.
|
|
171
|
+
*
|
|
172
|
+
* "Exactly" is the whole guard, and dropping it was measured on loam's own
|
|
173
|
+
* `examples/docs`: the members' longest common ancestor there is a group of
|
|
174
|
+
* five services while the subsystem holds two, so an unguarded include would
|
|
175
|
+
* generate a box labelled for the group with two of its five children drawn
|
|
176
|
+
* inside it — a diagram asserting something false, in a file the reader is
|
|
177
|
+
* told not to edit. Tightness is judged over ELEMENTS rather than over
|
|
178
|
+
* documented services: mid-adoption a group commonly holds systems no
|
|
179
|
+
* `services/<id>/` exists for yet, and counting only the documented ones would
|
|
180
|
+
* draw that same false box for the whole of an adoption.
|
|
181
|
+
*
|
|
182
|
+
* An ancestor that is itself a service's element is refused separately: one
|
|
183
|
+
* service's box containing another's is a picture of nothing.
|
|
184
|
+
*/
|
|
185
|
+
function owningAncestor(ctx, sub) {
|
|
186
|
+
const members = memberElements(ctx, sub);
|
|
187
|
+
if (members.length === 0)
|
|
188
|
+
return null;
|
|
189
|
+
const owned = new Set(members);
|
|
190
|
+
for (const candidate of ancestorChain(members)) {
|
|
191
|
+
if (!ctx.elementIds.has(candidate) || ctx.isServiceElement(candidate))
|
|
192
|
+
continue;
|
|
193
|
+
if (ownsExactly(ctx, candidate, owned))
|
|
194
|
+
return candidate;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
/** The deepest common ancestor the map declares at all — the disclosure's subject. */
|
|
199
|
+
function sharedAncestor(ctx, members) {
|
|
200
|
+
return ancestorChain(members).find((id) => ctx.elementIds.has(id)) ?? null;
|
|
201
|
+
}
|
|
202
|
+
/** The common proper ancestors of every member id, deepest first. */
|
|
203
|
+
function ancestorChain(members) {
|
|
204
|
+
const segments = members.map((id) => id.split("."));
|
|
205
|
+
let common = Math.min(...segments.map((s) => s.length - 1));
|
|
206
|
+
for (let i = 0; i < common; i++) {
|
|
207
|
+
if (!segments.every((s) => s[i] === segments[0][i])) {
|
|
208
|
+
common = i;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const out = [];
|
|
213
|
+
for (let depth = common; depth > 0; depth--)
|
|
214
|
+
out.push(segments[0].slice(0, depth).join("."));
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Does every element beneath `ancestor` belong to this subsystem — as a
|
|
219
|
+
* member, as one of a member's own containers, or as a container of one?
|
|
220
|
+
*/
|
|
221
|
+
function ownsExactly(ctx, ancestor, members) {
|
|
222
|
+
const prefix = `${ancestor}.`;
|
|
223
|
+
for (const id of ctx.elementIds) {
|
|
224
|
+
if (!id.startsWith(prefix))
|
|
225
|
+
continue;
|
|
226
|
+
const claimed = [...members].some((m) => id === m || id.startsWith(`${m}.`) || m.startsWith(`${id}.`));
|
|
227
|
+
if (!claimed)
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The landscape element standing for each service id: for every element,
|
|
234
|
+
* resolve it through the shared join, and let the SHALLOWEST element win
|
|
235
|
+
* (document order breaking ties). Shallowest, because `serviceResolver`
|
|
236
|
+
* resolves a service's own containers to the service too, and a view should
|
|
237
|
+
* include the service-level box, not whichever nested container happened to be
|
|
238
|
+
* declared first.
|
|
239
|
+
*/
|
|
240
|
+
function elementByService(tree, elements, resolve) {
|
|
241
|
+
const known = new Set(tree.services.map((s) => s.id));
|
|
242
|
+
const depth = (id) => id.split(".").length;
|
|
243
|
+
const out = new Map();
|
|
244
|
+
for (const e of elements) {
|
|
245
|
+
const service = resolve(e.id);
|
|
246
|
+
if (!known.has(service))
|
|
247
|
+
continue;
|
|
248
|
+
const held = out.get(service);
|
|
249
|
+
if (held === undefined || depth(e.id) < depth(held))
|
|
250
|
+
out.set(service, e.id);
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */
|
|
255
|
+
function membersOf(tree, sub) {
|
|
256
|
+
return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=body.js.map
|