@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joins.js","sourceRoot":"","sources":["../../../src/core/diff/joins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The cross-service joins `loam diff` reports appearing or going away between\n * the base ref and the working tree — `diff.consumer-added` /\n * `diff.consumer-removed`.\n *\n * These lived in `./victims.ts` beside the per-removal victim index until that\n * index moved down to `core/consumers/` to become the one consumer answer the\n * coherence gate and the archive planner read too. The joins below stayed: they\n * compare two WHOLE states, which only the diff has, while the index answers\n * about one fleet.\n */\nimport type { Requirement } from \"../document/spec.js\";\nimport type { ServiceState } from \"../consumers/state.js\";\nimport { ownsOperation, REQUIREMENT_AXES } from \"../consumers/consumers.js\";\n\n/** The requirements one service contributes to a join — both spec files, a REMOVED one governing nothing. */\nfunction reqsOf(state: ServiceState): Requirement[] {\n return REQUIREMENT_AXES.flatMap((axis) => {\n const a = state[axis];\n return a.kind === \"read\" ? a.value.filter((r) => r.kind !== \"REMOVED\") : [];\n });\n}\n\n/**\n * One cross-service join: `consumer`'s living requirements name a `token` that\n * `provider`'s contract defines. The same \"who depends on what\" question the\n * consumer index (`core/consumers/consumers.ts`) answers per removal, asked\n * here of a WHOLE state — base or current — so `./semantic.ts` can report\n * joins that appeared or went away.\n */\nexport interface ConsumerTuple {\n kind: \"op\" | \"message\";\n consumer: string;\n provider: string;\n token: string;\n}\n\n/** The map key a tuple is joined on between the two states. */\nfunction tupleKey(t: ConsumerTuple): string {\n return `${t.kind}\\0${t.consumer}\\0${t.provider}\\0${t.token}`;\n}\n\n/**\n * Every cross-service join one state holds, keyed for the base/current join.\n * The rules are the consumer index's, read off it rather than restated: both\n * spec files for `Operations:` and `Consumes:` alike (`REQUIREMENT_AXES`), and\n * an `Operations:` line naming an id the consumer's own contract defines is\n * that consumer's own operation, no join onto anybody else's\n * (`ownsOperation`). An unreadable axis simply contributes nothing here — no\n * line from a spec file nobody could read, no ownership from a contract nobody\n * could read — and `tupleVisible` below guards the GRADING, so a join is never\n * called added or removed off a side nobody could read, and `ownershipFlicker`\n * keeps one from being called so off a consumer's own contract that merely\n * stopped or started parsing.\n */\nexport function consumerTuples(states: ReadonlyMap<string, ServiceState>): Map<string, ConsumerTuple> {\n const providersOf = (pick: (s: ServiceState) => string[]): Map<string, string[]> => {\n const map = new Map<string, string[]>();\n for (const id of [...states.keys()].sort()) {\n for (const token of pick(states.get(id)!)) map.set(token, [...(map.get(token) ?? []), id]);\n }\n return map;\n };\n const opProviders = providersOf((s) => (s.openapi.kind === \"read\" ? s.openapi.value.map((op) => op.id) : []));\n const msgProviders = providersOf((s) => (s.asyncapi.kind === \"read\" ? s.asyncapi.value.map((m) => m.name) : []));\n const out = new Map<string, ConsumerTuple>();\n const add = (t: ConsumerTuple): void => {\n out.set(tupleKey(t), t);\n };\n for (const consumer of [...states.keys()].sort()) {\n const state = states.get(consumer)!;\n for (const r of reqsOf(state)) {\n for (const op of r.operations) {\n if (ownsOperation(state, op)) continue;\n for (const p of opProviders.get(op) ?? []) if (p !== consumer) add({ kind: \"op\", consumer, provider: p, token: op });\n }\n for (const name of r.consumes) {\n for (const p of msgProviders.get(name) ?? []) if (p !== consumer) add({ kind: \"message\", consumer, provider: p, token: name });\n }\n }\n }\n return out;\n}\n\n/** May a tuple be graded off this side? Absence answers; unreadable does not; an ambiguous base identity answers nothing. */\nfunction tupleVisible(states: ReadonlyMap<string, ServiceState>, ambiguous: ReadonlySet<string>, t: ConsumerTuple): boolean {\n if (ambiguous.has(t.consumer) || ambiguous.has(t.provider)) return false;\n const readable = (id: string, axes: (\"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\")[]): boolean => {\n const state = states.get(id);\n return state === undefined || axes.every((axis) => state[axis].kind !== \"unreadable\");\n };\n // Both spec files on the consumer's side, for either kind: a line in the file\n // nobody could read may be the very one the join came or went by. NOT the\n // consumer's own contract, although an operation join reads it: it is asked\n // only whether the line names the consumer's OWN operation, and an\n // unreadable contract proves no ownership (`ownsOperation`), so the line\n // counts on that side — the index's own rule. A side holding it unread can\n // only have MORE joins, never hide one, and suppressing the join there kept\n // `diff.consumer-added` / `-removed` off a report whose victims named that\n // very consumer (the contract review, finding 4). Those extra joins are a\n // change of their own only when the readable side does not prove the\n // ownership the unread one could not — `ownershipFlicker` below.\n return readable(t.consumer, [\"spec\", \"archSpec\"]) && readable(t.provider, [t.kind === \"op\" ? \"openapi\" : \"asyncapi\"]);\n}\n\n/**\n * Is this operation join held only because the consumer's own contract did not\n * parse on the side that holds it, while on the other side it reads and\n * defines the id? Then the line names the consumer's OWN operation — the\n * readable side says so — and nothing about the join moved but the contract's\n * readability: the line that never changed \"went away\" when the contract was\n * fixed and \"appeared\" when it broke, once per provider of a shared id such as\n * `healthCheck` (the verification of finding 4, N1). The unreadable contract\n * is already its own `unreadable[]` entry on the consumer, which carries the\n * run to exit 1; a second, false finding on every provider said nothing true.\n *\n * Only that shape is skipped. A readable side that defines no such id proves\n * no ownership either, so the join is the line's and is graded as before; a\n * message join asks no ownership at all (`ownsOperation` says why).\n */\nfunction ownershipFlicker(sides: { holding: ReadonlyMap<string, ServiceState>; other: ReadonlyMap<string, ServiceState> }, t: ConsumerTuple): boolean {\n if (t.kind !== \"op\" || sides.holding.get(t.consumer)?.openapi.kind !== \"unreadable\") return false;\n const other = sides.other.get(t.consumer);\n return other !== undefined && ownsOperation(other, t.token);\n}\n\n/** One graded cross-service join change between the two states. */\nexport interface ConsumerChange {\n t: ConsumerTuple;\n change: \"added\" | \"removed\";\n}\n\n/**\n * The joins that appeared or went away between base and current, sorted for\n * deterministic output (provider, kind, token, consumer). The visibility\n * guard is the containment rule: a join is never called \"added\" off a base\n * side nobody could read, nor \"removed\" off an unreadable working file — the\n * suspended axis already carries that fact. The flicker guard is its mirror on\n * the side that HOLDS the join: one held only because the consumer's own\n * contract did not read there, over an id the other side shows it owns, is no\n * change either.\n */\nexport function consumerJoinChanges(input: {\n base: ReadonlyMap<string, ServiceState>;\n baseAmbiguous: ReadonlySet<string>;\n current: ReadonlyMap<string, ServiceState>;\n currentAmbiguous: ReadonlySet<string>;\n}): ConsumerChange[] {\n const baseTuples = consumerTuples(input.base);\n const currentTuples = consumerTuples(input.current);\n const graded: ConsumerChange[] = [];\n const added = { holding: input.current, other: input.base };\n const removed = { holding: input.base, other: input.current };\n for (const [key, t] of currentTuples) {\n if (baseTuples.has(key) || ownershipFlicker(added, t)) continue;\n if (tupleVisible(input.base, input.baseAmbiguous, t)) graded.push({ t, change: \"added\" });\n }\n for (const [key, t] of baseTuples) {\n if (currentTuples.has(key) || ownershipFlicker(removed, t)) continue;\n if (tupleVisible(input.current, input.currentAmbiguous, t)) graded.push({ t, change: \"removed\" });\n }\n return graded.sort((a, b) => {\n const ka = `${a.t.provider}\\0${a.t.kind}\\0${a.t.token}\\0${a.t.consumer}\\0${a.change}`;\n const kb = `${b.t.provider}\\0${b.t.kind}\\0${b.t.token}\\0${b.t.consumer}\\0${b.change}`;\n return ka < kb ? -1 : 1;\n });\n}\n"]}
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* directory is at the five-file limit and because this is a different subject
|
|
16
16
|
* from the semantic diff beside it: `diff` compares two states of the fleet and
|
|
17
17
|
* reports what moved, this one only decides WHICH SUBJECTS a run should look
|
|
18
|
-
* at. Nothing here reads a document
|
|
18
|
+
* at. Nothing here reads a document; the one question that has to — which call
|
|
19
|
+
* edges on the fleet map are this branch's — is `./edges.ts`'s, and it reads
|
|
20
|
+
* only the map documents the branch changed.
|
|
19
21
|
*
|
|
20
22
|
* What it does NOT do, and the line is not negotiable: a diff-scoped validate
|
|
21
23
|
* cannot see a victim boundary the change did not touch. Removing an operation
|
|
@@ -25,10 +27,17 @@
|
|
|
25
27
|
* the two gates answer "did you document what you touched" and "did you break a
|
|
26
28
|
* consumer", and neither substitutes for the other. Selling a green here that
|
|
27
29
|
* hides a broken join would be worse than shipping no flag at all.
|
|
30
|
+
*
|
|
31
|
+
* A join the branch WROTE is a different case, and it is graded: an edge the
|
|
32
|
+
* branch added or edited on the fleet map is checked against its provider's
|
|
33
|
+
* contract even with the provider out of scope (`edges` below), because the
|
|
34
|
+
* edge is the document this branch touched and the provider is only where its
|
|
35
|
+
* answer lives.
|
|
28
36
|
*/
|
|
29
37
|
import { declineDetail, gitText } from "../../provenance/git.js";
|
|
30
38
|
import { repoPath } from "../../envelope/json.js";
|
|
31
39
|
import { resolveBase } from "../base-git.js";
|
|
40
|
+
import { edgesSince } from "./edges.js";
|
|
32
41
|
/**
|
|
33
42
|
* Every path that differs between `ref` and the WORKING TREE, docs-relative.
|
|
34
43
|
*
|
|
@@ -88,6 +97,10 @@ async function changedPaths(docsDir, commit, prefix) {
|
|
|
88
97
|
function inside(path, dir) {
|
|
89
98
|
return dir !== "" && (path === dir || path.startsWith(`${dir}/`));
|
|
90
99
|
}
|
|
100
|
+
/** Did this branch change `rel` — a docs-relative file, or anything under a docs-relative directory? */
|
|
101
|
+
export function touched(scope, rel) {
|
|
102
|
+
return scope.changed.some((path) => inside(path, rel));
|
|
103
|
+
}
|
|
91
104
|
/** The fleet's own documents — the landscape target's subject, which owns no service directory. */
|
|
92
105
|
const ARCHITECTURE_DIR = "architecture";
|
|
93
106
|
/**
|
|
@@ -134,15 +147,18 @@ export async function scopeSince(request) {
|
|
|
134
147
|
};
|
|
135
148
|
}
|
|
136
149
|
const dirOf = (abs) => repoPath(docsDir, abs);
|
|
150
|
+
// `architecture/` is the landscape target's whole directory: the fleet map,
|
|
151
|
+
// the permission/capability/obligation vocabularies and the fleet ADRs all
|
|
152
|
+
// live there, and every one of them is graded by the landscape target
|
|
153
|
+
// rather than by any service.
|
|
154
|
+
const architecture = changed.filter((path) => inside(path, ARCHITECTURE_DIR));
|
|
137
155
|
return {
|
|
138
156
|
kind: "scope",
|
|
139
157
|
ref,
|
|
140
158
|
commit: base.commit,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// rather than by any service.
|
|
145
|
-
landscape: changed.some((path) => inside(path, ARCHITECTURE_DIR)),
|
|
159
|
+
landscape: architecture.length > 0,
|
|
160
|
+
changed,
|
|
161
|
+
edges: await edgesSince({ docsDir, commit: base.commit, prefix: base.prefix }, architecture.filter((path) => path.endsWith(".likec4"))),
|
|
146
162
|
services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),
|
|
147
163
|
features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),
|
|
148
164
|
totals: { services: request.services.length, features: request.features.length },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changed-paths.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/changed-paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which targets a base git ref puts in scope — the narrowing behind\n * `loam validate --all --base <ref>`.\n *\n * The defect this exists for: a partly-adopted fleet has no CI setting that is\n * both passing today and tightening over time. `--all` is green over eleven\n * undocumented services (they warn, and warnings do not gate) and `--strict` is\n * red from the first minute of adoption until the last boundary is written, so\n * a team installs either a gate that never notices adoption stalling at 8% or\n * one it cannot turn on. `--base` is the third setting: grade what this branch\n * TOUCHED, at full `--strict` severity if the caller wants it, and let the\n * untouched majority stay untouched.\n *\n * A SUB-PACKAGE of `core/diff/` rather than a sixth file in it, because that\n * directory is at the five-file limit and because this is a different subject\n * from the semantic diff beside it: `diff` compares two states of the fleet and\n * reports what moved, this one only decides WHICH SUBJECTS a run should look\n * at. Nothing here reads a document.\n *\n * What it does NOT do, and the line is not negotiable: a diff-scoped validate\n * cannot see a victim boundary the change did not touch. Removing an operation\n * from `payment-service` puts `payment-service` in scope and leaves\n * `checkout-web` — whose living requirement still names the operation — out of\n * it. That join is `loam diff --base`'s question and it stays `loam diff`'s;\n * the two gates answer \"did you document what you touched\" and \"did you break a\n * consumer\", and neither substitutes for the other. Selling a green here that\n * hides a broken join would be worse than shipping no flag at all.\n */\nimport { declineDetail, gitText } from \"../../provenance/git.js\";\nimport { repoPath, type ErrorCode } from \"../../envelope/json.js\";\nimport { resolveBase } from \"../base-git.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { FeatureEntry, ServiceEntry } from \"../../repo/entries.js\";\n\n/** Everything the narrowing needs: where to ask, what to ask about, and what there is to narrow. */\nexport interface ScopeRequest {\n docsDir: DocsDir;\n /** The ref as the caller spelled it — echoed in every message and in the payload. */\n ref: string;\n /** The run's full service enumeration, at whatever depth the tree walk found each one. */\n services: readonly ServiceEntry[];\n /** The run's full ACTIVE feature enumeration. */\n features: readonly FeatureEntry[];\n}\n\n/** The narrowed run: which targets the branch touched, and what it narrowed from. */\nexport interface ValidateScope {\n kind: \"scope\";\n ref: string;\n /** The 40-hex commit `ref` resolved to — what a rerun would have to pass to reproduce this scope. */\n commit: string;\n /** Did `architecture/` change? The landscape target is the fleet's own subject and has no directory of its own. */\n landscape: boolean;\n services: ServiceEntry[];\n features: FeatureEntry[];\n /** The denominators — what the run would have graded without `--base`. */\n totals: { services: number; features: number };\n}\n\n/**\n * A refusal, already spelled. The code is one `loam diff` already emits for the\n * same two conditions and the message is in the same register, deliberately: a\n * second stable code for \"that ref does not resolve\" would be a second thing to\n * document, branch on and keep in step, for a condition that is identical.\n *\n * The prose is built here rather than in the command because it is the same\n * sentence for every caller and because `base-git.ts`'s `detail` — git's own\n * words, bounded — is what makes it diagnosable; a command that only received a\n * `kind` would have to reinvent the sentence to say why.\n */\nexport interface ScopeRefusal {\n kind: \"refused\";\n code: ErrorCode;\n message: string;\n}\n\nexport type ScopeOutcome = ValidateScope | ScopeRefusal;\n\n/**\n * Every path that differs between `ref` and the WORKING TREE, docs-relative.\n *\n * Two questions, not one, and the second is not optional: `git diff` compares\n * the tree git tracks, so a service adopted five minutes ago — a brand new\n * `services/<id>/spec.md` nobody has committed — is invisible to it. `validate`\n * grades the working tree, so the scope has to be the working tree's, and an\n * untracked new boundary that the run then skipped would be the flag's very\n * first user reporting that it graded nothing.\n *\n * `--no-renames` on purpose: with rename detection on (git's default since\n * 2.9) a service moved into a subsystem reports only its DESTINATION, and the\n * checks that grade the fleet from the old placement would be out of scope.\n * Two paths for a move is the honest answer.\n *\n * `-z` because git quotes non-ASCII and space-bearing paths in line output —\n * the same reason `gitTrackedFiles` asks that way, and the same phantom\n * directory named `\"src` if it did not.\n */\nasync function changedPaths(docsDir: DocsDir, commit: string, prefix: string): Promise<string[] | { detail: string }> {\n // Both run from docsDir. `git diff` ignores cwd and answers for the whole\n // repository with root-relative paths; `git ls-files` restricts itself to\n // cwd, and `--full-name` makes ITS paths root-relative too — so one prefix\n // strip below is correct for both, exactly as `listBaseTree` needs one.\n const tracked = await gitText(docsDir, [\"diff\", \"--name-only\", \"--no-renames\", \"-z\", commit, \"--\"]);\n if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {\n return { detail: tracked.overflowed ? \"the changed-path listing exceeded the output cap.\" : declineDetail(tracked) };\n }\n const untracked = await gitText(docsDir, [\"ls-files\", \"--others\", \"--exclude-standard\", \"--full-name\", \"-z\"]);\n if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {\n return { detail: untracked.overflowed ? \"the untracked-path listing exceeded the output cap.\" : declineDetail(untracked) };\n }\n const seen = new Set<string>();\n for (const raw of [...tracked.out.split(\"\\0\"), ...untracked.out.split(\"\\0\")]) {\n // A docs repo checked out as a subdirectory of a larger repository answers\n // in the whole repository's paths; anything outside the docs dir is\n // somebody else's change and is not a target here.\n if (raw === \"\" || !raw.startsWith(prefix)) continue;\n const rel = raw.slice(prefix.length);\n if (rel !== \"\") seen.add(rel);\n }\n return [...seen].sort();\n}\n\n/**\n * Is `path` inside `dir`? Both docs-relative, forward-slashed.\n *\n * A prefix test on the directory plus a separator, never a split on \"/\" and a\n * read of the second segment: a service filed under a subsystem lives at\n * `services/platform/payment-service/`, not at `services/<id>/`, and a\n * path-splitting implementation drops every filed service out of scope\n * SILENTLY — the run stays green because it graded nothing, which is the exact\n * failure this flag is not allowed to have. The separator is what stops\n * `services/pay` from claiming `services/payments/spec.md`.\n */\nfunction inside(path: string, dir: string): boolean {\n return dir !== \"\" && (path === dir || path.startsWith(`${dir}/`));\n}\n\n/** The fleet's own documents — the landscape target's subject, which owns no service directory. */\nconst ARCHITECTURE_DIR = \"architecture\";\n\n/**\n * Narrow a `--all` run to what changed since `ref`.\n *\n * Targets are matched through the ENUMERATIONS' own directories\n * (`ServiceEntry.dir`, `FeatureEntry.dir`), turned docs-relative by the same\n * `repoPath` the JSON contract uses. That is the whole reason the enumerations\n * are passed in rather than re-derived: they already know where each service\n * sits, at whatever depth, and asking them is what makes a filed fleet work.\n */\nexport async function scopeSince(request: ScopeRequest): Promise<ScopeOutcome> {\n const { docsDir, ref } = request;\n const base = await resolveBase(docsDir, ref);\n if (base.kind === \"no-git\") {\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +\n `--base narrows the run to what changed since a ref, which it reads with read-only git questions ` +\n `(rev-parse/diff/ls-files). Clone the docs repo with its history, then re-run — or drop --base to grade every target.`,\n };\n }\n if (base.kind === \"no-ref\") {\n return {\n kind: \"refused\",\n code: \"unknown-target\",\n message:\n `--base ${ref} does not resolve to a commit in the docs repo at ${docsDir}` +\n `${base.detail === \"\" ? \"\" : ` (${base.detail})`}. ` +\n `Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,\n };\n }\n\n const changed = await changedPaths(docsDir, base.commit, base.prefix);\n if (!Array.isArray(changed)) {\n // Git resolved the ref and then would not say what moved. There is no\n // partial answer to give: a scope built from half an answer grades a\n // subset nobody chose and reports it as the branch's whole footprint.\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `git could not list what changed since ${ref} in the docs repo at ${docsDir} (${changed.detail}) — ` +\n `without that list there is no honest scope, and a run over a guessed subset would report a green ` +\n `for targets nobody graded. Re-run once git can answer, or drop --base to grade every target.`,\n };\n }\n\n const dirOf = (abs: string): string => repoPath(docsDir, abs);\n return {\n kind: \"scope\",\n ref,\n commit: base.commit,\n // `architecture/` is the landscape target's whole directory: the fleet map,\n // the permission/capability/obligation vocabularies and the fleet ADRs all\n // live there, and every one of them is graded by the landscape target\n // rather than by any service.\n landscape: changed.some((path) => inside(path, ARCHITECTURE_DIR)),\n services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),\n features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),\n totals: { services: request.services.length, features: request.features.length },\n };\n}\n\n/**\n * The `scope` object the `--json` payload carries — the run saying out loud\n * what it looked at.\n *\n * Additive, and emitted only under `--base`, so no consumer of today's payload\n * moves. It exists because the alternative is a payload that is byte-shaped\n * like a whole-fleet run and is not one: `summary.services` would report two\n * where the fleet has twelve, `valid: true` would read as a claim about the\n * fleet, and nothing in the document would say otherwise.\n */\nexport function scopeJson(scope: ValidateScope): Record<string, unknown> {\n return {\n base: scope.ref,\n commit: scope.commit,\n landscape: scope.landscape,\n services: scope.services.map((svc) => svc.id),\n features: scope.features.map((feat) => feat.id),\n totals: scope.totals,\n };\n}\n\n/**\n * The one line both the text view and a reader of a CI log need: how much of\n * the fleet this run actually graded, and against what.\n *\n * The \"nothing was graded\" clause is the load-bearing half. A `--base` run with\n * no targets in scope has nothing to report and exits 0 — correctly, the branch\n * touched no governed boundary — and a 0 that reads like a green over the\n * system is the exact failure the docs-repo readiness gate exists to prevent.\n * So the sentence says it, rather than letting an empty report say nothing.\n */\nexport function scopeLine(scope: ValidateScope): string {\n const graded = scope.services.length + scope.features.length + (scope.landscape ? 1 : 0);\n return (\n `${scope.services.length} of ${scope.totals.services} services, ` +\n `${scope.features.length} of ${scope.totals.features} features in scope since ${scope.ref}` +\n (scope.landscape ? \", plus the landscape\" : \"\") +\n (graded === 0 ? \" — nothing was graded\" : \"\")\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"changed-paths.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/changed-paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which targets a base git ref puts in scope — the narrowing behind\n * `loam validate --all --base <ref>`.\n *\n * The defect this exists for: a partly-adopted fleet has no CI setting that is\n * both passing today and tightening over time. `--all` is green over eleven\n * undocumented services (they warn, and warnings do not gate) and `--strict` is\n * red from the first minute of adoption until the last boundary is written, so\n * a team installs either a gate that never notices adoption stalling at 8% or\n * one it cannot turn on. `--base` is the third setting: grade what this branch\n * TOUCHED, at full `--strict` severity if the caller wants it, and let the\n * untouched majority stay untouched.\n *\n * A SUB-PACKAGE of `core/diff/` rather than a sixth file in it, because that\n * directory is at the five-file limit and because this is a different subject\n * from the semantic diff beside it: `diff` compares two states of the fleet and\n * reports what moved, this one only decides WHICH SUBJECTS a run should look\n * at. Nothing here reads a document; the one question that has to — which call\n * edges on the fleet map are this branch's — is `./edges.ts`'s, and it reads\n * only the map documents the branch changed.\n *\n * What it does NOT do, and the line is not negotiable: a diff-scoped validate\n * cannot see a victim boundary the change did not touch. Removing an operation\n * from `payment-service` puts `payment-service` in scope and leaves\n * `checkout-web` — whose living requirement still names the operation — out of\n * it. That join is `loam diff --base`'s question and it stays `loam diff`'s;\n * the two gates answer \"did you document what you touched\" and \"did you break a\n * consumer\", and neither substitutes for the other. Selling a green here that\n * hides a broken join would be worse than shipping no flag at all.\n *\n * A join the branch WROTE is a different case, and it is graded: an edge the\n * branch added or edited on the fleet map is checked against its provider's\n * contract even with the provider out of scope (`edges` below), because the\n * edge is the document this branch touched and the provider is only where its\n * answer lives.\n */\nimport { declineDetail, gitText } from \"../../provenance/git.js\";\nimport { repoPath, type ErrorCode } from \"../../envelope/json.js\";\nimport { resolveBase } from \"../base-git.js\";\nimport { edgesSince } from \"./edges.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { FeatureEntry, ServiceEntry } from \"../../repo/entries.js\";\n\n/** Everything the narrowing needs: where to ask, what to ask about, and what there is to narrow. */\nexport interface ScopeRequest {\n docsDir: DocsDir;\n /** The ref as the caller spelled it — echoed in every message and in the payload. */\n ref: string;\n /** The run's full service enumeration, at whatever depth the tree walk found each one. */\n services: readonly ServiceEntry[];\n /** The run's full ACTIVE feature enumeration. */\n features: readonly FeatureEntry[];\n}\n\n/** The narrowed run: which targets the branch touched, and what it narrowed from. */\nexport interface ValidateScope {\n kind: \"scope\";\n ref: string;\n /** The 40-hex commit `ref` resolved to — what a rerun would have to pass to reproduce this scope. */\n commit: string;\n /** Did `architecture/` change? The landscape target is the fleet's own subject and has no directory of its own. */\n landscape: boolean;\n /**\n * Every docs-relative path that differs from the base, untracked files\n * included. Kept because the landscape target grades more files than the\n * map — the fleet vocabularies, AGENTS.md — and `touched` is how the run asks\n * which of them this branch changed.\n */\n changed: readonly string[];\n /**\n * The fleet-map call edges this branch introduced or changed, as\n * `./edges.ts`'s `edgeKey`s. Empty unless `architecture/` changed.\n */\n edges: ReadonlySet<string>;\n services: ServiceEntry[];\n features: FeatureEntry[];\n /** The denominators — what the run would have graded without `--base`. */\n totals: { services: number; features: number };\n}\n\n/**\n * A refusal, already spelled. The code is one `loam diff` already emits for the\n * same two conditions and the message is in the same register, deliberately: a\n * second stable code for \"that ref does not resolve\" would be a second thing to\n * document, branch on and keep in step, for a condition that is identical.\n *\n * The prose is built here rather than in the command because it is the same\n * sentence for every caller and because `base-git.ts`'s `detail` — git's own\n * words, bounded — is what makes it diagnosable; a command that only received a\n * `kind` would have to reinvent the sentence to say why.\n */\nexport interface ScopeRefusal {\n kind: \"refused\";\n code: ErrorCode;\n message: string;\n}\n\nexport type ScopeOutcome = ValidateScope | ScopeRefusal;\n\n/**\n * Every path that differs between `ref` and the WORKING TREE, docs-relative.\n *\n * Two questions, not one, and the second is not optional: `git diff` compares\n * the tree git tracks, so a service adopted five minutes ago — a brand new\n * `services/<id>/spec.md` nobody has committed — is invisible to it. `validate`\n * grades the working tree, so the scope has to be the working tree's, and an\n * untracked new boundary that the run then skipped would be the flag's very\n * first user reporting that it graded nothing.\n *\n * `--no-renames` on purpose: with rename detection on (git's default since\n * 2.9) a service moved into a subsystem reports only its DESTINATION, and the\n * checks that grade the fleet from the old placement would be out of scope.\n * Two paths for a move is the honest answer.\n *\n * `-z` because git quotes non-ASCII and space-bearing paths in line output —\n * the same reason `gitTrackedFiles` asks that way, and the same phantom\n * directory named `\"src` if it did not.\n */\nasync function changedPaths(docsDir: DocsDir, commit: string, prefix: string): Promise<string[] | { detail: string }> {\n // Both run from docsDir. `git diff` ignores cwd and answers for the whole\n // repository with root-relative paths; `git ls-files` restricts itself to\n // cwd, and `--full-name` makes ITS paths root-relative too — so one prefix\n // strip below is correct for both, exactly as `listBaseTree` needs one.\n const tracked = await gitText(docsDir, [\"diff\", \"--name-only\", \"--no-renames\", \"-z\", commit, \"--\"]);\n if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {\n return { detail: tracked.overflowed ? \"the changed-path listing exceeded the output cap.\" : declineDetail(tracked) };\n }\n const untracked = await gitText(docsDir, [\"ls-files\", \"--others\", \"--exclude-standard\", \"--full-name\", \"-z\"]);\n if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {\n return { detail: untracked.overflowed ? \"the untracked-path listing exceeded the output cap.\" : declineDetail(untracked) };\n }\n const seen = new Set<string>();\n for (const raw of [...tracked.out.split(\"\\0\"), ...untracked.out.split(\"\\0\")]) {\n // A docs repo checked out as a subdirectory of a larger repository answers\n // in the whole repository's paths; anything outside the docs dir is\n // somebody else's change and is not a target here.\n if (raw === \"\" || !raw.startsWith(prefix)) continue;\n const rel = raw.slice(prefix.length);\n if (rel !== \"\") seen.add(rel);\n }\n return [...seen].sort();\n}\n\n/**\n * Is `path` inside `dir`? Both docs-relative, forward-slashed.\n *\n * A prefix test on the directory plus a separator, never a split on \"/\" and a\n * read of the second segment: a service filed under a subsystem lives at\n * `services/platform/payment-service/`, not at `services/<id>/`, and a\n * path-splitting implementation drops every filed service out of scope\n * SILENTLY — the run stays green because it graded nothing, which is the exact\n * failure this flag is not allowed to have. The separator is what stops\n * `services/pay` from claiming `services/payments/spec.md`.\n */\nfunction inside(path: string, dir: string): boolean {\n return dir !== \"\" && (path === dir || path.startsWith(`${dir}/`));\n}\n\n/** Did this branch change `rel` — a docs-relative file, or anything under a docs-relative directory? */\nexport function touched(scope: ValidateScope, rel: string): boolean {\n return scope.changed.some((path) => inside(path, rel));\n}\n\n/** The fleet's own documents — the landscape target's subject, which owns no service directory. */\nconst ARCHITECTURE_DIR = \"architecture\";\n\n/**\n * Narrow a `--all` run to what changed since `ref`.\n *\n * Targets are matched through the ENUMERATIONS' own directories\n * (`ServiceEntry.dir`, `FeatureEntry.dir`), turned docs-relative by the same\n * `repoPath` the JSON contract uses. That is the whole reason the enumerations\n * are passed in rather than re-derived: they already know where each service\n * sits, at whatever depth, and asking them is what makes a filed fleet work.\n */\nexport async function scopeSince(request: ScopeRequest): Promise<ScopeOutcome> {\n const { docsDir, ref } = request;\n const base = await resolveBase(docsDir, ref);\n if (base.kind === \"no-git\") {\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +\n `--base narrows the run to what changed since a ref, which it reads with read-only git questions ` +\n `(rev-parse/diff/ls-files). Clone the docs repo with its history, then re-run — or drop --base to grade every target.`,\n };\n }\n if (base.kind === \"no-ref\") {\n return {\n kind: \"refused\",\n code: \"unknown-target\",\n message:\n `--base ${ref} does not resolve to a commit in the docs repo at ${docsDir}` +\n `${base.detail === \"\" ? \"\" : ` (${base.detail})`}. ` +\n `Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,\n };\n }\n\n const changed = await changedPaths(docsDir, base.commit, base.prefix);\n if (!Array.isArray(changed)) {\n // Git resolved the ref and then would not say what moved. There is no\n // partial answer to give: a scope built from half an answer grades a\n // subset nobody chose and reports it as the branch's whole footprint.\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `git could not list what changed since ${ref} in the docs repo at ${docsDir} (${changed.detail}) — ` +\n `without that list there is no honest scope, and a run over a guessed subset would report a green ` +\n `for targets nobody graded. Re-run once git can answer, or drop --base to grade every target.`,\n };\n }\n\n const dirOf = (abs: string): string => repoPath(docsDir, abs);\n // `architecture/` is the landscape target's whole directory: the fleet map,\n // the permission/capability/obligation vocabularies and the fleet ADRs all\n // live there, and every one of them is graded by the landscape target\n // rather than by any service.\n const architecture = changed.filter((path) => inside(path, ARCHITECTURE_DIR));\n return {\n kind: \"scope\",\n ref,\n commit: base.commit,\n landscape: architecture.length > 0,\n changed,\n edges: await edgesSince(\n { docsDir, commit: base.commit, prefix: base.prefix },\n architecture.filter((path) => path.endsWith(\".likec4\")),\n ),\n services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),\n features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),\n totals: { services: request.services.length, features: request.features.length },\n };\n}\n\n/**\n * The `scope` object the `--json` payload carries — the run saying out loud\n * what it looked at.\n *\n * Additive, and emitted only under `--base`, so no consumer of today's payload\n * moves. It exists because the alternative is a payload that is byte-shaped\n * like a whole-fleet run and is not one: `summary.services` would report two\n * where the fleet has twelve, `valid: true` would read as a claim about the\n * fleet, and nothing in the document would say otherwise.\n */\nexport function scopeJson(scope: ValidateScope): Record<string, unknown> {\n return {\n base: scope.ref,\n commit: scope.commit,\n landscape: scope.landscape,\n services: scope.services.map((svc) => svc.id),\n features: scope.features.map((feat) => feat.id),\n totals: scope.totals,\n };\n}\n\n/**\n * The one line both the text view and a reader of a CI log need: how much of\n * the fleet this run actually graded, and against what.\n *\n * The \"nothing was graded\" clause is the load-bearing half. A `--base` run with\n * no targets in scope has nothing to report and exits 0 — correctly, the branch\n * touched no governed boundary — and a 0 that reads like a green over the\n * system is the exact failure the docs-repo readiness gate exists to prevent.\n * So the sentence says it, rather than letting an empty report say nothing.\n */\nexport function scopeLine(scope: ValidateScope): string {\n const graded = scope.services.length + scope.features.length + (scope.landscape ? 1 : 0);\n return (\n `${scope.services.length} of ${scope.totals.services} services, ` +\n `${scope.features.length} of ${scope.totals.features} features in scope since ${scope.ref}` +\n (scope.landscape ? \", plus the landscape\" : \"\") +\n (graded === 0 ? \" — nothing was graded\" : \"\")\n );\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which fleet-map call edges a branch introduced — the one half of the `--base`
|
|
3
|
+
* narrowing that reads a document.
|
|
4
|
+
*
|
|
5
|
+
* The defect this exists for: `spine.op-undefined` is graded on the PROVIDER's
|
|
6
|
+
* service target, and an `architecture/` change only ever put the landscape in
|
|
7
|
+
* scope. So the hand edit the spine-first path is built on — naming the
|
|
8
|
+
* operation on a call edge — was graded by no gate a PR runs: README's own
|
|
9
|
+
* one-letter example (`authorisePayment` against a contract spelling it
|
|
10
|
+
* `authorizePayment`) merged green through `loam diff --base` and through
|
|
11
|
+
* `validate --all --base --strict`, on the adopted example fleet and on a
|
|
12
|
+
* freshly seeded one alike. This module answers the question that grade needs
|
|
13
|
+
* — which edges are this branch's — and `commands/validate/scope.ts` grades
|
|
14
|
+
* exactly those against their providers' contracts.
|
|
15
|
+
*
|
|
16
|
+
* AN EDGE IS ITS (source, target, op) TUPLE. A tuple present now and absent at
|
|
17
|
+
* the base is new or changed — a new edge, a re-pointed one, a renamed
|
|
18
|
+
* endpoint, an op named or re-spelled — and every one of those is a join the
|
|
19
|
+
* branch wrote. A tuple present at both ends is not, wherever in the file it
|
|
20
|
+
* moved and however it was re-indented: the key is what the edge MEANS, so a
|
|
21
|
+
* reformat grades nothing.
|
|
22
|
+
*
|
|
23
|
+
* TEXT, NOT A PARSE, at both ends. The base side would otherwise need a second
|
|
24
|
+
* LikeC4 workspace over bytes read out of git — the most expensive thing loam
|
|
25
|
+
* does, paid on every map PR to answer a question about a handful of lines.
|
|
26
|
+
* `scanModel` is the archive's splice map and resolves endpoints the way LikeC4
|
|
27
|
+
* does; `test/likec4-scan-parity.test.ts` pins the two readings together, and
|
|
28
|
+
* the key below is built from the same three fields the parsed `Rel` carries, so
|
|
29
|
+
* a scanned key names the edge the parsed map holds.
|
|
30
|
+
*
|
|
31
|
+
* ONLY THE DOCUMENTS THAT CHANGED are read, at both ends: an edge in an
|
|
32
|
+
* untouched document is by construction not this branch's, and an edge moved
|
|
33
|
+
* between two documents changes both, so its base key is still read.
|
|
34
|
+
*
|
|
35
|
+
* Which way a failure falls, and why: a base document git will not show — new
|
|
36
|
+
* on this branch, or a git that declines — contributes no base keys, so every
|
|
37
|
+
* edge in it reads as new and is graded. Grading an edge the branch did not
|
|
38
|
+
* write costs a reader one finding; skipping one it did write is the green this
|
|
39
|
+
* module exists to remove.
|
|
40
|
+
*/
|
|
41
|
+
import { existsSync } from "node:fs";
|
|
42
|
+
import { readFile } from "node:fs/promises";
|
|
43
|
+
import { join } from "node:path";
|
|
44
|
+
import { scanModel } from "../../c4/source-scan.js";
|
|
45
|
+
import { inOrder } from "../../kernel/concurrency.js";
|
|
46
|
+
import { showBaseFile } from "../base-git.js";
|
|
47
|
+
/**
|
|
48
|
+
* One edge's identity as a string a `Set` can hold. JSON rather than a joined
|
|
49
|
+
* string because an element id may legally carry any separator a join would
|
|
50
|
+
* pick, and two different tuples must never spell one key.
|
|
51
|
+
*/
|
|
52
|
+
export function edgeKey(edge) {
|
|
53
|
+
return JSON.stringify([edge.source, edge.target, edge.op ?? null]);
|
|
54
|
+
}
|
|
55
|
+
function keysOf(text) {
|
|
56
|
+
return text === null ? [] : (scanModel(text)?.rels ?? []).map(edgeKey);
|
|
57
|
+
}
|
|
58
|
+
/** The document as the base commit held it, or null when git will not show it (see the banner for why that is safe). */
|
|
59
|
+
async function baseText(base, rel) {
|
|
60
|
+
const read = await showBaseFile(base, rel);
|
|
61
|
+
return read.kind === "ok" ? read.bytes.toString("utf8") : null;
|
|
62
|
+
}
|
|
63
|
+
/** The document as the working tree holds it, or null when the branch deleted it. */
|
|
64
|
+
async function currentText(base, rel) {
|
|
65
|
+
const abs = join(base.docsDir, rel);
|
|
66
|
+
return existsSync(abs) ? readFile(abs, "utf8") : null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The `edgeKey`s of every edge `documents` hold now and did not hold at the
|
|
70
|
+
* base. `documents` are docs-relative `.likec4` paths the branch changed —
|
|
71
|
+
* the caller's filter, so this module never has to know which directory the
|
|
72
|
+
* fleet map lives in.
|
|
73
|
+
*/
|
|
74
|
+
export async function edgesSince(base, documents) {
|
|
75
|
+
const sides = await inOrder(documents, async (rel) => {
|
|
76
|
+
const [before, after] = await Promise.all([baseText(base, rel), currentText(base, rel)]);
|
|
77
|
+
return { before: keysOf(before), after: keysOf(after) };
|
|
78
|
+
});
|
|
79
|
+
const before = new Set(sides.flatMap((side) => side.before));
|
|
80
|
+
return new Set(sides.flatMap((side) => side.after).filter((key) => !before.has(key)));
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=edges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edges.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/edges.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AASjE;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,IAAe;IACrC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,MAAM,CAAC,IAAmB;IACjC,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACzE,CAAC;AAED,wHAAwH;AACxH,KAAK,UAAU,QAAQ,CAAC,IAAkB,EAAE,GAAW;IACrD,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,WAAW,CAAC,IAAkB,EAAE,GAAW;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAkB,EAAE,SAA4B;IAC/E,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnD,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACzF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACxF,CAAC","sourcesContent":["/**\n * Which fleet-map call edges a branch introduced — the one half of the `--base`\n * narrowing that reads a document.\n *\n * The defect this exists for: `spine.op-undefined` is graded on the PROVIDER's\n * service target, and an `architecture/` change only ever put the landscape in\n * scope. So the hand edit the spine-first path is built on — naming the\n * operation on a call edge — was graded by no gate a PR runs: README's own\n * one-letter example (`authorisePayment` against a contract spelling it\n * `authorizePayment`) merged green through `loam diff --base` and through\n * `validate --all --base --strict`, on the adopted example fleet and on a\n * freshly seeded one alike. This module answers the question that grade needs\n * — which edges are this branch's — and `commands/validate/scope.ts` grades\n * exactly those against their providers' contracts.\n *\n * AN EDGE IS ITS (source, target, op) TUPLE. A tuple present now and absent at\n * the base is new or changed — a new edge, a re-pointed one, a renamed\n * endpoint, an op named or re-spelled — and every one of those is a join the\n * branch wrote. A tuple present at both ends is not, wherever in the file it\n * moved and however it was re-indented: the key is what the edge MEANS, so a\n * reformat grades nothing.\n *\n * TEXT, NOT A PARSE, at both ends. The base side would otherwise need a second\n * LikeC4 workspace over bytes read out of git — the most expensive thing loam\n * does, paid on every map PR to answer a question about a handful of lines.\n * `scanModel` is the archive's splice map and resolves endpoints the way LikeC4\n * does; `test/likec4-scan-parity.test.ts` pins the two readings together, and\n * the key below is built from the same three fields the parsed `Rel` carries, so\n * a scanned key names the edge the parsed map holds.\n *\n * ONLY THE DOCUMENTS THAT CHANGED are read, at both ends: an edge in an\n * untouched document is by construction not this branch's, and an edge moved\n * between two documents changes both, so its base key is still read.\n *\n * Which way a failure falls, and why: a base document git will not show — new\n * on this branch, or a git that declines — contributes no base keys, so every\n * edge in it reads as new and is graded. Grading an edge the branch did not\n * write costs a reader one finding; skipping one it did write is the green this\n * module exists to remove.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { scanModel } from \"../../c4/source-scan.js\";\nimport { inOrder } from \"../../kernel/concurrency.js\";\nimport { showBaseFile, type ResolvedBase } from \"../base-git.js\";\n\n/** The fields of an edge that make it the same join — shared by a parsed `Rel` and a scanned one. */\ninterface EdgeTuple {\n source: string;\n target: string;\n op?: string;\n}\n\n/**\n * One edge's identity as a string a `Set` can hold. JSON rather than a joined\n * string because an element id may legally carry any separator a join would\n * pick, and two different tuples must never spell one key.\n */\nexport function edgeKey(edge: EdgeTuple): string {\n return JSON.stringify([edge.source, edge.target, edge.op ?? null]);\n}\n\nfunction keysOf(text: string | null): string[] {\n return text === null ? [] : (scanModel(text)?.rels ?? []).map(edgeKey);\n}\n\n/** The document as the base commit held it, or null when git will not show it (see the banner for why that is safe). */\nasync function baseText(base: ResolvedBase, rel: string): Promise<string | null> {\n const read = await showBaseFile(base, rel);\n return read.kind === \"ok\" ? read.bytes.toString(\"utf8\") : null;\n}\n\n/** The document as the working tree holds it, or null when the branch deleted it. */\nasync function currentText(base: ResolvedBase, rel: string): Promise<string | null> {\n const abs = join(base.docsDir, rel);\n return existsSync(abs) ? readFile(abs, \"utf8\") : null;\n}\n\n/**\n * The `edgeKey`s of every edge `documents` hold now and did not hold at the\n * base. `documents` are docs-relative `.likec4` paths the branch changed —\n * the caller's filter, so this module never has to know which directory the\n * fleet map lives in.\n */\nexport async function edgesSince(base: ResolvedBase, documents: readonly string[]): Promise<ReadonlySet<string>> {\n const sides = await inOrder(documents, async (rel) => {\n const [before, after] = await Promise.all([baseText(base, rel), currentText(base, rel)]);\n return { before: keysOf(before), after: keysOf(after) };\n });\n const before = new Set(sides.flatMap((side) => side.before));\n return new Set(sides.flatMap((side) => side.after).filter((key) => !before.has(key)));\n}\n"]}
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The diff computation: join the base fleet (`./base-state.ts`) against the
|
|
3
|
-
* working tree (
|
|
3
|
+
* working tree (`core/consumers/current.ts`) and say, per service, what changed in
|
|
4
4
|
* FLEET-MEANINGFUL terms — requirements by identity and digest, operations
|
|
5
|
-
* and messages by their join tokens
|
|
6
|
-
*
|
|
5
|
+
* and messages by their join tokens and their `$ref`-closure digests,
|
|
6
|
+
* cross-service joins by tuple — with the authored consumers of every
|
|
7
|
+
* removal, deprecation and modification named (`core/consumers/consumers.ts`),
|
|
8
|
+
* and the cross-service joins that appeared or went away (`./joins.ts`).
|
|
7
9
|
*
|
|
8
10
|
* Two disciplines rule every branch here. Removal is graded against who still
|
|
9
11
|
* depends on it: a removal nobody names is a warning, a removal the fleet
|
|
10
|
-
* still joins to is an error and sets `breaking`.
|
|
12
|
+
* still joins to is an error and sets `breaking`. A MODIFICATION is never an
|
|
13
|
+
* error, whoever is joined to it: whether a changed schema breaks its readers
|
|
14
|
+
* is a field-level question loam hands to oasdiff on purpose, so the finding
|
|
15
|
+
* names the consumers and the tool, and stops there. And "nobody could look" is
|
|
11
16
|
* never "nothing changed": an unreadable artifact on EITHER side suspends
|
|
12
17
|
* that subject's axis — recorded in `unreadable[]`, counted in the summary,
|
|
13
18
|
* carried into the exit code by the command — instead of letting an
|
|
14
19
|
* unreadable base read as everything-added or an unreadable working file
|
|
15
|
-
* read as everything-removed.
|
|
20
|
+
* read as everything-removed. The one thing that is not a suspension is a
|
|
21
|
+
* contract the base held byte-for-byte: somebody COULD look, at the bytes,
|
|
22
|
+
* and they did not change (`comparable`, `UNTOUCHED_SKIPS`).
|
|
16
23
|
*/
|
|
17
24
|
import { requirementDigest } from "../document/spec.js";
|
|
18
|
-
import {
|
|
25
|
+
import { rawServiceId } from "../kernel/ids/service.js";
|
|
26
|
+
import { consumerStance, scanDetails } from "../consumers/scan.js";
|
|
27
|
+
import { consumerJoinChanges } from "./joins.js";
|
|
19
28
|
/** Severity is a fact about the code, not a call-site choice — one table, no drift. */
|
|
20
29
|
const DIFF_SEVERITY = {
|
|
21
30
|
"diff.service-added": "ok",
|
|
@@ -27,9 +36,11 @@ const DIFF_SEVERITY = {
|
|
|
27
36
|
"diff.op-removed": "warn",
|
|
28
37
|
"diff.op-removed-consumed": "error",
|
|
29
38
|
"diff.op-deprecated": "warn",
|
|
39
|
+
"diff.op-modified": "warn",
|
|
30
40
|
"diff.message-added": "ok",
|
|
31
41
|
"diff.message-removed": "warn",
|
|
32
42
|
"diff.message-removed-consumed": "error",
|
|
43
|
+
"diff.message-modified": "warn",
|
|
33
44
|
"diff.consumer-added": "ok",
|
|
34
45
|
"diff.consumer-removed": "ok",
|
|
35
46
|
};
|
|
@@ -37,15 +48,43 @@ function found(code, message, details) {
|
|
|
37
48
|
const carry = details !== undefined && details.length > 0;
|
|
38
49
|
return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };
|
|
39
50
|
}
|
|
40
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* A side the service does not exist on: absence everywhere, honestly. The id is
|
|
53
|
+
* a key of one of the two sides' maps, each keyed by a directory's leaf name.
|
|
54
|
+
*/
|
|
41
55
|
const ABSENT = { kind: "absent" };
|
|
42
|
-
const emptyState = (id) => ({ id, dir: "", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });
|
|
56
|
+
const emptyState = (id) => ({ id: rawServiceId(id), dir: "", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });
|
|
57
|
+
/**
|
|
58
|
+
* The axes on which an unreadable document the branch left byte-identical is
|
|
59
|
+
* SKIPPED rather than suspended: the two contracts. Identical bytes are a
|
|
60
|
+
* proof, not a guess — the same parser over the same bytes cannot read
|
|
61
|
+
* differently on the two sides, so there is nothing of this document's own
|
|
62
|
+
* to diff, and suspending it anyway made every `loam diff` exit 1 over a
|
|
63
|
+
* fleet that holds one AsyncAPI 2.x file (unreadable by policy since
|
|
64
|
+
* core/asyncapi/read.ts refused pre-3.0), whatever the branch changed.
|
|
65
|
+
*
|
|
66
|
+
* Not the spec axes, and that is the victims scan's doing, not an oversight:
|
|
67
|
+
* an unreadable spec.md or arch.spec.md is also the document ANOTHER
|
|
68
|
+
* service's removal could not be checked against (`core/consumers/consumers.ts`'s
|
|
69
|
+
* `scanRequirements`), and its `unreadable[]` entry here is what carries that
|
|
70
|
+
* suspension to exit 1 (`opsDiff`'s removal branch says so). No scan reads a
|
|
71
|
+
* contract it could not parse as evidence of anything — `producers` takes
|
|
72
|
+
* positive evidence only — so skipping one hides no other answer.
|
|
73
|
+
*/
|
|
74
|
+
const UNTOUCHED_SKIPS = new Set(["openapi", "asyncapi"]);
|
|
43
75
|
/**
|
|
44
76
|
* Guard one axis pair: record unreadable sides on the sink and say whether
|
|
45
77
|
* the axis may be compared at all. Reading one good side against a suspended
|
|
46
|
-
* one would grade absence it never proved.
|
|
78
|
+
* one would grade absence it never proved. A contract unreadable on BOTH
|
|
79
|
+
* sides with one byte hash is the branch's untouched file: not compared and
|
|
80
|
+
* not recorded either (`UNTOUCHED_SKIPS`). A changed one, or one only one
|
|
81
|
+
* side cannot read, still suspends.
|
|
47
82
|
*/
|
|
48
83
|
function comparable(sink, axis, pair) {
|
|
84
|
+
const { base, current } = pair;
|
|
85
|
+
if (UNTOUCHED_SKIPS.has(axis) && base.kind === "unreadable" && current.kind === "unreadable" &&
|
|
86
|
+
base.bytes !== undefined && base.bytes === current.bytes)
|
|
87
|
+
return false;
|
|
49
88
|
for (const side of ["base", "current"]) {
|
|
50
89
|
const state = pair[side];
|
|
51
90
|
if (state.kind === "unreadable")
|
|
@@ -92,6 +131,13 @@ function requirementsDiff(sink, pair) {
|
|
|
92
131
|
sink.findings.push(found("diff.requirement-removed", `living requirement removed since base: '${reqLabel(r)}'`));
|
|
93
132
|
}
|
|
94
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The second half of every modification message. loam joins consumers to the
|
|
136
|
+
* thing that moved; it does not read what a schema SAYS (core/brief/unchecked.ts
|
|
137
|
+
* lists that as unchecked), so a breaking rename and a new optional field are
|
|
138
|
+
* the same finding here — the specialist diff tells them apart.
|
|
139
|
+
*/
|
|
140
|
+
const notGraded = (tool) => `loam does not grade whether the change is breaking — run ${tool} on the two states of this contract`;
|
|
95
141
|
async function opsDiff(sink, pair, victims) {
|
|
96
142
|
if (!comparable(sink, "openapi", { base: pair.base.openapi, current: pair.current.openapi }))
|
|
97
143
|
return;
|
|
@@ -104,11 +150,17 @@ async function opsDiff(sink, pair, victims) {
|
|
|
104
150
|
}
|
|
105
151
|
else if (!before.deprecated && op.deprecated) {
|
|
106
152
|
const scan = await victims.opConsumers(sink.id, id);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
153
|
+
sink.findings.push(found("diff.op-deprecated", `operation '${id}' is deprecated since base — ${consumerStance(scan)}`, scanDetails(scan)));
|
|
154
|
+
}
|
|
155
|
+
else if (before.closureDigest !== op.closureDigest || before.path !== op.path || before.method !== op.method) {
|
|
156
|
+
// `else`, because setting the flag moves the digest by itself (it is a
|
|
157
|
+
// key of the node): a deprecation already names the same consumers, and
|
|
158
|
+
// a second finding over them would say nothing new. The digest covers
|
|
159
|
+
// the `$ref` closure — a field renamed in a shared component is exactly
|
|
160
|
+
// the change this code exists for, and the operation's own bytes never
|
|
161
|
+
// move for it. A relocated slot is a change too, though the node is not.
|
|
162
|
+
const scan = await victims.opConsumers(sink.id, id);
|
|
163
|
+
sink.findings.push(found("diff.op-modified", `operation '${id}' (${op.method.toUpperCase()} ${op.path}) changed since base, in its own definition or a schema it references — ${consumerStance(scan)}; ${notGraded("`oasdiff breaking`")}`, scanDetails(scan)));
|
|
112
164
|
}
|
|
113
165
|
}
|
|
114
166
|
for (const [id] of [...base].sort(byKey)) {
|
|
@@ -116,16 +168,18 @@ async function opsDiff(sink, pair, victims) {
|
|
|
116
168
|
continue;
|
|
117
169
|
const scan = await victims.opConsumers(sink.id, id);
|
|
118
170
|
if (scan.victims.length > 0) {
|
|
119
|
-
sink.findings.push(found("diff.op-removed-consumed", `operation '${id}' was removed since base and the current fleet still names it — these joins break`,
|
|
171
|
+
sink.findings.push(found("diff.op-removed-consumed", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, scanDetails(scan)));
|
|
120
172
|
}
|
|
121
173
|
else if (scan.suspended.length > 0) {
|
|
122
|
-
// "Nobody could be
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
174
|
+
// "Nobody could be checked" is not "nobody names it", so this message
|
|
175
|
+
// must not assert a negative the scan never proved. An unreadable
|
|
176
|
+
// consumer document carries the run to exit 1 through its own
|
|
177
|
+
// unreadable[] entry; a caller drawn with no op does not — it stays this
|
|
178
|
+
// warning, so a seeded, op-less map does not fail every removal.
|
|
179
|
+
sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} possible consumer(s) could not be checked`, scanDetails(scan)));
|
|
126
180
|
}
|
|
127
181
|
else {
|
|
128
|
-
sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; no current
|
|
182
|
+
sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; no current edge, requirement or use-case hop names it`));
|
|
129
183
|
}
|
|
130
184
|
}
|
|
131
185
|
}
|
|
@@ -134,33 +188,46 @@ async function messagesDiff(sink, pair, victims) {
|
|
|
134
188
|
return;
|
|
135
189
|
const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);
|
|
136
190
|
const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);
|
|
137
|
-
for (const [name] of [...current].sort(byKey)) {
|
|
138
|
-
|
|
191
|
+
for (const [name, message] of [...current].sort(byKey)) {
|
|
192
|
+
const before = base.get(name);
|
|
193
|
+
if (before === undefined) {
|
|
139
194
|
sink.findings.push(found("diff.message-added", `message '${name}' added since base`));
|
|
195
|
+
}
|
|
196
|
+
else if (before.closureDigest !== message.closureDigest) {
|
|
197
|
+
const scan = await victims.messageConsumers(sink.id, name);
|
|
198
|
+
sink.findings.push(found("diff.message-modified", `message '${name}' changed since base, in its declaration or a schema its payload references — ${consumerStance(scan)}; ${notGraded("`asyncapi diff`")}`, scanDetails(scan)));
|
|
199
|
+
}
|
|
140
200
|
}
|
|
141
201
|
for (const [name] of [...base].sort(byKey)) {
|
|
142
202
|
if (current.has(name))
|
|
143
203
|
continue;
|
|
144
|
-
// Fleet-global join: a name another current service still
|
|
145
|
-
// orphaned (positive evidence only — an unreadable contract never vouches
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
204
|
+
// Fleet-global join: a name another current service still SENDS is not
|
|
205
|
+
// orphaned (positive evidence only — an unreadable contract never vouches,
|
|
206
|
+
// and a service that only receives it is one of its consumers, never its
|
|
207
|
+
// producer: `producers` says why that distinction is the gate's too). Nor
|
|
208
|
+
// is one an #external element still publishes, by validate's own rule
|
|
209
|
+
// (`externalProducer` says why): counting the fleet's senders alone made
|
|
210
|
+
// this removal an error that the next `validate --all` called clean.
|
|
211
|
+
const elsewhere = (await victims.producers(name)).filter((d) => d !== sink.id);
|
|
212
|
+
const outside = await victims.externalProducer(name);
|
|
213
|
+
const senders = outside === null ? elsewhere : [...elsewhere, `${outside} (#external)`];
|
|
214
|
+
if (senders.length > 0) {
|
|
215
|
+
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; still sent by ${senders.join(", ")}`));
|
|
149
216
|
continue;
|
|
150
217
|
}
|
|
151
218
|
const scan = await victims.messageConsumers(sink.id, name);
|
|
152
219
|
if (scan.victims.length > 0) {
|
|
153
|
-
sink.findings.push(found("diff.message-removed-consumed", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`,
|
|
220
|
+
sink.findings.push(found("diff.message-removed-consumed", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, scanDetails(scan)));
|
|
154
221
|
}
|
|
155
222
|
else if (scan.suspended.length > 0) {
|
|
156
|
-
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan
|
|
223
|
+
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scanDetails(scan)));
|
|
157
224
|
}
|
|
158
225
|
else {
|
|
159
226
|
sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; nothing currently consumes it`));
|
|
160
227
|
}
|
|
161
228
|
}
|
|
162
229
|
}
|
|
163
|
-
/** The graded join changes (
|
|
230
|
+
/** The graded join changes (`./joins.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */
|
|
164
231
|
function consumerFindings(input) {
|
|
165
232
|
const changes = consumerJoinChanges({
|
|
166
233
|
base: input.base.services,
|
|
@@ -233,7 +300,9 @@ export async function computeDiff(input) {
|
|
|
233
300
|
summary: {
|
|
234
301
|
added: count((f) => f.code.endsWith("-added")),
|
|
235
302
|
removed: count((f) => f.code.endsWith("-removed") || f.code.endsWith("-removed-consumed")),
|
|
236
|
-
|
|
303
|
+
// Requirements, operations and messages alike: a reader scanning the one
|
|
304
|
+
// summary line must not see "0 modified" over a PR that renamed a field.
|
|
305
|
+
modified: count((f) => f.code.endsWith("-modified")),
|
|
237
306
|
deprecated: count((f) => f.code === "diff.op-deprecated"),
|
|
238
307
|
unreadable: services.reduce((n, s) => n + s.unreadable.length, 0) +
|
|
239
308
|
services.filter((s) => s.ambiguous !== undefined).length,
|