@ybotok/loam 0.1.0-beta.3 → 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 +2548 -86
- 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 +2355 -256
- 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 +72 -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/{agents-stamp.js → agent/agents-stamp.js} +50 -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.js → asyncapi/read.js} +95 -109
- 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 +7 -64
- 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 +120 -78
- 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 -295
- 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 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- 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 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- 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/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- 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 -596
- 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 -489
- 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 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- 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 -1132
- 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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
11
11
|
import { isMap, parse, parseDocument } from "yaml";
|
|
12
|
-
import { decodeDocument, NotUtf8DocumentError } from "
|
|
12
|
+
import { decodeDocument, NotUtf8DocumentError } from "../kernel/document-bytes.js";
|
|
13
13
|
/**
|
|
14
14
|
* Drop a leading byte-order mark.
|
|
15
15
|
*
|
|
@@ -132,8 +132,24 @@ export function listField(fm, key) {
|
|
|
132
132
|
* A document with no frontmatter gets one, and the text that was there becomes
|
|
133
133
|
* the body. A header that does not parse (or is not a mapping) is replaced
|
|
134
134
|
* rather than merged into — it cannot be edited without guessing what it meant.
|
|
135
|
+
*
|
|
136
|
+
* Keys named in `remove` are DELETED. That is not a convenience: `loam vouch`
|
|
137
|
+
* stamps `vouch_scope` when a person read only a sample of the document, and
|
|
138
|
+
* a full vouch afterwards has to take that claim back out. Left behind, a
|
|
139
|
+
* stale scope would keep a fully-read document reading as sampled forever —
|
|
140
|
+
* on `loam list`, in `loam validate`'s `sources.sampled-vouch`, and in the
|
|
141
|
+
* frontmatter a person opens — which is the same class of lie as a stamp with
|
|
142
|
+
* nothing behind it, pointing the other way.
|
|
143
|
+
*
|
|
144
|
+
* Deletion is a separate LIST rather than a null value in `fields`, and the
|
|
145
|
+
* difference is the compiler's: `string | null` is the most common field shape
|
|
146
|
+
* in this codebase (`stringField(fm, k) ?? null` appears in a dozen readers),
|
|
147
|
+
* so a writer that stamped a field it had just read back — `{ owner }` — would
|
|
148
|
+
* silently delete `owner` on every document that never had one, where today
|
|
149
|
+
* that line does not compile. Clearing a key is something a call site says,
|
|
150
|
+
* never something a value happens to be.
|
|
135
151
|
*/
|
|
136
|
-
export function withFrontmatterFields(source, fields) {
|
|
152
|
+
export function withFrontmatterFields(source, fields, remove = []) {
|
|
137
153
|
// Stamping a BOM-prefixed document drops the BOM. It sits above the header, so
|
|
138
154
|
// the promise about the body still holds — and leaving it would re-break the
|
|
139
155
|
// file for every parser anchored at position 0, including ours.
|
|
@@ -144,6 +160,15 @@ export function withFrontmatterFields(source, fields) {
|
|
|
144
160
|
const doc = editable ? parsed : parseDocument("");
|
|
145
161
|
for (const [key, value] of Object.entries(fields))
|
|
146
162
|
doc.set(key, value);
|
|
163
|
+
// Guarded, not because a missing key is a problem — deleting one that is not
|
|
164
|
+
// there is a no-op — but because yaml's `Document.delete` THROWS ("Expected
|
|
165
|
+
// a YAML collection as document contents") when the document has no mapping
|
|
166
|
+
// at all: the empty header of a document that had none, and the replacement
|
|
167
|
+
// built for a header that would not parse. Both are states this writer
|
|
168
|
+
// reaches on purpose, and in both there is nothing to clear.
|
|
169
|
+
if (isMap(doc.contents))
|
|
170
|
+
for (const key of remove)
|
|
171
|
+
doc.delete(key);
|
|
147
172
|
const yamlText = doc.toString();
|
|
148
173
|
if (at === null)
|
|
149
174
|
return `---\n${yamlText}---\n\n${md}`;
|
|
@@ -196,4 +221,25 @@ export async function readFrontmatter(path) {
|
|
|
196
221
|
/** The statuses each kind of artifact may carry. */
|
|
197
222
|
export const SERVICE_STATUSES = ["draft", "verified"];
|
|
198
223
|
export const FEATURE_STATUSES = ["proposed", "in_progress", "built", "done"];
|
|
224
|
+
/**
|
|
225
|
+
* A document's prose with any leading frontmatter block dropped — what
|
|
226
|
+
* `loam delta` and `loam context` print as a feature's intent.
|
|
227
|
+
*
|
|
228
|
+
* Moved verbatim from `commands/delta/slices.ts` when the context pack became
|
|
229
|
+
* its second caller. It deliberately keeps its own, looser fence rule
|
|
230
|
+
* (`\n---` as a substring, not `bounds()`'s line-exact match) rather than
|
|
231
|
+
* delegating to `parseFrontmatter(md).body`: the two agree on every header
|
|
232
|
+
* loam writes, but the delta brief's output is pinned byte-for-byte by its
|
|
233
|
+
* tests, and unifying the rules is a behaviour change to make deliberately,
|
|
234
|
+
* not as a side effect of a move.
|
|
235
|
+
*/
|
|
236
|
+
export function stripFrontmatter(md) {
|
|
237
|
+
if (!md.startsWith("---"))
|
|
238
|
+
return md;
|
|
239
|
+
const close = md.indexOf("\n---", 3);
|
|
240
|
+
if (close === -1)
|
|
241
|
+
return md;
|
|
242
|
+
const nl = md.indexOf("\n", close + 1);
|
|
243
|
+
return nl === -1 ? "" : md.slice(nl + 1).trimStart();
|
|
244
|
+
}
|
|
199
245
|
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../../src/core/document/frontmatter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * YAML frontmatter — the provenance of an artifact.\n *\n * `status` says whether a human has vouched for the document, `sources` says\n * which code it was written from. When the prose is written by an agent, these\n * are the only deterministic tie to reality: everything else loam checks is\n * internal consistency, which a fluent enough fiction satisfies perfectly.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { isMap, parse, parseDocument } from \"yaml\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../kernel/document-bytes.js\";\n\nexport interface Frontmatter {\n /** True when a terminated `---` block was found, even if it was empty. */\n present: boolean;\n /**\n * True when the block was found but its YAML cannot be read as a mapping —\n * a parse error, or a scalar/sequence document. `data` is then {}, which is\n * NOT the same fact as an empty header: the fields are unreadable, not\n * absent, and a checker that grades them \"missing\" sends the author adding\n * lines to a block YAML refuses to parse. Validate turns this flag into\n * `frontmatter.malformed`; the writer (withFrontmatterFields) has its own\n * replace-don't-merge rule for the same state.\n */\n malformed: boolean;\n /**\n * Why the FILE could not be read as text at all, or absent when it could —\n * set only by `readFrontmatter`, since a caller that already holds a string\n * has nothing left to decode.\n *\n * A third state, not a shade of the other two: `present: false` here means\n * nobody could look, where everywhere else it means nobody wrote a header.\n * A checker must ask this first, or it reports \"owner/status/sources missing\"\n * over a file whose lines are right there — the same false cascade\n * `malformed` exists to stop, one layer lower down.\n */\n unreadable?: string;\n /** The parsed mapping, or {} when absent or malformed. */\n data: Record<string, unknown>;\n /** The document with the frontmatter removed. */\n body: string;\n}\n\n/** Where the header sits in the raw text. */\ninterface Bounds {\n /** First character of the YAML text. */\n start: number;\n /** One past its last character — the newline before the closing fence included. */\n end: number;\n /** First character of the body. */\n bodyStart: number;\n}\n\n/**\n * Drop a leading byte-order mark.\n *\n * With one in front, the opening `---` is no longer at position 0, so the whole\n * header reads as absent: a documented artifact silently becomes an\n * undocumented one — no owner, no status, no `sources` — and nothing says why.\n * The same blindness `spec.ts` had at `^##`.\n *\n * Only at position 0: elsewhere U+FEFF is a zero-width no-break space and is the\n * author's content. Callers strip before measuring, so the offsets `bounds`\n * returns index the same string the caller then slices.\n */\nfunction stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\n/**\n * A fence is a LINE of exactly three dashes. Trailing whitespace is tolerated\n * (`\\s` also swallows the CR of a CRLF file), trailing text is not: `--- title`\n * is prose, `----` is a horizontal rule. Matching the substring `---` instead —\n * as this used to — made both of those fences, so a stray rule closed the\n * header early and a `--- note` closer silently ate its own trailing text.\n */\nconst FENCE_RE = /^---\\s*$/;\n\n/**\n * Locate the frontmatter block. The opener is the very first line, the closer\n * the first subsequent fence line — a later horizontal rule is body, not a\n * second fence. No closer means an unterminated opener: not frontmatter at all.\n *\n * The reader and the writer both go through this, so they can never disagree\n * about which bytes are the header — the writer's promise to leave the body\n * untouched depends on that being one decision, not two.\n */\nfunction bounds(md: string): Bounds | null {\n const firstNl = md.indexOf(\"\\n\");\n if (!FENCE_RE.test(firstNl === -1 ? md : md.slice(0, firstNl))) return null;\n // A lone `---` with nothing after it is an unterminated opener.\n if (firstNl === -1) return null;\n const start = firstNl + 1;\n for (let lineStart = start; ; ) {\n const nl = md.indexOf(\"\\n\", lineStart);\n const line = nl === -1 ? md.slice(lineStart) : md.slice(lineStart, nl);\n if (FENCE_RE.test(line)) {\n // An empty block (`---\\n---`) has no text between the fences: start === end.\n return { start, end: lineStart, bodyStart: nl === -1 ? md.length : nl + 1 };\n }\n if (nl === -1) return null;\n lineStart = nl + 1;\n }\n}\n\n/**\n * Split a markdown document into its frontmatter and body. A document with no\n * frontmatter is returned whole rather than truncated.\n */\nexport function parseFrontmatter(source: string): Frontmatter {\n const md = stripBom(source);\n const at = bounds(md);\n if (at === null) return { present: false, malformed: false, data: {}, body: md };\n\n const yamlText = md.slice(at.start, at.end);\n const body = md.slice(at.bodyStart).trimStart();\n\n let data: Record<string, unknown> = {};\n let malformed = false;\n try {\n const parsed: unknown = parse(yamlText);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n data = parsed as Record<string, unknown>;\n } else if (parsed !== null) {\n // A scalar or sequence header holds no fields to read — as unreadable as\n // broken YAML. null is the empty block (`---\\n---`): legal, and empty.\n malformed = true;\n }\n } catch {\n // A bad header must not make the document unreadable (the body is still the\n // body, and never crashes a command) — but the failure travels as a flag,\n // so the caller can say `frontmatter.malformed` instead of the false\n // cascade \"owner/status missing\" the silent {} used to produce.\n malformed = true;\n }\n return { present: true, malformed, data, body };\n}\n\n/** A field as text. Non-string scalars (a bare date, a number) are stringified. */\nexport function stringField(fm: Frontmatter, key: string): string | undefined {\n const v = fm.data[key];\n if (v === undefined || v === null) return undefined;\n if (typeof v === \"string\") return v;\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n if (v instanceof Date) return v.toISOString().slice(0, 10);\n return undefined;\n}\n\n/** A field as a list. A lone scalar counts as a list of one. */\nexport function listField(fm: Frontmatter, key: string): string[] {\n const v = fm.data[key];\n if (v === undefined || v === null) return [];\n if (Array.isArray(v)) return v.map((x) => String(x)).filter((s) => s.length > 0);\n const one = stringField(fm, key);\n return one === undefined ? [] : [one];\n}\n\n/**\n * Return `md` with these frontmatter fields set and everything below the closing\n * fence byte-identical.\n *\n * The header is edited through yaml's document model rather than rebuilt from\n * the parsed data: keys keep their order and their comments, fields nobody named\n * are left exactly as they were, and a value that would read back as something\n * else — a digest of all digits, say — gets quoted for us. Stamping a document\n * is not a licence to reformat it: the diff a reviewer sees should be the lines\n * that changed, and nothing else.\n *\n * A document with no frontmatter gets one, and the text that was there becomes\n * the body. A header that does not parse (or is not a mapping) is replaced\n * rather than merged into — it cannot be edited without guessing what it meant.\n *\n * Keys named in `remove` are DELETED. That is not a convenience: `loam vouch`\n * stamps `vouch_scope` when a person read only a sample of the document, and\n * a full vouch afterwards has to take that claim back out. Left behind, a\n * stale scope would keep a fully-read document reading as sampled forever —\n * on `loam list`, in `loam validate`'s `sources.sampled-vouch`, and in the\n * frontmatter a person opens — which is the same class of lie as a stamp with\n * nothing behind it, pointing the other way.\n *\n * Deletion is a separate LIST rather than a null value in `fields`, and the\n * difference is the compiler's: `string | null` is the most common field shape\n * in this codebase (`stringField(fm, k) ?? null` appears in a dozen readers),\n * so a writer that stamped a field it had just read back — `{ owner }` — would\n * silently delete `owner` on every document that never had one, where today\n * that line does not compile. Clearing a key is something a call site says,\n * never something a value happens to be.\n */\nexport function withFrontmatterFields(\n source: string,\n fields: Record<string, string>,\n remove: readonly string[] = [],\n): string {\n // Stamping a BOM-prefixed document drops the BOM. It sits above the header, so\n // the promise about the body still holds — and leaving it would re-break the\n // file for every parser anchored at position 0, including ours.\n const md = stripBom(source);\n const at = bounds(md);\n const parsed = parseDocument(at === null ? \"\" : md.slice(at.start, at.end));\n const editable = parsed.errors.length === 0 && (parsed.contents === null || isMap(parsed.contents));\n const doc = editable ? parsed : parseDocument(\"\");\n\n for (const [key, value] of Object.entries(fields)) doc.set(key, value);\n // Guarded, not because a missing key is a problem — deleting one that is not\n // there is a no-op — but because yaml's `Document.delete` THROWS (\"Expected\n // a YAML collection as document contents\") when the document has no mapping\n // at all: the empty header of a document that had none, and the replacement\n // built for a header that would not parse. Both are states this writer\n // reaches on purpose, and in both there is nothing to clear.\n if (isMap(doc.contents)) for (const key of remove) doc.delete(key);\n const yamlText = doc.toString();\n\n if (at === null) return `---\\n${yamlText}---\\n\\n${md}`;\n return md.slice(0, at.start) + yamlText + md.slice(at.end);\n}\n\n/**\n * The document's body, byte-exact: everything after the frontmatter block —\n * the closing `---` line and its newline belong to the HEADER, every byte\n * after them (blank lines included) is body. A document with no frontmatter is\n * all body. This is what `content_digest` hashes, and it deliberately does NOT\n * go through `parseFrontmatter` (whose `body` is trimmed for callers that want\n * text): the digest's writer and its checker must slice the same bytes, so\n * both go through here.\n */\nexport function rawBody(source: string): string {\n const md = stripBom(source);\n const at = bounds(md);\n return at === null ? md : md.slice(at.bodyStart);\n}\n\n/**\n * Read a markdown file's frontmatter; a missing file reads as absent.\n *\n * Bytes in, decoded through `decodeDocument`, because `readFile(path, \"utf8\")`\n * turns a UTF-16 spec.md into mojibake whose opening `---` is no longer at\n * position 0 — so the header reads as ABSENT, and `status`, `owner` and\n * `sources` all come back missing over a file that spells every one of them.\n * The same blindness `stripBom` exists to stop, one encoding wider.\n *\n * The failure travels as a FLAG rather than an exception, which is how every\n * reader here degrades (`readOpenapi`'s `unreadable`) and is load-bearing at\n * exactly this call: this is the read behind the fleet ENUMERATION\n * (repo.ts `listServices`), so throwing would turn one PowerShell-saved file\n * into a `loam list` that reports nothing about the other 119 services.\n * Parsers never diagnose; a checker grades the flag.\n */\nexport async function readFrontmatter(path: string): Promise<Frontmatter> {\n if (!existsSync(path)) return { present: false, malformed: false, data: {}, body: \"\" };\n let text: string;\n try {\n text = decodeDocument(await readFile(path), path);\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return { present: false, malformed: false, unreadable: err.message, data: {}, body: \"\" };\n }\n return parseFrontmatter(text);\n}\n\n/** The statuses each kind of artifact may carry. */\nexport const SERVICE_STATUSES = [\"draft\", \"verified\"] as const;\nexport const FEATURE_STATUSES = [\"proposed\", \"in_progress\", \"built\", \"done\"] as const;\n\n/**\n * A document's prose with any leading frontmatter block dropped — what\n * `loam delta` and `loam context` print as a feature's intent.\n *\n * Moved verbatim from `commands/delta/slices.ts` when the context pack became\n * its second caller. It deliberately keeps its own, looser fence rule\n * (`\\n---` as a substring, not `bounds()`'s line-exact match) rather than\n * delegating to `parseFrontmatter(md).body`: the two agree on every header\n * loam writes, but the delta brief's output is pinned byte-for-byte by its\n * tests, and unifying the rules is a behaviour change to make deliberately,\n * not as a side effect of a move.\n */\nexport function stripFrontmatter(md: string): string {\n if (!md.startsWith(\"---\")) return md;\n const close = md.indexOf(\"\\n---\", 3);\n if (close === -1) return md;\n const nl = md.indexOf(\"\\n\", close + 1);\n return nl === -1 ? \"\" : md.slice(nl + 1).trimStart();\n}\n"]}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The requirement BODY-LINE grammar, and what a key nearly was.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `commands/validate/service/specs.ts`, which reads and grades a
|
|
5
|
+
* service's two spec axes — a different subject: that module is about one
|
|
6
|
+
* service's documents, this one is about the shape of a requirement body
|
|
7
|
+
* wherever it appears. Nothing here touches the filesystem or knows what a
|
|
8
|
+
* service is, which is what lets it sit in `core/` beside the parser whose
|
|
9
|
+
* patterns it borrows.
|
|
10
|
+
*
|
|
11
|
+
* Its sibling is `../scenarios.ts`: same shape (requirements in, findings out,
|
|
12
|
+
* no I/O), one level down the document — that one grades a scenario's steps,
|
|
13
|
+
* this one grades the keys in the body above them.
|
|
14
|
+
*/
|
|
15
|
+
import {} from "../../vocabulary/report.js";
|
|
16
|
+
import { BASED_ON_LINE_RE, CAPABILITY_LINE_RE, REALIZES_LINE_RE, REQUIREMENT_ID_LINE_RE, } from "../spec.js";
|
|
17
|
+
/**
|
|
18
|
+
* Every body-line directive the requirement grammar recognises, as the patterns
|
|
19
|
+
* that recognise them.
|
|
20
|
+
*
|
|
21
|
+
* The REGEXES are the recognition half and they are not a copy: three are the
|
|
22
|
+
* exported constants `core/document/spec.ts` owns, and the rest are the same
|
|
23
|
+
* literals `core/document/parse.ts` matches with. Recognition therefore cannot
|
|
24
|
+
* drift into calling a real directive unknown.
|
|
25
|
+
*
|
|
26
|
+
* The NAMES below are the did-you-mean half, and they are the half that could
|
|
27
|
+
* drift — so `test/spec-unknown-directive.test.ts` asserts that every name here,
|
|
28
|
+
* spelled as a line, is matched by one of the patterns above it. A name that
|
|
29
|
+
* stops being a directive fails that test rather than going on being suggested.
|
|
30
|
+
*/
|
|
31
|
+
const DIRECTIVE_PATTERNS = [
|
|
32
|
+
REQUIREMENT_ID_LINE_RE,
|
|
33
|
+
BASED_ON_LINE_RE,
|
|
34
|
+
CAPABILITY_LINE_RE,
|
|
35
|
+
REALIZES_LINE_RE,
|
|
36
|
+
/^\s*Operations?:\s*(.+?)\s*$/i,
|
|
37
|
+
/^\s*Covers?:\s*(.+?)\s*$/i,
|
|
38
|
+
/^\s*Publishes?:\s*(.+?)\s*$/i,
|
|
39
|
+
/^\s*Consumes?:\s*(.+?)\s*$/i,
|
|
40
|
+
/^\s*Requires?:\s*(.+?)\s*$/i,
|
|
41
|
+
];
|
|
42
|
+
/** The canonical spelling of each directive — what a near miss is measured against. */
|
|
43
|
+
export const DIRECTIVE_NAMES = [
|
|
44
|
+
"Requirement-ID",
|
|
45
|
+
"Based-On",
|
|
46
|
+
"Capabilities",
|
|
47
|
+
"Realizes",
|
|
48
|
+
"Operations",
|
|
49
|
+
"Covers",
|
|
50
|
+
"Publishes",
|
|
51
|
+
"Consumes",
|
|
52
|
+
"Requires",
|
|
53
|
+
];
|
|
54
|
+
/** A body line that LOOKS like a directive: a short capitalised key, a colon, a value. */
|
|
55
|
+
const CANDIDATE_LINE_RE = /^\s*([A-Za-z][A-Za-z-]{2,30}):\s*\S/;
|
|
56
|
+
/**
|
|
57
|
+
* A fenced block's opening or closing line — three or more backticks or
|
|
58
|
+
* tildes, optionally indented, optionally with an info string.
|
|
59
|
+
*
|
|
60
|
+
* A requirement body legitimately holds a fenced example, and inside one
|
|
61
|
+
* `Realises:` is a sample value rather than a misspelled directive. Grading it
|
|
62
|
+
* would make the check fire on documents that are exactly right, which is the
|
|
63
|
+
* one thing a near-miss guard may never do — a warning nobody can act on is
|
|
64
|
+
* worse than no warning. Tables need no equivalent: a `| Realises: |` cell
|
|
65
|
+
* begins with the pipe, so `CANDIDATE_LINE_RE` never matches it.
|
|
66
|
+
*/
|
|
67
|
+
const FENCE_LINE_RE = /^\s*(`{3,}|~{3,})/;
|
|
68
|
+
/**
|
|
69
|
+
* Levenshtein distance, capped — a local one rather than `closeIds`, and the
|
|
70
|
+
* difference matters here.
|
|
71
|
+
*
|
|
72
|
+
* `closeIds` accepts a shared three-character prefix, which is right for ids
|
|
73
|
+
* drawn from a known vocabulary the author was aiming at. Applied to arbitrary
|
|
74
|
+
* prose keys it fires on words that merely start alike: `Context:` would be
|
|
75
|
+
* reported as a near miss for `Consumes:`, in a check whose whole value is that
|
|
76
|
+
* it does not cry wolf. Edit distance separates the two cleanly — `Context` is
|
|
77
|
+
* five edits from `Consumes`, `Realises` is one from `Realizes`.
|
|
78
|
+
*/
|
|
79
|
+
function editDistance(a, b) {
|
|
80
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
81
|
+
for (let i = 1; i <= a.length; i++) {
|
|
82
|
+
const row = [i];
|
|
83
|
+
for (let j = 1; j <= b.length; j++) {
|
|
84
|
+
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
85
|
+
}
|
|
86
|
+
prev = row;
|
|
87
|
+
}
|
|
88
|
+
return prev[b.length];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* `spec.unknown-directive` — a body line whose key is one or two characters
|
|
92
|
+
* away from a directive and is not one.
|
|
93
|
+
*
|
|
94
|
+
* THE ONE PLACE THE CORPUS IS QUIETER THAN IT LOOKS. Every join in the product
|
|
95
|
+
* is an existence check on a parsed value, so a line the parser did not
|
|
96
|
+
* recognise produces no value, no join, and therefore no finding: `Realises:`,
|
|
97
|
+
* `Capabilties:` and `Require:` all read as ordinary prose, and every check
|
|
98
|
+
* downstream stays green because there is nothing to fail. This is the same
|
|
99
|
+
* judgement `obligation.unknown` already makes one axis over — "a mistyped tag
|
|
100
|
+
* reads exactly like a rule" — applied to the keys themselves.
|
|
101
|
+
*
|
|
102
|
+
* A WARNING, not an error, and the threshold is deliberately mean. A body line
|
|
103
|
+
* is free prose and a fleet may legitimately write `Owner:` or `Context:` in
|
|
104
|
+
* one; the check only speaks when the key is within two edits of a directive,
|
|
105
|
+
* which is close enough that a typo is far likelier than a coincidence. It
|
|
106
|
+
* gates nothing.
|
|
107
|
+
*/
|
|
108
|
+
export function unknownDirectiveFindings(reqs, target) {
|
|
109
|
+
const findings = [];
|
|
110
|
+
for (const r of reqs) {
|
|
111
|
+
if (r.kind === "REMOVED")
|
|
112
|
+
continue;
|
|
113
|
+
let inFence = false;
|
|
114
|
+
for (const line of r.text) {
|
|
115
|
+
if (FENCE_LINE_RE.test(line)) {
|
|
116
|
+
inFence = !inFence;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (inFence)
|
|
120
|
+
continue;
|
|
121
|
+
const candidate = CANDIDATE_LINE_RE.exec(line);
|
|
122
|
+
if (candidate === null)
|
|
123
|
+
continue;
|
|
124
|
+
if (DIRECTIVE_PATTERNS.some((re) => re.test(line)))
|
|
125
|
+
continue;
|
|
126
|
+
const key = candidate[1];
|
|
127
|
+
const near = DIRECTIVE_NAMES.filter((name) => editDistance(key.toLowerCase(), name.toLowerCase()) <= 2);
|
|
128
|
+
if (near.length === 0)
|
|
129
|
+
continue;
|
|
130
|
+
findings.push({
|
|
131
|
+
severity: "warn",
|
|
132
|
+
code: "spec.unknown-directive",
|
|
133
|
+
subject: target.subject,
|
|
134
|
+
message: `${target.where}: requirement '${r.name}' has a line beginning '${key}:', which is not a directive loam reads — ` +
|
|
135
|
+
`did you mean ${near.map((n) => `\`${n}:\``).join(" or ")}? ` +
|
|
136
|
+
"A key loam does not recognise is prose: the join it looks like simply does not exist, and every check over it " +
|
|
137
|
+
"stays green because there is nothing to fail. Correct the spelling, or reword the line so it does not read as a directive",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return findings;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=directives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directives.js","sourceRoot":"","sources":["../../../../src/core/document/grammar/directives.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The requirement BODY-LINE grammar, and what a key nearly was.\n *\n * Split out of `commands/validate/service/specs.ts`, which reads and grades a\n * service's two spec axes — a different subject: that module is about one\n * service's documents, this one is about the shape of a requirement body\n * wherever it appears. Nothing here touches the filesystem or knows what a\n * service is, which is what lets it sit in `core/` beside the parser whose\n * patterns it borrows.\n *\n * Its sibling is `../scenarios.ts`: same shape (requirements in, findings out,\n * no I/O), one level down the document — that one grades a scenario's steps,\n * this one grades the keys in the body above them.\n */\nimport { type Finding } from \"../../vocabulary/report.js\";\nimport {\n BASED_ON_LINE_RE,\n CAPABILITY_LINE_RE,\n REALIZES_LINE_RE,\n REQUIREMENT_ID_LINE_RE,\n type Requirement,\n} from \"../spec.js\";\n\n/**\n * Every body-line directive the requirement grammar recognises, as the patterns\n * that recognise them.\n *\n * The REGEXES are the recognition half and they are not a copy: three are the\n * exported constants `core/document/spec.ts` owns, and the rest are the same\n * literals `core/document/parse.ts` matches with. Recognition therefore cannot\n * drift into calling a real directive unknown.\n *\n * The NAMES below are the did-you-mean half, and they are the half that could\n * drift — so `test/spec-unknown-directive.test.ts` asserts that every name here,\n * spelled as a line, is matched by one of the patterns above it. A name that\n * stops being a directive fails that test rather than going on being suggested.\n */\nconst DIRECTIVE_PATTERNS: readonly RegExp[] = [\n REQUIREMENT_ID_LINE_RE,\n BASED_ON_LINE_RE,\n CAPABILITY_LINE_RE,\n REALIZES_LINE_RE,\n /^\\s*Operations?:\\s*(.+?)\\s*$/i,\n /^\\s*Covers?:\\s*(.+?)\\s*$/i,\n /^\\s*Publishes?:\\s*(.+?)\\s*$/i,\n /^\\s*Consumes?:\\s*(.+?)\\s*$/i,\n /^\\s*Requires?:\\s*(.+?)\\s*$/i,\n];\n\n/** The canonical spelling of each directive — what a near miss is measured against. */\nexport const DIRECTIVE_NAMES: readonly string[] = [\n \"Requirement-ID\",\n \"Based-On\",\n \"Capabilities\",\n \"Realizes\",\n \"Operations\",\n \"Covers\",\n \"Publishes\",\n \"Consumes\",\n \"Requires\",\n];\n\n/** A body line that LOOKS like a directive: a short capitalised key, a colon, a value. */\nconst CANDIDATE_LINE_RE = /^\\s*([A-Za-z][A-Za-z-]{2,30}):\\s*\\S/;\n\n/**\n * A fenced block's opening or closing line — three or more backticks or\n * tildes, optionally indented, optionally with an info string.\n *\n * A requirement body legitimately holds a fenced example, and inside one\n * `Realises:` is a sample value rather than a misspelled directive. Grading it\n * would make the check fire on documents that are exactly right, which is the\n * one thing a near-miss guard may never do — a warning nobody can act on is\n * worse than no warning. Tables need no equivalent: a `| Realises: |` cell\n * begins with the pipe, so `CANDIDATE_LINE_RE` never matches it.\n */\nconst FENCE_LINE_RE = /^\\s*(`{3,}|~{3,})/;\n\n/**\n * Levenshtein distance, capped — a local one rather than `closeIds`, and the\n * difference matters here.\n *\n * `closeIds` accepts a shared three-character prefix, which is right for ids\n * drawn from a known vocabulary the author was aiming at. Applied to arbitrary\n * prose keys it fires on words that merely start alike: `Context:` would be\n * reported as a near miss for `Consumes:`, in a check whose whole value is that\n * it does not cry wolf. Edit distance separates the two cleanly — `Context` is\n * five edits from `Consumes`, `Realises` is one from `Realizes`.\n */\nfunction editDistance(a: string, b: string): number {\n let prev = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i++) {\n const row = [i];\n for (let j = 1; j <= b.length; j++) {\n row[j] = Math.min(\n prev[j]! + 1,\n row[j - 1]! + 1,\n prev[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1),\n );\n }\n prev = row;\n }\n return prev[b.length]!;\n}\n\n/**\n * `spec.unknown-directive` — a body line whose key is one or two characters\n * away from a directive and is not one.\n *\n * THE ONE PLACE THE CORPUS IS QUIETER THAN IT LOOKS. Every join in the product\n * is an existence check on a parsed value, so a line the parser did not\n * recognise produces no value, no join, and therefore no finding: `Realises:`,\n * `Capabilties:` and `Require:` all read as ordinary prose, and every check\n * downstream stays green because there is nothing to fail. This is the same\n * judgement `obligation.unknown` already makes one axis over — \"a mistyped tag\n * reads exactly like a rule\" — applied to the keys themselves.\n *\n * A WARNING, not an error, and the threshold is deliberately mean. A body line\n * is free prose and a fleet may legitimately write `Owner:` or `Context:` in\n * one; the check only speaks when the key is within two edits of a directive,\n * which is close enough that a typo is far likelier than a coincidence. It\n * gates nothing.\n */\nexport function unknownDirectiveFindings(\n reqs: readonly Requirement[],\n target: { where: string; subject: string },\n): Finding[] {\n const findings: Finding[] = [];\n for (const r of reqs) {\n if (r.kind === \"REMOVED\") continue;\n let inFence = false;\n for (const line of r.text) {\n if (FENCE_LINE_RE.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) continue;\n const candidate = CANDIDATE_LINE_RE.exec(line);\n if (candidate === null) continue;\n if (DIRECTIVE_PATTERNS.some((re) => re.test(line))) continue;\n const key = candidate[1]!;\n const near = DIRECTIVE_NAMES.filter(\n (name) => editDistance(key.toLowerCase(), name.toLowerCase()) <= 2,\n );\n if (near.length === 0) continue;\n findings.push({\n severity: \"warn\",\n code: \"spec.unknown-directive\",\n subject: target.subject,\n message:\n `${target.where}: requirement '${r.name}' has a line beginning '${key}:', which is not a directive loam reads — ` +\n `did you mean ${near.map((n) => `\\`${n}:\\``).join(\" or \")}? ` +\n \"A key loam does not recognise is prose: the join it looks like simply does not exist, and every check over it \" +\n \"stays green because there is nothing to fail. Correct the spelling, or reword the line so it does not read as a directive\",\n });\n }\n }\n return findings;\n}\n"]}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown in: headings, fences, and the one `## Requirements` cut point.
|
|
3
|
+
*
|
|
4
|
+
* The cut point is spelled once here on purpose. The archive cut, archive's
|
|
5
|
+
* stray-requirement guard and the delta-shape exemption used to spell the same
|
|
6
|
+
* invariant three different ways, and one of them was a PREFIX match that also
|
|
7
|
+
* hit `## Requirements Extra`.
|
|
8
|
+
*
|
|
9
|
+
* Fence tracking is why this is not a set of regexes at the call sites: a
|
|
10
|
+
* `### Requirement:` inside a fenced code block is prose about a requirement,
|
|
11
|
+
* not one, and every heading walk here has to agree about that.
|
|
12
|
+
*/
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
14
|
+
import { decodeDocument } from "../kernel/document-bytes.js";
|
|
15
|
+
import { fenceTracker } from "../kernel/fences.js";
|
|
16
|
+
import { BASED_ON_LINE_RE, CAPABILITY_LINE_RE, KIND_RE, REALIZES_LINE_RE, REQUIREMENT_ID_LINE_RE, } from "./spec.js";
|
|
17
|
+
const REQ_RE = /^###\s+Requirement:\s*(.+?)\s*$/;
|
|
18
|
+
const SCN_RE = /^####\s+Scenario:\s*(.+?)\s*$/;
|
|
19
|
+
/**
|
|
20
|
+
* THE requirements section: the one H2 a living spec keeps its requirements
|
|
21
|
+
* under, the one section `loam archive` rewrites, and the one heading a delta
|
|
22
|
+
* may legally quote the living state beneath. One definition on purpose — the
|
|
23
|
+
* archive cut point, archive's stray-requirement guard, and the delta-shape
|
|
24
|
+
* exemption (core/delta/document.ts) used to spell this invariant three different ways,
|
|
25
|
+
* and the cut point's substring `indexOf("\n## Requirements")` was a PREFIX
|
|
26
|
+
* match that also hit `## Requirements Extra`. Full-line match on a trimmed
|
|
27
|
+
* heading, with KIND_RE's case and interior-whitespace tolerance.
|
|
28
|
+
*/
|
|
29
|
+
const REQUIREMENTS_SECTION_RE = /^##\s+Requirements\s*$/i;
|
|
30
|
+
/** Is this H2 heading line (as sectionHeadings/`Requirement.section` spell it) the `## Requirements` heading? */
|
|
31
|
+
export function isRequirementsHeading(heading) {
|
|
32
|
+
return REQUIREMENTS_SECTION_RE.test(heading.trim());
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Strip a leading UTF-8 BOM (U+FEFF), which editors on Windows write unasked.
|
|
36
|
+
*
|
|
37
|
+
* It has to happen here, at the two functions that take raw markdown, rather than
|
|
38
|
+
* at each `readFile` call: those are spread across half the commands, so any one of
|
|
39
|
+
* them could forget, and the failure is invisible. One byte was enough to void an
|
|
40
|
+
* entire delta — `KIND_RE` is anchored `^##`, delta files routinely open with
|
|
41
|
+
* `## MODIFIED Requirements` on line 1, and behind a BOM that line stopped being a
|
|
42
|
+
* heading, so every requirement in the file fell back to BASE and
|
|
43
|
+
* `applyRequirementDelta` skips BASE. Archive merged nothing and said nothing.
|
|
44
|
+
* OpenSpec's own reader strips it for exactly this reason.
|
|
45
|
+
*
|
|
46
|
+
* Only at position 0: elsewhere U+FEFF is a zero-width no-break space and belongs
|
|
47
|
+
* to the author's text.
|
|
48
|
+
*/
|
|
49
|
+
function stripBom(md) {
|
|
50
|
+
return md.charCodeAt(0) === 0xfeff ? md.slice(1) : md;
|
|
51
|
+
}
|
|
52
|
+
/** Every H2 heading outside a fenced block, with its 1-based line number. */
|
|
53
|
+
export function sectionHeadings(md) {
|
|
54
|
+
const fenced = fenceTracker();
|
|
55
|
+
const out = [];
|
|
56
|
+
stripBom(md).split(/\r?\n/).forEach((line, i) => {
|
|
57
|
+
if (fenced(line))
|
|
58
|
+
return;
|
|
59
|
+
if (/^##\s+/.test(line) && !line.startsWith("###"))
|
|
60
|
+
out.push({ text: line.trim(), line: i + 1 });
|
|
61
|
+
});
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Split a living spec around its requirements RUN — the stretch of
|
|
66
|
+
* `### Requirement:` blocks inside the `## Requirements` section — so a merge
|
|
67
|
+
* can rewrite exactly that and nothing else. All three slices reassemble the
|
|
68
|
+
* input byte-for-byte (`head + run + tail === text`):
|
|
69
|
+
*
|
|
70
|
+
* - `head` — everything before the first requirement in the section: the
|
|
71
|
+
* intro, the heading line itself, any prose under the heading.
|
|
72
|
+
* - `run` — from the first `### Requirement:` line to the section's end (the
|
|
73
|
+
* next `## ` heading, or EOF). Empty when the section holds no requirements.
|
|
74
|
+
* - `tail` — everything from the section's end onward.
|
|
75
|
+
*
|
|
76
|
+
* Returns null when the document has no `## Requirements` heading at all.
|
|
77
|
+
* Only the FIRST matching heading opens the section; a caller that must not
|
|
78
|
+
* lose content behind a duplicate (archive) counts matches via
|
|
79
|
+
* sectionHeadings() and refuses. Heading detection matches parseRequirements:
|
|
80
|
+
* fenced lines are never structure, and a leading BOM hides no heading — but
|
|
81
|
+
* the slices themselves are raw bytes, BOM and CRLF included.
|
|
82
|
+
*/
|
|
83
|
+
export function splitRequirementsSection(text) {
|
|
84
|
+
const fenced = fenceTracker();
|
|
85
|
+
let inSection = false;
|
|
86
|
+
let sectionStart = -1;
|
|
87
|
+
let runStart = -1;
|
|
88
|
+
let sectionEnd = text.length;
|
|
89
|
+
let offset = 0;
|
|
90
|
+
while (offset < text.length) {
|
|
91
|
+
const nl = text.indexOf("\n", offset);
|
|
92
|
+
const lineEnd = nl === -1 ? text.length : nl + 1;
|
|
93
|
+
let line = text.slice(offset, nl === -1 ? text.length : nl);
|
|
94
|
+
if (line.endsWith("\r"))
|
|
95
|
+
line = line.slice(0, -1);
|
|
96
|
+
// Position 0 only, mirroring stripBom: elsewhere U+FEFF is content.
|
|
97
|
+
if (offset === 0 && line.charCodeAt(0) === 0xfeff)
|
|
98
|
+
line = line.slice(1);
|
|
99
|
+
if (!fenced(line)) {
|
|
100
|
+
if (/^##\s+/.test(line) && !line.startsWith("###")) {
|
|
101
|
+
if (inSection) {
|
|
102
|
+
sectionEnd = offset;
|
|
103
|
+
inSection = false;
|
|
104
|
+
}
|
|
105
|
+
else if (sectionStart === -1 && isRequirementsHeading(line)) {
|
|
106
|
+
sectionStart = offset;
|
|
107
|
+
inSection = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (inSection && runStart === -1 && REQ_RE.test(line)) {
|
|
111
|
+
runStart = offset;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
offset = lineEnd;
|
|
115
|
+
}
|
|
116
|
+
if (sectionStart === -1)
|
|
117
|
+
return null;
|
|
118
|
+
const cut = runStart === -1 ? sectionEnd : runStart;
|
|
119
|
+
return { head: text.slice(0, cut), run: text.slice(cut, sectionEnd), tail: text.slice(sectionEnd) };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A requirement document from disk as text — decoded, or refused by name.
|
|
123
|
+
*
|
|
124
|
+
* The read that belongs with the parser, because `readFile(path, "utf8")` and
|
|
125
|
+
* `parseRequirements` together are a trap: a UTF-16 spec.md decodes to a string
|
|
126
|
+
* in which no line matches `^###\s+Requirement:`, so the parser returns [] and
|
|
127
|
+
* the document grades as an empty baseline instead of an unreadable one. Every
|
|
128
|
+
* question loam asks of a spec — is it covered, does the delta merge, what does
|
|
129
|
+
* the C4 spine touch — is then answered about a file nobody read.
|
|
130
|
+
*
|
|
131
|
+
* It THROWS (`NotUtf8DocumentError`, which names the path), unlike
|
|
132
|
+
* `readFrontmatter` next door, because the two failures are not the same
|
|
133
|
+
* failure. Frontmatter is read by the fleet enumeration, where one bad file
|
|
134
|
+
* must not cost the other 119 services, so there it is a flag. A requirement
|
|
135
|
+
* document is read by ONE target, and the caller is the only one who knows what
|
|
136
|
+
* to do about it: a read-only command turns it into a finding on that target
|
|
137
|
+
* (validate's `guarded`, which names the file and keeps grading the rest), a
|
|
138
|
+
* command that writes refuses outright rather than computing over mojibake.
|
|
139
|
+
*/
|
|
140
|
+
export async function readRequirementsDocument(path) {
|
|
141
|
+
return decodeDocument(await readFile(path), path);
|
|
142
|
+
}
|
|
143
|
+
/** Parse all requirements (with their scenarios and delta kind) from a markdown doc. */
|
|
144
|
+
export function parseRequirements(md) {
|
|
145
|
+
const out = [];
|
|
146
|
+
let kind = "BASE";
|
|
147
|
+
let section;
|
|
148
|
+
let req = null;
|
|
149
|
+
let scn = null;
|
|
150
|
+
const fenced = fenceTracker();
|
|
151
|
+
for (const [index, line] of stripBom(md).split(/\r?\n/).entries()) {
|
|
152
|
+
if (fenced(line)) {
|
|
153
|
+
// Fenced content, marker included: body of whatever is open, never structure.
|
|
154
|
+
if (scn)
|
|
155
|
+
scn.lines.push(line);
|
|
156
|
+
else if (req)
|
|
157
|
+
req.text.push(line);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// Any H2 heading ends the current requirement/scenario capture — section prose
|
|
161
|
+
// must not leak into the previous scenario's body.
|
|
162
|
+
if (/^##\s+/.test(line) && !line.startsWith("###")) {
|
|
163
|
+
req = null;
|
|
164
|
+
scn = null;
|
|
165
|
+
section = line.trim();
|
|
166
|
+
const mk = KIND_RE.exec(line);
|
|
167
|
+
// A non-delta H2 (## Notes, ## Requirements…) starts an unrelated section:
|
|
168
|
+
// requirements under it are plain BASE, not part of a stale delta section.
|
|
169
|
+
kind = mk ? mk[1].toUpperCase() : "BASE";
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const mr = REQ_RE.exec(line);
|
|
173
|
+
if (mr) {
|
|
174
|
+
req = {
|
|
175
|
+
kind,
|
|
176
|
+
name: mr[1],
|
|
177
|
+
text: [],
|
|
178
|
+
operations: [],
|
|
179
|
+
covers: [],
|
|
180
|
+
publishes: [],
|
|
181
|
+
consumes: [],
|
|
182
|
+
requires: [],
|
|
183
|
+
capabilities: [],
|
|
184
|
+
realizes: [],
|
|
185
|
+
scenarios: [],
|
|
186
|
+
section,
|
|
187
|
+
line: index + 1,
|
|
188
|
+
};
|
|
189
|
+
out.push(req);
|
|
190
|
+
scn = null;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const ms = SCN_RE.exec(line);
|
|
194
|
+
if (ms && req) {
|
|
195
|
+
scn = { name: ms[1], lines: [] };
|
|
196
|
+
req.scenarios.push(scn);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (scn) {
|
|
200
|
+
scn.lines.push(line);
|
|
201
|
+
}
|
|
202
|
+
else if (req) {
|
|
203
|
+
req.text.push(line);
|
|
204
|
+
const mi = REQUIREMENT_ID_LINE_RE.exec(line);
|
|
205
|
+
if (mi)
|
|
206
|
+
req.id = mi[1].trim();
|
|
207
|
+
// Same keep-last quirk as Operations:/Covers: below — a repeated line is
|
|
208
|
+
// a document problem, reported as one (`delta.baseline-invalid`), never
|
|
209
|
+
// resolved differently by whoever happens to be reading.
|
|
210
|
+
const mb = BASED_ON_LINE_RE.exec(line);
|
|
211
|
+
if (mb)
|
|
212
|
+
req.basedOn = mb[1].trim();
|
|
213
|
+
const mo = /^\s*Operations?:\s*(.+?)\s*$/i.exec(line);
|
|
214
|
+
if (mo)
|
|
215
|
+
req.operations = mo[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
216
|
+
// The Covers: mirror — same placement (requirement body, not a scenario),
|
|
217
|
+
// same comma grammar, and deliberately the same quirk: a second line
|
|
218
|
+
// REPLACES the first (assignment, not append), so the two lists can never
|
|
219
|
+
// drift apart in how they read a repeated line.
|
|
220
|
+
const mc = /^\s*Covers?:\s*(.+?)\s*$/i.exec(line);
|
|
221
|
+
if (mc)
|
|
222
|
+
req.covers = mc[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
223
|
+
// The event axis, same grammar and the same keep-last quirk again. Note
|
|
224
|
+
// what is NOT happening here: the line stays in `req.text` like every
|
|
225
|
+
// other body line (the push above is unconditional), so it round-trips
|
|
226
|
+
// through `serializeRequirements` and rides inside `requirementDigest`
|
|
227
|
+
// exactly as `Operations:` does. That is what makes this parse purely
|
|
228
|
+
// additive — no living document's digest moves, and no `Based-On:` pin
|
|
229
|
+
// goes stale because loam learned to read one more line.
|
|
230
|
+
const mp = /^\s*Publishes?:\s*(.+?)\s*$/i.exec(line);
|
|
231
|
+
if (mp)
|
|
232
|
+
req.publishes = mp[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
233
|
+
const mcon = /^\s*Consumes?:\s*(.+?)\s*$/i.exec(line);
|
|
234
|
+
if (mcon)
|
|
235
|
+
req.consumes = mcon[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
236
|
+
// The authorization axis. Same grammar, same keep-last quirk, same purely
|
|
237
|
+
// additive parse — the line rides in `req.text` and therefore inside
|
|
238
|
+
// `requirementDigest`, so no living document moves because loam learned
|
|
239
|
+
// to read it.
|
|
240
|
+
const mreq = /^\s*Requires?:\s*(.+?)\s*$/i.exec(line);
|
|
241
|
+
if (mreq)
|
|
242
|
+
req.requires = mreq[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
243
|
+
// The capability axis. Same grammar, same keep-last quirk, same purely
|
|
244
|
+
// additive parse as Requires: above — but note the OPT-IN differs: the
|
|
245
|
+
// vocabulary FILE (architecture/capabilities.yaml) is the opt-in for this
|
|
246
|
+
// axis, not the line, so entries parsed here grade as nothing at all
|
|
247
|
+
// until a fleet writes that file (core/capabilities/findings.ts).
|
|
248
|
+
const mcap = CAPABILITY_LINE_RE.exec(line);
|
|
249
|
+
if (mcap)
|
|
250
|
+
req.capabilities = mcap[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
251
|
+
// The join INTO the authored business tree: `<capability-id>#<Requirement-ID>`
|
|
252
|
+
// entries, same comma grammar and same keep-last quirk as every line
|
|
253
|
+
// above. Split into its two halves nowhere near here —
|
|
254
|
+
// `core/capabilities/realizes/join.ts` owns that, because the rollup and
|
|
255
|
+
// the grades must not be able to disagree about where the separator is.
|
|
256
|
+
const mrl = REALIZES_LINE_RE.exec(line);
|
|
257
|
+
if (mrl)
|
|
258
|
+
req.realizes = mrl[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../src/core/document/parse.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Markdown in: headings, fences, and the one `## Requirements` cut point.\n *\n * The cut point is spelled once here on purpose. The archive cut, archive's\n * stray-requirement guard and the delta-shape exemption used to spell the same\n * invariant three different ways, and one of them was a PREFIX match that also\n * hit `## Requirements Extra`.\n *\n * Fence tracking is why this is not a set of regexes at the call sites: a\n * `### Requirement:` inside a fenced code block is prose about a requirement,\n * not one, and every heading walk here has to agree about that.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { decodeDocument } from \"../kernel/document-bytes.js\";\nimport { fenceTracker } from \"../kernel/fences.js\";\nimport {\n BASED_ON_LINE_RE, CAPABILITY_LINE_RE, KIND_RE, REALIZES_LINE_RE, REQUIREMENT_ID_LINE_RE,\n type DeltaKind, type Requirement, type Scenario,\n} from \"./spec.js\";\n\nconst REQ_RE = /^###\\s+Requirement:\\s*(.+?)\\s*$/;\nconst SCN_RE = /^####\\s+Scenario:\\s*(.+?)\\s*$/;\n\n/**\n * THE requirements section: the one H2 a living spec keeps its requirements\n * under, the one section `loam archive` rewrites, and the one heading a delta\n * may legally quote the living state beneath. One definition on purpose — the\n * archive cut point, archive's stray-requirement guard, and the delta-shape\n * exemption (core/delta/document.ts) used to spell this invariant three different ways,\n * and the cut point's substring `indexOf(\"\\n## Requirements\")` was a PREFIX\n * match that also hit `## Requirements Extra`. Full-line match on a trimmed\n * heading, with KIND_RE's case and interior-whitespace tolerance.\n */\nconst REQUIREMENTS_SECTION_RE = /^##\\s+Requirements\\s*$/i;\n\n/** Is this H2 heading line (as sectionHeadings/`Requirement.section` spell it) the `## Requirements` heading? */\nexport function isRequirementsHeading(heading: string): boolean {\n return REQUIREMENTS_SECTION_RE.test(heading.trim());\n}\n\n/**\n * Strip a leading UTF-8 BOM (U+FEFF), which editors on Windows write unasked.\n *\n * It has to happen here, at the two functions that take raw markdown, rather than\n * at each `readFile` call: those are spread across half the commands, so any one of\n * them could forget, and the failure is invisible. One byte was enough to void an\n * entire delta — `KIND_RE` is anchored `^##`, delta files routinely open with\n * `## MODIFIED Requirements` on line 1, and behind a BOM that line stopped being a\n * heading, so every requirement in the file fell back to BASE and\n * `applyRequirementDelta` skips BASE. Archive merged nothing and said nothing.\n * OpenSpec's own reader strips it for exactly this reason.\n *\n * Only at position 0: elsewhere U+FEFF is a zero-width no-break space and belongs\n * to the author's text.\n */\nfunction stripBom(md: string): string {\n return md.charCodeAt(0) === 0xfeff ? md.slice(1) : md;\n}\n\n/** Every H2 heading outside a fenced block, with its 1-based line number. */\nexport function sectionHeadings(md: string): Array<{ text: string; line: number }> {\n const fenced = fenceTracker();\n const out: Array<{ text: string; line: number }> = [];\n stripBom(md).split(/\\r?\\n/).forEach((line, i) => {\n if (fenced(line)) return;\n if (/^##\\s+/.test(line) && !line.startsWith(\"###\")) out.push({ text: line.trim(), line: i + 1 });\n });\n return out;\n}\n\n/**\n * Split a living spec around its requirements RUN — the stretch of\n * `### Requirement:` blocks inside the `## Requirements` section — so a merge\n * can rewrite exactly that and nothing else. All three slices reassemble the\n * input byte-for-byte (`head + run + tail === text`):\n *\n * - `head` — everything before the first requirement in the section: the\n * intro, the heading line itself, any prose under the heading.\n * - `run` — from the first `### Requirement:` line to the section's end (the\n * next `## ` heading, or EOF). Empty when the section holds no requirements.\n * - `tail` — everything from the section's end onward.\n *\n * Returns null when the document has no `## Requirements` heading at all.\n * Only the FIRST matching heading opens the section; a caller that must not\n * lose content behind a duplicate (archive) counts matches via\n * sectionHeadings() and refuses. Heading detection matches parseRequirements:\n * fenced lines are never structure, and a leading BOM hides no heading — but\n * the slices themselves are raw bytes, BOM and CRLF included.\n */\nexport function splitRequirementsSection(\n text: string,\n): { head: string; run: string; tail: string } | null {\n const fenced = fenceTracker();\n let inSection = false;\n let sectionStart = -1;\n let runStart = -1;\n let sectionEnd = text.length;\n let offset = 0;\n while (offset < text.length) {\n const nl = text.indexOf(\"\\n\", offset);\n const lineEnd = nl === -1 ? text.length : nl + 1;\n let line = text.slice(offset, nl === -1 ? text.length : nl);\n if (line.endsWith(\"\\r\")) line = line.slice(0, -1);\n // Position 0 only, mirroring stripBom: elsewhere U+FEFF is content.\n if (offset === 0 && line.charCodeAt(0) === 0xfeff) line = line.slice(1);\n if (!fenced(line)) {\n if (/^##\\s+/.test(line) && !line.startsWith(\"###\")) {\n if (inSection) {\n sectionEnd = offset;\n inSection = false;\n } else if (sectionStart === -1 && isRequirementsHeading(line)) {\n sectionStart = offset;\n inSection = true;\n }\n } else if (inSection && runStart === -1 && REQ_RE.test(line)) {\n runStart = offset;\n }\n }\n offset = lineEnd;\n }\n if (sectionStart === -1) return null;\n const cut = runStart === -1 ? sectionEnd : runStart;\n return { head: text.slice(0, cut), run: text.slice(cut, sectionEnd), tail: text.slice(sectionEnd) };\n}\n\n/**\n * A requirement document from disk as text — decoded, or refused by name.\n *\n * The read that belongs with the parser, because `readFile(path, \"utf8\")` and\n * `parseRequirements` together are a trap: a UTF-16 spec.md decodes to a string\n * in which no line matches `^###\\s+Requirement:`, so the parser returns [] and\n * the document grades as an empty baseline instead of an unreadable one. Every\n * question loam asks of a spec — is it covered, does the delta merge, what does\n * the C4 spine touch — is then answered about a file nobody read.\n *\n * It THROWS (`NotUtf8DocumentError`, which names the path), unlike\n * `readFrontmatter` next door, because the two failures are not the same\n * failure. Frontmatter is read by the fleet enumeration, where one bad file\n * must not cost the other 119 services, so there it is a flag. A requirement\n * document is read by ONE target, and the caller is the only one who knows what\n * to do about it: a read-only command turns it into a finding on that target\n * (validate's `guarded`, which names the file and keeps grading the rest), a\n * command that writes refuses outright rather than computing over mojibake.\n */\nexport async function readRequirementsDocument(path: string): Promise<string> {\n return decodeDocument(await readFile(path), path);\n}\n\n/** Parse all requirements (with their scenarios and delta kind) from a markdown doc. */\nexport function parseRequirements(md: string): Requirement[] {\n const out: Requirement[] = [];\n let kind: DeltaKind = \"BASE\";\n let section: string | undefined;\n let req: Requirement | null = null;\n let scn: Scenario | null = null;\n const fenced = fenceTracker();\n\n for (const [index, line] of stripBom(md).split(/\\r?\\n/).entries()) {\n if (fenced(line)) {\n // Fenced content, marker included: body of whatever is open, never structure.\n if (scn) scn.lines.push(line);\n else if (req) req.text.push(line);\n continue;\n }\n // Any H2 heading ends the current requirement/scenario capture — section prose\n // must not leak into the previous scenario's body.\n if (/^##\\s+/.test(line) && !line.startsWith(\"###\")) {\n req = null;\n scn = null;\n section = line.trim();\n const mk = KIND_RE.exec(line);\n // A non-delta H2 (## Notes, ## Requirements…) starts an unrelated section:\n // requirements under it are plain BASE, not part of a stale delta section.\n kind = mk ? (mk[1]!.toUpperCase() as DeltaKind) : \"BASE\";\n continue;\n }\n const mr = REQ_RE.exec(line);\n if (mr) {\n req = {\n kind,\n name: mr[1]!,\n text: [],\n operations: [],\n covers: [],\n publishes: [],\n consumes: [],\n requires: [],\n capabilities: [],\n realizes: [],\n scenarios: [],\n section,\n line: index + 1,\n };\n out.push(req);\n scn = null;\n continue;\n }\n const ms = SCN_RE.exec(line);\n if (ms && req) {\n scn = { name: ms[1]!, lines: [] };\n req.scenarios.push(scn);\n continue;\n }\n if (scn) {\n scn.lines.push(line);\n } else if (req) {\n req.text.push(line);\n const mi = REQUIREMENT_ID_LINE_RE.exec(line);\n if (mi) req.id = mi[1]!.trim();\n // Same keep-last quirk as Operations:/Covers: below — a repeated line is\n // a document problem, reported as one (`delta.baseline-invalid`), never\n // resolved differently by whoever happens to be reading.\n const mb = BASED_ON_LINE_RE.exec(line);\n if (mb) req.basedOn = mb[1]!.trim();\n const mo = /^\\s*Operations?:\\s*(.+?)\\s*$/i.exec(line);\n if (mo) req.operations = mo[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n // The Covers: mirror — same placement (requirement body, not a scenario),\n // same comma grammar, and deliberately the same quirk: a second line\n // REPLACES the first (assignment, not append), so the two lists can never\n // drift apart in how they read a repeated line.\n const mc = /^\\s*Covers?:\\s*(.+?)\\s*$/i.exec(line);\n if (mc) req.covers = mc[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n // The event axis, same grammar and the same keep-last quirk again. Note\n // what is NOT happening here: the line stays in `req.text` like every\n // other body line (the push above is unconditional), so it round-trips\n // through `serializeRequirements` and rides inside `requirementDigest`\n // exactly as `Operations:` does. That is what makes this parse purely\n // additive — no living document's digest moves, and no `Based-On:` pin\n // goes stale because loam learned to read one more line.\n const mp = /^\\s*Publishes?:\\s*(.+?)\\s*$/i.exec(line);\n if (mp) req.publishes = mp[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n const mcon = /^\\s*Consumes?:\\s*(.+?)\\s*$/i.exec(line);\n if (mcon) req.consumes = mcon[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n // The authorization axis. Same grammar, same keep-last quirk, same purely\n // additive parse — the line rides in `req.text` and therefore inside\n // `requirementDigest`, so no living document moves because loam learned\n // to read it.\n const mreq = /^\\s*Requires?:\\s*(.+?)\\s*$/i.exec(line);\n if (mreq) req.requires = mreq[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n // The capability axis. Same grammar, same keep-last quirk, same purely\n // additive parse as Requires: above — but note the OPT-IN differs: the\n // vocabulary FILE (architecture/capabilities.yaml) is the opt-in for this\n // axis, not the line, so entries parsed here grade as nothing at all\n // until a fleet writes that file (core/capabilities/findings.ts).\n const mcap = CAPABILITY_LINE_RE.exec(line);\n if (mcap) req.capabilities = mcap[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n // The join INTO the authored business tree: `<capability-id>#<Requirement-ID>`\n // entries, same comma grammar and same keep-last quirk as every line\n // above. Split into its two halves nowhere near here —\n // `core/capabilities/realizes/join.ts` owns that, because the rollup and\n // the grades must not be able to disagree about where the separator is.\n const mrl = REALIZES_LINE_RE.exec(line);\n if (mrl) req.realizes = mrl[1]!.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0);\n }\n }\n\n return out;\n}\n"]}
|