@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
|
@@ -13,22 +13,48 @@
|
|
|
13
13
|
* without starting LikeC4 at all.
|
|
14
14
|
*
|
|
15
15
|
* THE CHEAP GATE IS A BYTE SCAN, and it is sound rather than heuristic. A view
|
|
16
|
-
* is a use case only if it carries a reserved tag — `#cap-<slug
|
|
17
|
-
* `#req-<slug>`, `isUseCase` below is the whole test — and
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
16
|
+
* is a use case only if it carries a reserved tag — `#cap-<slug>`,
|
|
17
|
+
* `#req-<slug>` or `#suite-<name>`, `isUseCase` below is the whole test — and
|
|
18
|
+
* a view carries a tag only through a REFERENCE written in the document that
|
|
19
|
+
* declares the view. LikeC4's grammar spells every tag reference `HashId`: the
|
|
20
|
+
* `#` terminal, then the name. Measured at the 1.59.2 pin, the `#` is
|
|
21
|
+
* mandatory — a bare `suite-smoke` in a view body fails to resolve, and
|
|
22
|
+
* `tags #suite-smoke` fails to parse — but LikeC4's four hidden tokens may sit
|
|
23
|
+
* between the `#` and the name: `# suite-smoke`, a `#` ending one line with
|
|
24
|
+
* the name on the next, and a block comment in between all parse to the tag
|
|
25
|
+
* `suite-smoke`. So the scan finds each `#`, skips exactly those tokens
|
|
26
|
+
* (`referencesReservedTag`), and asks whether a reserved prefix starts there.
|
|
27
|
+
* A document set in which no file does declares no use case, however badly it
|
|
28
|
+
* parses, and `{ views: [] }` is the correct answer, not an evasion. A
|
|
29
|
+
* `tag cap-…` line in a `specification` block is a declaration, not a
|
|
30
|
+
* reference, and does not open the gate: a tag no view carries opts nothing
|
|
31
|
+
* in.
|
|
32
|
+
*
|
|
33
|
+
* THE `#` IS WHAT KEEPS PROSE OUT. The scan used to look for the bare
|
|
34
|
+
* substrings, so a view titled 'What the e2e-suite-a job exercises', or a
|
|
35
|
+
* comment naming `req-` in passing, opened it. At fleet scope that cost one
|
|
36
|
+
* project load, because a map that does not parse is `landscape.invalid`
|
|
37
|
+
* whether or not the gate opens; at service scope (`./service/flows.ts`) it
|
|
38
|
+
* turned an untagged hand-drawn sibling that did not parse into
|
|
39
|
+
* `usecase.flow-invalid` — an error, exit 1 — over a diagram that opted
|
|
40
|
+
* nothing in. A `#` followed by a reserved prefix still opens it wherever it
|
|
41
|
+
* is written, a comment or a description included: that over-scan is
|
|
42
|
+
* accepted, because it is spelled as the tag syntax, and the scan must never
|
|
43
|
+
* be narrower than the parser.
|
|
44
|
+
*
|
|
45
|
+
* The scan reads bytes rather than decoded text on purpose: it looks for
|
|
46
|
+
* ASCII, and `readFile(path, "utf8")` on a document that is not UTF-8 would
|
|
47
|
+
* substitute U+FFFD and could hide it. Decoded as latin1 instead, every byte is
|
|
48
|
+
* one character, and the bytes of a multi-byte sequence never read as ASCII.
|
|
49
|
+
*
|
|
50
|
+
* EVERY PREFIX, because the gate and the predicate answer one question and a
|
|
27
51
|
* gate narrower than the predicate is silence rather than a smaller answer. A
|
|
28
|
-
* fleet whose only flow is tagged `#req-` alone
|
|
29
|
-
* `cap
|
|
30
|
-
*
|
|
31
|
-
*
|
|
52
|
+
* fleet whose only flow is tagged `#req-` alone writes `cap-` nowhere, so a
|
|
53
|
+
* `cap-`-only scan returned `false` and none of the five readers ever loaded
|
|
54
|
+
* the project — the widening below would have fixed nothing without this one.
|
|
55
|
+
* The suite prefix joined for the same reason the day a `#suite-`-only view
|
|
56
|
+
* became a use case: both now read one list, `USE_CASE_TAG_PREFIXES`, so a
|
|
57
|
+
* fourth prefix cannot be added to one and not the other.
|
|
32
58
|
*
|
|
33
59
|
* The gate FAILS CLOSED. A document this module cannot open is treated as one
|
|
34
60
|
* that might mention the prefix, so the project load happens and reports the
|
|
@@ -38,27 +64,48 @@
|
|
|
38
64
|
*/
|
|
39
65
|
import { readFile } from "node:fs/promises";
|
|
40
66
|
import { CAP_TAG_PREFIX, REQ_TAG_PREFIX } from "../capabilities/usecase-join.js";
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
67
|
+
import { architectureProjectDocuments, loadArchitecture } from "../c4/project/architecture.js";
|
|
68
|
+
import { errorText } from "../c4/likec4.js";
|
|
43
69
|
import { serviceResolver } from "../c4/resolve/service.js";
|
|
44
|
-
import {
|
|
70
|
+
import { SUITE_TAG_PREFIX } from "./suites/suites.js";
|
|
45
71
|
/**
|
|
46
|
-
*
|
|
72
|
+
* Every reserved prefix that opts a view in — the one list both the byte gate
|
|
73
|
+
* and `isUseCase` read, so the two cannot disagree about which tags make a use
|
|
74
|
+
* case. `cap-` and `req-` are spelled by the capability join that resolves
|
|
75
|
+
* them; `suite-` by `./suites/suites.ts`, which groups by it and resolves
|
|
76
|
+
* nothing, because a suite name is checked by LikeC4's own undeclared-tag
|
|
77
|
+
* refusal rather than by a vocabulary.
|
|
78
|
+
*/
|
|
79
|
+
const USE_CASE_TAG_PREFIXES = [CAP_TAG_PREFIX, REQ_TAG_PREFIX, SUITE_TAG_PREFIX];
|
|
80
|
+
/**
|
|
81
|
+
* Does any document in the set reference a reserved tag — a `#`, then only
|
|
82
|
+
* LikeC4's hidden tokens, then `cap-`, `req-` or `suite-`?
|
|
47
83
|
*
|
|
48
84
|
* Sequential and short-circuiting ON PURPOSE — the usual "no await in a loop"
|
|
49
85
|
* rule is about independent work that all has to finish, and this loop exists
|
|
50
86
|
* precisely so that a fleet whose first file declares a use case stops reading
|
|
51
87
|
* the other ninety-nine.
|
|
52
88
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
89
|
+
* What the scan lets through costs differently at its two callers, and the
|
|
90
|
+
* header above names both. A `#req-…` written in a comment opens it: at fleet
|
|
91
|
+
* scope that is one project load loam could have skipped, a slower right
|
|
92
|
+
* answer; at service scope it loads a sibling set that may not parse, and a
|
|
93
|
+
* broken one is then `usecase.flow-invalid` — an error, but over a document
|
|
94
|
+
* that spelled the tag syntax. Prose without the `#` opens neither. Missing a
|
|
95
|
+
* reference would cost a wrong answer at both: a use case nobody graded.
|
|
96
|
+
*
|
|
97
|
+
* Exported for the service-scope reader (`./service/flows.ts`), which asks the
|
|
98
|
+
* same question of the `.likec4` files beside one `model.likec4`: a sibling set
|
|
99
|
+
* that references no reserved tag declares no use case, so a hand-drawn
|
|
100
|
+
* `views.likec4` costs that service no LikeC4 workspace. One gate, two callers,
|
|
101
|
+
* because a second byte scan spelled in the service package would be free to
|
|
102
|
+
* disagree with this one about which prefixes opt a view in.
|
|
56
103
|
*/
|
|
57
|
-
async function mentionsTagPrefix(paths) {
|
|
104
|
+
export async function mentionsTagPrefix(paths) {
|
|
58
105
|
for (const path of paths) {
|
|
59
106
|
try {
|
|
60
107
|
const bytes = await readFile(path);
|
|
61
|
-
if (
|
|
108
|
+
if (referencesReservedTag(bytes.toString("latin1")))
|
|
62
109
|
return true;
|
|
63
110
|
}
|
|
64
111
|
catch {
|
|
@@ -70,14 +117,105 @@ async function mentionsTagPrefix(paths) {
|
|
|
70
117
|
}
|
|
71
118
|
return false;
|
|
72
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Is a reserved prefix written as a tag reference anywhere in this text? Every
|
|
122
|
+
* `#` is tried, wherever it stands — in a view body, a comment, a string —
|
|
123
|
+
* because the scan may over-approximate the parser and never under-approximate
|
|
124
|
+
* it.
|
|
125
|
+
*
|
|
126
|
+
* A text with no `#`, or with no reserved prefix anywhere in it, cannot answer
|
|
127
|
+
* yes, so it is answered before the table below is built: that is nearly every
|
|
128
|
+
* document, and it costs those two native searches and no allocation.
|
|
129
|
+
*
|
|
130
|
+
* Exported for its own test (test/usecase-byte-gate.test.ts), which holds it to
|
|
131
|
+
* the walk the first version made from each `#` over crafted inputs, and holds
|
|
132
|
+
* its cost to one pass over inputs that walk took minutes on.
|
|
133
|
+
*/
|
|
134
|
+
export function referencesReservedTag(text) {
|
|
135
|
+
const first = text.indexOf("#");
|
|
136
|
+
if (first === -1 || !USE_CASE_TAG_PREFIXES.some((prefix) => text.includes(prefix)))
|
|
137
|
+
return false;
|
|
138
|
+
const nameAt = hiddenRunEnds(text);
|
|
139
|
+
for (let hash = first; hash !== -1; hash = text.indexOf("#", hash + 1)) {
|
|
140
|
+
const name = nameAt[hash + 1];
|
|
141
|
+
if (USE_CASE_TAG_PREFIXES.some((prefix) => text.startsWith(prefix, name)))
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* For every position in `text`, where the next token LikeC4 would read starts
|
|
148
|
+
* from there: past whitespace, line breaks, `//` line comments and block
|
|
149
|
+
* comments — the four terminals the 1.59.2 grammar marks hidden (`WS`, `NL`,
|
|
150
|
+
* `LINE_COMMENT`, `BLOCK_COMMENT`), which its lexer drops between the `#` of a
|
|
151
|
+
* tag reference and the name. Index `text.length` is the end itself.
|
|
152
|
+
*
|
|
153
|
+
* ONE PASS, FROM THE END, so every answer is built from answers already known
|
|
154
|
+
* and the whole table costs one visit per character, however the `#`s and the
|
|
155
|
+
* comments interleave. The first version walked forward from each `#` instead,
|
|
156
|
+
* and a run of hidden tokens with `#`s inside it was re-walked once per `#`:
|
|
157
|
+
* `#/*` repeated took 17.6 s on 100 KB (each unclosed opener searched the rest
|
|
158
|
+
* of the file for a closer), and `#//` repeated on one line, or the same `#/*`
|
|
159
|
+
* run closed once at its end, were quadratic the same way. Remembering only
|
|
160
|
+
* where the closer search last failed would have fixed the first of those
|
|
161
|
+
* three and left the other two; nothing short of reusing every answer fixes
|
|
162
|
+
* the shape, and this scan runs over every document on every reader's path.
|
|
163
|
+
*
|
|
164
|
+
* A hand loop rather than one regular expression: a repeated group of
|
|
165
|
+
* alternatives that can each consume a comment backtracks exponentially on a
|
|
166
|
+
* long run of slashes. An UNTERMINATED block comment is not one LikeC4 can lex
|
|
167
|
+
* as a comment, so only the `/*` itself is stepped over and a name written
|
|
168
|
+
* after it still counts: that can open the gate more often, never less, and
|
|
169
|
+
* the document cannot parse anyway — the load the gate opens is what reports
|
|
170
|
+
* it. The closer is searched from two past the opener, so the `*` of an opener
|
|
171
|
+
* never doubles as the `*` of its closer, exactly as the lexer reads it.
|
|
172
|
+
*/
|
|
173
|
+
function hiddenRunEnds(text) {
|
|
174
|
+
const end = new Int32Array(text.length + 1);
|
|
175
|
+
end[text.length] = text.length;
|
|
176
|
+
// The nearest line break at or after the position being read, and the
|
|
177
|
+
// nearest `*/` at or after the NEXT position and the one after that — an
|
|
178
|
+
// opener at `at` searches from `at + 2`, and the pass meets that position
|
|
179
|
+
// before it meets `at`. -1 is "none".
|
|
180
|
+
let lineBreak = text.length;
|
|
181
|
+
let closeFromNext = -1;
|
|
182
|
+
let closeFromAfterNext = -1;
|
|
183
|
+
for (let at = text.length - 1; at >= 0; at -= 1) {
|
|
184
|
+
const char = text[at];
|
|
185
|
+
const closeFromHere = char === "*" && text[at + 1] === "/" ? at : closeFromNext;
|
|
186
|
+
if (char === "\r" || char === "\n")
|
|
187
|
+
lineBreak = at;
|
|
188
|
+
if (char === " " || char === "\t" || char === "\r" || char === "\n") {
|
|
189
|
+
end[at] = end[at + 1];
|
|
190
|
+
}
|
|
191
|
+
else if (char === "/" && text[at + 1] === "/") {
|
|
192
|
+
// The comment runs to the line break and leaves it unread; the break is
|
|
193
|
+
// whitespace, so the run goes on from there. No break: the text ends.
|
|
194
|
+
end[at] = end[lineBreak];
|
|
195
|
+
}
|
|
196
|
+
else if (char === "/" && text[at + 1] === "*") {
|
|
197
|
+
end[at] = closeFromAfterNext === -1 ? end[at + 2] : end[closeFromAfterNext + 2];
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
end[at] = at;
|
|
201
|
+
}
|
|
202
|
+
closeFromAfterNext = closeFromNext;
|
|
203
|
+
closeFromNext = closeFromHere;
|
|
204
|
+
}
|
|
205
|
+
return end;
|
|
206
|
+
}
|
|
73
207
|
/**
|
|
74
208
|
* Is this a use case at all? — the ONE answer, for `validate` and every reader
|
|
75
209
|
* alike.
|
|
76
210
|
*
|
|
77
|
-
*
|
|
211
|
+
* ANY RESERVED PREFIX OPTS A VIEW IN. A view carrying `#req-CHK-ONCE` and no
|
|
78
212
|
* `#cap-` tag is an author who asked to be graded and forgot half the claim; it
|
|
79
213
|
* earns `usecase.requirement-unresolved`'s `unscoped` arm rather than silence,
|
|
80
|
-
* and SCHEMA.md has said so since the tag shipped.
|
|
214
|
+
* and SCHEMA.md has said so since the tag shipped. A view carrying only
|
|
215
|
+
* `#suite-<name>` claims no capability and no requirement and is still a use
|
|
216
|
+
* case — the owner's call, recorded in SCHEMA.md: putting a journey in a suite
|
|
217
|
+
* is asking for it to be run, and a journey a test stand is provisioned from
|
|
218
|
+
* must be held to the model hop by hop like any other.
|
|
81
219
|
*
|
|
82
220
|
* It is exported because it was the asymmetry, not the rule, that was the
|
|
83
221
|
* defect. `validate --all` widened here and the readers did not, so a
|
|
@@ -88,9 +226,10 @@ async function mentionsTagPrefix(paths) {
|
|
|
88
226
|
* that spells `startsWith` itself is a second answer waiting to drift.
|
|
89
227
|
*
|
|
90
228
|
* The parameter is the smallest shape the test needs rather than `ParsedView`,
|
|
91
|
-
* so a caller holding a view-like row does not have to build one to ask.
|
|
92
|
-
* prefix
|
|
93
|
-
*
|
|
229
|
+
* so a caller holding a view-like row does not have to build one to ask. Every
|
|
230
|
+
* prefix test matches its reader's exactly, case included, so a view can never
|
|
231
|
+
* be opted in here and read as untagged in `resolveCapabilityTags` or
|
|
232
|
+
* `suiteNames`.
|
|
94
233
|
*
|
|
95
234
|
* NOT the same question as `flowsClaiming`'s. That one asks which flows claim
|
|
96
235
|
* capability X and matches the whole `cap-<slug>` tag; a `#req-`-only view
|
|
@@ -98,7 +237,7 @@ async function mentionsTagPrefix(paths) {
|
|
|
98
237
|
* counting as a use case here.
|
|
99
238
|
*/
|
|
100
239
|
export function isUseCase(view) {
|
|
101
|
-
return view.tags.some((tag) =>
|
|
240
|
+
return view.tags.some((tag) => USE_CASE_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)));
|
|
102
241
|
}
|
|
103
242
|
/**
|
|
104
243
|
* The fleet's declared use cases, with the model they are drawn over.
|
|
@@ -116,22 +255,42 @@ export async function readUseCases(req) {
|
|
|
116
255
|
model: { elements: [], relationships: [], known: req.known },
|
|
117
256
|
resolve: (id) => id,
|
|
118
257
|
};
|
|
119
|
-
|
|
120
|
-
|
|
258
|
+
// EXACTLY the documents `loadArchitecture` will load two lines down, root
|
|
259
|
+
// `exclude` and all — `architectureProjectDocuments` is that one answer
|
|
260
|
+
// (`c4/project/architecture.ts`). The gate used to walk `architecture/` for
|
|
261
|
+
// itself and skip the exclude, which could only over-scan: the byte gate
|
|
262
|
+
// reading a document the loader then drops decides "there might be a use
|
|
263
|
+
// case here" and pays for a project load that finds none. Never a wrong
|
|
264
|
+
// answer, but two lists that disagree about what the project is, and the
|
|
265
|
+
// next reader to take the wider one for the project's would be wrong.
|
|
266
|
+
const documents = await architectureProjectDocuments(req.docsDir);
|
|
121
267
|
if (!(await mentionsTagPrefix(documents)))
|
|
122
268
|
return none;
|
|
123
|
-
|
|
269
|
+
return useCasesIn(await loadArchitecture(req.docsDir), req.known);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The use cases of an `architecture/` project somebody has ALREADY loaded —
|
|
273
|
+
* `readUseCases` minus its byte gate and its load.
|
|
274
|
+
*
|
|
275
|
+
* For a reader that needs the project anyway: the consumer index
|
|
276
|
+
* (`core/consumers/`) reads the project's edges and its hops off one load, and
|
|
277
|
+
* going through `readUseCases` there would parse the same documents a second
|
|
278
|
+
* time behind a gate whose whole purpose is to skip a load that has already
|
|
279
|
+
* happened. The answer is the same one: a project with no reserved-tag view
|
|
280
|
+
* yields `read` with no views, exactly what the gate returns without loading.
|
|
281
|
+
*/
|
|
282
|
+
export function useCasesIn(doc, known) {
|
|
124
283
|
// loam's standing rule, at project altitude: errors mean no model. A use-case
|
|
125
284
|
// file with one unresolved element leaves the whole project unusable, and
|
|
126
285
|
// grading a flow against half a map is the wrong answer this refuses to give.
|
|
127
286
|
if (doc.errors.length > 0) {
|
|
128
|
-
return { kind: "unreadable", errors: doc.errors.map(
|
|
287
|
+
return { kind: "unreadable", errors: doc.errors.map(errorText) };
|
|
129
288
|
}
|
|
130
289
|
return {
|
|
131
290
|
kind: "read",
|
|
132
291
|
views: (doc.views ?? []).filter(isUseCase),
|
|
133
|
-
model: { elements: doc.elements, relationships: doc.relationships, known
|
|
134
|
-
resolve: serviceResolver(doc.elements,
|
|
292
|
+
model: { elements: doc.elements, relationships: doc.relationships, known },
|
|
293
|
+
resolve: serviceResolver(doc.elements, known),
|
|
135
294
|
};
|
|
136
295
|
}
|
|
137
296
|
//# sourceMappingURL=fleet.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet.js","sourceRoot":"","sources":["../../../src/core/usecases/fleet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet's use cases, read once for whoever asks — and NOT read at all when\n * the fleet demonstrably has none.\n *\n * `validate --all` grades use cases as part of a run that was already loading\n * `architecture/` as a LikeC4 project, so it pays nothing extra. Every reader\n * added in phase 4 is in a different position: `loam diff`, `loam delta`,\n * `loam context` and `loam explore` did not load the fleet map as a project\n * before, and `loam delta` in particular sits in `/loam-implement`'s inner loop\n * — a Langium workspace spin-up there is felt on every iteration of somebody's\n * day. So this module's whole job is to answer \"which use cases does the fleet\n * declare?\" and, in the common case where the answer is none, to answer it\n * without starting LikeC4 at all.\n *\n * THE CHEAP GATE IS A BYTE SCAN, and it is sound rather than heuristic. A view\n * is a use case only if it carries a reserved tag — `#cap-<slug>` or\n * `#req-<slug>`, `isUseCase` below is the whole test — and LikeC4 refuses an\n * undeclared tag, so a fleet with a use case has the literal text `cap-` or\n * `req-` in the file that declares the view AND in the file that declares the\n * tag. A document set in which no file contains either substring therefore\n * declares no use case, and `{ views: [] }` is the correct answer, not an\n * evasion. The scan reads bytes rather than decoded text on purpose: it is\n * looking for an ASCII substring, and `readFile(path, \"utf8\")` on a document\n * that is not UTF-8 would substitute U+FFFD and could hide it.\n *\n * BOTH PREFIXES, because the gate and the predicate answer one question and a\n * gate narrower than the predicate is silence rather than a smaller answer. A\n * fleet whose only flow is tagged `#req-` alone declares `tag req-…` and no\n * `cap-` anywhere, so a `cap-`-only scan returned `false` and none of the five\n * readers ever loaded the project — the widening below would have fixed nothing\n * without this one.\n *\n * The gate FAILS CLOSED. A document this module cannot open is treated as one\n * that might mention the prefix, so the project load happens and reports the\n * failure as a hole. The alternative — reading an EACCES as \"no tag here\" — is\n * the fail-open shape docs/CODE-STYLE.md forbids at a validator, arriving in an\n * optimisation.\n */\nimport { readFile } from \"node:fs/promises\";\nimport type { StepScope } from \"../c4/arch.js\";\nimport { CAP_TAG_PREFIX, REQ_TAG_PREFIX } from \"../capabilities/usecase-join.js\";\nimport type { ParsedView } from \"../c4/parsed/dynamic-views.js\";\nimport { architectureDir, loadArchitecture } from \"../c4/project/architecture.js\";\nimport { architectureDocuments } from \"../c4/project/documents.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { subsystemViewsPath } from \"../repo/paths.js\";\n\n/**\n * What the fleet's use cases are, or the honest refusal to say.\n *\n * Two variants rather than a record with an `unreadable` flag, because the\n * fields are not independent: a project that did not parse has no elements to\n * resolve against and no views to grade, and a caller handed\n * `{ views: [], unreadable: true }` is one `.length` check away from printing\n * \"no use cases touch this service\" about a fleet nobody could read. Tagged,\n * that state stops being constructible — every caller has to name the\n * `unreadable` arm to get at the views.\n *\n * `read` with an empty `views` is the ordinary answer for a fleet that draws no\n * use cases, and it is exactly what the cheap gate returns without loading\n * anything: the gate is an optimisation over a question with a known answer,\n * never a third verdict.\n */\nexport type UseCaseScan =\n | {\n kind: \"read\";\n /** The reserved-tag views ONLY — `isUseCase` below is the opt-in, and the only one. */\n views: readonly ParsedView[];\n /**\n * What a hop is attributed against, built ONCE per scan. It carries the\n * fleet set that `resolve` below was built from, which is what stops\n * \"which service is this element\" being answered one way by\n * `attributeStep`'s fallback tier and another by a caller's own join —\n * the same reason `validate`'s `StepGrading` holds both together.\n *\n * `known` is REQUIRED here while `StepScope` leaves it optional, and the\n * narrowing is load-bearing rather than tidy: every scan is built from a\n * `UseCaseRequest` that has one, so a consumer reading `model.known` was\n * writing an `undefined` branch that could not be reached and could not be\n * tested. A `StepScope` is still what `attributeStep` receives.\n */\n model: StepScope & { known: ReadonlySet<string> };\n /** One element→service resolver for the whole scan, built with the enumerated fleet. */\n resolve: (id: string) => string;\n }\n | {\n kind: \"unreadable\";\n /** LikeC4's own messages, so a caller can say WHICH document broke. */\n errors: string[];\n };\n\nexport interface UseCaseRequest {\n docsDir: DocsDir;\n /**\n * The enumerated fleet. It rides into the resolver for the reason every other\n * edge join carries it: without it, a step drawn into a modelled container\n * `payment.api` resolves to a service called \"api\" that has never existed\n * (docs/DESIGN.md's serviceResolver-known row).\n */\n known: ReadonlySet<string>;\n}\n\n/**\n * Does any document in the set even mention a reserved tag prefix?\n *\n * Sequential and short-circuiting ON PURPOSE — the usual \"no await in a loop\"\n * rule is about independent work that all has to finish, and this loop exists\n * precisely so that a fleet whose first file declares a use case stops reading\n * the other ninety-nine.\n *\n * `req-` is the looser of the two substrings and that is accepted knowingly: a\n * document mentioning it in prose costs one project load loam would otherwise\n * have skipped, which is a slower right answer. Dropping it costs a wrong one.\n */\nasync function mentionsTagPrefix(paths: readonly string[]): Promise<boolean> {\n for (const path of paths) {\n try {\n const bytes = await readFile(path);\n if (bytes.includes(CAP_TAG_PREFIX) || bytes.includes(REQ_TAG_PREFIX)) return true;\n } catch {\n // Unreadable is not \"no tag\": the project load below owns that failure and\n // will report it as a hole naming the document. Answering `false` here\n // would let an unopenable file grade the fleet as having no use cases.\n return true;\n }\n }\n return false;\n}\n\n/**\n * Is this a use case at all? — the ONE answer, for `validate` and every reader\n * alike.\n *\n * EITHER RESERVED PREFIX OPTS A VIEW IN. A view carrying `#req-CHK-ONCE` and no\n * `#cap-` tag is an author who asked to be graded and forgot half the claim; it\n * earns `usecase.requirement-unresolved`'s `unscoped` arm rather than silence,\n * and SCHEMA.md has said so since the tag shipped.\n *\n * It is exported because it was the asymmetry, not the rule, that was the\n * defect. `validate --all` widened here and the readers did not, so a\n * `#req-`-only flow was graded hop by hop by the gate and invisible to `loam\n * diff`, `delta`, `status`, the packs and `list capabilities` — a fleet where\n * the gate and the report disagree about what exists, which is worse than\n * either answer alone. One predicate is what stops that recurring: a caller\n * that spells `startsWith` itself is a second answer waiting to drift.\n *\n * The parameter is the smallest shape the test needs rather than `ParsedView`,\n * so a caller holding a view-like row does not have to build one to ask. Both\n * prefix tests match their resolver's exactly, case included, so a view can\n * never be opted in here and read as untagged in `resolveCapabilityTags`.\n *\n * NOT the same question as `flowsClaiming`'s. That one asks which flows claim\n * capability X and matches the whole `cap-<slug>` tag; a `#req-`-only view\n * claims none, and it is correct for it to stay out of that answer while\n * counting as a use case here.\n */\nexport function isUseCase(view: { tags: readonly string[] }): boolean {\n return view.tags.some((tag) => tag.startsWith(CAP_TAG_PREFIX) || tag.startsWith(REQ_TAG_PREFIX));\n}\n\n/**\n * The fleet's declared use cases, with the model they are drawn over.\n *\n * Reads only, throws nothing, and refuses nothing: every failure is the\n * `unreadable` arm. Callers memoise this per invocation — nothing is cached\n * here, because a module-level cache keyed on a docsDir would leak across the\n * `chdir`-per-test processes AGENTS.md warns about and, in a long-running host,\n * across invocations.\n */\nexport async function readUseCases(req: UseCaseRequest): Promise<UseCaseScan> {\n const none: UseCaseScan = {\n kind: \"read\",\n views: [],\n model: { elements: [], relationships: [], known: req.known },\n resolve: (id) => id,\n };\n const dir = architectureDir(req.docsDir);\n const documents = await architectureDocuments(dir, [subsystemViewsPath(req.docsDir)]);\n if (!(await mentionsTagPrefix(documents))) return none;\n\n const doc = await loadArchitecture(req.docsDir);\n // loam's standing rule, at project altitude: errors mean no model. A use-case\n // file with one unresolved element leaves the whole project unusable, and\n // grading a flow against half a map is the wrong answer this refuses to give.\n if (doc.errors.length > 0) {\n return { kind: \"unreadable\", errors: doc.errors.map((e) => e.message) };\n }\n return {\n kind: \"read\",\n views: (doc.views ?? []).filter(isUseCase),\n model: { elements: doc.elements, relationships: doc.relationships, known: req.known },\n resolve: serviceResolver(doc.elements, req.known),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleet.js","sourceRoot":"","sources":["../../../src/core/usecases/fleet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet's use cases, read once for whoever asks — and NOT read at all when\n * the fleet demonstrably has none.\n *\n * `validate --all` grades use cases as part of a run that was already loading\n * `architecture/` as a LikeC4 project, so it pays nothing extra. Every reader\n * added in phase 4 is in a different position: `loam diff`, `loam delta`,\n * `loam context` and `loam explore` did not load the fleet map as a project\n * before, and `loam delta` in particular sits in `/loam-implement`'s inner loop\n * — a Langium workspace spin-up there is felt on every iteration of somebody's\n * day. So this module's whole job is to answer \"which use cases does the fleet\n * declare?\" and, in the common case where the answer is none, to answer it\n * without starting LikeC4 at all.\n *\n * THE CHEAP GATE IS A BYTE SCAN, and it is sound rather than heuristic. A view\n * is a use case only if it carries a reserved tag — `#cap-<slug>`,\n * `#req-<slug>` or `#suite-<name>`, `isUseCase` below is the whole test — and\n * a view carries a tag only through a REFERENCE written in the document that\n * declares the view. LikeC4's grammar spells every tag reference `HashId`: the\n * `#` terminal, then the name. Measured at the 1.59.2 pin, the `#` is\n * mandatory — a bare `suite-smoke` in a view body fails to resolve, and\n * `tags #suite-smoke` fails to parse — but LikeC4's four hidden tokens may sit\n * between the `#` and the name: `# suite-smoke`, a `#` ending one line with\n * the name on the next, and a block comment in between all parse to the tag\n * `suite-smoke`. So the scan finds each `#`, skips exactly those tokens\n * (`referencesReservedTag`), and asks whether a reserved prefix starts there.\n * A document set in which no file does declares no use case, however badly it\n * parses, and `{ views: [] }` is the correct answer, not an evasion. A\n * `tag cap-…` line in a `specification` block is a declaration, not a\n * reference, and does not open the gate: a tag no view carries opts nothing\n * in.\n *\n * THE `#` IS WHAT KEEPS PROSE OUT. The scan used to look for the bare\n * substrings, so a view titled 'What the e2e-suite-a job exercises', or a\n * comment naming `req-` in passing, opened it. At fleet scope that cost one\n * project load, because a map that does not parse is `landscape.invalid`\n * whether or not the gate opens; at service scope (`./service/flows.ts`) it\n * turned an untagged hand-drawn sibling that did not parse into\n * `usecase.flow-invalid` — an error, exit 1 — over a diagram that opted\n * nothing in. A `#` followed by a reserved prefix still opens it wherever it\n * is written, a comment or a description included: that over-scan is\n * accepted, because it is spelled as the tag syntax, and the scan must never\n * be narrower than the parser.\n *\n * The scan reads bytes rather than decoded text on purpose: it looks for\n * ASCII, and `readFile(path, \"utf8\")` on a document that is not UTF-8 would\n * substitute U+FFFD and could hide it. Decoded as latin1 instead, every byte is\n * one character, and the bytes of a multi-byte sequence never read as ASCII.\n *\n * EVERY PREFIX, because the gate and the predicate answer one question and a\n * gate narrower than the predicate is silence rather than a smaller answer. A\n * fleet whose only flow is tagged `#req-` alone writes `cap-` nowhere, so a\n * `cap-`-only scan returned `false` and none of the five readers ever loaded\n * the project — the widening below would have fixed nothing without this one.\n * The suite prefix joined for the same reason the day a `#suite-`-only view\n * became a use case: both now read one list, `USE_CASE_TAG_PREFIXES`, so a\n * fourth prefix cannot be added to one and not the other.\n *\n * The gate FAILS CLOSED. A document this module cannot open is treated as one\n * that might mention the prefix, so the project load happens and reports the\n * failure as a hole. The alternative — reading an EACCES as \"no tag here\" — is\n * the fail-open shape docs/CODE-STYLE.md forbids at a validator, arriving in an\n * optimisation.\n */\nimport { readFile } from \"node:fs/promises\";\nimport type { StepScope } from \"../c4/resolve/steps.js\";\nimport { CAP_TAG_PREFIX, REQ_TAG_PREFIX } from \"../capabilities/usecase-join.js\";\nimport type { ParsedView } from \"../c4/parsed/dynamic-views.js\";\nimport { architectureProjectDocuments, loadArchitecture } from \"../c4/project/architecture.js\";\nimport { errorText, type LoadedDoc } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { SUITE_TAG_PREFIX } from \"./suites/suites.js\";\n\n/**\n * Every reserved prefix that opts a view in — the one list both the byte gate\n * and `isUseCase` read, so the two cannot disagree about which tags make a use\n * case. `cap-` and `req-` are spelled by the capability join that resolves\n * them; `suite-` by `./suites/suites.ts`, which groups by it and resolves\n * nothing, because a suite name is checked by LikeC4's own undeclared-tag\n * refusal rather than by a vocabulary.\n */\nconst USE_CASE_TAG_PREFIXES: readonly string[] = [CAP_TAG_PREFIX, REQ_TAG_PREFIX, SUITE_TAG_PREFIX];\n\n/**\n * What the fleet's use cases are, or the honest refusal to say.\n *\n * Two variants rather than a record with an `unreadable` flag, because the\n * fields are not independent: a project that did not parse has no elements to\n * resolve against and no views to grade, and a caller handed\n * `{ views: [], unreadable: true }` is one `.length` check away from printing\n * \"no use cases touch this service\" about a fleet nobody could read. Tagged,\n * that state stops being constructible — every caller has to name the\n * `unreadable` arm to get at the views.\n *\n * `read` with an empty `views` is the ordinary answer for a fleet that draws no\n * use cases, and it is exactly what the cheap gate returns without loading\n * anything: the gate is an optimisation over a question with a known answer,\n * never a third verdict.\n */\nexport type UseCaseScan =\n | {\n kind: \"read\";\n /** The reserved-tag views ONLY — `isUseCase` below is the opt-in, and the only one. */\n views: readonly ParsedView[];\n /**\n * What a hop is attributed against, built ONCE per scan. It carries the\n * fleet set that `resolve` below was built from, which is what stops\n * \"which service is this element\" being answered one way by\n * `attributeStep`'s fallback tier and another by a caller's own join —\n * the same reason `validate`'s `StepGrading` holds both together.\n *\n * `known` is REQUIRED here while `StepScope` leaves it optional, and the\n * narrowing is load-bearing rather than tidy: every scan is built from a\n * `UseCaseRequest` that has one, so a consumer reading `model.known` was\n * writing an `undefined` branch that could not be reached and could not be\n * tested. A `StepScope` is still what `attributeStep` receives.\n */\n model: StepScope & { known: ReadonlySet<string> };\n /** One element→service resolver for the whole scan, built with the enumerated fleet. */\n resolve: (id: string) => string;\n }\n | {\n kind: \"unreadable\";\n /**\n * LikeC4's own messages, so a caller can say WHICH document broke —\n * spelled through `errorText` like every other diagnostic loam prints.\n * They used to travel raw, so the 1-based line was missing and one\n * dropped quote in the map put a 143 KB parser message into the\n * `useCaseScan.error` of every service's `loam context --json`.\n */\n errors: string[];\n };\n\nexport interface UseCaseRequest {\n docsDir: DocsDir;\n /**\n * The enumerated fleet. It rides into the resolver for the reason every other\n * edge join carries it: without it, a step drawn into a modelled container\n * `payment.api` resolves to a service called \"api\" that has never existed\n * (docs/DESIGN.md's serviceResolver-known row).\n */\n known: ReadonlySet<string>;\n}\n\n/**\n * Does any document in the set reference a reserved tag — a `#`, then only\n * LikeC4's hidden tokens, then `cap-`, `req-` or `suite-`?\n *\n * Sequential and short-circuiting ON PURPOSE — the usual \"no await in a loop\"\n * rule is about independent work that all has to finish, and this loop exists\n * precisely so that a fleet whose first file declares a use case stops reading\n * the other ninety-nine.\n *\n * What the scan lets through costs differently at its two callers, and the\n * header above names both. A `#req-…` written in a comment opens it: at fleet\n * scope that is one project load loam could have skipped, a slower right\n * answer; at service scope it loads a sibling set that may not parse, and a\n * broken one is then `usecase.flow-invalid` — an error, but over a document\n * that spelled the tag syntax. Prose without the `#` opens neither. Missing a\n * reference would cost a wrong answer at both: a use case nobody graded.\n *\n * Exported for the service-scope reader (`./service/flows.ts`), which asks the\n * same question of the `.likec4` files beside one `model.likec4`: a sibling set\n * that references no reserved tag declares no use case, so a hand-drawn\n * `views.likec4` costs that service no LikeC4 workspace. One gate, two callers,\n * because a second byte scan spelled in the service package would be free to\n * disagree with this one about which prefixes opt a view in.\n */\nexport async function mentionsTagPrefix(paths: readonly string[]): Promise<boolean> {\n for (const path of paths) {\n try {\n const bytes = await readFile(path);\n if (referencesReservedTag(bytes.toString(\"latin1\"))) return true;\n } catch {\n // Unreadable is not \"no tag\": the project load below owns that failure and\n // will report it as a hole naming the document. Answering `false` here\n // would let an unopenable file grade the fleet as having no use cases.\n return true;\n }\n }\n return false;\n}\n\n/**\n * Is a reserved prefix written as a tag reference anywhere in this text? Every\n * `#` is tried, wherever it stands — in a view body, a comment, a string —\n * because the scan may over-approximate the parser and never under-approximate\n * it.\n *\n * A text with no `#`, or with no reserved prefix anywhere in it, cannot answer\n * yes, so it is answered before the table below is built: that is nearly every\n * document, and it costs those two native searches and no allocation.\n *\n * Exported for its own test (test/usecase-byte-gate.test.ts), which holds it to\n * the walk the first version made from each `#` over crafted inputs, and holds\n * its cost to one pass over inputs that walk took minutes on.\n */\nexport function referencesReservedTag(text: string): boolean {\n const first = text.indexOf(\"#\");\n if (first === -1 || !USE_CASE_TAG_PREFIXES.some((prefix) => text.includes(prefix))) return false;\n const nameAt = hiddenRunEnds(text);\n for (let hash = first; hash !== -1; hash = text.indexOf(\"#\", hash + 1)) {\n const name = nameAt[hash + 1]!;\n if (USE_CASE_TAG_PREFIXES.some((prefix) => text.startsWith(prefix, name))) return true;\n }\n return false;\n}\n\n/**\n * For every position in `text`, where the next token LikeC4 would read starts\n * from there: past whitespace, line breaks, `//` line comments and block\n * comments — the four terminals the 1.59.2 grammar marks hidden (`WS`, `NL`,\n * `LINE_COMMENT`, `BLOCK_COMMENT`), which its lexer drops between the `#` of a\n * tag reference and the name. Index `text.length` is the end itself.\n *\n * ONE PASS, FROM THE END, so every answer is built from answers already known\n * and the whole table costs one visit per character, however the `#`s and the\n * comments interleave. The first version walked forward from each `#` instead,\n * and a run of hidden tokens with `#`s inside it was re-walked once per `#`:\n * `#/*` repeated took 17.6 s on 100 KB (each unclosed opener searched the rest\n * of the file for a closer), and `#//` repeated on one line, or the same `#/*`\n * run closed once at its end, were quadratic the same way. Remembering only\n * where the closer search last failed would have fixed the first of those\n * three and left the other two; nothing short of reusing every answer fixes\n * the shape, and this scan runs over every document on every reader's path.\n *\n * A hand loop rather than one regular expression: a repeated group of\n * alternatives that can each consume a comment backtracks exponentially on a\n * long run of slashes. An UNTERMINATED block comment is not one LikeC4 can lex\n * as a comment, so only the `/*` itself is stepped over and a name written\n * after it still counts: that can open the gate more often, never less, and\n * the document cannot parse anyway — the load the gate opens is what reports\n * it. The closer is searched from two past the opener, so the `*` of an opener\n * never doubles as the `*` of its closer, exactly as the lexer reads it.\n */\nfunction hiddenRunEnds(text: string): Int32Array {\n const end = new Int32Array(text.length + 1);\n end[text.length] = text.length;\n // The nearest line break at or after the position being read, and the\n // nearest `*/` at or after the NEXT position and the one after that — an\n // opener at `at` searches from `at + 2`, and the pass meets that position\n // before it meets `at`. -1 is \"none\".\n let lineBreak = text.length;\n let closeFromNext = -1;\n let closeFromAfterNext = -1;\n for (let at = text.length - 1; at >= 0; at -= 1) {\n const char = text[at];\n const closeFromHere = char === \"*\" && text[at + 1] === \"/\" ? at : closeFromNext;\n if (char === \"\\r\" || char === \"\\n\") lineBreak = at;\n if (char === \" \" || char === \"\\t\" || char === \"\\r\" || char === \"\\n\") {\n end[at] = end[at + 1]!;\n } else if (char === \"/\" && text[at + 1] === \"/\") {\n // The comment runs to the line break and leaves it unread; the break is\n // whitespace, so the run goes on from there. No break: the text ends.\n end[at] = end[lineBreak]!;\n } else if (char === \"/\" && text[at + 1] === \"*\") {\n end[at] = closeFromAfterNext === -1 ? end[at + 2]! : end[closeFromAfterNext + 2]!;\n } else {\n end[at] = at;\n }\n closeFromAfterNext = closeFromNext;\n closeFromNext = closeFromHere;\n }\n return end;\n}\n\n/**\n * Is this a use case at all? — the ONE answer, for `validate` and every reader\n * alike.\n *\n * ANY RESERVED PREFIX OPTS A VIEW IN. A view carrying `#req-CHK-ONCE` and no\n * `#cap-` tag is an author who asked to be graded and forgot half the claim; it\n * earns `usecase.requirement-unresolved`'s `unscoped` arm rather than silence,\n * and SCHEMA.md has said so since the tag shipped. A view carrying only\n * `#suite-<name>` claims no capability and no requirement and is still a use\n * case — the owner's call, recorded in SCHEMA.md: putting a journey in a suite\n * is asking for it to be run, and a journey a test stand is provisioned from\n * must be held to the model hop by hop like any other.\n *\n * It is exported because it was the asymmetry, not the rule, that was the\n * defect. `validate --all` widened here and the readers did not, so a\n * `#req-`-only flow was graded hop by hop by the gate and invisible to `loam\n * diff`, `delta`, `status`, the packs and `list capabilities` — a fleet where\n * the gate and the report disagree about what exists, which is worse than\n * either answer alone. One predicate is what stops that recurring: a caller\n * that spells `startsWith` itself is a second answer waiting to drift.\n *\n * The parameter is the smallest shape the test needs rather than `ParsedView`,\n * so a caller holding a view-like row does not have to build one to ask. Every\n * prefix test matches its reader's exactly, case included, so a view can never\n * be opted in here and read as untagged in `resolveCapabilityTags` or\n * `suiteNames`.\n *\n * NOT the same question as `flowsClaiming`'s. That one asks which flows claim\n * capability X and matches the whole `cap-<slug>` tag; a `#req-`-only view\n * claims none, and it is correct for it to stay out of that answer while\n * counting as a use case here.\n */\nexport function isUseCase(view: { tags: readonly string[] }): boolean {\n return view.tags.some((tag) => USE_CASE_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)));\n}\n\n/**\n * The fleet's declared use cases, with the model they are drawn over.\n *\n * Reads only, throws nothing, and refuses nothing: every failure is the\n * `unreadable` arm. Callers memoise this per invocation — nothing is cached\n * here, because a module-level cache keyed on a docsDir would leak across the\n * `chdir`-per-test processes AGENTS.md warns about and, in a long-running host,\n * across invocations.\n */\nexport async function readUseCases(req: UseCaseRequest): Promise<UseCaseScan> {\n const none: UseCaseScan = {\n kind: \"read\",\n views: [],\n model: { elements: [], relationships: [], known: req.known },\n resolve: (id) => id,\n };\n // EXACTLY the documents `loadArchitecture` will load two lines down, root\n // `exclude` and all — `architectureProjectDocuments` is that one answer\n // (`c4/project/architecture.ts`). The gate used to walk `architecture/` for\n // itself and skip the exclude, which could only over-scan: the byte gate\n // reading a document the loader then drops decides \"there might be a use\n // case here\" and pays for a project load that finds none. Never a wrong\n // answer, but two lists that disagree about what the project is, and the\n // next reader to take the wider one for the project's would be wrong.\n const documents = await architectureProjectDocuments(req.docsDir);\n if (!(await mentionsTagPrefix(documents))) return none;\n\n return useCasesIn(await loadArchitecture(req.docsDir), req.known);\n}\n\n/**\n * The use cases of an `architecture/` project somebody has ALREADY loaded —\n * `readUseCases` minus its byte gate and its load.\n *\n * For a reader that needs the project anyway: the consumer index\n * (`core/consumers/`) reads the project's edges and its hops off one load, and\n * going through `readUseCases` there would parse the same documents a second\n * time behind a gate whose whole purpose is to skip a load that has already\n * happened. The answer is the same one: a project with no reserved-tag view\n * yields `read` with no views, exactly what the gate returns without loading.\n */\nexport function useCasesIn(doc: LoadedDoc, known: ReadonlySet<string>): UseCaseScan {\n // loam's standing rule, at project altitude: errors mean no model. A use-case\n // file with one unresolved element leaves the whole project unusable, and\n // grading a flow against half a map is the wrong answer this refuses to give.\n if (doc.errors.length > 0) {\n return { kind: \"unreadable\", errors: doc.errors.map(errorText) };\n }\n return {\n kind: \"read\",\n views: (doc.views ?? []).filter(isUseCase),\n model: { elements: doc.elements, relationships: doc.relationships, known },\n resolve: serviceResolver(doc.elements, known),\n };\n}\n"]}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* one view's tags are two chances to disagree about which capability a flow is
|
|
23
23
|
* about. The ladder that produces the suppressing `null` is
|
|
24
24
|
* `gradableCapabilityIds` in `core/capabilities/findings.ts` — one function, called by every site that needs the graded id list, this module's
|
|
25
|
-
*
|
|
25
|
+
* callers in `commands/validate/fleet/map/usecases.ts` and `./feature.ts` included. The FILE is this axis's opt-in, so a
|
|
26
26
|
* fleet with no `architecture/capabilities.yaml` produces no capability finding
|
|
27
27
|
* at all, and an unreadable one is `capability.invalid` alone rather than one
|
|
28
28
|
* unresolved tag per use case in the fleet, which is a cascade and not a
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
* `CapabilityVocabulary` anywhere in this package is the copy that goes stale
|
|
31
31
|
* the day a fourth un-gradable state is added, so call the function.
|
|
32
32
|
*/
|
|
33
|
-
import { CAP_TAG_PREFIX, tagSlug, } from "
|
|
34
|
-
import { viewPlace } from "./place.js";
|
|
33
|
+
import { CAP_TAG_PREFIX, tagSlug, } from "../../capabilities/usecase-join.js";
|
|
35
34
|
/** A capability id as an author must write it in a tag: `identity/tokens (#cap-identity-tokens)`. */
|
|
36
35
|
function idAndTag(id) {
|
|
37
36
|
return `${id} (#${CAP_TAG_PREFIX}${tagSlug(id)})`;
|
|
@@ -45,11 +44,11 @@ function idAndTag(id) {
|
|
|
45
44
|
* LikeC4 refuses to parse. The flattening rule is stated in the same breath so
|
|
46
45
|
* the reader can derive the next one without being told again.
|
|
47
46
|
*/
|
|
48
|
-
function noneMessage(
|
|
47
|
+
function noneMessage(where, claim) {
|
|
49
48
|
const hint = claim.close.length > 0
|
|
50
49
|
? `Did you mean: ${claim.close.map(idAndTag).join(", ")}?`
|
|
51
50
|
: "Declare it there (`capabilities: {<id>: {description, owner}}`), or fix the tag.";
|
|
52
|
-
return (`${
|
|
51
|
+
return (`${where} is tagged #${claim.tag}, and no capability declared in ` +
|
|
53
52
|
`architecture/capabilities.yaml flattens to '${claim.slug}'. ${hint} ` +
|
|
54
53
|
"A tag spells the id with every character outside `[A-Za-z0-9_-]` flattened to `-`, because that is all a LikeC4 tag name accepts.");
|
|
55
54
|
}
|
|
@@ -58,8 +57,8 @@ function noneMessage(view, claim) {
|
|
|
58
57
|
* message that stopped at the first would be reporting the ambiguity while
|
|
59
58
|
* hiding half of what makes it one.
|
|
60
59
|
*/
|
|
61
|
-
function manyMessage(
|
|
62
|
-
return (`${
|
|
60
|
+
function manyMessage(where, claim) {
|
|
61
|
+
return (`${where} is tagged #${claim.tag}, and ${claim.ids.length} declared capabilities flatten to ` +
|
|
63
62
|
`'${claim.slug}' (${claim.ids.join(", ")}) — nothing in the tag can say which one this use case realizes. ` +
|
|
64
63
|
"Rename one of them in architecture/capabilities.yaml: a guessed capability files every hop of the flow, " +
|
|
65
64
|
"and every rollup built over it, under an owner who never claimed it — and the wrong answer looks exactly " +
|
|
@@ -74,7 +73,7 @@ function manyMessage(view, claim) {
|
|
|
74
73
|
* swallow a broken tag beside a working one or lose the capability the view
|
|
75
74
|
* genuinely does realize; one tag, one verdict, one finding keeps both facts.
|
|
76
75
|
*/
|
|
77
|
-
export function capabilityTagFindings(view, claims) {
|
|
76
|
+
export function capabilityTagFindings(view, claims, place) {
|
|
78
77
|
const findings = [];
|
|
79
78
|
for (const claim of claims) {
|
|
80
79
|
if (claim.kind === "resolved")
|
|
@@ -86,7 +85,7 @@ export function capabilityTagFindings(view, claims) {
|
|
|
86
85
|
// `subjectsWith` counts one broken use case once however many of its tags
|
|
87
86
|
// are wrong.
|
|
88
87
|
subject: view.id,
|
|
89
|
-
message: claim.kind === "none" ? noneMessage(view, claim) : manyMessage(view, claim),
|
|
88
|
+
message: claim.kind === "none" ? noneMessage(place(view), claim) : manyMessage(place(view), claim),
|
|
90
89
|
});
|
|
91
90
|
}
|
|
92
91
|
return findings;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-tag.js","sourceRoot":"","sources":["../../../../src/core/usecases/grade/capability-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EACL,cAAc,EACd,OAAO,GAER,MAAM,oCAAoC,CAAC;AAK5C,qGAAqG;AACrG,SAAS,QAAQ,CAAC,EAAU;IAC1B,OAAO,GAAG,EAAE,MAAM,cAAc,GAAG,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC;AACpD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,KAAa,EAAE,KAAiD;IACnF,MAAM,IAAI,GACR,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACpB,CAAC,CAAC,iBAAiB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC1D,CAAC,CAAC,kFAAkF,CAAC;IACzF,OAAO,CACL,GAAG,KAAK,eAAe,KAAK,CAAC,GAAG,kCAAkC;QAClE,+CAA+C,KAAK,CAAC,IAAI,MAAM,IAAI,GAAG;QACtE,mIAAmI,CACpI,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAa,EAAE,KAAiD;IACnF,OAAO,CACL,GAAG,KAAK,eAAe,KAAK,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,CAAC,MAAM,oCAAoC;QAC7F,IAAI,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,mEAAmE;QAC3G,0GAA0G;QAC1G,2GAA2G;QAC3G,qBAAqB,CACtB,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAgB,EAAE,MAAkC,EAAE,KAAgB;IAC1G,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,+BAA+B;YACrC,wEAAwE;YACxE,0EAA0E;YAC1E,aAAa;YACb,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,OAAO,EAAE,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;SACnG,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * `usecase.capability-unresolved` — the `#cap-` tag that names no declared\n * capability, or more than one.\n *\n * The join itself lives in `core/capabilities/usecase-join.ts` and none of it is\n * repeated here: this module owns the grade and the sentence, nothing else. That\n * split is why the tag can answer \"many\" at all — the slug is not injective, so\n * `payments/refunds` and `payments-refunds` both flatten to\n * `payments-refunds` — and why the close candidates a message offers are real\n * declared ids rather than loam's flattened spelling of them.\n *\n * ERROR, the same grade `capability.unknown` carries on a `Capability:` line and\n * for its reason: an invented capability reads exactly like a real one. Here it\n * reads that way at flow altitude — the tag is what files a whole business\n * sequence under an owner, so a claim that resolves to nothing produces a use\n * case nobody can find and a capability nobody can see is realized.\n *\n * SUPPRESSED ENTIRELY when the vocabulary is absent or unreadable: the caller\n * resolves no claims at all in that case, so this answers with nothing. The\n * claims are RESOLVED BY THE CALLER rather than here because the `#req-` grade\n * beside this one is scoped by the very same verdicts, and two resolutions of\n * one view's tags are two chances to disagree about which capability a flow is\n * about. The ladder that produces the suppressing `null` is\n * `gradableCapabilityIds` in `core/capabilities/findings.ts` — one function, called by every site that needs the graded id list, this module's\n * callers in `commands/validate/fleet/map/usecases.ts` and `./feature.ts` included. The FILE is this axis's opt-in, so a\n * fleet with no `architecture/capabilities.yaml` produces no capability finding\n * at all, and an unreadable one is `capability.invalid` alone rather than one\n * unresolved tag per use case in the fleet, which is a cascade and not a\n * diagnosis. That rule is not re-decided here: a second reading of\n * `CapabilityVocabulary` anywhere in this package is the copy that goes stale\n * the day a fourth un-gradable state is added, so call the function.\n */\nimport {\n CAP_TAG_PREFIX,\n tagSlug,\n type CapabilityClaim,\n} from \"../../capabilities/usecase-join.js\";\nimport type { ParsedView } from \"../../c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport type { ViewPlace } from \"../place.js\";\n\n/** A capability id as an author must write it in a tag: `identity/tokens (#cap-identity-tokens)`. */\nfunction idAndTag(id: string): string {\n return `${id} (#${CAP_TAG_PREFIX}${tagSlug(id)})`;\n}\n\n/**\n * A tag nothing declares, with the close names spelled as TAGS as well as ids.\n *\n * Both spellings, because either alone sends the author somewhere wrong: the id\n * is what `capabilities.yaml` holds and the tag is what the view must carry, and\n * an author handed only `identity/tokens` writes `#cap-identity/tokens`, which\n * LikeC4 refuses to parse. The flattening rule is stated in the same breath so\n * the reader can derive the next one without being told again.\n */\nfunction noneMessage(where: string, claim: Extract<CapabilityClaim, { kind: \"none\" }>): string {\n const hint =\n claim.close.length > 0\n ? `Did you mean: ${claim.close.map(idAndTag).join(\", \")}?`\n : \"Declare it there (`capabilities: {<id>: {description, owner}}`), or fix the tag.\";\n return (\n `${where} is tagged #${claim.tag}, and no capability declared in ` +\n `architecture/capabilities.yaml flattens to '${claim.slug}'. ${hint} ` +\n \"A tag spells the id with every character outside `[A-Za-z0-9_-]` flattened to `-`, because that is all a LikeC4 tag name accepts.\"\n );\n}\n\n/**\n * A tag two or more declared ids answer to. Every colliding id is named — a\n * message that stopped at the first would be reporting the ambiguity while\n * hiding half of what makes it one.\n */\nfunction manyMessage(where: string, claim: Extract<CapabilityClaim, { kind: \"many\" }>): string {\n return (\n `${where} is tagged #${claim.tag}, and ${claim.ids.length} declared capabilities flatten to ` +\n `'${claim.slug}' (${claim.ids.join(\", \")}) — nothing in the tag can say which one this use case realizes. ` +\n \"Rename one of them in architecture/capabilities.yaml: a guessed capability files every hop of the flow, \" +\n \"and every rollup built over it, under an owner who never claimed it — and the wrong answer looks exactly \" +\n \"like the right one.\"\n );\n}\n\n/**\n * One finding per `#cap-` tag that does not resolve — never one per view.\n *\n * A view may legitimately carry two capability tags (a checkout flow that also\n * issues the identity token realizes both), and `resolveCapabilityTags` answers\n * each on its own terms for that reason. Collapsing them here would either\n * swallow a broken tag beside a working one or lose the capability the view\n * genuinely does realize; one tag, one verdict, one finding keeps both facts.\n */\nexport function capabilityTagFindings(view: ParsedView, claims: readonly CapabilityClaim[], place: ViewPlace): Finding[] {\n const findings: Finding[] = [];\n for (const claim of claims) {\n if (claim.kind === \"resolved\") continue;\n findings.push({\n severity: \"error\",\n code: \"usecase.capability-unresolved\",\n // The VIEW, not the tag: a subject is what a reader goes and opens, and\n // `subjectsWith` counts one broken use case once however many of its tags\n // are wrong.\n subject: view.id,\n message: claim.kind === \"none\" ? noneMessage(place(view), claim) : manyMessage(place(view), claim),\n });\n }\n return findings;\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A feature's own flows, graded the way `validate --all` will grade them once
|
|
3
|
+
* the archive has copied them into `architecture/usecases/`.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. The feature gate used to READ a feature's flows — parsed
|
|
6
|
+
* over the map its own merge would leave (`../delta/overlay.ts`), which is
|
|
7
|
+
* `usecase.flow-invalid` — and then grade only the promises they keep. Two
|
|
8
|
+
* flows the very next `validate --all` convicts passed it: one whose hop joins
|
|
9
|
+
* two services nothing on that map connects (`usecase.step-unbacked`), and one
|
|
10
|
+
* tagged `#req-` with no `#cap-` to scope it, or with a `#req-` naming no
|
|
11
|
+
* requirement (`usecase.requirement-unresolved`). Both archived at exit 0 under
|
|
12
|
+
* "living spec + landscape are now complete + current." (the real-fleet review,
|
|
13
|
+
* 2026-10-21). SCHEMA.md already said feature flows are graded against the map
|
|
14
|
+
* the merge leaves behind; for every grade but the parse, they were not.
|
|
15
|
+
*
|
|
16
|
+
* THE FLEET'S GRADER, NOT A COPY. `./usecases.ts` is `validate --all`'s own
|
|
17
|
+
* per-view loop — the opt-in, the capability and requirement tags, every hop —
|
|
18
|
+
* handed the staged post-merge model, the vocabulary and requirement index the
|
|
19
|
+
* merge would leave, and a `place` naming the file the author is still editing.
|
|
20
|
+
* A second verdict→code mapping for this scope would be the drift that
|
|
21
|
+
* package's banner forbids, and the drift would show up first as the gate
|
|
22
|
+
* passing what the fleet then fails.
|
|
23
|
+
*
|
|
24
|
+
* Issues, because the archive gate files Issues: every finding the grader
|
|
25
|
+
* writes carries a code the `IssueCode` union admits for exactly this
|
|
26
|
+
* (`core/vocabulary/issue.ts`). The severities travel unchanged — the two
|
|
27
|
+
* errors gate the archive, the two hop warnings and nothing else advise.
|
|
28
|
+
*/
|
|
29
|
+
import { relative } from "node:path";
|
|
30
|
+
import { useCaseFindings } from "./usecases.js";
|
|
31
|
+
/**
|
|
32
|
+
* The codes `useCaseFindings` writes, each mapped to its `IssueCode` member — a
|
|
33
|
+
* table rather than a cast, so a sixth grade added to the fleet grader is a
|
|
34
|
+
* thrown error the first time a feature trips it rather than a finding this
|
|
35
|
+
* gate silently drops.
|
|
36
|
+
*/
|
|
37
|
+
const AS_ISSUE = {
|
|
38
|
+
"usecase.capability-unresolved": "usecase.capability-unresolved",
|
|
39
|
+
"usecase.requirement-unresolved": "usecase.requirement-unresolved",
|
|
40
|
+
"usecase.step-unbacked": "usecase.step-unbacked",
|
|
41
|
+
"usecase.step-contested": "usecase.step-contested",
|
|
42
|
+
"usecase.step-unlinked": "usecase.step-unlinked",
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Where a feature flow's finding says the view is:
|
|
46
|
+
* `features/FEAT-1-split/usecases/checkout.likec4 — dynamic view 'uc_checkout'`.
|
|
47
|
+
* The staged parse has already mapped `sourcePath` back to the authored file;
|
|
48
|
+
* this spells it docs-relative, the way every other feature finding names a file.
|
|
49
|
+
*/
|
|
50
|
+
function featurePlace(docsDir) {
|
|
51
|
+
return (view) => {
|
|
52
|
+
const file = view.sourcePath === undefined ? "usecases/" : relative(docsDir, view.sourcePath).split(/[\\/]/).join("/");
|
|
53
|
+
return `${file} — dynamic view '${view.id}'`;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function asIssue(finding) {
|
|
57
|
+
if (finding.severity === "ok")
|
|
58
|
+
return [];
|
|
59
|
+
const code = AS_ISSUE[finding.code];
|
|
60
|
+
if (code === undefined) {
|
|
61
|
+
throw new Error(`the use-case grader wrote '${finding.code}', which the feature gate has no IssueCode for — add it to AS_ISSUE`);
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
severity: finding.severity,
|
|
66
|
+
code,
|
|
67
|
+
...(finding.subject === undefined ? {} : { subject: finding.subject }),
|
|
68
|
+
...(finding.details === undefined ? {} : { details: finding.details }),
|
|
69
|
+
message: finding.message,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
export function featureFlowIssues(scope) {
|
|
74
|
+
const { scan } = scope;
|
|
75
|
+
if (scan.views.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
return useCaseFindings({
|
|
78
|
+
views: scan.views,
|
|
79
|
+
elements: scan.model.elements,
|
|
80
|
+
relationships: scan.model.relationships,
|
|
81
|
+
services: scan.model.known,
|
|
82
|
+
resolve: scan.resolve,
|
|
83
|
+
capabilities: scope.capabilities,
|
|
84
|
+
requirementsOf: scope.requirementsOf,
|
|
85
|
+
place: featurePlace(scope.docsDir),
|
|
86
|
+
}).flatMap(asIssue);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/usecases/grade/feature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAMrC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAahD;;;;;GAKG;AACH,MAAM,QAAQ,GAAwC;IACpD,+BAA+B,EAAE,+BAA+B;IAChE,gCAAgC,EAAE,gCAAgC;IAClE,uBAAuB,EAAE,uBAAuB;IAChD,wBAAwB,EAAE,wBAAwB;IAClD,uBAAuB,EAAE,uBAAuB;CACjD,CAAC;AAEF;;;;;GAKG;AACH,SAAS,YAAY,CAAC,OAAgB;IACpC,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvH,OAAO,GAAG,IAAI,oBAAoB,IAAI,CAAC,EAAE,GAAG,CAAC;IAC/C,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,OAAgB;IAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,CAAC,IAAI,qEAAqE,CAAC,CAAC;IACnI,CAAC;IACD,OAAO;QACL;YACE,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,IAAI;YACJ,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YACtE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YACtE,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAuB;IACvD,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,eAAe,CAAC;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;QAC7B,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa;QACvC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC;KACnC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACtB,CAAC","sourcesContent":["/**\n * A feature's own flows, graded the way `validate --all` will grade them once\n * the archive has copied them into `architecture/usecases/`.\n *\n * WHY THIS EXISTS. The feature gate used to READ a feature's flows — parsed\n * over the map its own merge would leave (`../delta/overlay.ts`), which is\n * `usecase.flow-invalid` — and then grade only the promises they keep. Two\n * flows the very next `validate --all` convicts passed it: one whose hop joins\n * two services nothing on that map connects (`usecase.step-unbacked`), and one\n * tagged `#req-` with no `#cap-` to scope it, or with a `#req-` naming no\n * requirement (`usecase.requirement-unresolved`). Both archived at exit 0 under\n * \"living spec + landscape are now complete + current.\" (the real-fleet review,\n * 2026-10-21). SCHEMA.md already said feature flows are graded against the map\n * the merge leaves behind; for every grade but the parse, they were not.\n *\n * THE FLEET'S GRADER, NOT A COPY. `./usecases.ts` is `validate --all`'s own\n * per-view loop — the opt-in, the capability and requirement tags, every hop —\n * handed the staged post-merge model, the vocabulary and requirement index the\n * merge would leave, and a `place` naming the file the author is still editing.\n * A second verdict→code mapping for this scope would be the drift that\n * package's banner forbids, and the drift would show up first as the gate\n * passing what the fleet then fails.\n *\n * Issues, because the archive gate files Issues: every finding the grader\n * writes carries a code the `IssueCode` union admits for exactly this\n * (`core/vocabulary/issue.ts`). The severities travel unchanged — the two\n * errors gate the archive, the two hop warnings and nothing else advise.\n */\nimport { relative } from \"node:path\";\nimport type { ParsedView } from \"../../c4/parsed/dynamic-views.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { Issue, IssueCode } from \"../../vocabulary/issue.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport type { UseCaseScan } from \"../fleet.js\";\nimport { useCaseFindings } from \"./usecases.js\";\n\n/** What the feature's flows are graded against — everything as the merge would leave it. */\nexport interface FeatureFlowScope {\n docsDir: DocsDir;\n /** The feature's flows, parsed over the post-merge map — the readable arm only; unreadable is `usecase.flow-invalid`. */\n scan: Extract<UseCaseScan, { kind: \"read\" }>;\n /** The capability ids the merge would leave declared, or `null` when the vocabulary suspends the tag grades. */\n capabilities: readonly string[] | null;\n /** The requirement ids each capability document would declare after the merge. */\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined;\n}\n\n/**\n * The codes `useCaseFindings` writes, each mapped to its `IssueCode` member — a\n * table rather than a cast, so a sixth grade added to the fleet grader is a\n * thrown error the first time a feature trips it rather than a finding this\n * gate silently drops.\n */\nconst AS_ISSUE: Readonly<Record<string, IssueCode>> = {\n \"usecase.capability-unresolved\": \"usecase.capability-unresolved\",\n \"usecase.requirement-unresolved\": \"usecase.requirement-unresolved\",\n \"usecase.step-unbacked\": \"usecase.step-unbacked\",\n \"usecase.step-contested\": \"usecase.step-contested\",\n \"usecase.step-unlinked\": \"usecase.step-unlinked\",\n};\n\n/**\n * Where a feature flow's finding says the view is:\n * `features/FEAT-1-split/usecases/checkout.likec4 — dynamic view 'uc_checkout'`.\n * The staged parse has already mapped `sourcePath` back to the authored file;\n * this spells it docs-relative, the way every other feature finding names a file.\n */\nfunction featurePlace(docsDir: DocsDir): (view: ParsedView) => string {\n return (view) => {\n const file = view.sourcePath === undefined ? \"usecases/\" : relative(docsDir, view.sourcePath).split(/[\\\\/]/).join(\"/\");\n return `${file} — dynamic view '${view.id}'`;\n };\n}\n\nfunction asIssue(finding: Finding): Issue[] {\n if (finding.severity === \"ok\") return [];\n const code = AS_ISSUE[finding.code];\n if (code === undefined) {\n throw new Error(`the use-case grader wrote '${finding.code}', which the feature gate has no IssueCode for — add it to AS_ISSUE`);\n }\n return [\n {\n severity: finding.severity,\n code,\n ...(finding.subject === undefined ? {} : { subject: finding.subject }),\n ...(finding.details === undefined ? {} : { details: finding.details }),\n message: finding.message,\n },\n ];\n}\n\nexport function featureFlowIssues(scope: FeatureFlowScope): Issue[] {\n const { scan } = scope;\n if (scan.views.length === 0) return [];\n return useCaseFindings({\n views: scan.views,\n elements: scan.model.elements,\n relationships: scan.model.relationships,\n services: scan.model.known,\n resolve: scan.resolve,\n capabilities: scope.capabilities,\n requirementsOf: scope.requirementsOf,\n place: featurePlace(scope.docsDir),\n }).flatMap(asIssue);\n}\n"]}
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* the intended reading: one says the tag is wrong, the other says the promise is
|
|
21
21
|
* still unkept.
|
|
22
22
|
*/
|
|
23
|
-
import { REQ_TAG_PREFIX, tagSlug, resolveRequirementTags, } from "
|
|
24
|
-
import { viewPlace } from "./place.js";
|
|
23
|
+
import { REQ_TAG_PREFIX, tagSlug, resolveRequirementTags, } from "../../capabilities/usecase-join.js";
|
|
25
24
|
/** A `Requirement-ID` as an author must write it in a tag: `CHK.ONCE (#req-CHK-ONCE)`. */
|
|
26
25
|
function idAndTag(id) {
|
|
27
26
|
return `${id} (#${REQ_TAG_PREFIX}${tagSlug(id)})`;
|
|
@@ -75,7 +74,7 @@ function advice(claim) {
|
|
|
75
74
|
* scope `capabilityTagFindings` graded are the same list, and an unresolved
|
|
76
75
|
* `#cap-` tag cannot silently scope a requirement lookup.
|
|
77
76
|
*/
|
|
78
|
-
export function requirementTagFindings(view, claims, requirementsOf) {
|
|
77
|
+
export function requirementTagFindings(view, claims, requirementsOf, place) {
|
|
79
78
|
const scope = claims.flatMap((claim) => (claim.kind === "resolved" ? [claim.id] : []));
|
|
80
79
|
const findings = [];
|
|
81
80
|
for (const claim of resolveRequirementTags(view.tags, scope, requirementsOf)) {
|
|
@@ -87,7 +86,7 @@ export function requirementTagFindings(view, claims, requirementsOf) {
|
|
|
87
86
|
// The VIEW, not the tag — a subject is what a reader goes and opens, and
|
|
88
87
|
// one broken use case counts once however many of its tags are wrong.
|
|
89
88
|
subject: view.id,
|
|
90
|
-
message: `${
|
|
89
|
+
message: `${place(view)} is tagged #${claim.tag}, and it does not resolve: ${advice(claim)}`,
|
|
91
90
|
});
|
|
92
91
|
}
|
|
93
92
|
return findings;
|