@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
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 +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/brief.js +0 -733
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -559
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -479
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1104
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The joins from a use case's tags: back to a declared capability, and to one
|
|
3
|
+
* of that capability's requirements.
|
|
4
|
+
*
|
|
5
|
+
* TWO TAGS, ONE CLAIM. `#cap-<slug>` says which capability a flow is about;
|
|
6
|
+
* `#req-<slug>` says which of its named promises the flow satisfies. The second
|
|
7
|
+
* is the architect's ANSWER to the analyst's requirement, and it is the only
|
|
8
|
+
* carrier a cross-service criterion has: "I enter a login and a password and I
|
|
9
|
+
* am in" cannot be promised by any single service's spec, because each promises
|
|
10
|
+
* only its own part — but a flow can, because it IS the hop sequence. That is
|
|
11
|
+
* why `Realizes:` on a service requirement is not enough on its own, and why
|
|
12
|
+
* this join exists at all.
|
|
13
|
+
*
|
|
14
|
+
* THE SECOND TAG IS SCOPED BY THE FIRST. A `Requirement-ID` is unique only
|
|
15
|
+
* inside its own document, so `#req-CHK-ONCE` means nothing until the view has
|
|
16
|
+
* said which capability it is about. A view carrying `#req-` with no resolved
|
|
17
|
+
* `#cap-`, or with two, is refused rather than guessed at — the same stance the
|
|
18
|
+
* `many` arm takes one join down.
|
|
19
|
+
*
|
|
20
|
+
* A LikeC4 `dynamic view` opts in to being graded as a business use case by
|
|
21
|
+
* carrying a tag `#cap-<slug>`, and this module is the whole of what both tags
|
|
22
|
+
* mean. It is deliberately the narrowest layer that can mean it: no file is
|
|
23
|
+
* read, no finding is emitted, and nothing is thrown — a pure function over ids
|
|
24
|
+
* somebody else supplies. That last property is not tidiness. Its caller runs
|
|
25
|
+
* inside `loam validate --all` over every document a fleet has, and the one
|
|
26
|
+
* behaviour a check added to that run may never have is failing the run it was
|
|
27
|
+
* added to.
|
|
28
|
+
*
|
|
29
|
+
* WHY A SLUG AT ALL. Neither id is spelled in characters a tag name accepts. A
|
|
30
|
+
* capability id spells its nesting inside the id — `identity/tokens` and
|
|
31
|
+
* `payments/settlement` both ship in loam's own
|
|
32
|
+
* `examples/docs/architecture/capabilities.yaml`, and `./capabilities.ts` keeps
|
|
33
|
+
* the key exactly as the requirement writes it, slashes preserved — while a
|
|
34
|
+
* `Requirement-ID` may legally contain a `.`. A LikeC4 tag name accepts exactly
|
|
35
|
+
* `[A-Za-z0-9_-]` (measured at the 1.59.2 pin, pinned in
|
|
36
|
+
* `test/likec4-view-shape.test.ts`), so both tags carry their id with the rest
|
|
37
|
+
* flattened and both joins run over the flattened form.
|
|
38
|
+
*
|
|
39
|
+
* WHY THE JOIN CAN ANSWER "MANY". The slug is NOT injective. `payments/refunds`
|
|
40
|
+
* and `payments-refunds` are two distinct, legal, declarable ids that flatten to
|
|
41
|
+
* one slug, so `#cap-payments-refunds` names both and nothing in the tag can say
|
|
42
|
+
* which. loam refuses and names ambiguity rather than guessing, and here the
|
|
43
|
+
* cost of guessing is unusually high: a silently-picked capability attributes a
|
|
44
|
+
* whole business flow — every hop, every service, every rollup built over it —
|
|
45
|
+
* to the wrong owner, and goes on doing so invisibly because the wrong answer
|
|
46
|
+
* looks exactly like the right one. The "many" arm is why this returns a union
|
|
47
|
+
* at all instead of `string | undefined`.
|
|
48
|
+
*
|
|
49
|
+
* WHAT THE CALLER STILL OWES. The vocabulary ladder in `./findings.ts` — an
|
|
50
|
+
* absent or unreadable `architecture/capabilities.yaml` means total silence, not
|
|
51
|
+
* a fleet full of unresolved tags — is applied BEFORE calling here. An empty
|
|
52
|
+
* `declared` list at this boundary therefore means "the fleet declares no
|
|
53
|
+
* capabilities", which is a real answer this module will happily grade; it does
|
|
54
|
+
* not mean "there is no vocabulary", and this module cannot tell the two apart.
|
|
55
|
+
*/
|
|
56
|
+
import { closeIds } from "../c4/arch.js";
|
|
57
|
+
import { compareIds } from "../repo/entries.js";
|
|
58
|
+
/**
|
|
59
|
+
* The reserved tag prefix, matched EXACTLY — case included.
|
|
60
|
+
*
|
|
61
|
+
* A view tag that does not start with it is not a capability claim and is
|
|
62
|
+
* invisible here: `#FEAT-101`, `#platform` and `#deprecated` are the author's
|
|
63
|
+
* own vocabulary and grading them would turn every existing hand-drawn view in
|
|
64
|
+
* a brownfield fleet red on upgrade. The match is case-sensitive because what
|
|
65
|
+
* survives it is a slug that must round-trip to a `capabilities.yaml` key, and
|
|
66
|
+
* those keys are case-sensitive YAML; accepting `#Cap-checkout` here would hand
|
|
67
|
+
* the lookup a slug whose case loam had already stopped believing in.
|
|
68
|
+
*/
|
|
69
|
+
export const CAP_TAG_PREFIX = "cap-";
|
|
70
|
+
/**
|
|
71
|
+
* Every character a LikeC4 tag name accepts, at the 1.59.2 pin. A WHITELIST,
|
|
72
|
+
* and that is the load-bearing choice: the complement is infinite, so a rule
|
|
73
|
+
* written as "flatten a slash" is a rule that is right about one character and
|
|
74
|
+
* silent about the rest.
|
|
75
|
+
*
|
|
76
|
+
* Measured, not assumed (`test/likec4-view-shape.test.ts` pins it): `.`, `/`,
|
|
77
|
+
* `:`, a space, `+`, `@` and a non-ASCII letter each make the DECLARATION a
|
|
78
|
+
* parse error — and the failure is worse than a refusal, because the parser
|
|
79
|
+
* TRUNCATES the name at the offending character. `#x-a.b` comes back as
|
|
80
|
+
* `["x-a"]`. Nothing in loam reads a model with errors, so the truncation
|
|
81
|
+
* cannot reach a join today; it is recorded because the day something does read
|
|
82
|
+
* one, a truncated tag resolves to a different, possibly real, capability.
|
|
83
|
+
*/
|
|
84
|
+
const TAG_NAME_SAFE = /[^A-Za-z0-9_-]/g;
|
|
85
|
+
/**
|
|
86
|
+
* An id as its tag spells it: every character a tag name cannot carry flattened
|
|
87
|
+
* to a hyphen.
|
|
88
|
+
*
|
|
89
|
+
* Used by BOTH tags this module resolves, which is why it is no longer called
|
|
90
|
+
* `capabilitySlug`. A capability id spells its nesting with `/`
|
|
91
|
+
* (`identity/tokens`) and a `Requirement-ID` may legally contain `.`
|
|
92
|
+
* (`REQUIREMENT_ID_RE` allows it) — two different characters, one rule, because
|
|
93
|
+
* an author who learns that a tag flattens the awkward character in one id
|
|
94
|
+
* should not have to learn it again for the other.
|
|
95
|
+
*
|
|
96
|
+
* Deliberately lossy, and the `many` arms below carry the whole cost of that:
|
|
97
|
+
* two ids can produce one slug and nothing here notices.
|
|
98
|
+
*/
|
|
99
|
+
export function tagSlug(id) {
|
|
100
|
+
return id.replace(TAG_NAME_SAFE, "-");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Every declared id that produces each slug.
|
|
104
|
+
*
|
|
105
|
+
* A map rather than a scan per tag because the "many" arm needs the WHOLE
|
|
106
|
+
* colliding set: a lookup that stopped at the first hit could still report
|
|
107
|
+
* "resolved" for a slug two ids answer to, which is precisely the silent wrong
|
|
108
|
+
* answer this module exists to refuse.
|
|
109
|
+
*/
|
|
110
|
+
function idsBySlug(declared) {
|
|
111
|
+
const bySlug = new Map();
|
|
112
|
+
for (const id of declared) {
|
|
113
|
+
const slug = tagSlug(id);
|
|
114
|
+
const ids = bySlug.get(slug);
|
|
115
|
+
if (ids === undefined)
|
|
116
|
+
bySlug.set(slug, [id]);
|
|
117
|
+
else
|
|
118
|
+
ids.push(id);
|
|
119
|
+
}
|
|
120
|
+
return bySlug;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Close names for a slug nothing declares — suggested against the SLUGS, then
|
|
124
|
+
* mapped back to the ids that produced them.
|
|
125
|
+
*
|
|
126
|
+
* Both halves are load-bearing. Comparing against slugs is what lets
|
|
127
|
+
* `#cap-identiy-tokns` reach `identity/tokens` at all: `closeIds` matches on
|
|
128
|
+
* substring and shared prefix, and the slash sits where the author's typo does
|
|
129
|
+
* not. Mapping back is what keeps the suggestion writable — a hint naming
|
|
130
|
+
* `identity-tokens` would send the author to look for a capability declared
|
|
131
|
+
* under a name that appears nowhere in `capabilities.yaml`.
|
|
132
|
+
*
|
|
133
|
+
* A winning slug may map back to more than one id, and the expansion is not
|
|
134
|
+
* re-capped: `closeIds` caps the SLUGS it offers at five, and dropping a real
|
|
135
|
+
* declared id afterwards to keep an arithmetic promise nobody made would hide
|
|
136
|
+
* the very collision the "many" arm is here to name.
|
|
137
|
+
*/
|
|
138
|
+
function closeCapabilityIds(slug, bySlug) {
|
|
139
|
+
const near = closeIds(slug, [...bySlug.keys()]);
|
|
140
|
+
// `?? []` rather than `!`: `closeIds` promises every name it offers is one it
|
|
141
|
+
// was given, so a miss here would mean that promise broke — and the honest
|
|
142
|
+
// answer to a broken invariant inside a hint is one fewer suggestion, not a
|
|
143
|
+
// `TypeError` thrown out of a `validate --all` run over somebody's fleet.
|
|
144
|
+
return [...new Set(near.flatMap((s) => bySlug.get(s) ?? []))].sort(compareIds);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The capability claims a view's tags make: one claim per `#cap-` tag, each
|
|
148
|
+
* answered on its own terms.
|
|
149
|
+
*
|
|
150
|
+
* A VIEW MAY CARRY MORE THAN ONE `#cap-` TAG, and the tags are never collapsed
|
|
151
|
+
* into a single verdict for the view. Both halves of that are deliberate.
|
|
152
|
+
*
|
|
153
|
+
* A use case realizing two capabilities is a real thing — a checkout flow that
|
|
154
|
+
* also issues the identity token realizes both, and there is no honest way to
|
|
155
|
+
* make its author pick one — so a second tag must NOT be read the way the "many"
|
|
156
|
+
* arm reads a second colliding id. Those two look alike and are opposites: in
|
|
157
|
+
* "many" the author wrote one name and loam cannot tell which id it means, here
|
|
158
|
+
* the author wrote two names and meant both.
|
|
159
|
+
*
|
|
160
|
+
* And a view whose tags disagree — one resolving, one naming nothing — is not a
|
|
161
|
+
* view that resolved. Collapsing to the first success would swallow the broken
|
|
162
|
+
* tag and grade the view as if the typo were not there; collapsing to the first
|
|
163
|
+
* failure would lose the capability the view genuinely does realize. One tag,
|
|
164
|
+
* one verdict, is the only shape that keeps both facts, and it leaves the caller
|
|
165
|
+
* free to emit one finding per failing tag while still collecting every id the
|
|
166
|
+
* view resolved.
|
|
167
|
+
*
|
|
168
|
+
* Claims come back SORTED BY TAG rather than in the order LikeC4 handed the tags
|
|
169
|
+
* over. Nothing in loam has measured that LikeC4 preserves the author's tag
|
|
170
|
+
* order, and an output ordered by an unmeasured upstream detail is one that
|
|
171
|
+
* reorders under a version bump — the diff-stability rule `./rollup.ts` states
|
|
172
|
+
* for its rows, applied to a smaller list. A repeated tag collapses to one
|
|
173
|
+
* claim, for the reason one breach earns one finding.
|
|
174
|
+
*
|
|
175
|
+
* An empty result means the view makes no capability claim at all — either it
|
|
176
|
+
* carries no tags or none of them are reserved — which is the opt-in, not a
|
|
177
|
+
* failure to resolve.
|
|
178
|
+
*/
|
|
179
|
+
export function resolveCapabilityTags(tags, declared) {
|
|
180
|
+
const bySlug = idsBySlug(declared);
|
|
181
|
+
const claimed = [...new Set(tags.filter((tag) => tag.startsWith(CAP_TAG_PREFIX)))].sort(compareIds);
|
|
182
|
+
return claimed.map((tag) => {
|
|
183
|
+
// A bare `#cap-` slugs to the empty string, and it stays a claim on purpose:
|
|
184
|
+
// the prefix is the author's opt-in however little follows it, so dropping
|
|
185
|
+
// the tag as unreserved would leave a view that asked to be graded silently
|
|
186
|
+
// ungraded. What that empty slug then resolves to is the lookup's business
|
|
187
|
+
// like any other — `none` in every real fleet, `resolved` in the one that
|
|
188
|
+
// declared an empty id, and neither is decided here.
|
|
189
|
+
const slug = tag.slice(CAP_TAG_PREFIX.length);
|
|
190
|
+
// Destructured rather than indexed so the three arms are told apart by the
|
|
191
|
+
// shape of the match itself — no bounds assertion, and no `undefined` check
|
|
192
|
+
// separate from the "nothing declared this" answer it already is.
|
|
193
|
+
const [first, ...rest] = bySlug.get(slug) ?? [];
|
|
194
|
+
if (first === undefined)
|
|
195
|
+
return { tag, slug, kind: "none", close: closeCapabilityIds(slug, bySlug) };
|
|
196
|
+
if (rest.length === 0)
|
|
197
|
+
return { tag, slug, kind: "resolved", id: first };
|
|
198
|
+
return { tag, slug, kind: "many", ids: [first, ...rest].sort(compareIds) };
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The reserved tag prefix for the requirement claim, matched EXACTLY as
|
|
203
|
+
* `CAP_TAG_PREFIX` is and for the same two reasons: a tag that does not start
|
|
204
|
+
* with it is the author's own vocabulary and is invisible here, and the slug
|
|
205
|
+
* that survives the match must round-trip to a case-sensitive `Requirement-ID:`.
|
|
206
|
+
*/
|
|
207
|
+
export const REQ_TAG_PREFIX = "req-";
|
|
208
|
+
/**
|
|
209
|
+
* Resolve every `#req-` tag a view carries against the capability it is about.
|
|
210
|
+
*
|
|
211
|
+
* `scope` is the capability ids the view's OWN `#cap-` tags resolved to —
|
|
212
|
+
* `resolveCapabilityTags`' `resolved` arms, and nothing else. An unresolved
|
|
213
|
+
* `#cap-` tag is already `usecase.capability-unresolved`, and letting it also
|
|
214
|
+
* scope a requirement lookup would build a second finding on top of a name loam
|
|
215
|
+
* has just said it cannot place.
|
|
216
|
+
*
|
|
217
|
+
* Claims come back SORTED BY TAG for `resolveCapabilityTags`' reason: nothing in
|
|
218
|
+
* loam has measured that LikeC4 preserves the author's tag order, and an output
|
|
219
|
+
* ordered by an unmeasured upstream detail reorders under a version bump. A
|
|
220
|
+
* repeated tag collapses to one claim, for the reason one breach earns one
|
|
221
|
+
* finding.
|
|
222
|
+
*
|
|
223
|
+
* SEVERAL `#req-` TAGS ARE LEGAL AND NORMAL. One flow commonly satisfies two of
|
|
224
|
+
* a capability's promises — a checkout flow that charges once AND honours the
|
|
225
|
+
* price — and there is no honest way to make its author pick one. That is the
|
|
226
|
+
* same shape `resolveCapabilityTags` allows for `#cap-`, and it is the opposite
|
|
227
|
+
* of the `many` arm: there the author wrote one name loam cannot place, here
|
|
228
|
+
* they wrote two and meant both.
|
|
229
|
+
*/
|
|
230
|
+
export function resolveRequirementTags(tags, scope, requirementsOf) {
|
|
231
|
+
const claimed = [...new Set(tags.filter((tag) => tag.startsWith(REQ_TAG_PREFIX)))].sort(compareIds);
|
|
232
|
+
if (claimed.length === 0)
|
|
233
|
+
return [];
|
|
234
|
+
const capability = scope.length === 1 ? scope[0] : null;
|
|
235
|
+
return claimed.map((tag) => {
|
|
236
|
+
// A bare `#req-` slugs to the empty string and stays a claim, exactly as a
|
|
237
|
+
// bare `#cap-` does: the prefix is the opt-in however little follows it, and
|
|
238
|
+
// dropping it would leave a view that asked to be graded silently ungraded.
|
|
239
|
+
const slug = tag.slice(REQ_TAG_PREFIX.length);
|
|
240
|
+
if (capability === null)
|
|
241
|
+
return { tag, slug, kind: "unscoped", capabilities: [...scope].sort(compareIds) };
|
|
242
|
+
const ids = requirementsOf(capability);
|
|
243
|
+
if (ids === undefined)
|
|
244
|
+
return { tag, slug, kind: "undocumented", capability };
|
|
245
|
+
if (ids.size === 0)
|
|
246
|
+
return { tag, slug, kind: "empty", capability };
|
|
247
|
+
const matching = [...ids].filter((id) => tagSlug(id) === slug).sort(compareIds);
|
|
248
|
+
const [first, ...rest] = matching;
|
|
249
|
+
if (first === undefined) {
|
|
250
|
+
return { tag, slug, kind: "none", capability, close: closeRequirementIds(slug, ids) };
|
|
251
|
+
}
|
|
252
|
+
if (rest.length === 0)
|
|
253
|
+
return { tag, slug, kind: "resolved", capability, id: first };
|
|
254
|
+
return { tag, slug, kind: "many", capability, ids: matching };
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Close ids for a slug nothing in the document matches — suggested against the
|
|
259
|
+
* SLUGS, then mapped back to the real ids, exactly as `closeCapabilityIds`
|
|
260
|
+
* does and for its two reasons: comparing on slugs is what lets a typo reach an
|
|
261
|
+
* id whose awkward character sits where the typo does not, and mapping back is
|
|
262
|
+
* what keeps the suggestion writable.
|
|
263
|
+
*/
|
|
264
|
+
function closeRequirementIds(slug, ids) {
|
|
265
|
+
const bySlug = new Map();
|
|
266
|
+
for (const id of ids) {
|
|
267
|
+
const key = tagSlug(id);
|
|
268
|
+
const found = bySlug.get(key);
|
|
269
|
+
if (found === undefined)
|
|
270
|
+
bySlug.set(key, [id]);
|
|
271
|
+
else
|
|
272
|
+
found.push(id);
|
|
273
|
+
}
|
|
274
|
+
const near = closeIds(slug, [...bySlug.keys()]);
|
|
275
|
+
return [...new Set(near.flatMap((s) => bySlug.get(s) ?? []))].sort(compareIds);
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=usecase-join.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usecase-join.js","sourceRoot":"","sources":["../../../src/core/capabilities/usecase-join.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The joins from a use case's tags: back to a declared capability, and to one\n * of that capability's requirements.\n *\n * TWO TAGS, ONE CLAIM. `#cap-<slug>` says which capability a flow is about;\n * `#req-<slug>` says which of its named promises the flow satisfies. The second\n * is the architect's ANSWER to the analyst's requirement, and it is the only\n * carrier a cross-service criterion has: \"I enter a login and a password and I\n * am in\" cannot be promised by any single service's spec, because each promises\n * only its own part — but a flow can, because it IS the hop sequence. That is\n * why `Realizes:` on a service requirement is not enough on its own, and why\n * this join exists at all.\n *\n * THE SECOND TAG IS SCOPED BY THE FIRST. A `Requirement-ID` is unique only\n * inside its own document, so `#req-CHK-ONCE` means nothing until the view has\n * said which capability it is about. A view carrying `#req-` with no resolved\n * `#cap-`, or with two, is refused rather than guessed at — the same stance the\n * `many` arm takes one join down.\n *\n * A LikeC4 `dynamic view` opts in to being graded as a business use case by\n * carrying a tag `#cap-<slug>`, and this module is the whole of what both tags\n * mean. It is deliberately the narrowest layer that can mean it: no file is\n * read, no finding is emitted, and nothing is thrown — a pure function over ids\n * somebody else supplies. That last property is not tidiness. Its caller runs\n * inside `loam validate --all` over every document a fleet has, and the one\n * behaviour a check added to that run may never have is failing the run it was\n * added to.\n *\n * WHY A SLUG AT ALL. Neither id is spelled in characters a tag name accepts. A\n * capability id spells its nesting inside the id — `identity/tokens` and\n * `payments/settlement` both ship in loam's own\n * `examples/docs/architecture/capabilities.yaml`, and `./capabilities.ts` keeps\n * the key exactly as the requirement writes it, slashes preserved — while a\n * `Requirement-ID` may legally contain a `.`. A LikeC4 tag name accepts exactly\n * `[A-Za-z0-9_-]` (measured at the 1.59.2 pin, pinned in\n * `test/likec4-view-shape.test.ts`), so both tags carry their id with the rest\n * flattened and both joins run over the flattened form.\n *\n * WHY THE JOIN CAN ANSWER \"MANY\". The slug is NOT injective. `payments/refunds`\n * and `payments-refunds` are two distinct, legal, declarable ids that flatten to\n * one slug, so `#cap-payments-refunds` names both and nothing in the tag can say\n * which. loam refuses and names ambiguity rather than guessing, and here the\n * cost of guessing is unusually high: a silently-picked capability attributes a\n * whole business flow — every hop, every service, every rollup built over it —\n * to the wrong owner, and goes on doing so invisibly because the wrong answer\n * looks exactly like the right one. The \"many\" arm is why this returns a union\n * at all instead of `string | undefined`.\n *\n * WHAT THE CALLER STILL OWES. The vocabulary ladder in `./findings.ts` — an\n * absent or unreadable `architecture/capabilities.yaml` means total silence, not\n * a fleet full of unresolved tags — is applied BEFORE calling here. An empty\n * `declared` list at this boundary therefore means \"the fleet declares no\n * capabilities\", which is a real answer this module will happily grade; it does\n * not mean \"there is no vocabulary\", and this module cannot tell the two apart.\n */\nimport { closeIds } from \"../c4/arch.js\";\nimport { compareIds } from \"../repo/entries.js\";\n\n/**\n * The reserved tag prefix, matched EXACTLY — case included.\n *\n * A view tag that does not start with it is not a capability claim and is\n * invisible here: `#FEAT-101`, `#platform` and `#deprecated` are the author's\n * own vocabulary and grading them would turn every existing hand-drawn view in\n * a brownfield fleet red on upgrade. The match is case-sensitive because what\n * survives it is a slug that must round-trip to a `capabilities.yaml` key, and\n * those keys are case-sensitive YAML; accepting `#Cap-checkout` here would hand\n * the lookup a slug whose case loam had already stopped believing in.\n */\nexport const CAP_TAG_PREFIX = \"cap-\";\n\n/**\n * Every character a LikeC4 tag name accepts, at the 1.59.2 pin. A WHITELIST,\n * and that is the load-bearing choice: the complement is infinite, so a rule\n * written as \"flatten a slash\" is a rule that is right about one character and\n * silent about the rest.\n *\n * Measured, not assumed (`test/likec4-view-shape.test.ts` pins it): `.`, `/`,\n * `:`, a space, `+`, `@` and a non-ASCII letter each make the DECLARATION a\n * parse error — and the failure is worse than a refusal, because the parser\n * TRUNCATES the name at the offending character. `#x-a.b` comes back as\n * `[\"x-a\"]`. Nothing in loam reads a model with errors, so the truncation\n * cannot reach a join today; it is recorded because the day something does read\n * one, a truncated tag resolves to a different, possibly real, capability.\n */\nconst TAG_NAME_SAFE = /[^A-Za-z0-9_-]/g;\n\n/**\n * An id as its tag spells it: every character a tag name cannot carry flattened\n * to a hyphen.\n *\n * Used by BOTH tags this module resolves, which is why it is no longer called\n * `capabilitySlug`. A capability id spells its nesting with `/`\n * (`identity/tokens`) and a `Requirement-ID` may legally contain `.`\n * (`REQUIREMENT_ID_RE` allows it) — two different characters, one rule, because\n * an author who learns that a tag flattens the awkward character in one id\n * should not have to learn it again for the other.\n *\n * Deliberately lossy, and the `many` arms below carry the whole cost of that:\n * two ids can produce one slug and nothing here notices.\n */\nexport function tagSlug(id: string): string {\n return id.replace(TAG_NAME_SAFE, \"-\");\n}\n\n/** The tag a claim is about, and the slug left after the prefix. */\ninterface ClaimTag {\n /** The tag verbatim, as the view declares it — no `#`, prefix still on. */\n tag: string;\n /** The tag with its reserved prefix removed: the name the join actually ran on. */\n slug: string;\n}\n\n/**\n * What one `#cap-` tag joins to. Branch on `kind`; every arm carries the tag it\n * is about, so a message can name the tag, the slug and the answer without\n * re-deriving any of them from the view.\n */\nexport type CapabilityClaim =\n /** Exactly one declared id flattens to this tag. `id` is the real id, slashes intact. */\n | (ClaimTag & { kind: \"resolved\"; id: string })\n /**\n * No declared id flattens to it. `close` holds real capability ids — names the\n * author can paste into `capabilities.yaml` or into the tag — never the slugs\n * the comparison was actually made against, because a slug is loam's spelling\n * of a name and offering it as a suggestion would be loam inventing a\n * capability id that nobody declared.\n */\n | (ClaimTag & { kind: \"none\"; close: string[] })\n /** Two or more declared ids flatten to it. Every colliding id, sorted, so a message can name them all. */\n | (ClaimTag & { kind: \"many\"; ids: string[] });\n\n/**\n * Every declared id that produces each slug.\n *\n * A map rather than a scan per tag because the \"many\" arm needs the WHOLE\n * colliding set: a lookup that stopped at the first hit could still report\n * \"resolved\" for a slug two ids answer to, which is precisely the silent wrong\n * answer this module exists to refuse.\n */\nfunction idsBySlug(declared: readonly string[]): Map<string, string[]> {\n const bySlug = new Map<string, string[]>();\n for (const id of declared) {\n const slug = tagSlug(id);\n const ids = bySlug.get(slug);\n if (ids === undefined) bySlug.set(slug, [id]);\n else ids.push(id);\n }\n return bySlug;\n}\n\n/**\n * Close names for a slug nothing declares — suggested against the SLUGS, then\n * mapped back to the ids that produced them.\n *\n * Both halves are load-bearing. Comparing against slugs is what lets\n * `#cap-identiy-tokns` reach `identity/tokens` at all: `closeIds` matches on\n * substring and shared prefix, and the slash sits where the author's typo does\n * not. Mapping back is what keeps the suggestion writable — a hint naming\n * `identity-tokens` would send the author to look for a capability declared\n * under a name that appears nowhere in `capabilities.yaml`.\n *\n * A winning slug may map back to more than one id, and the expansion is not\n * re-capped: `closeIds` caps the SLUGS it offers at five, and dropping a real\n * declared id afterwards to keep an arithmetic promise nobody made would hide\n * the very collision the \"many\" arm is here to name.\n */\nfunction closeCapabilityIds(slug: string, bySlug: ReadonlyMap<string, string[]>): string[] {\n const near = closeIds(slug, [...bySlug.keys()]);\n // `?? []` rather than `!`: `closeIds` promises every name it offers is one it\n // was given, so a miss here would mean that promise broke — and the honest\n // answer to a broken invariant inside a hint is one fewer suggestion, not a\n // `TypeError` thrown out of a `validate --all` run over somebody's fleet.\n return [...new Set(near.flatMap((s) => bySlug.get(s) ?? []))].sort(compareIds);\n}\n\n/**\n * The capability claims a view's tags make: one claim per `#cap-` tag, each\n * answered on its own terms.\n *\n * A VIEW MAY CARRY MORE THAN ONE `#cap-` TAG, and the tags are never collapsed\n * into a single verdict for the view. Both halves of that are deliberate.\n *\n * A use case realizing two capabilities is a real thing — a checkout flow that\n * also issues the identity token realizes both, and there is no honest way to\n * make its author pick one — so a second tag must NOT be read the way the \"many\"\n * arm reads a second colliding id. Those two look alike and are opposites: in\n * \"many\" the author wrote one name and loam cannot tell which id it means, here\n * the author wrote two names and meant both.\n *\n * And a view whose tags disagree — one resolving, one naming nothing — is not a\n * view that resolved. Collapsing to the first success would swallow the broken\n * tag and grade the view as if the typo were not there; collapsing to the first\n * failure would lose the capability the view genuinely does realize. One tag,\n * one verdict, is the only shape that keeps both facts, and it leaves the caller\n * free to emit one finding per failing tag while still collecting every id the\n * view resolved.\n *\n * Claims come back SORTED BY TAG rather than in the order LikeC4 handed the tags\n * over. Nothing in loam has measured that LikeC4 preserves the author's tag\n * order, and an output ordered by an unmeasured upstream detail is one that\n * reorders under a version bump — the diff-stability rule `./rollup.ts` states\n * for its rows, applied to a smaller list. A repeated tag collapses to one\n * claim, for the reason one breach earns one finding.\n *\n * An empty result means the view makes no capability claim at all — either it\n * carries no tags or none of them are reserved — which is the opt-in, not a\n * failure to resolve.\n */\nexport function resolveCapabilityTags(\n tags: readonly string[],\n declared: readonly string[],\n): CapabilityClaim[] {\n const bySlug = idsBySlug(declared);\n const claimed = [...new Set(tags.filter((tag) => tag.startsWith(CAP_TAG_PREFIX)))].sort(compareIds);\n\n return claimed.map((tag) => {\n // A bare `#cap-` slugs to the empty string, and it stays a claim on purpose:\n // the prefix is the author's opt-in however little follows it, so dropping\n // the tag as unreserved would leave a view that asked to be graded silently\n // ungraded. What that empty slug then resolves to is the lookup's business\n // like any other — `none` in every real fleet, `resolved` in the one that\n // declared an empty id, and neither is decided here.\n const slug = tag.slice(CAP_TAG_PREFIX.length);\n // Destructured rather than indexed so the three arms are told apart by the\n // shape of the match itself — no bounds assertion, and no `undefined` check\n // separate from the \"nothing declared this\" answer it already is.\n const [first, ...rest] = bySlug.get(slug) ?? [];\n if (first === undefined) return { tag, slug, kind: \"none\" as const, close: closeCapabilityIds(slug, bySlug) };\n if (rest.length === 0) return { tag, slug, kind: \"resolved\" as const, id: first };\n return { tag, slug, kind: \"many\" as const, ids: [first, ...rest].sort(compareIds) };\n });\n}\n\n/**\n * The reserved tag prefix for the requirement claim, matched EXACTLY as\n * `CAP_TAG_PREFIX` is and for the same two reasons: a tag that does not start\n * with it is the author's own vocabulary and is invisible here, and the slug\n * that survives the match must round-trip to a case-sensitive `Requirement-ID:`.\n */\nexport const REQ_TAG_PREFIX = \"req-\";\n\n/**\n * What one `#req-` tag joins to, given the capabilities the same view resolved.\n *\n * Five arms because there are five different fixes, and they are deliberately\n * the same five `RealizesClaim` draws in `./realizes/join.ts`: the two carriers\n * of one claim must fail in the same vocabulary, or an author who learns the\n * `Realizes:` messages learns nothing about the tag.\n */\nexport type RequirementClaim =\n /** Exactly one requirement of the scoping capability flattens to this tag. */\n | (ClaimTag & { kind: \"resolved\"; capability: string; id: string })\n /**\n * The view resolved no capability, or more than one, so a `Requirement-ID`\n * has no document to be unique inside. `capabilities` is what it DID resolve\n * — empty, or the two-or-more that make the scope ambiguous.\n */\n | (ClaimTag & { kind: \"unscoped\"; capabilities: string[] })\n /** The scoping capability is declared but has no `capabilities/<id>/spec.md`, so it carries no requirements. */\n | (ClaimTag & { kind: \"undocumented\"; capability: string })\n /** Its document exists and declares no requirements yet. */\n | (ClaimTag & { kind: \"empty\"; capability: string })\n /**\n * Its document declares requirements, none flattening to this slug. `close`\n * holds real `Requirement-ID`s — never the slugs the comparison ran against,\n * because a slug is loam's spelling and offering it would send the author\n * looking for an id that appears nowhere in the document.\n */\n | (ClaimTag & { kind: \"none\"; capability: string; close: string[] })\n /** Two or more of its requirement ids flatten to this slug. Every colliding id, sorted. */\n | (ClaimTag & { kind: \"many\"; capability: string; ids: string[] });\n\n/**\n * Resolve every `#req-` tag a view carries against the capability it is about.\n *\n * `scope` is the capability ids the view's OWN `#cap-` tags resolved to —\n * `resolveCapabilityTags`' `resolved` arms, and nothing else. An unresolved\n * `#cap-` tag is already `usecase.capability-unresolved`, and letting it also\n * scope a requirement lookup would build a second finding on top of a name loam\n * has just said it cannot place.\n *\n * Claims come back SORTED BY TAG for `resolveCapabilityTags`' reason: nothing in\n * loam has measured that LikeC4 preserves the author's tag order, and an output\n * ordered by an unmeasured upstream detail reorders under a version bump. A\n * repeated tag collapses to one claim, for the reason one breach earns one\n * finding.\n *\n * SEVERAL `#req-` TAGS ARE LEGAL AND NORMAL. One flow commonly satisfies two of\n * a capability's promises — a checkout flow that charges once AND honours the\n * price — and there is no honest way to make its author pick one. That is the\n * same shape `resolveCapabilityTags` allows for `#cap-`, and it is the opposite\n * of the `many` arm: there the author wrote one name loam cannot place, here\n * they wrote two and meant both.\n */\nexport function resolveRequirementTags(\n tags: readonly string[],\n scope: readonly string[],\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined,\n): RequirementClaim[] {\n const claimed = [...new Set(tags.filter((tag) => tag.startsWith(REQ_TAG_PREFIX)))].sort(compareIds);\n if (claimed.length === 0) return [];\n const capability = scope.length === 1 ? scope[0]! : null;\n\n return claimed.map((tag): RequirementClaim => {\n // A bare `#req-` slugs to the empty string and stays a claim, exactly as a\n // bare `#cap-` does: the prefix is the opt-in however little follows it, and\n // dropping it would leave a view that asked to be graded silently ungraded.\n const slug = tag.slice(REQ_TAG_PREFIX.length);\n if (capability === null) return { tag, slug, kind: \"unscoped\", capabilities: [...scope].sort(compareIds) };\n const ids = requirementsOf(capability);\n if (ids === undefined) return { tag, slug, kind: \"undocumented\", capability };\n if (ids.size === 0) return { tag, slug, kind: \"empty\", capability };\n const matching = [...ids].filter((id) => tagSlug(id) === slug).sort(compareIds);\n const [first, ...rest] = matching;\n if (first === undefined) {\n return { tag, slug, kind: \"none\", capability, close: closeRequirementIds(slug, ids) };\n }\n if (rest.length === 0) return { tag, slug, kind: \"resolved\", capability, id: first };\n return { tag, slug, kind: \"many\", capability, ids: matching };\n });\n}\n\n/**\n * Close ids for a slug nothing in the document matches — suggested against the\n * SLUGS, then mapped back to the real ids, exactly as `closeCapabilityIds`\n * does and for its two reasons: comparing on slugs is what lets a typo reach an\n * id whose awkward character sits where the typo does not, and mapping back is\n * what keeps the suggestion writable.\n */\nfunction closeRequirementIds(slug: string, ids: ReadonlySet<string>): string[] {\n const bySlug = new Map<string, string[]>();\n for (const id of ids) {\n const key = tagSlug(id);\n const found = bySlug.get(key);\n if (found === undefined) bySlug.set(key, [id]);\n else found.push(id);\n }\n const near = closeIds(slug, [...bySlug.keys()]);\n return [...new Set(near.flatMap((s) => bySlug.get(s) ?? []))].sort(compareIds);\n}\n"]}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gate between `loam new` and `loam archive`: a scaffold nobody edited must
|
|
3
|
+
* not become the living truth.
|
|
4
|
+
*
|
|
5
|
+
* `loam new` writes documents whose whole point is to be replaced — a TODO
|
|
6
|
+
* requirement heading, angle-bracket step fill-ins, a TODO service description,
|
|
7
|
+
* an intent whose Why/Scope are comments. Every one of them parsed as real
|
|
8
|
+
* content, validated clean, and archived at exit 0, which put literal `TODO —
|
|
9
|
+
* name the behaviour` into a living spec the rest of the fleet then reads as a
|
|
10
|
+
* requirement somebody meant. These checks refuse that merge.
|
|
11
|
+
*
|
|
12
|
+
* Both are warnings that GATE (issue.ts: severity says the DOCUMENT is legal —
|
|
13
|
+
* it is, it parses — gating says the MERGE is unsafe). `--approve` overrides,
|
|
14
|
+
* as with every judgment about the feature.
|
|
15
|
+
*
|
|
16
|
+
* BOTH DELTA CORPORA ARE READ, because `loam new` scaffolds into both. A
|
|
17
|
+
* `--capability` run writes `features/<FEAT>/capabilities/<id>/spec.md` from a
|
|
18
|
+
* template whose example sits inside an HTML comment exactly like the two spec
|
|
19
|
+
* templates', and an author who copies that block out and archives without
|
|
20
|
+
* editing it would publish `TODO — name the promise` into a LIVING capability
|
|
21
|
+
* document — which is strictly worse than the service case this gate was built
|
|
22
|
+
* for: a capability document outlives every service that realizes it, and a
|
|
23
|
+
* `Realizes:` line pointed at the placeholder's id would then be a join to a
|
|
24
|
+
* promise nobody wrote. The capability deltas are read HERE rather than handed
|
|
25
|
+
* in through `AuthoringScope`, because this is a walk over the feature's own
|
|
26
|
+
* documents and the scope already names the feature; the read is one
|
|
27
|
+
* `existsSync` for the fleets that have not adopted the axis, and the memo when
|
|
28
|
+
* a context was threaded.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync } from "node:fs";
|
|
31
|
+
import { readFile } from "node:fs/promises";
|
|
32
|
+
import { parseRequirements } from "../../document/parse.js";
|
|
33
|
+
import { featureCapabilityDeltas } from "../../capabilities/delta/tree.js";
|
|
34
|
+
import { featurePaths, featureSpecPaths, SPEC_AXES } from "../../repo/paths.js";
|
|
35
|
+
import { BODY_SENTINELS, HEADING_SENTINELS, SERVICE_DESCRIPTION_SENTINEL, } from "./sentinels.js";
|
|
36
|
+
export async function authoringIssues(scope) {
|
|
37
|
+
const issues = [];
|
|
38
|
+
await intentIssue(scope, issues);
|
|
39
|
+
for (const e of scope.splicedEls) {
|
|
40
|
+
if (e.description !== SERVICE_DESCRIPTION_SENTINEL)
|
|
41
|
+
continue;
|
|
42
|
+
issues.push({
|
|
43
|
+
severity: "warn",
|
|
44
|
+
gates: true,
|
|
45
|
+
code: "scaffold.placeholder",
|
|
46
|
+
message: `delta.likec4: '${e.title}' (${e.id}) still carries the scaffolded description '${SERVICE_DESCRIPTION_SENTINEL}' — the merge would publish it to the fleet map. Say what the service owns, or archive with --approve.`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
for (const svc of scope.svcNames) {
|
|
50
|
+
const paths = featureSpecPaths(scope.featureDir, svc);
|
|
51
|
+
for (const axis of SPEC_AXES) {
|
|
52
|
+
const path = paths[axis.key];
|
|
53
|
+
if (!existsSync(path))
|
|
54
|
+
continue;
|
|
55
|
+
const found = unauthored(await requirementsOf(scope, path));
|
|
56
|
+
if (found.size === 0)
|
|
57
|
+
continue;
|
|
58
|
+
issues.push({
|
|
59
|
+
severity: "warn",
|
|
60
|
+
gates: true,
|
|
61
|
+
code: "scaffold.placeholder",
|
|
62
|
+
subject: svc,
|
|
63
|
+
message: `${svc}: ${axis.file} still carries scaffold placeholder(s) nobody authored — ${quote(found)}. The merge would write them into the living spec as requirements somebody meant. Replace them, or archive with --approve.`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// The business corpus. `subject` is the capability id — a THIRD provenance
|
|
68
|
+
// for this code's subject, after a service id and none at all — and the
|
|
69
|
+
// artifact table resolves it by MEMBERSHIP against the capability documents
|
|
70
|
+
// the feature carries (`status/feature/artifacts.ts`), the same resolution
|
|
71
|
+
// every `delta.*` issue on this axis already relies on. So the capability's
|
|
72
|
+
// own row turns draft, and a service row does too only where a fleet holds a
|
|
73
|
+
// service and a capability of one name, which is that module's deliberately
|
|
74
|
+
// pessimistic answer rather than a mis-attribution here.
|
|
75
|
+
const capabilities = scope.context === undefined
|
|
76
|
+
? await featureCapabilityDeltas(scope.featureDir)
|
|
77
|
+
: await scope.context.featureCapabilityDeltas(scope.featureDir);
|
|
78
|
+
for (const doc of capabilities.docs) {
|
|
79
|
+
const found = unauthored(await requirementsOf(scope, doc.spec));
|
|
80
|
+
if (found.size === 0)
|
|
81
|
+
continue;
|
|
82
|
+
issues.push({
|
|
83
|
+
severity: "warn",
|
|
84
|
+
gates: true,
|
|
85
|
+
code: "scaffold.placeholder",
|
|
86
|
+
subject: doc.id,
|
|
87
|
+
message: `capability ${doc.id}: spec.md still carries scaffold placeholder(s) nobody authored — ${quote(found)}. The merge would write them into the living capabilities/${doc.id}/spec.md as promises somebody meant — and a capability document outlives every service that realizes it. Replace them, or archive with --approve.`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return issues;
|
|
91
|
+
}
|
|
92
|
+
/** One delta document's requirements, through the invocation's read index when there is one. */
|
|
93
|
+
async function requirementsOf(scope, path) {
|
|
94
|
+
if (scope.context === undefined)
|
|
95
|
+
return parseRequirements(await readFile(path, "utf8"));
|
|
96
|
+
return scope.context.readRequirements(path);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The scaffold strings a delta's MERGING requirements still carry.
|
|
100
|
+
*
|
|
101
|
+
* ADDED/MODIFIED only: those are the texts this MERGE would write. A BASE
|
|
102
|
+
* requirement is a quote of the living state — if a sentinel sits there, some
|
|
103
|
+
* earlier archive published it and this feature is not the author to gate on it
|
|
104
|
+
* — and a REMOVED one is leaving the document anyway.
|
|
105
|
+
*/
|
|
106
|
+
function unauthored(reqs) {
|
|
107
|
+
const found = new Set();
|
|
108
|
+
for (const r of reqs) {
|
|
109
|
+
if (r.kind === "ADDED" || r.kind === "MODIFIED")
|
|
110
|
+
sentinelsIn(r, found);
|
|
111
|
+
}
|
|
112
|
+
return found;
|
|
113
|
+
}
|
|
114
|
+
/** The found sentinels as a message fragment — one spelling, so the two corpora read alike. */
|
|
115
|
+
function quote(found) {
|
|
116
|
+
return [...found].map((s) => `'${s}'`).join(", ");
|
|
117
|
+
}
|
|
118
|
+
/** The exact template strings this requirement still carries, if any. */
|
|
119
|
+
function sentinelsIn(r, found) {
|
|
120
|
+
for (const heading of HEADING_SENTINELS) {
|
|
121
|
+
if (r.name === heading)
|
|
122
|
+
found.add(heading);
|
|
123
|
+
for (const s of r.scenarios) {
|
|
124
|
+
if (s.name === heading)
|
|
125
|
+
found.add(heading);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const body = [r.text.join("\n"), ...r.scenarios.map((s) => s.lines.join("\n"))].join("\n");
|
|
129
|
+
for (const marker of BODY_SENTINELS) {
|
|
130
|
+
if (body.includes(marker))
|
|
131
|
+
found.add(marker);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Does intent.md SAY anything? The scaffold's Why/Scope bodies are HTML
|
|
136
|
+
* comments, so "strip frontmatter, comments and headings, look for prose" is
|
|
137
|
+
* exactly the question "did a person write the sentence the reviewer reads
|
|
138
|
+
* first". A missing file gates the same way: a feature with no stated intent
|
|
139
|
+
* is not less anonymous for not having the file.
|
|
140
|
+
*/
|
|
141
|
+
async function intentIssue(scope, issues) {
|
|
142
|
+
const path = featurePaths(scope.featureDir).intent;
|
|
143
|
+
const empty = { severity: "warn", gates: true, code: "intent.empty" };
|
|
144
|
+
if (!existsSync(path)) {
|
|
145
|
+
issues.push({
|
|
146
|
+
...empty,
|
|
147
|
+
message: `intent.md is missing — nothing says why ${scope.featureId} exists. Write the Why before archiving it into the living docs, or archive with --approve.`,
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (hasProse(await readFile(path, "utf8")))
|
|
152
|
+
return;
|
|
153
|
+
issues.push({
|
|
154
|
+
...empty,
|
|
155
|
+
message: `intent.md says nothing yet — its Why and Scope are still the scaffold's comments. Write why ${scope.featureId} exists before archiving it into the living docs, or archive with --approve.`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** Is there any authored text outside frontmatter, HTML comments and headings? */
|
|
159
|
+
function hasProse(text) {
|
|
160
|
+
// Normalized BEFORE the fence regex, the discipline document/parse.ts
|
|
161
|
+
// applies to every other markdown read: `---\r` is not `---`, so a CRLF
|
|
162
|
+
// rewrite (or a BOM some Windows editor prefixed) kept the frontmatter,
|
|
163
|
+
// which then counted as prose — and the untouched scaffold archived at
|
|
164
|
+
// exit 0 through the exact gate built to refuse it.
|
|
165
|
+
// The BOM is spelled as an escape, never as the raw character — a raw one is
|
|
166
|
+
// invisible in review and to grep, which is how it would survive a rewrite.
|
|
167
|
+
let body = text.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
|
|
168
|
+
// Frontmatter: the leading `---` block only, exactly where the scaffold puts it.
|
|
169
|
+
const fm = /^---\n[\s\S]*?\n---\n/.exec(body);
|
|
170
|
+
if (fm !== null)
|
|
171
|
+
body = body.slice(fm[0].length);
|
|
172
|
+
body = body.replace(/<!--[\s\S]*?-->/g, "");
|
|
173
|
+
return body
|
|
174
|
+
.split("\n")
|
|
175
|
+
.some((line) => line.trim().length > 0 && !line.trimStart().startsWith("#"));
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../../src/core/coherence/authoring/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The gate between `loam new` and `loam archive`: a scaffold nobody edited must\n * not become the living truth.\n *\n * `loam new` writes documents whose whole point is to be replaced — a TODO\n * requirement heading, angle-bracket step fill-ins, a TODO service description,\n * an intent whose Why/Scope are comments. Every one of them parsed as real\n * content, validated clean, and archived at exit 0, which put literal `TODO —\n * name the behaviour` into a living spec the rest of the fleet then reads as a\n * requirement somebody meant. These checks refuse that merge.\n *\n * Both are warnings that GATE (issue.ts: severity says the DOCUMENT is legal —\n * it is, it parses — gating says the MERGE is unsafe). `--approve` overrides,\n * as with every judgment about the feature.\n *\n * BOTH DELTA CORPORA ARE READ, because `loam new` scaffolds into both. A\n * `--capability` run writes `features/<FEAT>/capabilities/<id>/spec.md` from a\n * template whose example sits inside an HTML comment exactly like the two spec\n * templates', and an author who copies that block out and archives without\n * editing it would publish `TODO — name the promise` into a LIVING capability\n * document — which is strictly worse than the service case this gate was built\n * for: a capability document outlives every service that realizes it, and a\n * `Realizes:` line pointed at the placeholder's id would then be a join to a\n * promise nobody wrote. The capability deltas are read HERE rather than handed\n * in through `AuthoringScope`, because this is a walk over the feature's own\n * documents and the scope already names the feature; the read is one\n * `existsSync` for the fleets that have not adopted the axis, and the memo when\n * a context was threaded.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { Elem } from \"../../c4/likec4.js\";\nimport type { PathableService } from \"../../kernel/ids/service.js\";\nimport type { Issue } from \"../../vocabulary/issue.js\";\nimport type { FleetContext } from \"../../fleet-context.js\";\nimport type { Requirement } from \"../../document/spec.js\";\nimport { parseRequirements } from \"../../document/parse.js\";\nimport { featureCapabilityDeltas } from \"../../capabilities/delta/tree.js\";\nimport { featurePaths, featureSpecPaths, SPEC_AXES } from \"../../repo/paths.js\";\nimport type { FeatureDir } from \"../../kernel/ids/dirs.js\";\nimport {\n BODY_SENTINELS,\n HEADING_SENTINELS,\n SERVICE_DESCRIPTION_SENTINEL,\n} from \"./sentinels.js\";\n\n/** What the placeholder gate reads: the feature's own documents, nothing else. */\nexport interface AuthoringScope {\n featureDir: FeatureDir;\n featureId: string;\n /**\n * Every element whose authored block the landscape merge would SPLICE — the\n * tagged elements and everything nested inside their blocks. The scope is\n * the splice's, not the tag's, for `c4.service-binding-invalid`'s reason\n * (coherence.ts): the merge carries a tagged element's block over byte for\n * byte, untagged children included, so a TODO description on a nested child\n * reaches the fleet map exactly as its tagged parent's would.\n */\n splicedEls: Elem[];\n svcNames: PathableService[];\n context?: FleetContext;\n}\n\nexport async function authoringIssues(scope: AuthoringScope): Promise<Issue[]> {\n const issues: Issue[] = [];\n await intentIssue(scope, issues);\n\n for (const e of scope.splicedEls) {\n if (e.description !== SERVICE_DESCRIPTION_SENTINEL) continue;\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"scaffold.placeholder\",\n message: `delta.likec4: '${e.title}' (${e.id}) still carries the scaffolded description '${SERVICE_DESCRIPTION_SENTINEL}' — the merge would publish it to the fleet map. Say what the service owns, or archive with --approve.`,\n });\n }\n\n for (const svc of scope.svcNames) {\n const paths = featureSpecPaths(scope.featureDir, svc);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n const found = unauthored(await requirementsOf(scope, path));\n if (found.size === 0) continue;\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"scaffold.placeholder\",\n subject: svc,\n message: `${svc}: ${axis.file} still carries scaffold placeholder(s) nobody authored — ${quote(found)}. The merge would write them into the living spec as requirements somebody meant. Replace them, or archive with --approve.`,\n });\n }\n }\n\n // The business corpus. `subject` is the capability id — a THIRD provenance\n // for this code's subject, after a service id and none at all — and the\n // artifact table resolves it by MEMBERSHIP against the capability documents\n // the feature carries (`status/feature/artifacts.ts`), the same resolution\n // every `delta.*` issue on this axis already relies on. So the capability's\n // own row turns draft, and a service row does too only where a fleet holds a\n // service and a capability of one name, which is that module's deliberately\n // pessimistic answer rather than a mis-attribution here.\n const capabilities =\n scope.context === undefined\n ? await featureCapabilityDeltas(scope.featureDir)\n : await scope.context.featureCapabilityDeltas(scope.featureDir);\n for (const doc of capabilities.docs) {\n const found = unauthored(await requirementsOf(scope, doc.spec));\n if (found.size === 0) continue;\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"scaffold.placeholder\",\n subject: doc.id,\n message: `capability ${doc.id}: spec.md still carries scaffold placeholder(s) nobody authored — ${quote(found)}. The merge would write them into the living capabilities/${doc.id}/spec.md as promises somebody meant — and a capability document outlives every service that realizes it. Replace them, or archive with --approve.`,\n });\n }\n return issues;\n}\n\n/** One delta document's requirements, through the invocation's read index when there is one. */\nasync function requirementsOf(scope: AuthoringScope, path: string): Promise<Requirement[]> {\n if (scope.context === undefined) return parseRequirements(await readFile(path, \"utf8\"));\n return scope.context.readRequirements(path);\n}\n\n/**\n * The scaffold strings a delta's MERGING requirements still carry.\n *\n * ADDED/MODIFIED only: those are the texts this MERGE would write. A BASE\n * requirement is a quote of the living state — if a sentinel sits there, some\n * earlier archive published it and this feature is not the author to gate on it\n * — and a REMOVED one is leaving the document anyway.\n */\nfunction unauthored(reqs: Requirement[]): Set<string> {\n const found = new Set<string>();\n for (const r of reqs) {\n if (r.kind === \"ADDED\" || r.kind === \"MODIFIED\") sentinelsIn(r, found);\n }\n return found;\n}\n\n/** The found sentinels as a message fragment — one spelling, so the two corpora read alike. */\nfunction quote(found: ReadonlySet<string>): string {\n return [...found].map((s) => `'${s}'`).join(\", \");\n}\n\n/** The exact template strings this requirement still carries, if any. */\nfunction sentinelsIn(r: Requirement, found: Set<string>): void {\n for (const heading of HEADING_SENTINELS) {\n if (r.name === heading) found.add(heading);\n for (const s of r.scenarios) {\n if (s.name === heading) found.add(heading);\n }\n }\n const body = [r.text.join(\"\\n\"), ...r.scenarios.map((s) => s.lines.join(\"\\n\"))].join(\"\\n\");\n for (const marker of BODY_SENTINELS) {\n if (body.includes(marker)) found.add(marker);\n }\n}\n\n/**\n * Does intent.md SAY anything? The scaffold's Why/Scope bodies are HTML\n * comments, so \"strip frontmatter, comments and headings, look for prose\" is\n * exactly the question \"did a person write the sentence the reviewer reads\n * first\". A missing file gates the same way: a feature with no stated intent\n * is not less anonymous for not having the file.\n */\nasync function intentIssue(scope: AuthoringScope, issues: Issue[]): Promise<void> {\n const path = featurePaths(scope.featureDir).intent;\n const empty = { severity: \"warn\", gates: true, code: \"intent.empty\" } as const;\n if (!existsSync(path)) {\n issues.push({\n ...empty,\n message: `intent.md is missing — nothing says why ${scope.featureId} exists. Write the Why before archiving it into the living docs, or archive with --approve.`,\n });\n return;\n }\n if (hasProse(await readFile(path, \"utf8\"))) return;\n issues.push({\n ...empty,\n message: `intent.md says nothing yet — its Why and Scope are still the scaffold's comments. Write why ${scope.featureId} exists before archiving it into the living docs, or archive with --approve.`,\n });\n}\n\n/** Is there any authored text outside frontmatter, HTML comments and headings? */\nfunction hasProse(text: string): boolean {\n // Normalized BEFORE the fence regex, the discipline document/parse.ts\n // applies to every other markdown read: `---\\r` is not `---`, so a CRLF\n // rewrite (or a BOM some Windows editor prefixed) kept the frontmatter,\n // which then counted as prose — and the untouched scaffold archived at\n // exit 0 through the exact gate built to refuse it.\n // The BOM is spelled as an escape, never as the raw character — a raw one is\n // invisible in review and to grep, which is how it would survive a rewrite.\n let body = text.replace(/^\\uFEFF/, \"\").replace(/\\r\\n/g, \"\\n\");\n // Frontmatter: the leading `---` block only, exactly where the scaffold puts it.\n const fm = /^---\\n[\\s\\S]*?\\n---\\n/.exec(body);\n if (fm !== null) body = body.slice(fm[0].length);\n body = body.replace(/<!--[\\s\\S]*?-->/g, \"\");\n return body\n .split(\"\\n\")\n .some((line) => line.trim().length > 0 && !line.trimStart().startsWith(\"#\"));\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The placeholder strings `loam new` scaffolds, spelled ONCE.
|
|
3
|
+
*
|
|
4
|
+
* Both sides of the placeholder gate read this module: the templates in
|
|
5
|
+
* `commands/new/templates.ts` write these exact strings, and the coherence
|
|
6
|
+
* check in `./scaffold.ts` refuses to archive them. Spelling a sentinel in two
|
|
7
|
+
* places is how the gate stops matching the scaffold — a one-character rewording
|
|
8
|
+
* of a template would silently retire the check that guards it.
|
|
9
|
+
*
|
|
10
|
+
* Detection is EXACT on purpose. A requirement whose author genuinely writes
|
|
11
|
+
* "TODO" in prose is that author's call to make; only the strings loam itself
|
|
12
|
+
* scaffolded are ones nobody wrote.
|
|
13
|
+
*/
|
|
14
|
+
/** The scaffolded business-requirement heading (`specs/<svc>/spec.md`). */
|
|
15
|
+
export const REQUIREMENT_SENTINEL = "TODO — name the behaviour";
|
|
16
|
+
/** The scaffolded architecture-requirement heading (`specs/<svc>/arch.spec.md`). */
|
|
17
|
+
export const ARCH_REQUIREMENT_SENTINEL = "TODO — name the architectural obligation";
|
|
18
|
+
/**
|
|
19
|
+
* The scaffolded capability-requirement heading
|
|
20
|
+
* (`features/<FEAT>/capabilities/<id>/spec.md`).
|
|
21
|
+
*
|
|
22
|
+
* Its own string rather than a reuse of `REQUIREMENT_SENTINEL`, because the two
|
|
23
|
+
* documents ask for different things and the heading is where the template says
|
|
24
|
+
* so: a service requirement names a BEHAVIOUR the service performs, a capability
|
|
25
|
+
* requirement names a PROMISE the fleet keeps. A shared "TODO — name the
|
|
26
|
+
* behaviour" would scaffold the business document with the altitude
|
|
27
|
+
* `capability.requirement-service-scoped` exists to refuse.
|
|
28
|
+
*/
|
|
29
|
+
export const CAPABILITY_REQUIREMENT_SENTINEL = "TODO — name the promise";
|
|
30
|
+
/** The scaffolded scenario heading, shared by both spec axes. */
|
|
31
|
+
export const SCENARIO_SENTINEL = "TODO — name the case";
|
|
32
|
+
/** The scaffolded description on a `--new-service` element in delta.likec4. */
|
|
33
|
+
export const SERVICE_DESCRIPTION_SENTINEL = "TODO — what this service owns";
|
|
34
|
+
/** The scaffolded SHALL fill-in on the business axis. */
|
|
35
|
+
export const SHALL_SENTINEL = "<observable behaviour, testable without reading the code>";
|
|
36
|
+
/** The scaffolded SHALL fill-in on the architecture axis. */
|
|
37
|
+
export const ARCH_SHALL_SENTINEL = "<the operational/integration behaviour, observable in test>";
|
|
38
|
+
/** The scaffolded SHALL fill-in on the business axis — a promise, not a mechanism. */
|
|
39
|
+
export const CAPABILITY_SHALL_SENTINEL = "<the promise, observable outside the fleet — a customer could check it>";
|
|
40
|
+
/** The scaffolded Given/When/Then fill-ins, shared by both spec axes. Three
|
|
41
|
+
* constants rather than one keyed object because a `then` property makes an
|
|
42
|
+
* object thenable — one accidental `await` away from a hang the linter
|
|
43
|
+
* refuses outright. */
|
|
44
|
+
export const GIVEN_SENTINEL = "<the starting state>";
|
|
45
|
+
export const WHEN_SENTINEL = "<the trigger>";
|
|
46
|
+
export const THEN_SENTINEL = "<the observable outcome>";
|
|
47
|
+
/**
|
|
48
|
+
* The angle-bracket fill-ins inside scaffolded requirement bodies and steps.
|
|
49
|
+
* Any one of them surviving to archive means the line around it was never
|
|
50
|
+
* authored — the brackets are the template telling the author what to replace.
|
|
51
|
+
*/
|
|
52
|
+
export const BODY_SENTINELS = [
|
|
53
|
+
SHALL_SENTINEL,
|
|
54
|
+
ARCH_SHALL_SENTINEL,
|
|
55
|
+
CAPABILITY_SHALL_SENTINEL,
|
|
56
|
+
GIVEN_SENTINEL,
|
|
57
|
+
WHEN_SENTINEL,
|
|
58
|
+
THEN_SENTINEL,
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Every scaffolded heading a requirement or scenario may still carry.
|
|
62
|
+
*
|
|
63
|
+
* The list is not partitioned by which document scaffolded which string, and
|
|
64
|
+
* that is deliberate: the gate asks "did anybody author this line", and a
|
|
65
|
+
* capability heading pasted into a service delta was authored exactly as little
|
|
66
|
+
* as it was in the document it came from.
|
|
67
|
+
*/
|
|
68
|
+
export const HEADING_SENTINELS = [
|
|
69
|
+
REQUIREMENT_SENTINEL,
|
|
70
|
+
ARCH_REQUIREMENT_SENTINEL,
|
|
71
|
+
CAPABILITY_REQUIREMENT_SENTINEL,
|
|
72
|
+
SCENARIO_SENTINEL,
|
|
73
|
+
];
|
|
74
|
+
//# sourceMappingURL=sentinels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentinels.js","sourceRoot":"","sources":["../../../../src/core/coherence/authoring/sentinels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAC;AAEhE,oFAAoF;AACpF,MAAM,CAAC,MAAM,yBAAyB,GAAG,0CAA0C,CAAC;AAEpF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,yBAAyB,CAAC;AAEzE,iEAAiE;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAExD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,4BAA4B,GAAG,+BAA+B,CAAC;AAE5E,yDAAyD;AACzD,MAAM,CAAC,MAAM,cAAc,GAAG,2DAA2D,CAAC;AAE1F,6DAA6D;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAG,6DAA6D,CAAC;AAEjG,sFAAsF;AACtF,MAAM,CAAC,MAAM,yBAAyB,GAAG,yEAAyE,CAAC;AAEnH;;;uBAGuB;AACvB,MAAM,CAAC,MAAM,cAAc,GAAG,sBAAsB,CAAC;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,CAAC,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAExD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,cAAc;IACd,mBAAmB;IACnB,yBAAyB;IACzB,cAAc;IACd,aAAa;IACb,aAAa;CACd,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,oBAAoB;IACpB,yBAAyB;IACzB,+BAA+B;IAC/B,iBAAiB;CAClB,CAAC","sourcesContent":["/**\n * The placeholder strings `loam new` scaffolds, spelled ONCE.\n *\n * Both sides of the placeholder gate read this module: the templates in\n * `commands/new/templates.ts` write these exact strings, and the coherence\n * check in `./scaffold.ts` refuses to archive them. Spelling a sentinel in two\n * places is how the gate stops matching the scaffold — a one-character rewording\n * of a template would silently retire the check that guards it.\n *\n * Detection is EXACT on purpose. A requirement whose author genuinely writes\n * \"TODO\" in prose is that author's call to make; only the strings loam itself\n * scaffolded are ones nobody wrote.\n */\n\n/** The scaffolded business-requirement heading (`specs/<svc>/spec.md`). */\nexport const REQUIREMENT_SENTINEL = \"TODO — name the behaviour\";\n\n/** The scaffolded architecture-requirement heading (`specs/<svc>/arch.spec.md`). */\nexport const ARCH_REQUIREMENT_SENTINEL = \"TODO — name the architectural obligation\";\n\n/**\n * The scaffolded capability-requirement heading\n * (`features/<FEAT>/capabilities/<id>/spec.md`).\n *\n * Its own string rather than a reuse of `REQUIREMENT_SENTINEL`, because the two\n * documents ask for different things and the heading is where the template says\n * so: a service requirement names a BEHAVIOUR the service performs, a capability\n * requirement names a PROMISE the fleet keeps. A shared \"TODO — name the\n * behaviour\" would scaffold the business document with the altitude\n * `capability.requirement-service-scoped` exists to refuse.\n */\nexport const CAPABILITY_REQUIREMENT_SENTINEL = \"TODO — name the promise\";\n\n/** The scaffolded scenario heading, shared by both spec axes. */\nexport const SCENARIO_SENTINEL = \"TODO — name the case\";\n\n/** The scaffolded description on a `--new-service` element in delta.likec4. */\nexport const SERVICE_DESCRIPTION_SENTINEL = \"TODO — what this service owns\";\n\n/** The scaffolded SHALL fill-in on the business axis. */\nexport const SHALL_SENTINEL = \"<observable behaviour, testable without reading the code>\";\n\n/** The scaffolded SHALL fill-in on the architecture axis. */\nexport const ARCH_SHALL_SENTINEL = \"<the operational/integration behaviour, observable in test>\";\n\n/** The scaffolded SHALL fill-in on the business axis — a promise, not a mechanism. */\nexport const CAPABILITY_SHALL_SENTINEL = \"<the promise, observable outside the fleet — a customer could check it>\";\n\n/** The scaffolded Given/When/Then fill-ins, shared by both spec axes. Three\n * constants rather than one keyed object because a `then` property makes an\n * object thenable — one accidental `await` away from a hang the linter\n * refuses outright. */\nexport const GIVEN_SENTINEL = \"<the starting state>\";\nexport const WHEN_SENTINEL = \"<the trigger>\";\nexport const THEN_SENTINEL = \"<the observable outcome>\";\n\n/**\n * The angle-bracket fill-ins inside scaffolded requirement bodies and steps.\n * Any one of them surviving to archive means the line around it was never\n * authored — the brackets are the template telling the author what to replace.\n */\nexport const BODY_SENTINELS: readonly string[] = [\n SHALL_SENTINEL,\n ARCH_SHALL_SENTINEL,\n CAPABILITY_SHALL_SENTINEL,\n GIVEN_SENTINEL,\n WHEN_SENTINEL,\n THEN_SENTINEL,\n];\n\n/**\n * Every scaffolded heading a requirement or scenario may still carry.\n *\n * The list is not partitioned by which document scaffolded which string, and\n * that is deliberate: the gate asks \"did anybody author this line\", and a\n * capability heading pasted into a service delta was authored exactly as little\n * as it was in the document it came from.\n */\nexport const HEADING_SENTINELS: readonly string[] = [\n REQUIREMENT_SENTINEL,\n ARCH_REQUIREMENT_SENTINEL,\n CAPABILITY_REQUIREMENT_SENTINEL,\n SCENARIO_SENTINEL,\n];\n"]}
|