@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,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where every file of the feature will be once this archive lands — the half of
|
|
3
|
+
* the link rewrite that `core/links/parse.ts`'s `rebaseLinks` cannot know.
|
|
4
|
+
*
|
|
5
|
+
* THE DEFECT THIS CLOSES. The requirement merge has re-expressed a delta's
|
|
6
|
+
* relative links from the directory they were written in to the one they land
|
|
7
|
+
* in since the glossary axis shipped. But it re-expressed them against the
|
|
8
|
+
* target's OLD place, and the same archive moves the targets that sit inside
|
|
9
|
+
* the feature: a link from a delta to `features/<FEAT>/glossary/order.md` was
|
|
10
|
+
* rewritten faithfully into the living spec and resolved to nothing one rename
|
|
11
|
+
* later, when the term had been copied to `glossary/order.md` and the feature
|
|
12
|
+
* directory had gone to `features/archive/<FEAT>/`. Every such link was
|
|
13
|
+
* `link.unresolved` on the first `validate --all` after a clean archive — an
|
|
14
|
+
* error the feature gate could not see, because before the archive the link
|
|
15
|
+
* was correct.
|
|
16
|
+
*
|
|
17
|
+
* TWO RULES, and they are the archive's own moves rather than a guess:
|
|
18
|
+
*
|
|
19
|
+
* - A file the archive COPIES into the living tree — a glossary term, a flow,
|
|
20
|
+
* a topology document — is cited at its living copy. That is the document
|
|
21
|
+
* readers are meant to find, and the one `glossary.unlinked` counts a
|
|
22
|
+
* citation of: a link left on the archived copy would resolve and still
|
|
23
|
+
* leave the living term reading as a word nobody uses.
|
|
24
|
+
* - Every other file under the feature directory — its ADRs above all, the
|
|
25
|
+
* intent, the deltas themselves — is cited where the feature now lives,
|
|
26
|
+
* `features/archive/<FEAT>/`. Nothing else copies a feature ADR anywhere: it
|
|
27
|
+
* records a decision taken for this change, and it stays with the change
|
|
28
|
+
* (SCHEMA "Layout" says so beside the slot).
|
|
29
|
+
*
|
|
30
|
+
* Built per call from three directory walks — the glossary, the flows and the
|
|
31
|
+
* topology documents, the same walks `../specs.ts` and `../landscape.ts` make
|
|
32
|
+
* to copy them; a `Gated` field would be one more value the gate computes for
|
|
33
|
+
* a phase it does not own.
|
|
34
|
+
*
|
|
35
|
+
* THE OTHER TWO HALVES ARE THE LINKS NO MERGE CARRIES. The feature's own
|
|
36
|
+
* documents (`planFeatureLinks` below) stay in the feature and ride the rename,
|
|
37
|
+
* so their links out of it are one directory short the moment it lands. Every
|
|
38
|
+
* other document in the corpus (`planInboundLinks` below) stays where it is
|
|
39
|
+
* while the files it cites inside the feature move away from under it.
|
|
40
|
+
*/
|
|
41
|
+
import { dirname, resolve } from "node:path";
|
|
42
|
+
import { repoPath } from "../../../../core/envelope/json.js";
|
|
43
|
+
import { movedUnderVouch, parseFrontmatter, stringField } from "../../../../core/document/frontmatter.js";
|
|
44
|
+
import { parseRequirements } from "../../../../core/document/parse.js";
|
|
45
|
+
import { requirementDigest } from "../../../../core/document/spec.js";
|
|
46
|
+
import { featureGlossary, livingTermPath } from "../../../../core/glossary/delta.js";
|
|
47
|
+
import { allAuthoredDocuments, movedMarkdown } from "../../../../core/links/corpus.js";
|
|
48
|
+
import { rebaseLinks } from "../../../../core/links/parse.js";
|
|
49
|
+
import { inOrder } from "../../../../core/kernel/concurrency.js";
|
|
50
|
+
import { acrossRename, isPathInside } from "../../../../core/kernel/path-safety.js";
|
|
51
|
+
import { featuresDir, servicePathsAt } from "../../../../core/repo/paths.js";
|
|
52
|
+
import { listServices } from "../../../../core/repo/repo.js";
|
|
53
|
+
import { featureFlows, livingFlowPath } from "../../../../core/usecases/delta/flows.js";
|
|
54
|
+
import { featureDeployments, livingDeploymentPath } from "../../../../core/deployment/delta.js";
|
|
55
|
+
import { NotUtf8Error, planWrite, readUtf8 } from "../../../../core/staging/writes.js";
|
|
56
|
+
export async function featureRetarget(docsDir, gated) {
|
|
57
|
+
const copies = new Map();
|
|
58
|
+
for (const term of (await featureGlossary(gated.featureDir)).terms) {
|
|
59
|
+
copies.set(resolve(term.path), resolve(livingTermPath(docsDir, term.id)));
|
|
60
|
+
}
|
|
61
|
+
for (const flow of await featureFlows(gated.featureDir)) {
|
|
62
|
+
copies.set(resolve(flow.path), resolve(livingFlowPath(docsDir, flow.rel)));
|
|
63
|
+
}
|
|
64
|
+
for (const doc of await featureDeployments(gated.featureDir)) {
|
|
65
|
+
copies.set(resolve(doc.path), resolve(livingDeploymentPath(docsDir, doc.rel)));
|
|
66
|
+
}
|
|
67
|
+
return (target) => copies.get(resolve(target)) ?? followsFeature(gated, target);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The feature's own markdown documents with their relative links re-expressed
|
|
71
|
+
* for `features/archive/<FEAT>/`, planned at their CURRENT paths so the swap
|
|
72
|
+
* lands before the rename carries them (`../../run.ts`).
|
|
73
|
+
*
|
|
74
|
+
* THE DEFECT THIS CLOSES. The archive is a directory rename, and every document
|
|
75
|
+
* that stays in the feature — the intent, its ADRs, the deltas, a capability
|
|
76
|
+
* delta, the archived copy of a term — kept links written one directory
|
|
77
|
+
* shallower than where they now sit: `../../services/x/spec.md` from
|
|
78
|
+
* `features/<FEAT>/intent.md` names `features/services/x/spec.md` once the
|
|
79
|
+
* intent is `features/archive/<FEAT>/intent.md`. Nothing grades an archived
|
|
80
|
+
* feature (`core/links/corpus.ts` says why), so nothing said so — but the
|
|
81
|
+
* living documents link INTO the archived feature's ADRs (`featureRetarget`
|
|
82
|
+
* above), and a reader who followed one landed on a decision whose own links
|
|
83
|
+
* were all off by one.
|
|
84
|
+
*
|
|
85
|
+
* ONE RULE, the rename's: a target inside the feature moves with it and every
|
|
86
|
+
* other target stays put — `subsystem move`'s rule for its own renames
|
|
87
|
+
* (`commands/subsystem/txn/links.ts`). So a link from the intent to its own
|
|
88
|
+
* ADR or its own glossary term is left exactly as written: the archived
|
|
89
|
+
* record keeps citing the archived copy, which is the text the feature was
|
|
90
|
+
* reviewed against, rather than being redirected to a living copy that will go
|
|
91
|
+
* on changing without it. Only the routes the rename broke are touched, and a
|
|
92
|
+
* link whose text still reaches its target keeps its spelling
|
|
93
|
+
* (`rebaseLinks`).
|
|
94
|
+
*
|
|
95
|
+
* The undo is the snapshot's, like every other write here: each rewritten
|
|
96
|
+
* document is a staged write, so its pre-image is parked and `loam unarchive`
|
|
97
|
+
* puts the authored bytes back (`core/staging/snapshot/snapshot.ts` says where
|
|
98
|
+
* it records them).
|
|
99
|
+
*
|
|
100
|
+
* A document that is not UTF-8 is left exactly as written, and the text
|
|
101
|
+
* output says so — `--json` has no key for it, and minting one for a document
|
|
102
|
+
* nothing grades was not worth a contract change. Every other read here would
|
|
103
|
+
* refuse it, but this one only corrects links, and refusing the archive over
|
|
104
|
+
* such a document would be the larger harm.
|
|
105
|
+
*
|
|
106
|
+
* Read with bounded concurrency and applied in input order (`inOrder`), as
|
|
107
|
+
* `subsystem move`'s rewrite reads its own: a large feature — one
|
|
108
|
+
* `loam migrate-openspec` filled with its legacy documents — is read in full
|
|
109
|
+
* under the lock.
|
|
110
|
+
*/
|
|
111
|
+
export async function planFeatureLinks(config, gated, planned, say) {
|
|
112
|
+
const { docsDir } = config;
|
|
113
|
+
const retarget = (target) => followsFeature(gated, target);
|
|
114
|
+
const read = await inOrder(await movedMarkdown(gated.featureDir), async (path) => {
|
|
115
|
+
try {
|
|
116
|
+
return { path, text: await readUtf8(path) };
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
if (!(err instanceof NotUtf8Error))
|
|
120
|
+
throw err;
|
|
121
|
+
return { path, text: null };
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const relinked = [];
|
|
125
|
+
for (const { path, text } of read) {
|
|
126
|
+
const lands = followsFeature(gated, path);
|
|
127
|
+
if (text === null) {
|
|
128
|
+
say(` ⚠ ${repoPath(docsDir, lands)} is not UTF-8 — its links were left as written`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const next = rebaseLinks(text, { from: dirname(path), to: dirname(lands), retarget });
|
|
132
|
+
if (next === text)
|
|
133
|
+
continue;
|
|
134
|
+
planned.writes.push(planWrite(path, next));
|
|
135
|
+
relinked.push(repoPath(docsDir, lands));
|
|
136
|
+
}
|
|
137
|
+
if (relinked.length === 0)
|
|
138
|
+
return;
|
|
139
|
+
say(` feature documents: ${relinked.length} relinked for ${repoPath(docsDir, gated.archiveDest)}/`);
|
|
140
|
+
for (const path of relinked)
|
|
141
|
+
say(` ${path}`);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Every other document in the link corpus whose links reach INTO the feature,
|
|
145
|
+
* re-expressed for where this archive leaves what they cite.
|
|
146
|
+
*
|
|
147
|
+
* THE DEFECT THIS CLOSES. Another change in flight builds on this one's
|
|
148
|
+
* decision (`../FEAT/adrs/0001-x.md` from its intent), or a living ADR cites
|
|
149
|
+
* the decision taken for it while the change is still open. Correct the moment
|
|
150
|
+
* before the archive, so no gate could say otherwise; `link.unresolved` the
|
|
151
|
+
* moment after, because the rename took the directory away from under the
|
|
152
|
+
* link. And no edit the author could make in advance would have helped: the
|
|
153
|
+
* archived path does not exist until the archive does. `subsystem move`
|
|
154
|
+
* rewrites the links into the directories it renames
|
|
155
|
+
* (`commands/subsystem/txn/links.ts`); this is the archive's rename owing the
|
|
156
|
+
* same correction.
|
|
157
|
+
*
|
|
158
|
+
* THE RULE IS `featureRetarget`'s, not the rename's. These documents are not
|
|
159
|
+
* the record of the change; they are the living corpus and the other changes
|
|
160
|
+
* in flight, read against the tree as it is. So a term the archive copies into
|
|
161
|
+
* `glossary/` is cited at the living copy — the one a reader is meant to find
|
|
162
|
+
* and the one `glossary.unlinked` counts — and everything that stays with the
|
|
163
|
+
* change, its ADRs and intent above all, is cited where the feature now lives.
|
|
164
|
+
* Only the routes into the feature change; every other link keeps its
|
|
165
|
+
* spelling (`rebaseLinks`).
|
|
166
|
+
*
|
|
167
|
+
* COMPOSED ONTO THE MERGE. A living spec the merge rewrites can cite the
|
|
168
|
+
* feature in its own narrative, and relinking the bytes on disk would plan a
|
|
169
|
+
* second write of its PRE-merge text — swapped after the merge's, it would
|
|
170
|
+
* undo the merge. So a document this plan already writes is relinked from the
|
|
171
|
+
* text the merge composed, and that one write is replaced in place.
|
|
172
|
+
*
|
|
173
|
+
* THE COSTS ARE SAID THE WAY THE MERGE SAYS ITS OWN. A rewritten route is still
|
|
174
|
+
* a changed byte. Under a vouch stamp it is `content.stale` until the owner
|
|
175
|
+
* re-vouches, so a vouched spec this moves joins `revouchOwed` exactly as one
|
|
176
|
+
* the merge moves does. Inside a living requirement it moves the digest a
|
|
177
|
+
* `Based-On:` pin holds, and the plan names the document. No git refusal of the
|
|
178
|
+
* kind `subsystem move` holds (`move-uncommitted`): archive never stages in
|
|
179
|
+
* git, and its undo is the snapshot — every write here is one of its rows, so
|
|
180
|
+
* `loam unarchive` puts the authored bytes back, a document edited since is
|
|
181
|
+
* `snapshot-stale` like any other, and one whose feature has been archived or
|
|
182
|
+
* removed since is left as it is (`commands/unarchive/restore.ts`).
|
|
183
|
+
*
|
|
184
|
+
* The two notes are the text output's alone — the walk that could not be made
|
|
185
|
+
* and the `Based-On:` cost. `--json` has no key for either: its `warnings` are
|
|
186
|
+
* coherence issues with codes, and a new key for an advisory the next
|
|
187
|
+
* `validate --all` grades in full was not worth a contract change. What an
|
|
188
|
+
* agent acts on is in the payload already: every relinked document is a `plan`
|
|
189
|
+
* row and every re-vouch it owes is on `revouchOwed`.
|
|
190
|
+
*/
|
|
191
|
+
export async function planInboundLinks(config, gated, planned, say) {
|
|
192
|
+
const { docsDir, fleet } = config;
|
|
193
|
+
let corpus;
|
|
194
|
+
try {
|
|
195
|
+
corpus = await allAuthoredDocuments(docsDir, fleet);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
// The gate enumerated this fleet already, so a walk that fails here is a
|
|
199
|
+
// tree `validate` will diagnose. The archive is not refused over a question
|
|
200
|
+
// it asks to keep the corpus readable — `subsystem move` decides the same —
|
|
201
|
+
// but it does not pretend the question was answered either.
|
|
202
|
+
say(` ⚠ links INTO ${repoPath(docsDir, gated.featureDir)}/ were not checked — the fleet's documents could not be walked ` +
|
|
203
|
+
`(${err instanceof Error ? err.message : String(err)}); \`loam validate --all\` names any that broke.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const retarget = await featureRetarget(docsDir, gated);
|
|
207
|
+
const vouchable = new Map();
|
|
208
|
+
for (const service of await listServices(docsDir, fleet)) {
|
|
209
|
+
const paths = servicePathsAt(service.dir);
|
|
210
|
+
vouchable.set(resolve(paths.spec), service.id);
|
|
211
|
+
vouchable.set(resolve(paths.archSpec), service.id);
|
|
212
|
+
}
|
|
213
|
+
const plannedAt = new Map(planned.writes.map((write, at) => [resolve(write.path), { at, write }]));
|
|
214
|
+
const seen = new Set();
|
|
215
|
+
const candidates = corpus.filter((path) => {
|
|
216
|
+
const key = resolve(path);
|
|
217
|
+
if (seen.has(key) || isPathInside(gated.featureDir, path))
|
|
218
|
+
return false;
|
|
219
|
+
seen.add(key);
|
|
220
|
+
return true;
|
|
221
|
+
});
|
|
222
|
+
const documents = await inOrder(candidates, async (path) => {
|
|
223
|
+
const composed = plannedAt.get(resolve(path));
|
|
224
|
+
if (composed !== undefined) {
|
|
225
|
+
// A delete or a Buffer is not text this rewrite may compose onto; no
|
|
226
|
+
// markdown planner hands over either.
|
|
227
|
+
const { content } = composed.write;
|
|
228
|
+
return { path, composed, text: typeof content === "string" ? content : null };
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return { path, composed, text: await readUtf8(path) };
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Absent (the corpus names every slot a service MAY fill) or not UTF-8,
|
|
235
|
+
// which `link.unreadable` already names: nothing loam could write back.
|
|
236
|
+
return { path, composed, text: null };
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
const pinned = (md) => parseRequirements(md).map(requirementDigest).join(",");
|
|
240
|
+
const relinked = [];
|
|
241
|
+
const notes = [];
|
|
242
|
+
for (const { path, composed, text } of documents) {
|
|
243
|
+
if (text === null)
|
|
244
|
+
continue;
|
|
245
|
+
const next = rebaseLinks(text, { from: dirname(path), to: dirname(path), retarget });
|
|
246
|
+
if (next === text)
|
|
247
|
+
continue;
|
|
248
|
+
// In place, at the index the merge's write already holds: nothing between
|
|
249
|
+
// here and the map above adds, removes or reorders a planned write.
|
|
250
|
+
if (composed === undefined)
|
|
251
|
+
planned.writes.push(planWrite(path, next));
|
|
252
|
+
else
|
|
253
|
+
planned.writes[composed.at] = { ...composed.write, content: next };
|
|
254
|
+
const where = repoPath(docsDir, path);
|
|
255
|
+
relinked.push(where);
|
|
256
|
+
// The merge's own question (`../specs.ts`), asked of the bytes about to be
|
|
257
|
+
// written — and once per document: a spec the merge already named is not
|
|
258
|
+
// owed a second re-vouch for the same commit.
|
|
259
|
+
const service = vouchable.get(resolve(path));
|
|
260
|
+
const fm = parseFrontmatter(next);
|
|
261
|
+
const named = planned.revouchOwed.some((owed) => resolve(owed.path) === resolve(path));
|
|
262
|
+
if (service !== undefined && !named && movedUnderVouch(fm, next)) {
|
|
263
|
+
planned.revouchOwed.push({ service, path, owner: stringField(fm, "owner") ?? null });
|
|
264
|
+
}
|
|
265
|
+
// Living documents only: a pin holds the digest of a LIVING requirement,
|
|
266
|
+
// so another feature's own delta text moving pins nothing.
|
|
267
|
+
if (!isPathInside(featuresDir(docsDir), path) && pinned(next) !== pinned(text)) {
|
|
268
|
+
notes.push(`${where}: links this archive rewrote sit inside requirements, so a feature in flight that pins one with Based-On: ` +
|
|
269
|
+
"reports delta.baseline-stale until `loam rebase` re-pins it.");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (relinked.length === 0)
|
|
273
|
+
return;
|
|
274
|
+
say(` links into the feature: ${relinked.length} document(s) relinked for ${repoPath(docsDir, gated.archiveDest)}/`);
|
|
275
|
+
for (const path of relinked)
|
|
276
|
+
say(` ${path}`);
|
|
277
|
+
for (const note of notes)
|
|
278
|
+
say(` note: ${note}`);
|
|
279
|
+
}
|
|
280
|
+
/** Where `target` will be once the feature directory has moved: its image under the archive when it is inside the feature, itself otherwise. */
|
|
281
|
+
function followsFeature(gated, target) {
|
|
282
|
+
return acrossRename(target, gated.featureDir, gated.archiveDest);
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/document/links.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where every file of the feature will be once this archive lands — the half of\n * the link rewrite that `core/links/parse.ts`'s `rebaseLinks` cannot know.\n *\n * THE DEFECT THIS CLOSES. The requirement merge has re-expressed a delta's\n * relative links from the directory they were written in to the one they land\n * in since the glossary axis shipped. But it re-expressed them against the\n * target's OLD place, and the same archive moves the targets that sit inside\n * the feature: a link from a delta to `features/<FEAT>/glossary/order.md` was\n * rewritten faithfully into the living spec and resolved to nothing one rename\n * later, when the term had been copied to `glossary/order.md` and the feature\n * directory had gone to `features/archive/<FEAT>/`. Every such link was\n * `link.unresolved` on the first `validate --all` after a clean archive — an\n * error the feature gate could not see, because before the archive the link\n * was correct.\n *\n * TWO RULES, and they are the archive's own moves rather than a guess:\n *\n * - A file the archive COPIES into the living tree — a glossary term, a flow,\n * a topology document — is cited at its living copy. That is the document\n * readers are meant to find, and the one `glossary.unlinked` counts a\n * citation of: a link left on the archived copy would resolve and still\n * leave the living term reading as a word nobody uses.\n * - Every other file under the feature directory — its ADRs above all, the\n * intent, the deltas themselves — is cited where the feature now lives,\n * `features/archive/<FEAT>/`. Nothing else copies a feature ADR anywhere: it\n * records a decision taken for this change, and it stays with the change\n * (SCHEMA \"Layout\" says so beside the slot).\n *\n * Built per call from three directory walks — the glossary, the flows and the\n * topology documents, the same walks `../specs.ts` and `../landscape.ts` make\n * to copy them; a `Gated` field would be one more value the gate computes for\n * a phase it does not own.\n *\n * THE OTHER TWO HALVES ARE THE LINKS NO MERGE CARRIES. The feature's own\n * documents (`planFeatureLinks` below) stay in the feature and ride the rename,\n * so their links out of it are one directory short the moment it lands. Every\n * other document in the corpus (`planInboundLinks` below) stays where it is\n * while the files it cites inside the feature move away from under it.\n */\nimport { dirname, resolve } from \"node:path\";\nimport { repoPath } from \"../../../../core/envelope/json.js\";\nimport { movedUnderVouch, parseFrontmatter, stringField } from \"../../../../core/document/frontmatter.js\";\nimport { parseRequirements } from \"../../../../core/document/parse.js\";\nimport { requirementDigest } from \"../../../../core/document/spec.js\";\nimport { featureGlossary, livingTermPath } from \"../../../../core/glossary/delta.js\";\nimport { allAuthoredDocuments, movedMarkdown } from \"../../../../core/links/corpus.js\";\nimport { rebaseLinks } from \"../../../../core/links/parse.js\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport { acrossRename, isPathInside } from \"../../../../core/kernel/path-safety.js\";\nimport { featuresDir, servicePathsAt } from \"../../../../core/repo/paths.js\";\nimport { listServices } from \"../../../../core/repo/repo.js\";\nimport { featureFlows, livingFlowPath } from \"../../../../core/usecases/delta/flows.js\";\nimport { featureDeployments, livingDeploymentPath } from \"../../../../core/deployment/delta.js\";\nimport { NotUtf8Error, planWrite, readUtf8 } from \"../../../../core/staging/writes.js\";\nimport type { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { Gated, Plan } from \"../state.js\";\n\n/** Absolute path in, the absolute path the same file will have after this archive out. */\nexport type Retarget = (target: string) => string;\n\nexport async function featureRetarget(docsDir: DocsDir, gated: Pick<Gated, \"featureDir\" | \"archiveDest\">): Promise<Retarget> {\n const copies = new Map<string, string>();\n for (const term of (await featureGlossary(gated.featureDir)).terms) {\n copies.set(resolve(term.path), resolve(livingTermPath(docsDir, term.id)));\n }\n for (const flow of await featureFlows(gated.featureDir)) {\n copies.set(resolve(flow.path), resolve(livingFlowPath(docsDir, flow.rel)));\n }\n for (const doc of await featureDeployments(gated.featureDir)) {\n copies.set(resolve(doc.path), resolve(livingDeploymentPath(docsDir, doc.rel)));\n }\n return (target: string): string => copies.get(resolve(target)) ?? followsFeature(gated, target);\n}\n\n/**\n * The feature's own markdown documents with their relative links re-expressed\n * for `features/archive/<FEAT>/`, planned at their CURRENT paths so the swap\n * lands before the rename carries them (`../../run.ts`).\n *\n * THE DEFECT THIS CLOSES. The archive is a directory rename, and every document\n * that stays in the feature — the intent, its ADRs, the deltas, a capability\n * delta, the archived copy of a term — kept links written one directory\n * shallower than where they now sit: `../../services/x/spec.md` from\n * `features/<FEAT>/intent.md` names `features/services/x/spec.md` once the\n * intent is `features/archive/<FEAT>/intent.md`. Nothing grades an archived\n * feature (`core/links/corpus.ts` says why), so nothing said so — but the\n * living documents link INTO the archived feature's ADRs (`featureRetarget`\n * above), and a reader who followed one landed on a decision whose own links\n * were all off by one.\n *\n * ONE RULE, the rename's: a target inside the feature moves with it and every\n * other target stays put — `subsystem move`'s rule for its own renames\n * (`commands/subsystem/txn/links.ts`). So a link from the intent to its own\n * ADR or its own glossary term is left exactly as written: the archived\n * record keeps citing the archived copy, which is the text the feature was\n * reviewed against, rather than being redirected to a living copy that will go\n * on changing without it. Only the routes the rename broke are touched, and a\n * link whose text still reaches its target keeps its spelling\n * (`rebaseLinks`).\n *\n * The undo is the snapshot's, like every other write here: each rewritten\n * document is a staged write, so its pre-image is parked and `loam unarchive`\n * puts the authored bytes back (`core/staging/snapshot/snapshot.ts` says where\n * it records them).\n *\n * A document that is not UTF-8 is left exactly as written, and the text\n * output says so — `--json` has no key for it, and minting one for a document\n * nothing grades was not worth a contract change. Every other read here would\n * refuse it, but this one only corrects links, and refusing the archive over\n * such a document would be the larger harm.\n *\n * Read with bounded concurrency and applied in input order (`inOrder`), as\n * `subsystem move`'s rewrite reads its own: a large feature — one\n * `loam migrate-openspec` filled with its legacy documents — is read in full\n * under the lock.\n */\nexport async function planFeatureLinks(\n config: { docsDir: DocsDir },\n gated: Pick<Gated, \"featureDir\" | \"archiveDest\">,\n planned: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { docsDir } = config;\n const retarget = (target: string): string => followsFeature(gated, target);\n const read = await inOrder(await movedMarkdown(gated.featureDir), async (path) => {\n try {\n return { path, text: await readUtf8(path) };\n } catch (err) {\n if (!(err instanceof NotUtf8Error)) throw err;\n return { path, text: null };\n }\n });\n const relinked: string[] = [];\n for (const { path, text } of read) {\n const lands = followsFeature(gated, path);\n if (text === null) {\n say(` ⚠ ${repoPath(docsDir, lands)} is not UTF-8 — its links were left as written`);\n continue;\n }\n const next = rebaseLinks(text, { from: dirname(path), to: dirname(lands), retarget });\n if (next === text) continue;\n planned.writes.push(planWrite(path, next));\n relinked.push(repoPath(docsDir, lands));\n }\n if (relinked.length === 0) return;\n say(` feature documents: ${relinked.length} relinked for ${repoPath(docsDir, gated.archiveDest)}/`);\n for (const path of relinked) say(` ${path}`);\n}\n\n/**\n * Every other document in the link corpus whose links reach INTO the feature,\n * re-expressed for where this archive leaves what they cite.\n *\n * THE DEFECT THIS CLOSES. Another change in flight builds on this one's\n * decision (`../FEAT/adrs/0001-x.md` from its intent), or a living ADR cites\n * the decision taken for it while the change is still open. Correct the moment\n * before the archive, so no gate could say otherwise; `link.unresolved` the\n * moment after, because the rename took the directory away from under the\n * link. And no edit the author could make in advance would have helped: the\n * archived path does not exist until the archive does. `subsystem move`\n * rewrites the links into the directories it renames\n * (`commands/subsystem/txn/links.ts`); this is the archive's rename owing the\n * same correction.\n *\n * THE RULE IS `featureRetarget`'s, not the rename's. These documents are not\n * the record of the change; they are the living corpus and the other changes\n * in flight, read against the tree as it is. So a term the archive copies into\n * `glossary/` is cited at the living copy — the one a reader is meant to find\n * and the one `glossary.unlinked` counts — and everything that stays with the\n * change, its ADRs and intent above all, is cited where the feature now lives.\n * Only the routes into the feature change; every other link keeps its\n * spelling (`rebaseLinks`).\n *\n * COMPOSED ONTO THE MERGE. A living spec the merge rewrites can cite the\n * feature in its own narrative, and relinking the bytes on disk would plan a\n * second write of its PRE-merge text — swapped after the merge's, it would\n * undo the merge. So a document this plan already writes is relinked from the\n * text the merge composed, and that one write is replaced in place.\n *\n * THE COSTS ARE SAID THE WAY THE MERGE SAYS ITS OWN. A rewritten route is still\n * a changed byte. Under a vouch stamp it is `content.stale` until the owner\n * re-vouches, so a vouched spec this moves joins `revouchOwed` exactly as one\n * the merge moves does. Inside a living requirement it moves the digest a\n * `Based-On:` pin holds, and the plan names the document. No git refusal of the\n * kind `subsystem move` holds (`move-uncommitted`): archive never stages in\n * git, and its undo is the snapshot — every write here is one of its rows, so\n * `loam unarchive` puts the authored bytes back, a document edited since is\n * `snapshot-stale` like any other, and one whose feature has been archived or\n * removed since is left as it is (`commands/unarchive/restore.ts`).\n *\n * The two notes are the text output's alone — the walk that could not be made\n * and the `Based-On:` cost. `--json` has no key for either: its `warnings` are\n * coherence issues with codes, and a new key for an advisory the next\n * `validate --all` grades in full was not worth a contract change. What an\n * agent acts on is in the payload already: every relinked document is a `plan`\n * row and every re-vouch it owes is on `revouchOwed`.\n */\nexport async function planInboundLinks(\n config: { docsDir: DocsDir; fleet: FleetContext },\n gated: Pick<Gated, \"featureDir\" | \"archiveDest\">,\n planned: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { docsDir, fleet } = config;\n let corpus: string[];\n try {\n corpus = await allAuthoredDocuments(docsDir, fleet);\n } catch (err) {\n // The gate enumerated this fleet already, so a walk that fails here is a\n // tree `validate` will diagnose. The archive is not refused over a question\n // it asks to keep the corpus readable — `subsystem move` decides the same —\n // but it does not pretend the question was answered either.\n say(\n ` ⚠ links INTO ${repoPath(docsDir, gated.featureDir)}/ were not checked — the fleet's documents could not be walked ` +\n `(${err instanceof Error ? err.message : String(err)}); \\`loam validate --all\\` names any that broke.`,\n );\n return;\n }\n const retarget = await featureRetarget(docsDir, gated);\n const vouchable = new Map<string, string>();\n for (const service of await listServices(docsDir, fleet)) {\n const paths = servicePathsAt(service.dir);\n vouchable.set(resolve(paths.spec), service.id);\n vouchable.set(resolve(paths.archSpec), service.id);\n }\n const plannedAt = new Map(planned.writes.map((write, at) => [resolve(write.path), { at, write }]));\n const seen = new Set<string>();\n const candidates = corpus.filter((path) => {\n const key = resolve(path);\n if (seen.has(key) || isPathInside(gated.featureDir, path)) return false;\n seen.add(key);\n return true;\n });\n const documents = await inOrder(candidates, async (path) => {\n const composed = plannedAt.get(resolve(path));\n if (composed !== undefined) {\n // A delete or a Buffer is not text this rewrite may compose onto; no\n // markdown planner hands over either.\n const { content } = composed.write;\n return { path, composed, text: typeof content === \"string\" ? content : null };\n }\n try {\n return { path, composed, text: await readUtf8(path) };\n } catch {\n // Absent (the corpus names every slot a service MAY fill) or not UTF-8,\n // which `link.unreadable` already names: nothing loam could write back.\n return { path, composed, text: null };\n }\n });\n const pinned = (md: string): string => parseRequirements(md).map(requirementDigest).join(\",\");\n const relinked: string[] = [];\n const notes: string[] = [];\n for (const { path, composed, text } of documents) {\n if (text === null) continue;\n const next = rebaseLinks(text, { from: dirname(path), to: dirname(path), retarget });\n if (next === text) continue;\n // In place, at the index the merge's write already holds: nothing between\n // here and the map above adds, removes or reorders a planned write.\n if (composed === undefined) planned.writes.push(planWrite(path, next));\n else planned.writes[composed.at] = { ...composed.write, content: next };\n const where = repoPath(docsDir, path);\n relinked.push(where);\n // The merge's own question (`../specs.ts`), asked of the bytes about to be\n // written — and once per document: a spec the merge already named is not\n // owed a second re-vouch for the same commit.\n const service = vouchable.get(resolve(path));\n const fm = parseFrontmatter(next);\n const named = planned.revouchOwed.some((owed) => resolve(owed.path) === resolve(path));\n if (service !== undefined && !named && movedUnderVouch(fm, next)) {\n planned.revouchOwed.push({ service, path, owner: stringField(fm, \"owner\") ?? null });\n }\n // Living documents only: a pin holds the digest of a LIVING requirement,\n // so another feature's own delta text moving pins nothing.\n if (!isPathInside(featuresDir(docsDir), path) && pinned(next) !== pinned(text)) {\n notes.push(\n `${where}: links this archive rewrote sit inside requirements, so a feature in flight that pins one with Based-On: ` +\n \"reports delta.baseline-stale until `loam rebase` re-pins it.\",\n );\n }\n }\n if (relinked.length === 0) return;\n say(` links into the feature: ${relinked.length} document(s) relinked for ${repoPath(docsDir, gated.archiveDest)}/`);\n for (const path of relinked) say(` ${path}`);\n for (const note of notes) say(` note: ${note}`);\n}\n\n/** Where `target` will be once the feature directory has moved: its image under the archive when it is inside the feature, itself otherwise. */\nfunction followsFeature(gated: Pick<Gated, \"featureDir\" | \"archiveDest\">, target: string): string {\n return acrossRename(target, gated.featureDir, gated.archiveDest);\n}\n"]}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-place half of a requirement merge: a living document that already
|
|
3
|
+
* exists has its requirements RUN rewritten, and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* Moved out of `../specs.ts` when that module took on what a CREATED document
|
|
6
|
+
* says (`./head.ts`) and where a merged link points (`./links.ts`): the three
|
|
7
|
+
* are the document-level questions a requirement merge answers, and the merge
|
|
8
|
+
* loop itself is what is left beside the contract and landscape planners.
|
|
9
|
+
*/
|
|
10
|
+
import { parseRequirements, splitRequirementsSection } from "../../../../core/document/parse.js";
|
|
11
|
+
import { serializeRequirements } from "../../../../core/document/spec.js";
|
|
12
|
+
/**
|
|
13
|
+
* Rewrite ONLY what the delta touched inside a living spec's requirements run.
|
|
14
|
+
* Byte-for-byte preserved: everything before the first requirement inside
|
|
15
|
+
* `## Requirements` (the intro, the heading line, prose under the heading) and
|
|
16
|
+
* everything from the section's end onward (the next `## ` heading to EOF) —
|
|
17
|
+
* the old cut was a substring `indexOf("\n## Requirements")`, a prefix match
|
|
18
|
+
* that also hit `## Requirements Extra` and silently destroyed every section
|
|
19
|
+
* after the requirements. `living` must be every living requirement — the gate's
|
|
20
|
+
* stray guard refuses any document whose requirements sit outside the section,
|
|
21
|
+
* using the same heading definition, before this output is ever written.
|
|
22
|
+
*
|
|
23
|
+
* Inside the run the unit is one requirement, and an untouched one is written
|
|
24
|
+
* back as its raw slice (`spliceRun`). The whole run used to be re-serialized,
|
|
25
|
+
* and `serializeRequirements` writes a blank line under every
|
|
26
|
+
* `### Requirement:` heading — so a living spec written without one, the shape
|
|
27
|
+
* the adopt brief's own examples taught, had that line inserted under every
|
|
28
|
+
* requirement the feature never mentioned. The diff, the vouch pack and the
|
|
29
|
+
* re-vouch all widened from the delta to the whole section.
|
|
30
|
+
*
|
|
31
|
+
* Exported for `../specs.ts`, which calls it for both corpora, and for
|
|
32
|
+
* test/archive.test.ts: the slice-to-requirement join (`verbatimSlices`) can
|
|
33
|
+
* only be handed a disagreeing pair directly, because the gate refuses every
|
|
34
|
+
* document that would produce one through the CLI.
|
|
35
|
+
*/
|
|
36
|
+
export function rewriteRequirementsRun(text, living, merged) {
|
|
37
|
+
const s = splitRequirementsSection(text);
|
|
38
|
+
// No `## Requirements` at all: the stray guard has already refused any doc
|
|
39
|
+
// whose requirements live elsewhere, so this one has none — open the section.
|
|
40
|
+
if (s === null)
|
|
41
|
+
return `${text.trimEnd()}\n\n## Requirements\n\n${serializeRequirements(merged)}`;
|
|
42
|
+
if (s.run !== "")
|
|
43
|
+
return s.head + spliceRun(s, living, merged) + s.tail;
|
|
44
|
+
// The section held no requirements yet: head and tail are raw slices, and the
|
|
45
|
+
// glue supplies the blank lines the author never had reason to write.
|
|
46
|
+
const headGlue = s.head.endsWith("\n\n") ? "" : s.head.endsWith("\n") ? "\n" : "\n\n";
|
|
47
|
+
const tailGlue = s.tail === "" ? "" : "\n";
|
|
48
|
+
return s.head + headGlue + serializeRequirements(merged) + tailGlue + s.tail;
|
|
49
|
+
}
|
|
50
|
+
/** The line breaks (and blank lines) a slice ends with — what separates it from whatever follows. */
|
|
51
|
+
const TRAILING_BREAKS = /(?:\r?\n[ \t]*)*$/;
|
|
52
|
+
/**
|
|
53
|
+
* The merged run: every living requirement the delta left alone as its raw
|
|
54
|
+
* slice, every one it added or replaced serialized, in `merged`'s order.
|
|
55
|
+
*
|
|
56
|
+
* Keyed on object identity, which `applyRequirementDelta` keeps for exactly the
|
|
57
|
+
* requirements it did not touch — so there is no second reading here of what
|
|
58
|
+
* the delta changed, and no way for the splice and the algebra to disagree.
|
|
59
|
+
* Which slice belongs to which living requirement is `verbatimSlices`' join.
|
|
60
|
+
*
|
|
61
|
+
* Prose BETWEEN requirements rides in the slice of the requirement above it,
|
|
62
|
+
* which is where parseRequirements attributes it too: it survives verbatim
|
|
63
|
+
* beside an untouched requirement and goes with a replaced one, as it always
|
|
64
|
+
* did — a MODIFIED carries its full new text.
|
|
65
|
+
*/
|
|
66
|
+
function spliceRun(section, living, merged) {
|
|
67
|
+
const verbatim = verbatimSlices(section.requirements, living);
|
|
68
|
+
let out = "";
|
|
69
|
+
let previousVerbatim = true;
|
|
70
|
+
for (const requirement of merged) {
|
|
71
|
+
const raw = verbatim.get(requirement);
|
|
72
|
+
// Two untouched neighbours keep whatever the author put between them. Next
|
|
73
|
+
// to a serialized requirement the canonical blank line is ADDED after the
|
|
74
|
+
// untouched text, never by rewriting it.
|
|
75
|
+
if (out !== "" && (raw === undefined || !previousVerbatim))
|
|
76
|
+
out = withBlankLine(out);
|
|
77
|
+
out += raw ?? serializeRequirements([requirement]);
|
|
78
|
+
previousVerbatim = raw !== undefined;
|
|
79
|
+
}
|
|
80
|
+
if (out === "")
|
|
81
|
+
return "";
|
|
82
|
+
// The run ends as it ended: whatever separated the old last requirement from
|
|
83
|
+
// the next heading (or EOF) now follows the new last one — and at least a
|
|
84
|
+
// newline when a merged requirement lands where the file used to stop without one.
|
|
85
|
+
const ending = section.run.slice(section.run.search(TRAILING_BREAKS));
|
|
86
|
+
const body = out.slice(0, out.search(TRAILING_BREAKS));
|
|
87
|
+
return body + (ending === "" && merged.at(-1) !== living.at(-1) ? "\n" : ending);
|
|
88
|
+
}
|
|
89
|
+
/** A requirement's identity as the merge selects it: `Requirement-ID:` when authored, the exact heading otherwise. */
|
|
90
|
+
const identity = (r) => (r.id === undefined ? `name:${r.name}` : `id:${r.id}`);
|
|
91
|
+
/**
|
|
92
|
+
* Each living requirement's raw slice, joined on its IDENTITY — the slice read
|
|
93
|
+
* back through the same parser that produced `living`, so a slice can only
|
|
94
|
+
* land in place of the requirement it actually is.
|
|
95
|
+
*
|
|
96
|
+
* The join used to be by position, guarded by a count. The two walks agree in
|
|
97
|
+
* order today (one heading-and-fence definition, core/document/parse.ts, and
|
|
98
|
+
* the gate's stray guard refusing any requirement outside the section), but
|
|
99
|
+
* nothing declared that, and equal counts paired differently would have written
|
|
100
|
+
* one requirement's bytes where another belongs — under its vouch stamp, with
|
|
101
|
+
* nothing to say so.
|
|
102
|
+
*
|
|
103
|
+
* Fails closed to the empty map, which serializes every requirement — the old
|
|
104
|
+
* whole-run rewrite, still a correct merge and only a wider diff: when the
|
|
105
|
+
* counts differ, when a slice does not read as exactly one requirement, when
|
|
106
|
+
* two slices claim one identity, or when a living requirement finds no slice
|
|
107
|
+
* of its own.
|
|
108
|
+
*/
|
|
109
|
+
function verbatimSlices(slices, living) {
|
|
110
|
+
const none = new Map();
|
|
111
|
+
if (slices.length !== living.length)
|
|
112
|
+
return none;
|
|
113
|
+
const byIdentity = new Map();
|
|
114
|
+
for (const slice of slices) {
|
|
115
|
+
const [only, ...more] = parseRequirements(slice);
|
|
116
|
+
if (only === undefined || more.length > 0 || byIdentity.has(identity(only)))
|
|
117
|
+
return none;
|
|
118
|
+
byIdentity.set(identity(only), slice);
|
|
119
|
+
}
|
|
120
|
+
const verbatim = new Map();
|
|
121
|
+
for (const requirement of living) {
|
|
122
|
+
const slice = byIdentity.get(identity(requirement));
|
|
123
|
+
if (slice === undefined)
|
|
124
|
+
return none;
|
|
125
|
+
// Taken once: a second living requirement of the same identity finds nothing.
|
|
126
|
+
byIdentity.delete(identity(requirement));
|
|
127
|
+
verbatim.set(requirement, slice);
|
|
128
|
+
}
|
|
129
|
+
return verbatim;
|
|
130
|
+
}
|
|
131
|
+
/** `text` ending in a blank line: line breaks appended to make one, nothing rewritten. */
|
|
132
|
+
function withBlankLine(text) {
|
|
133
|
+
const breaks = text.slice(text.search(TRAILING_BREAKS)).split("\n").length - 1;
|
|
134
|
+
return breaks >= 2 ? text : text + "\n".repeat(2 - breaks);
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/document/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The in-place half of a requirement merge: a living document that already\n * exists has its requirements RUN rewritten, and nothing else.\n *\n * Moved out of `../specs.ts` when that module took on what a CREATED document\n * says (`./head.ts`) and where a merged link points (`./links.ts`): the three\n * are the document-level questions a requirement merge answers, and the merge\n * loop itself is what is left beside the contract and landscape planners.\n */\nimport { parseRequirements, splitRequirementsSection } from \"../../../../core/document/parse.js\";\nimport { serializeRequirements, type Requirement } from \"../../../../core/document/spec.js\";\n\n/**\n * Rewrite ONLY what the delta touched inside a living spec's requirements run.\n * Byte-for-byte preserved: everything before the first requirement inside\n * `## Requirements` (the intro, the heading line, prose under the heading) and\n * everything from the section's end onward (the next `## ` heading to EOF) —\n * the old cut was a substring `indexOf(\"\\n## Requirements\")`, a prefix match\n * that also hit `## Requirements Extra` and silently destroyed every section\n * after the requirements. `living` must be every living requirement — the gate's\n * stray guard refuses any document whose requirements sit outside the section,\n * using the same heading definition, before this output is ever written.\n *\n * Inside the run the unit is one requirement, and an untouched one is written\n * back as its raw slice (`spliceRun`). The whole run used to be re-serialized,\n * and `serializeRequirements` writes a blank line under every\n * `### Requirement:` heading — so a living spec written without one, the shape\n * the adopt brief's own examples taught, had that line inserted under every\n * requirement the feature never mentioned. The diff, the vouch pack and the\n * re-vouch all widened from the delta to the whole section.\n *\n * Exported for `../specs.ts`, which calls it for both corpora, and for\n * test/archive.test.ts: the slice-to-requirement join (`verbatimSlices`) can\n * only be handed a disagreeing pair directly, because the gate refuses every\n * document that would produce one through the CLI.\n */\nexport function rewriteRequirementsRun(text: string, living: Requirement[], merged: Requirement[]): string {\n const s = splitRequirementsSection(text);\n // No `## Requirements` at all: the stray guard has already refused any doc\n // whose requirements live elsewhere, so this one has none — open the section.\n if (s === null) return `${text.trimEnd()}\\n\\n## Requirements\\n\\n${serializeRequirements(merged)}`;\n if (s.run !== \"\") return s.head + spliceRun(s, living, merged) + s.tail;\n // The section held no requirements yet: head and tail are raw slices, and the\n // glue supplies the blank lines the author never had reason to write.\n const headGlue = s.head.endsWith(\"\\n\\n\") ? \"\" : s.head.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const tailGlue = s.tail === \"\" ? \"\" : \"\\n\";\n return s.head + headGlue + serializeRequirements(merged) + tailGlue + s.tail;\n}\n\n/** The line breaks (and blank lines) a slice ends with — what separates it from whatever follows. */\nconst TRAILING_BREAKS = /(?:\\r?\\n[ \\t]*)*$/;\n\n/**\n * The merged run: every living requirement the delta left alone as its raw\n * slice, every one it added or replaced serialized, in `merged`'s order.\n *\n * Keyed on object identity, which `applyRequirementDelta` keeps for exactly the\n * requirements it did not touch — so there is no second reading here of what\n * the delta changed, and no way for the splice and the algebra to disagree.\n * Which slice belongs to which living requirement is `verbatimSlices`' join.\n *\n * Prose BETWEEN requirements rides in the slice of the requirement above it,\n * which is where parseRequirements attributes it too: it survives verbatim\n * beside an untouched requirement and goes with a replaced one, as it always\n * did — a MODIFIED carries its full new text.\n */\nfunction spliceRun(\n section: { run: string; requirements: string[] },\n living: Requirement[],\n merged: Requirement[],\n): string {\n const verbatim = verbatimSlices(section.requirements, living);\n let out = \"\";\n let previousVerbatim = true;\n for (const requirement of merged) {\n const raw = verbatim.get(requirement);\n // Two untouched neighbours keep whatever the author put between them. Next\n // to a serialized requirement the canonical blank line is ADDED after the\n // untouched text, never by rewriting it.\n if (out !== \"\" && (raw === undefined || !previousVerbatim)) out = withBlankLine(out);\n out += raw ?? serializeRequirements([requirement]);\n previousVerbatim = raw !== undefined;\n }\n if (out === \"\") return \"\";\n // The run ends as it ended: whatever separated the old last requirement from\n // the next heading (or EOF) now follows the new last one — and at least a\n // newline when a merged requirement lands where the file used to stop without one.\n const ending = section.run.slice(section.run.search(TRAILING_BREAKS));\n const body = out.slice(0, out.search(TRAILING_BREAKS));\n return body + (ending === \"\" && merged.at(-1) !== living.at(-1) ? \"\\n\" : ending);\n}\n\n/** A requirement's identity as the merge selects it: `Requirement-ID:` when authored, the exact heading otherwise. */\nconst identity = (r: Pick<Requirement, \"id\" | \"name\">): string => (r.id === undefined ? `name:${r.name}` : `id:${r.id}`);\n\n/**\n * Each living requirement's raw slice, joined on its IDENTITY — the slice read\n * back through the same parser that produced `living`, so a slice can only\n * land in place of the requirement it actually is.\n *\n * The join used to be by position, guarded by a count. The two walks agree in\n * order today (one heading-and-fence definition, core/document/parse.ts, and\n * the gate's stray guard refusing any requirement outside the section), but\n * nothing declared that, and equal counts paired differently would have written\n * one requirement's bytes where another belongs — under its vouch stamp, with\n * nothing to say so.\n *\n * Fails closed to the empty map, which serializes every requirement — the old\n * whole-run rewrite, still a correct merge and only a wider diff: when the\n * counts differ, when a slice does not read as exactly one requirement, when\n * two slices claim one identity, or when a living requirement finds no slice\n * of its own.\n */\nfunction verbatimSlices(slices: string[], living: Requirement[]): Map<Requirement, string> {\n const none = new Map<Requirement, string>();\n if (slices.length !== living.length) return none;\n const byIdentity = new Map<string, string>();\n for (const slice of slices) {\n const [only, ...more] = parseRequirements(slice);\n if (only === undefined || more.length > 0 || byIdentity.has(identity(only))) return none;\n byIdentity.set(identity(only), slice);\n }\n const verbatim = new Map<Requirement, string>();\n for (const requirement of living) {\n const slice = byIdentity.get(identity(requirement));\n if (slice === undefined) return none;\n // Taken once: a second living requirement of the same identity finds nothing.\n byIdentity.delete(identity(requirement));\n verbatim.set(requirement, slice);\n }\n return verbatim;\n}\n\n/** `text` ending in a blank line: line breaks appended to make one, nothing rewritten. */\nfunction withBlankLine(text: string): string {\n const breaks = text.slice(text.search(TRAILING_BREAKS)).split(\"\\n\").length - 1;\n return breaks >= 2 ? text : text + \"\\n\".repeat(2 - breaks);\n}\n"]}
|
|
@@ -20,7 +20,6 @@ import { readUtf8 } from "../../../core/staging/writes.js";
|
|
|
20
20
|
import { loadFile } from "../../../core/c4/likec4.js";
|
|
21
21
|
import { archiveDir as archiveRoot, featurePaths, featureSpecPaths, SPEC_AXES } from "../../../core/repo/paths.js";
|
|
22
22
|
import { livingCapabilityPaths } from "../../../core/repo/authored/paths.js";
|
|
23
|
-
import { featureCapabilityDeltas } from "../../../core/capabilities/delta/tree.js";
|
|
24
23
|
import { locateServicePaths } from "../../../core/repo/service-target.js";
|
|
25
24
|
import { featureSpecServices, missingFeatureMessage, resolveFeature } from "../../../core/repo/repo.js";
|
|
26
25
|
import { featureCoherence } from "../../../core/coherence/coherence.js";
|
|
@@ -31,7 +30,7 @@ import { refuseFindings, refuseJson, sayRecovery } from "./refusal.js";
|
|
|
31
30
|
import {} from "./state.js";
|
|
32
31
|
export async function gate(config, featureId, opts, say) {
|
|
33
32
|
const json = opts.json === true;
|
|
34
|
-
|
|
33
|
+
// Before anything is read: a commit that was killed between two renames left
|
|
35
34
|
// the living docs half-written, and every other command in loam would have
|
|
36
35
|
// planned against them as if they were the truth. Under the same lock, so the
|
|
37
36
|
// repair cannot race a writer. It refuses when the half-merged files have been
|
|
@@ -62,9 +61,7 @@ export async function gate(config, featureId, opts, say) {
|
|
|
62
61
|
// no-context path `core/coherence/declared.ts` already documents paying for
|
|
63
62
|
// the arch delta — so this is not the run's only walk, only the one every
|
|
64
63
|
// phase downstream of the gate shares.
|
|
65
|
-
const capabilityDeltas = (config.fleet
|
|
66
|
-
? await featureCapabilityDeltas(featureDir)
|
|
67
|
-
: await config.fleet.featureCapabilityDeltas(featureDir)).docs;
|
|
64
|
+
const capabilityDeltas = (await config.fleet.featureCapabilityDeltas(featureDir)).docs;
|
|
68
65
|
// A `specs/<svc>/` whose NAME is not a legal service id, refused before
|
|
69
66
|
// anything joins that name into a path: the conflict scan below probes
|
|
70
67
|
// `services/<svc>/<axis>` with it, and the requirements merge would
|
|
@@ -104,7 +101,7 @@ export async function gate(config, featureId, opts, say) {
|
|
|
104
101
|
// Git conflict markers in a LIVING document this merge would rewrite, before
|
|
105
102
|
// anything else is asked of it. Both sides of somebody's merge are in the
|
|
106
103
|
// file, so nothing it says is anyone's text — and the requirements rewrite
|
|
107
|
-
// deletes whichever marker lines fall inside
|
|
104
|
+
// deletes whichever marker lines fall inside a requirement it replaces, turning a
|
|
108
105
|
// conflict anyone can see into a file nobody can tell is wrong. Checked ahead
|
|
109
106
|
// of the coherence gate for `inspectLandscape`'s reason: a conflicted
|
|
110
107
|
// document parses, so every verdict downstream of it is an opinion about half
|
|
@@ -142,19 +139,23 @@ export async function gate(config, featureId, opts, say) {
|
|
|
142
139
|
// the rest. The register is issue.ts's `approveOverrides` — the same data
|
|
143
140
|
// the `overridable` key in the envelope is resolved from — never a code
|
|
144
141
|
// string spelled here, because two spellings of "what the flag moves" would
|
|
145
|
-
// drift.
|
|
146
|
-
// the id grammar refuses,
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// mechanical fact about the
|
|
151
|
-
// feature
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
142
|
+
// drift. What the register HOLDS has grown: it began as one code (a
|
|
143
|
+
// `metadata { service }` binding the id grammar refuses, where the merge
|
|
144
|
+
// would splice the name into the living map verbatim and probe
|
|
145
|
+
// `services/<binding>/` with it) and now holds the create-only collisions of
|
|
146
|
+
// every whole-file axis besides. They share the doctrine and nothing else —
|
|
147
|
+
// each is a mechanical fact about what the merge would write, never a
|
|
148
|
+
// judgment about the feature — which is why the message below counts them
|
|
149
|
+
// and lets each issue say its own sentence.
|
|
150
|
+
//
|
|
151
|
+
// IT USED TO NAME BINDINGS, and had been wrong for every other code in the
|
|
152
|
+
// register since the use-case slot landed: a feature whose flow collided
|
|
153
|
+
// with a living one was refused with "1 element binding(s) name an illegal
|
|
154
|
+
// service id", about a feature that binds nothing. A refusal headline that
|
|
155
|
+
// describes the wrong breach sends its reader to the wrong file first.
|
|
155
156
|
const nonOverridable = issues.filter((i) => !approveOverrides(i));
|
|
156
157
|
if (nonOverridable.length > 0) {
|
|
157
|
-
const msg = `archive ${id} — BLOCKED: ${nonOverridable.length}
|
|
158
|
+
const msg = `archive ${id} — BLOCKED: ${nonOverridable.length} issue(s) --approve does not override; each names what the merge would write`;
|
|
158
159
|
if (json) {
|
|
159
160
|
refuseJson("not-coherent", msg, issues, `features/${dirName}`);
|
|
160
161
|
return null;
|
|
@@ -166,7 +167,9 @@ export async function gate(config, featureId, opts, say) {
|
|
|
166
167
|
// line says which of the marks the flag cannot move.
|
|
167
168
|
for (const i of issues)
|
|
168
169
|
console.error(` ${SEVERITY_MARK[i.severity]} ${i.message}`);
|
|
169
|
-
console.error(`\nOf these, --approve does not override
|
|
170
|
+
console.error(`\nOf these, --approve does not override ${nonOverridable.length}: ${[...new Set(nonOverridable.map((i) => i.code))].sort().join(", ")}. ` +
|
|
171
|
+
"Each is a mechanical fact about what the merge would write — a name loam cannot resolve, or a file it would replace wholesale — not a judgement the flag can move. " +
|
|
172
|
+
"Fix those; the other issues gate as usual.");
|
|
170
173
|
sayExplain("not-coherent");
|
|
171
174
|
process.exitCode = 1;
|
|
172
175
|
return null;
|
|
@@ -298,7 +301,6 @@ export async function gate(config, featureId, opts, say) {
|
|
|
298
301
|
fail(json, "archive-exists", `archive ${id} — BLOCKED: features/archive/${dirName} already exists. Remove or rename it, then re-run.`);
|
|
299
302
|
return null;
|
|
300
303
|
}
|
|
301
|
-
say(`archive ${id}${dryRun ? " (dry run)" : ""}\n`);
|
|
302
304
|
return {
|
|
303
305
|
id,
|
|
304
306
|
dirName,
|