@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
|
@@ -10,23 +10,29 @@
|
|
|
10
10
|
* is not caught by anything: the feature validates, archives, and ships with a
|
|
11
11
|
* consumer nobody updated.
|
|
12
12
|
*
|
|
13
|
-
* So this states the work the same way `brief.ts` does. It reads the fleet map
|
|
13
|
+
* So this states the work the same way `brief/brief.ts` does. It reads the fleet map
|
|
14
14
|
* and the living contracts, reports the ring of services around the seeds, the
|
|
15
15
|
* features already in flight over the same ground, and how far each service's
|
|
16
16
|
* documentation has got — and stops. Which of those neighbours the feature
|
|
17
17
|
* really touches is a judgement about intent, and loam does not make those.
|
|
18
18
|
*
|
|
19
19
|
* The landscape is parsed ONCE for the whole exploration. Per-service reads of
|
|
20
|
-
* it exist (`brief.ts`'s `landscapeContext`) and are correct for one service;
|
|
20
|
+
* it exist (`brief/landscape.ts`'s `landscapeContext`) and are correct for one service;
|
|
21
21
|
* calling one of those per seed is how `validate --all` came to cost 13 seconds
|
|
22
22
|
* on 120 services, and this command sits in front of authoring, where that is
|
|
23
23
|
* the difference between a habit and a thing people skip.
|
|
24
24
|
*/
|
|
25
25
|
import { existsSync } from "node:fs";
|
|
26
|
-
import { FleetContext } from "
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
26
|
+
import { FleetContext } from "../fleet-context.js";
|
|
27
|
+
import {} from "../c4/likec4.js";
|
|
28
|
+
import { serviceResolver } from "../c4/resolve/service.js";
|
|
29
|
+
import {} from "../vocabulary/maturity.js";
|
|
30
|
+
import { capabilityRollup, unresolvedCapabilities } from "../capabilities/rollup.js";
|
|
31
|
+
import { compareIds, nearestIds } from "../repo/entries.js";
|
|
32
|
+
import { landscapePath } from "../repo/paths.js";
|
|
33
|
+
import { readUseCases } from "../usecases/fleet.js";
|
|
34
|
+
import { servicesInFlowsClaiming } from "../usecases/capability.js";
|
|
35
|
+
import { describe, newCommand, operationOwner } from "./describe.js";
|
|
30
36
|
/**
|
|
31
37
|
* Read the fleet around a proposed change. Reads only; writes nothing, and
|
|
32
38
|
* refuses nothing — every miss is reported as a field.
|
|
@@ -53,13 +59,48 @@ export async function explore(req) {
|
|
|
53
59
|
const opSeeds = [];
|
|
54
60
|
const unresolvedOperations = [];
|
|
55
61
|
for (const op of req.operations) {
|
|
56
|
-
const owner = await operationOwner(
|
|
62
|
+
const owner = await operationOwner(entries, op, context);
|
|
57
63
|
if (owner === null)
|
|
58
64
|
unresolvedOperations.push(op);
|
|
59
65
|
else
|
|
60
66
|
opSeeds.push(owner);
|
|
61
67
|
}
|
|
62
|
-
|
|
68
|
+
// Capability seeds resolve through the vocabulary and the fleet rollup —
|
|
69
|
+
// one rollup pass for ALL requested ids, through this exploration's shared
|
|
70
|
+
// context cache, never a fresh walk per capability. A realizing service is
|
|
71
|
+
// by construction an enumerated one, so every seed it adds is `known`.
|
|
72
|
+
//
|
|
73
|
+
// And through the fleet's USE CASES, which answer the same question off a
|
|
74
|
+
// different document. The rollup finds services whose living requirements
|
|
75
|
+
// carry a `Capability:` line; a `dynamic view` tagged `#cap-<slug>` finds the
|
|
76
|
+
// services the flow actually runs through — including ones whose spec.md has
|
|
77
|
+
// never named the capability, which on a brownfield fleet is most of them.
|
|
78
|
+
// The two are unioned rather than ranked because neither is a subset of the
|
|
79
|
+
// other: a service can realize a capability without appearing in its drawn
|
|
80
|
+
// flow, and a flow can pass through a service that has written nothing down.
|
|
81
|
+
//
|
|
82
|
+
// The scan gates its own load, and this whole block is already behind
|
|
83
|
+
// `--capability` being passed at all, so an exploration by service or by
|
|
84
|
+
// operation never touches `architecture/` as a project.
|
|
85
|
+
const capSeeds = [];
|
|
86
|
+
let unresolvedCaps = [];
|
|
87
|
+
if (req.capabilities.length > 0) {
|
|
88
|
+
const vocab = await context.capabilities(docsDir);
|
|
89
|
+
const rows = await capabilityRollup({ services: entries, vocab, read: (p) => context.readRequirements(p) });
|
|
90
|
+
const wanted = new Set(req.capabilities);
|
|
91
|
+
const realizing = new Set(rows.filter((r) => wanted.has(r.id)).flatMap((r) => r.services));
|
|
92
|
+
const scan = await readUseCases({ docsDir, known });
|
|
93
|
+
for (const id of servicesInFlowsClaiming(scan, req.capabilities))
|
|
94
|
+
realizing.add(id);
|
|
95
|
+
capSeeds.push(...entries.filter((e) => realizing.has(e.id)).map((e) => e.id));
|
|
96
|
+
// `unresolvedCapabilities` is unchanged on purpose: it grades the
|
|
97
|
+
// VOCABULARY — declared or not, realized by a living requirement or not —
|
|
98
|
+
// and a capability drawn in a flow but realized by nobody is still a
|
|
99
|
+
// capability nothing implements. Letting a tag silence that would hide the
|
|
100
|
+
// gap the field exists to name.
|
|
101
|
+
unresolvedCaps = unresolvedCapabilities(rows, req.capabilities);
|
|
102
|
+
}
|
|
103
|
+
const seeds = [...new Set([...req.services, ...opSeeds, ...capSeeds])];
|
|
63
104
|
const unknown = seeds
|
|
64
105
|
.filter((id) => !known.has(id))
|
|
65
106
|
.map((id) => ({ id, nearest: nearestIds(id, [...known]) }));
|
|
@@ -105,7 +146,6 @@ export async function explore(req) {
|
|
|
105
146
|
id,
|
|
106
147
|
reason,
|
|
107
148
|
entry: byId.get(id),
|
|
108
|
-
docsDir,
|
|
109
149
|
relationships,
|
|
110
150
|
svcOf,
|
|
111
151
|
elements,
|
|
@@ -123,6 +163,7 @@ export async function explore(req) {
|
|
|
123
163
|
seeds,
|
|
124
164
|
unknown,
|
|
125
165
|
unresolvedOperations,
|
|
166
|
+
unresolvedCapabilities: unresolvedCaps,
|
|
126
167
|
services,
|
|
127
168
|
neighbours: [...ring.keys()].sort(compareIds),
|
|
128
169
|
overlaps,
|
|
@@ -130,96 +171,4 @@ export async function explore(req) {
|
|
|
130
171
|
scaffold: newCommand(featureId, seeds, known),
|
|
131
172
|
};
|
|
132
173
|
}
|
|
133
|
-
async function describe(req) {
|
|
134
|
-
const { id, reason, entry, docsDir, relationships, svcOf } = req;
|
|
135
|
-
const inbound = [];
|
|
136
|
-
const outbound = [];
|
|
137
|
-
for (const r of relationships) {
|
|
138
|
-
const edge = { op: r.op ?? null, title: r.title ?? null };
|
|
139
|
-
if (svcOf(r.target) === id)
|
|
140
|
-
inbound.push({ service: svcOf(r.source), ...edge });
|
|
141
|
-
else if (svcOf(r.source) === id)
|
|
142
|
-
outbound.push({ service: svcOf(r.target), ...edge });
|
|
143
|
-
}
|
|
144
|
-
const modelled = req.elements.some((e) => svcOf(e.id) === id);
|
|
145
|
-
// A service that does not exist has no rung: `empty` would be a claim about a
|
|
146
|
-
// directory nobody has made, and it reads as "exists, nothing in it" — the
|
|
147
|
-
// one state a caller most needs to tell it apart from.
|
|
148
|
-
if (entry === undefined) {
|
|
149
|
-
return {
|
|
150
|
-
id,
|
|
151
|
-
reason,
|
|
152
|
-
known: false,
|
|
153
|
-
maturity: null,
|
|
154
|
-
missing: [],
|
|
155
|
-
modelled,
|
|
156
|
-
operations: [],
|
|
157
|
-
// Not `unreadable`: there is no contract here to fail to read. A service
|
|
158
|
-
// with no directory owes nothing, and reporting a parse failure over an
|
|
159
|
-
// absent file would send a reader looking for a document to fix.
|
|
160
|
-
openapi: { unreadable: false },
|
|
161
|
-
inbound,
|
|
162
|
-
outbound,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const archSpec = existsSync(servicePaths(docsDir, id).archSpec);
|
|
166
|
-
// Positive evidence only, and the rule is `list`'s verbatim: an inbound edge
|
|
167
|
-
// carrying an operation is proof somebody calls this service, while a
|
|
168
|
-
// landscape that is absent or does not parse proves nothing about who calls
|
|
169
|
-
// it — so the contract is still owed. Spelling this differently here is how
|
|
170
|
-
// the same service would grade `partial` in one command and `vouched` in the
|
|
171
|
-
// other.
|
|
172
|
-
const apiExpected = !req.parses || inbound.some((e) => e.op !== null);
|
|
173
|
-
const input = { entry, archSpec, apiExpected };
|
|
174
|
-
// `readOpenapi`, not `operationIds`: a contract that exists but does not
|
|
175
|
-
// parse comes back from the id list as an EMPTY set, indistinguishable from a
|
|
176
|
-
// service with no endpoints — and "this service offers nothing" is the worst
|
|
177
|
-
// possible lie to tell somebody deciding whether to call it.
|
|
178
|
-
const api = await req.context.readOpenapi(servicePaths(docsDir, id).openapi);
|
|
179
|
-
const operations = api.ops.filter((o) => !o.remove).map((o) => o.id);
|
|
180
|
-
return {
|
|
181
|
-
id,
|
|
182
|
-
reason,
|
|
183
|
-
known: true,
|
|
184
|
-
maturity: serviceMaturity(input),
|
|
185
|
-
missing: maturityGaps(input),
|
|
186
|
-
modelled,
|
|
187
|
-
operations,
|
|
188
|
-
openapi: {
|
|
189
|
-
unreadable: api.unreadable,
|
|
190
|
-
...(api.error === undefined ? {} : { error: api.error }),
|
|
191
|
-
},
|
|
192
|
-
inbound,
|
|
193
|
-
outbound,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* The service whose living contract defines an operationId, or null.
|
|
198
|
-
*
|
|
199
|
-
* Reads every service's `openapi.yaml`, which is the cost of asking a question
|
|
200
|
-
* nothing indexes. It is YAML, not LikeC4 — the expensive parse in this
|
|
201
|
-
* codebase — and it happens only when somebody passes `--op`.
|
|
202
|
-
*/
|
|
203
|
-
async function operationOwner(docsDir, entries, op, context) {
|
|
204
|
-
for (const entry of entries) {
|
|
205
|
-
if (!entry.has.openapi)
|
|
206
|
-
continue;
|
|
207
|
-
const api = await context.readOpenapi(servicePaths(docsDir, entry.id).openapi);
|
|
208
|
-
if (api.ops.some((o) => o.id === op && !o.remove))
|
|
209
|
-
return entry.id;
|
|
210
|
-
}
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* The `loam new` line the seeds imply. A seed that names no existing service is
|
|
215
|
-
* `--new-service`, not `--touches`: those are different scaffolds, and getting
|
|
216
|
-
* it wrong is what leaves a feature with a requirement delta for a service that
|
|
217
|
-
* has no directory to archive into.
|
|
218
|
-
*/
|
|
219
|
-
function newCommand(featureId, seeds, known) {
|
|
220
|
-
const flags = [...seeds]
|
|
221
|
-
.sort(compareIds)
|
|
222
|
-
.map((id) => (known.has(id) ? `--touches ${id}` : `--new-service ${id}`));
|
|
223
|
-
return `loam new ${featureId}${flags.length > 0 ? ` ${flags.join(" ")}` : ""}`;
|
|
224
|
-
}
|
|
225
174
|
//# sourceMappingURL=explore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explore.js","sourceRoot":"","sources":["../../../src/core/explore/explore.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The blast radius of a change nobody has written down yet.\n *\n * Everything else in loam answers a question about documents that exist. This\n * module answers the one that comes BEFORE the first document: which services\n * does this feature actually touch? On a fleet of a hundred that call is the\n * hardest one in the cycle and the only one made with no tool in hand —\n * `loam new FEAT-101 --touches a --touches b` takes the answer as an argument\n * and scaffolds whatever it was told. A `--touches` list short by one service\n * is not caught by anything: the feature validates, archives, and ships with a\n * consumer nobody updated.\n *\n * So this states the work the same way `brief/brief.ts` does. It reads the fleet map\n * and the living contracts, reports the ring of services around the seeds, the\n * features already in flight over the same ground, and how far each service's\n * documentation has got — and stops. Which of those neighbours the feature\n * really touches is a judgement about intent, and loam does not make those.\n *\n * The landscape is parsed ONCE for the whole exploration. Per-service reads of\n * it exist (`brief/landscape.ts`'s `landscapeContext`) and are correct for one service;\n * calling one of those per seed is how `validate --all` came to cost 13 seconds\n * on 120 services, and this command sits in front of authoring, where that is\n * the difference between a habit and a thing people skip.\n */\nimport { existsSync } from \"node:fs\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { type Maturity } from \"../vocabulary/maturity.js\";\nimport { capabilityRollup, unresolvedCapabilities } from \"../capabilities/rollup.js\";\nimport { compareIds, nearestIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath } from \"../repo/paths.js\";\nimport { readUseCases } from \"../usecases/fleet.js\";\nimport { servicesInFlowsClaiming } from \"../usecases/capability.js\";\nimport { describe, newCommand, operationOwner } from \"./describe.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/** One end of a call the fleet map already draws. */\nexport interface ExploreEdge {\n /** The service at the other end. */\n service: string;\n /** The operationId the call uses, or null for an edge nobody tied to one. */\n op: string | null;\n title: string | null;\n}\n\n/**\n * Why a service is in the answer. Seeds were asked for; the rest were derived.\n *\n * A neighbour on both sides of a seed reports `calls-seed` — see the overlap\n * rule in `explore`. The field is a hint about where to look, not a complete\n * description of the edges; `inbound`/`outbound` are that.\n */\nexport type ExploreReason = \"seed\" | \"calls-seed\" | \"called-by-seed\";\n\nexport interface ExploreService {\n id: string;\n reason: ExploreReason;\n /** Whether `services/<id>/` exists at all. */\n known: boolean;\n /**\n * How far this service's documentation has got, or null when it does not\n * exist yet. A `--touches` naming a service at `empty` or `partial` is a\n * different job from one naming a vouched service, and the difference decides\n * whether the feature starts with `loam adopt`.\n */\n maturity: Maturity | null;\n /** What stands between it and `vouched` — `maturityGaps`, so it matches `list --needs-work`. */\n missing: string[];\n /** Whether anything in the fleet map resolves to it. */\n modelled: boolean;\n /** operationIds its living contract exposes today, `x-loam-remove` markers excluded. */\n operations: string[];\n /**\n * Whether that contract could be read at all, beside the `error` the parser\n * gave when it could not.\n *\n * It rides next to `operations` rather than inside it for the reason `delta`\n * carries the same pair: an unreadable contract yields an EMPTY operation\n * list, indistinguishable from a service with no endpoints — and this is the\n * field the workflow tells an agent to read before deciding whether an\n * operation it is about to add already exists. Answering `[]` over a YAML\n * error is how a MODIFIED requirement gets authored as ADDED.\n */\n openapi: { unreadable: boolean; error?: string };\n inbound: ExploreEdge[];\n outbound: ExploreEdge[];\n}\n\n/** An active feature already working over ground the exploration covers. */\nexport interface ExploreOverlap {\n feature: string;\n /** The explored services it already carries a delta for. */\n services: string[];\n}\n\nexport interface Exploration {\n /** The seed ids, as asked for. */\n seeds: string[];\n /**\n * Seeds that name no `services/<id>/`, each with the closest real ids. A seed\n * may legitimately be a service the feature INTRODUCES, so this is reported\n * rather than refused — but a typo produces exactly the same shape, and the\n * near-miss list is what tells the two apart.\n */\n unknown: Array<{ id: string; nearest: string[] }>;\n /** Operation seeds that resolved to no service's living contract. */\n unresolvedOperations: string[];\n /**\n * Capability seeds that seeded nothing: not declared in\n * architecture/capabilities.yaml (the file may not exist at all — explore\n * refuses nothing, every miss is a field), or declared and realized by no\n * living requirement. One list on purpose; splitting the two miss kinds\n * waits for a consumer to ask.\n */\n unresolvedCapabilities: string[];\n /** Seeds first, then the ring, each in id order. */\n services: ExploreService[];\n /** Ids the ring added that were not asked for — the candidates to weigh. */\n neighbours: string[];\n overlaps: ExploreOverlap[];\n landscape: { present: boolean; parses: boolean };\n /**\n * The literal `loam new` line the seeds imply — seeds only. The ring is\n * deliberately NOT folded in: this module's whole claim is that it does not\n * know which neighbours the feature touches, and a command line that quietly\n * included them would make the guess on the reader's behalf and look derived\n * while doing it.\n *\n * Named for what it does rather than `command`, which is the envelope's key\n * for the command that RAN. The rename is not cosmetic: `emitJson` spreads\n * the payload LAST, so a payload field called `command` would silently\n * OVERRIDE the envelope's — `command` would come back as\n * `\"loam new FEAT-000 …\"`, and every consumer branching on\n * `command === \"explore\"` (the same key `status`, `doctor`, `dependencies`,\n * `archive` and `unarchive` emit) would quietly stop matching, with tsc and\n * the linter green throughout.\n */\n scaffold: string;\n}\n\nexport interface ExploreRequest {\n docsDir: DocsDir;\n /** Service ids to explore around. Checked at the command boundary. */\n services: ServiceEntry[\"id\"][];\n /** operationIds to explore around; each resolves to the service that defines it. */\n operations: string[];\n /** Declared capability ids to explore around; each seeds its realizing services. */\n capabilities: string[];\n /** Placeholder feature id for the suggested command line. */\n featureId: string;\n context?: FleetContext;\n}\n\n/**\n * Read the fleet around a proposed change. Reads only; writes nothing, and\n * refuses nothing — every miss is reported as a field.\n */\nexport async function explore(req: ExploreRequest): Promise<Exploration> {\n const { docsDir, featureId } = req;\n const context = req.context ?? new FleetContext();\n const entries = await context.listServices(docsDir);\n const byId = new Map<string, ServiceEntry>(entries.map((e) => [e.id, e]));\n const known: ReadonlySet<string> = new Set(byId.keys());\n\n const landPath = landscapePath(docsDir);\n const present = existsSync(landPath);\n const land = present ? await context.loadLikeC4(landPath) : null;\n const parses = land !== null && land.errors.length === 0;\n const elements = parses ? land.elements : [];\n const relationships: Rel[] = parses ? land.relationships : [];\n // One resolver for the whole scan, built with the real service ids so an edge\n // drawn into a modelled container (`paymentService.api`) counts for the\n // service that owns it — the same resolution `list`, `show` and `validate`\n // use, so no two commands can disagree about who calls whom.\n const svcOf = serviceResolver(elements, known);\n\n // Operation seeds resolve through the living contracts, not the map: the map\n // says who CALLS an operation, and the question here is who DEFINES it.\n const opSeeds: ServiceEntry[\"id\"][] = [];\n const unresolvedOperations: string[] = [];\n for (const op of req.operations) {\n const owner = await operationOwner(entries, op, context);\n if (owner === null) unresolvedOperations.push(op);\n else opSeeds.push(owner);\n }\n\n // Capability seeds resolve through the vocabulary and the fleet rollup —\n // one rollup pass for ALL requested ids, through this exploration's shared\n // context cache, never a fresh walk per capability. A realizing service is\n // by construction an enumerated one, so every seed it adds is `known`.\n //\n // And through the fleet's USE CASES, which answer the same question off a\n // different document. The rollup finds services whose living requirements\n // carry a `Capability:` line; a `dynamic view` tagged `#cap-<slug>` finds the\n // services the flow actually runs through — including ones whose spec.md has\n // never named the capability, which on a brownfield fleet is most of them.\n // The two are unioned rather than ranked because neither is a subset of the\n // other: a service can realize a capability without appearing in its drawn\n // flow, and a flow can pass through a service that has written nothing down.\n //\n // The scan gates its own load, and this whole block is already behind\n // `--capability` being passed at all, so an exploration by service or by\n // operation never touches `architecture/` as a project.\n const capSeeds: ServiceEntry[\"id\"][] = [];\n let unresolvedCaps: string[] = [];\n if (req.capabilities.length > 0) {\n const vocab = await context.capabilities(docsDir);\n const rows = await capabilityRollup({ services: entries, vocab, read: (p) => context.readRequirements(p) });\n const wanted = new Set(req.capabilities);\n const realizing = new Set(rows.filter((r) => wanted.has(r.id)).flatMap((r) => r.services));\n const scan = await readUseCases({ docsDir, known });\n for (const id of servicesInFlowsClaiming(scan, req.capabilities)) realizing.add(id);\n capSeeds.push(...entries.filter((e) => realizing.has(e.id)).map((e) => e.id));\n // `unresolvedCapabilities` is unchanged on purpose: it grades the\n // VOCABULARY — declared or not, realized by a living requirement or not —\n // and a capability drawn in a flow but realized by nobody is still a\n // capability nothing implements. Letting a tag silence that would hide the\n // gap the field exists to name.\n unresolvedCaps = unresolvedCapabilities(rows, req.capabilities);\n }\n\n const seeds = [...new Set([...req.services, ...opSeeds, ...capSeeds])];\n const unknown = seeds\n .filter((id) => !known.has(id))\n .map((id) => ({ id, nearest: nearestIds(id, [...known]) }));\n\n // The ring: one hop, both directions. A seed that is not modelled contributes\n // nothing here and that is the honest answer — an invisible service has no\n // neighbours anyone can derive, which is what `landscape.service-unmodelled`\n // is about.\n //\n // The two directions are collected separately rather than written into one\n // map as the loop goes, because a neighbour can be BOTH — it calls a seed and\n // the seed calls it — and a single-map version answers whichever edge the\n // parser happened to yield last. That is a field whose value depends on the\n // order lines appear in a `.likec4` file, which is the kind of answer that\n // reads as derived and is not.\n const seedSet = new Set<string>(seeds);\n const callsSeed = new Set<string>();\n const calledBySeed = new Set<string>();\n for (const r of relationships) {\n const from = svcOf(r.source);\n const to = svcOf(r.target);\n if (seedSet.has(from) && !seedSet.has(to)) calledBySeed.add(to);\n if (seedSet.has(to) && !seedSet.has(from)) callsSeed.add(from);\n }\n\n // `calls-seed` wins the overlap, deterministically. Of the two directions it\n // is the one that more often has to be touched: a consumer depends on the\n // seed's contract, so a change to that contract is what breaks it, while a\n // provider the seed merely calls is usually unaffected by the seed changing.\n const ring = new Map<string, ExploreReason>([\n ...[...calledBySeed].map((id) => [id, \"called-by-seed\" as const] as const),\n ...[...callsSeed].map((id) => [id, \"calls-seed\" as const] as const),\n ]);\n\n const ordered: Array<{ id: string; reason: ExploreReason }> = [\n ...[...seeds].sort(compareIds).map((id) => ({ id, reason: \"seed\" as const })),\n ...[...ring]\n .sort(([a], [b]) => compareIds(a, b))\n .map(([id, reason]) => ({ id, reason })),\n ];\n\n const services: ExploreService[] = [];\n for (const { id, reason } of ordered) {\n services.push(\n await describe({\n id,\n reason,\n entry: byId.get(id),\n relationships,\n svcOf,\n elements,\n parses,\n context,\n }),\n );\n }\n\n const covered = new Set(services.map((s) => s.id));\n const features = await context.listFeatures(docsDir);\n const overlaps: ExploreOverlap[] = features\n .map((f) => ({ feature: f.id, services: f.services.filter((s) => covered.has(s)) }))\n .filter((o) => o.services.length > 0)\n .sort((a, b) => compareIds(a.feature, b.feature));\n\n return {\n seeds,\n unknown,\n unresolvedOperations,\n unresolvedCapabilities: unresolvedCaps,\n services,\n neighbours: [...ring.keys()].sort(compareIds),\n overlaps,\n landscape: { present, parses },\n scaffold: newCommand(featureId, seeds, known),\n };\n}\n"]}
|
|
@@ -12,86 +12,31 @@
|
|
|
12
12
|
* (which `readFile(path, "utf8")` silently turns into U+FFFD, so a UTF-16
|
|
13
13
|
* spec.md parses as zero requirements and grades green), and git conflict
|
|
14
14
|
* markers left in a living document (which parse as prose, and which the next
|
|
15
|
-
* `loam archive` rewrites away). One place, so no reader can forget.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* `loam archive` rewrites away). One place, so no reader can forget. Both
|
|
16
|
+
* RULES live in leaves — the decode in `document-bytes.ts`, the marker scan
|
|
17
|
+
* and its findings in `conflict-markers.ts` — because the parsers and graders
|
|
18
|
+
* that touch a single file without an index owe the same rules, and must not
|
|
19
|
+
* have to import this module — and the fleet it pulls in behind it — to obey
|
|
20
|
+
* them.
|
|
19
21
|
*/
|
|
20
22
|
import { readFile } from "node:fs/promises";
|
|
21
23
|
import { resolve } from "node:path";
|
|
22
|
-
import { readAsyncapi } from "./asyncapi.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* The three-way merge left its markers in the file — 1-based line numbers.
|
|
36
|
-
*
|
|
37
|
-
* The rule is `loam doctor`'s, verbatim, and it lives here now because doctor
|
|
38
|
-
* checked exactly one file (`architecture/landscape.likec4`) from a command
|
|
39
|
-
* that gates nothing. The same markers in a living `services/<svc>/spec.md`
|
|
40
|
-
* parse as prose: `loam validate --all` reported `valid: true`, and the next
|
|
41
|
-
* `loam archive` rewrote the requirements section and dropped the `=======`
|
|
42
|
-
* line with it, turning a conflict anyone can see into a document nobody can
|
|
43
|
-
* tell is wrong. For a shared docs repo where a fleet lands through PRs this is
|
|
44
|
-
* the DEFAULT failure, not an edge.
|
|
45
|
-
*/
|
|
46
|
-
const CONFLICT_MARKERS = ["<<<<<<<", "=======", ">>>>>>>"];
|
|
47
|
-
export function conflictMarkerLines(source) {
|
|
48
|
-
const out = [];
|
|
49
|
-
source.split(/\r?\n/).forEach((line, i) => {
|
|
50
|
-
if (CONFLICT_MARKERS.some((m) => line.startsWith(m)))
|
|
51
|
-
out.push(i + 1);
|
|
52
|
-
});
|
|
53
|
-
return out;
|
|
54
|
-
}
|
|
55
|
-
/** One sentence for the breach, wherever it is found — doctor, validate, either axis. */
|
|
56
|
-
function conflictMessage(label, lines) {
|
|
57
|
-
return (`${label} still contains git conflict markers (line${lines.length === 1 ? "" : "s"} ${lines.join(", ")}) — ` +
|
|
58
|
-
`both sides of somebody's merge are in the file, so nothing it says is anyone's text. ` +
|
|
59
|
-
`Resolve the conflict before anything else: loam rewrites this document on archive, and a rewrite deletes ` +
|
|
60
|
-
`whichever marker lines fall inside the section it owns — a visible conflict becomes a silently malformed file.`);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* The conflict-marker finding for a requirement document (living spec.md /
|
|
64
|
-
* arch.spec.md, or a feature's delta of one). Null when the text is clean.
|
|
65
|
-
*
|
|
66
|
-
* An error because the document is not valid — nobody wrote what it now says —
|
|
67
|
-
* and because the merge that would consume it is unsafe. It does not carry
|
|
68
|
-
* `gates`: that field is coherence's (issue.ts), and the archive gate has to
|
|
69
|
-
* ask this question itself.
|
|
70
|
-
*/
|
|
71
|
-
export function documentConflictFinding(label, subject, text) {
|
|
72
|
-
const lines = conflictMarkerLines(text);
|
|
73
|
-
if (lines.length === 0)
|
|
74
|
-
return null;
|
|
75
|
-
return {
|
|
76
|
-
severity: "error",
|
|
77
|
-
code: "spec.merge-conflict",
|
|
78
|
-
subject,
|
|
79
|
-
message: conflictMessage(label, lines),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/** The same breach in the fleet map, where it costs every cross-service check at once. */
|
|
83
|
-
export function landscapeConflictFinding(label, text) {
|
|
84
|
-
const lines = conflictMarkerLines(text);
|
|
85
|
-
if (lines.length === 0)
|
|
86
|
-
return null;
|
|
87
|
-
return {
|
|
88
|
-
severity: "error",
|
|
89
|
-
code: "landscape.merge-conflict",
|
|
90
|
-
message: conflictMessage(label, lines),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
24
|
+
import { readAsyncapi } from "./asyncapi/read.js";
|
|
25
|
+
import { readCapabilityVocabulary } from "./capabilities/capabilities.js";
|
|
26
|
+
import { featureCapabilityDeltas } from "./capabilities/delta/tree.js";
|
|
27
|
+
import { conflictMarkerLines } from "./conflict-markers.js";
|
|
28
|
+
import { decodeDocument } from "./kernel/document-bytes.js";
|
|
29
|
+
import { loadFile } from "./c4/likec4.js";
|
|
30
|
+
import { loadBatch } from "./c4/workspace.js";
|
|
31
|
+
import { readOpenapi } from "./openapi/doc.js";
|
|
32
|
+
import {} from "./repo/entries.js";
|
|
33
|
+
import { featureSpecServices, listFeatures, listFleetTree, serviceEntries } from "./repo/repo.js";
|
|
34
|
+
import { parseRequirements } from "./document/parse.js";
|
|
35
|
+
import {} from "./document/spec.js";
|
|
36
|
+
const key = (path) => resolve(path);
|
|
93
37
|
/** One coherent filesystem snapshot for one command invocation. */
|
|
94
38
|
export class FleetContext {
|
|
39
|
+
trees = new Map();
|
|
95
40
|
services = new Map();
|
|
96
41
|
features = new Map();
|
|
97
42
|
featureServices = new Map();
|
|
@@ -99,6 +44,8 @@ export class FleetContext {
|
|
|
99
44
|
requirements = new Map();
|
|
100
45
|
openapis = new Map();
|
|
101
46
|
asyncapis = new Map();
|
|
47
|
+
capabilityVocabularies = new Map();
|
|
48
|
+
featureCapabilities = new Map();
|
|
102
49
|
likec4 = new Map();
|
|
103
50
|
counts = {
|
|
104
51
|
serviceEnumerations: 0,
|
|
@@ -108,18 +55,37 @@ export class FleetContext {
|
|
|
108
55
|
requirementParses: 0,
|
|
109
56
|
openapiParses: 0,
|
|
110
57
|
asyncapiParses: 0,
|
|
58
|
+
capabilityParses: 0,
|
|
59
|
+
featureCapabilityWalks: 0,
|
|
111
60
|
likec4Loads: 0,
|
|
112
61
|
};
|
|
113
62
|
/** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */
|
|
114
63
|
stats() {
|
|
115
64
|
return { ...this.counts };
|
|
116
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The classified `services/` tree — the ONE enumeration this invocation
|
|
68
|
+
* performs. `listServices` below derives its entries from this memo, so a
|
|
69
|
+
* command that asks for both the fleet and the tree's findings (`validate
|
|
70
|
+
* --all` does) still walks the directories exactly once. The counter keeps
|
|
71
|
+
* its historical meaning — walks of `services/` — it just counts the walk
|
|
72
|
+
* where the walk now happens.
|
|
73
|
+
*/
|
|
74
|
+
fleetTree(docsDir) {
|
|
75
|
+
const k = key(docsDir);
|
|
76
|
+
let pending = this.trees.get(k);
|
|
77
|
+
if (pending === undefined) {
|
|
78
|
+
this.counts.serviceEnumerations += 1;
|
|
79
|
+
pending = listFleetTree(docsDir);
|
|
80
|
+
this.trees.set(k, pending);
|
|
81
|
+
}
|
|
82
|
+
return pending;
|
|
83
|
+
}
|
|
117
84
|
listServices(docsDir) {
|
|
118
85
|
const k = key(docsDir);
|
|
119
86
|
let pending = this.services.get(k);
|
|
120
87
|
if (pending === undefined) {
|
|
121
|
-
this.
|
|
122
|
-
pending = listServices(docsDir);
|
|
88
|
+
pending = this.fleetTree(docsDir).then(serviceEntries);
|
|
123
89
|
this.services.set(k, pending);
|
|
124
90
|
}
|
|
125
91
|
return pending;
|
|
@@ -207,6 +173,44 @@ export class FleetContext {
|
|
|
207
173
|
}
|
|
208
174
|
return pending;
|
|
209
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* The capability vocabulary, read once per invocation. Direction rule,
|
|
178
|
+
* load-bearing: this file imports `capabilities/capabilities.js`, so nothing
|
|
179
|
+
* under src/core/capabilities/ may import this module back — that edge is a
|
|
180
|
+
* core-root↔capabilities package cycle `import/no-cycle` cannot see, which is
|
|
181
|
+
* why the functions there take plain data or a `read` function, never a FleetContext.
|
|
182
|
+
*/
|
|
183
|
+
capabilities(docsDir) {
|
|
184
|
+
const k = key(docsDir);
|
|
185
|
+
let pending = this.capabilityVocabularies.get(k);
|
|
186
|
+
if (pending === undefined) {
|
|
187
|
+
this.counts.capabilityParses += 1;
|
|
188
|
+
pending = readCapabilityVocabulary(docsDir);
|
|
189
|
+
this.capabilityVocabularies.set(k, pending);
|
|
190
|
+
}
|
|
191
|
+
return pending;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* One feature's `capabilities/` delta tree, walked once per invocation. Two
|
|
195
|
+
* readers ask for it in the same run — the delta-shape walk
|
|
196
|
+
* (`core/delta/delta.ts`) and the capability overlay
|
|
197
|
+
* (`core/coherence/declared.ts`) — and without the memo a feature carrying
|
|
198
|
+
* three capability deltas paid the readdirs twice.
|
|
199
|
+
*
|
|
200
|
+
* The direction rule stated on `capabilities()` above applies verbatim: the
|
|
201
|
+
* function this stands in front of takes a plain `FeatureDir`, never a
|
|
202
|
+
* FleetContext, so `src/core/capabilities/` never imports this module back.
|
|
203
|
+
*/
|
|
204
|
+
featureCapabilityDeltas(featureDir) {
|
|
205
|
+
const k = key(featureDir);
|
|
206
|
+
let pending = this.featureCapabilities.get(k);
|
|
207
|
+
if (pending === undefined) {
|
|
208
|
+
this.counts.featureCapabilityWalks += 1;
|
|
209
|
+
pending = featureCapabilityDeltas(featureDir);
|
|
210
|
+
this.featureCapabilities.set(k, pending);
|
|
211
|
+
}
|
|
212
|
+
return pending;
|
|
213
|
+
}
|
|
210
214
|
async operations(path) {
|
|
211
215
|
return (await this.readOpenapi(path)).ops;
|
|
212
216
|
}
|
|
@@ -220,7 +224,7 @@ export class FleetContext {
|
|
|
220
224
|
* interleaved removals with upserts, so a relocated operation answered
|
|
221
225
|
* "defined" through `archive` (no context) and "gone" through `validate` and
|
|
222
226
|
* `status` (context), or the reverse, depending on the order the feature's
|
|
223
|
-
* YAML happened to spell the two slots in. `core/openapi.ts` owns the rule and
|
|
227
|
+
* YAML happened to spell the two slots in. `core/openapi/doc.ts` owns the rule and
|
|
224
228
|
* threads a context through the reads underneath it, which is the only part
|
|
225
229
|
* this class was ever needed for.
|
|
226
230
|
*/
|
|
@@ -234,5 +238,43 @@ export class FleetContext {
|
|
|
234
238
|
}
|
|
235
239
|
return pending;
|
|
236
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)
|
|
243
|
+
* and seed the memo above, so every later `loadLikeC4` in this invocation is
|
|
244
|
+
* a hit. Purely an accelerator: the cache is still this instance's — one
|
|
245
|
+
* invocation, one snapshot, no module state — and the counter still means
|
|
246
|
+
* "documents parsed via LikeC4", incremented once per batch-parsed document.
|
|
247
|
+
*
|
|
248
|
+
* The failure story is the contract here. A batch-INFRASTRUCTURE failure —
|
|
249
|
+
* a sandboxed runner denying tmpdir writes is the environment class ROADMAP
|
|
250
|
+
* documents — degrades to today's per-path loads by seeding nothing: never
|
|
251
|
+
* an error, and never fewer findings, only the old speed. A path the batch
|
|
252
|
+
* could not stage (missing, unreadable) is simply absent from the result, so
|
|
253
|
+
* the later `loadLikeC4` reproduces today's per-path error exactly. And
|
|
254
|
+
* fewer than two misses returns without a workspace at all: one document
|
|
255
|
+
* gains nothing from batch isolation it already has.
|
|
256
|
+
*/
|
|
257
|
+
async prefetchLikeC4(paths) {
|
|
258
|
+
const missing = [...new Set(paths.map(key))].filter((k) => !this.likec4.has(k));
|
|
259
|
+
if (missing.length < 2)
|
|
260
|
+
return;
|
|
261
|
+
let docs;
|
|
262
|
+
try {
|
|
263
|
+
docs = await loadBatch(missing);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const [k, doc] of docs) {
|
|
269
|
+
// Keyed by the same `key()` as loadLikeC4 — loadBatch resolves paths the
|
|
270
|
+
// identical way, and `missing` was already resolved, so this holds by
|
|
271
|
+
// construction; the has() guard keeps a concurrent load's promise
|
|
272
|
+
// authoritative rather than double-counting the parse.
|
|
273
|
+
if (this.likec4.has(k))
|
|
274
|
+
continue;
|
|
275
|
+
this.counts.likec4Loads += 1;
|
|
276
|
+
this.likec4.set(k, Promise.resolve(doc));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
237
279
|
}
|
|
238
280
|
//# sourceMappingURL=fleet-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. The\n * decode itself lives in `document-bytes.ts`, a leaf: the parsers that read a\n * single file without an index owe the same rule, and must not have to import\n * this module — and the fleet it pulls in behind it — to obey it.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi, type AsyncapiDoc } from \"./asyncapi.js\";\nimport { decodeDocument } from \"./document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./likec4.js\";\nimport type { Finding } from \"./report.js\";\nimport {\n readOpenapi,\n type OpenapiDoc,\n type Operation,\n} from \"./openapi.js\";\nimport {\n featureSpecServices,\n listFeatures,\n listServices,\n type FeatureEntry,\n type ServiceEntry,\n} from \"./repo.js\";\nimport { parseRequirements, type Requirement } from \"./spec.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n likec4Loads: number;\n}\n\nfunction key(path: string): string {\n return resolve(path);\n}\n\n/* ------------------------------------------------------------------ */\n/* What a read must never swallow */\n/* ------------------------------------------------------------------ */\n\n/**\n * The three-way merge left its markers in the file — 1-based line numbers.\n *\n * The rule is `loam doctor`'s, verbatim, and it lives here now because doctor\n * checked exactly one file (`architecture/landscape.likec4`) from a command\n * that gates nothing. The same markers in a living `services/<svc>/spec.md`\n * parse as prose: `loam validate --all` reported `valid: true`, and the next\n * `loam archive` rewrote the requirements section and dropped the `=======`\n * line with it, turning a conflict anyone can see into a document nobody can\n * tell is wrong. For a shared docs repo where a fleet lands through PRs this is\n * the DEFAULT failure, not an edge.\n */\nconst CONFLICT_MARKERS = [\"<<<<<<<\", \"=======\", \">>>>>>>\"];\n\nexport function conflictMarkerLines(source: string): number[] {\n const out: number[] = [];\n source.split(/\\r?\\n/).forEach((line, i) => {\n if (CONFLICT_MARKERS.some((m) => line.startsWith(m))) out.push(i + 1);\n });\n return out;\n}\n\n/** One sentence for the breach, wherever it is found — doctor, validate, either axis. */\nfunction conflictMessage(label: string, lines: number[]): string {\n return (\n `${label} still contains git conflict markers (line${lines.length === 1 ? \"\" : \"s\"} ${lines.join(\", \")}) — ` +\n `both sides of somebody's merge are in the file, so nothing it says is anyone's text. ` +\n `Resolve the conflict before anything else: loam rewrites this document on archive, and a rewrite deletes ` +\n `whichever marker lines fall inside the section it owns — a visible conflict becomes a silently malformed file.`\n );\n}\n\n/**\n * The conflict-marker finding for a requirement document (living spec.md /\n * arch.spec.md, or a feature's delta of one). Null when the text is clean.\n *\n * An error because the document is not valid — nobody wrote what it now says —\n * and because the merge that would consume it is unsafe. It does not carry\n * `gates`: that field is coherence's (issue.ts), and the archive gate has to\n * ask this question itself.\n */\nexport function documentConflictFinding(label: string, subject: string, text: string): Finding | null {\n const lines = conflictMarkerLines(text);\n if (lines.length === 0) return null;\n return {\n severity: \"error\",\n code: \"spec.merge-conflict\",\n subject,\n message: conflictMessage(label, lines),\n };\n}\n\n/** The same breach in the fleet map, where it costs every cross-service check at once. */\nexport function landscapeConflictFinding(label: string, text: string): Finding | null {\n const lines = conflictMarkerLines(text);\n if (lines.length === 0) return null;\n return {\n severity: \"error\",\n code: \"landscape.merge-conflict\",\n message: conflictMessage(label, lines),\n };\n}\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<string[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n likec4Loads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n listServices(docsDir: string): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listServices(docsDir);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: string, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: string): Promise<string[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n const k = key(path);\n let pending = this.asyncapis.get(k);\n if (pending === undefined) {\n this.counts.asyncapiParses += 1;\n pending = readAsyncapi(path);\n this.asyncapis.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. Both\n * RULES live in leaves — the decode in `document-bytes.ts`, the marker scan\n * and its findings in `conflict-markers.ts` — because the parsers and graders\n * that touch a single file without an index owe the same rules, and must not\n * have to import this module — and the fleet it pulls in behind it — to obey\n * them.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi } from \"./asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"./asyncapi/model.js\";\nimport { readCapabilityVocabulary, type CapabilityVocabulary } from \"./capabilities/capabilities.js\";\nimport { featureCapabilityDeltas } from \"./capabilities/delta/tree.js\";\nimport type { CapabilityTree } from \"./capabilities/tree.js\";\nimport { conflictMarkerLines } from \"./conflict-markers.js\";\nimport { decodeDocument } from \"./kernel/document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./c4/likec4.js\";\nimport { loadBatch } from \"./c4/workspace.js\";\nimport { readOpenapi, type OpenapiDoc, type Operation } from \"./openapi/doc.js\";\nimport type { RawServiceId } from \"./kernel/ids/service.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"./repo/entries.js\";\nimport { featureSpecServices, listFeatures, listFleetTree, serviceEntries } from \"./repo/repo.js\";\nimport type { FleetTree } from \"./repo/tree/walk.js\";\nimport { parseRequirements } from \"./document/parse.js\";\nimport { type Requirement } from \"./document/spec.js\";\nimport type { DocsDir, FeatureDir } from \"./kernel/ids/dirs.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n /**\n * Reads of the capability vocabulary — `architecture/capabilities.yaml` AND\n * the `capabilities/` walk, which are one vocabulary and so one read. One per\n * invocation is the capability axis's exit criterion, and this counter is how\n * a test pins it. (Permissions predates the memo and re-parses per service —\n * do not copy that.)\n */\n capabilityParses: number;\n /**\n * Walks of ONE feature's `capabilities/` delta tree. Counted apart from\n * `capabilityParses` because it answers a different question about a\n * different directory, and because a fleet that has not adopted the business\n * axis must be able to prove it pays nothing: every feature's walk is a\n * single `existsSync` that finds no directory, so this counter rises while\n * `textReads` and `requirementParses` do not.\n */\n featureCapabilityWalks: number;\n likec4Loads: number;\n}\n\nconst key = (path: string): string => resolve(path);\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly trees = new Map<string, Promise<FleetTree>>();\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<RawServiceId[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly capabilityVocabularies = new Map<string, Promise<CapabilityVocabulary>>();\n private readonly featureCapabilities = new Map<string, Promise<CapabilityTree>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n capabilityParses: 0,\n featureCapabilityWalks: 0,\n likec4Loads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n /**\n * The classified `services/` tree — the ONE enumeration this invocation\n * performs. `listServices` below derives its entries from this memo, so a\n * command that asks for both the fleet and the tree's findings (`validate\n * --all` does) still walks the directories exactly once. The counter keeps\n * its historical meaning — walks of `services/` — it just counts the walk\n * where the walk now happens.\n */\n fleetTree(docsDir: DocsDir): Promise<FleetTree> {\n const k = key(docsDir);\n let pending = this.trees.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listFleetTree(docsDir);\n this.trees.set(k, pending);\n }\n return pending;\n }\n\n listServices(docsDir: DocsDir): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n pending = this.fleetTree(docsDir).then(serviceEntries);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: DocsDir, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: FeatureDir): Promise<RawServiceId[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n const k = key(path);\n let pending = this.asyncapis.get(k);\n if (pending === undefined) {\n this.counts.asyncapiParses += 1;\n pending = readAsyncapi(path);\n this.asyncapis.set(k, pending);\n }\n return pending;\n }\n\n /**\n * The capability vocabulary, read once per invocation. Direction rule,\n * load-bearing: this file imports `capabilities/capabilities.js`, so nothing\n * under src/core/capabilities/ may import this module back — that edge is a\n * core-root↔capabilities package cycle `import/no-cycle` cannot see, which is\n * why the functions there take plain data or a `read` function, never a FleetContext.\n */\n capabilities(docsDir: DocsDir): Promise<CapabilityVocabulary> {\n const k = key(docsDir);\n let pending = this.capabilityVocabularies.get(k);\n if (pending === undefined) {\n this.counts.capabilityParses += 1;\n pending = readCapabilityVocabulary(docsDir);\n this.capabilityVocabularies.set(k, pending);\n }\n return pending;\n }\n\n /**\n * One feature's `capabilities/` delta tree, walked once per invocation. Two\n * readers ask for it in the same run — the delta-shape walk\n * (`core/delta/delta.ts`) and the capability overlay\n * (`core/coherence/declared.ts`) — and without the memo a feature carrying\n * three capability deltas paid the readdirs twice.\n *\n * The direction rule stated on `capabilities()` above applies verbatim: the\n * function this stands in front of takes a plain `FeatureDir`, never a\n * FleetContext, so `src/core/capabilities/` never imports this module back.\n */\n featureCapabilityDeltas(featureDir: FeatureDir): Promise<CapabilityTree> {\n const k = key(featureDir);\n let pending = this.featureCapabilities.get(k);\n if (pending === undefined) {\n this.counts.featureCapabilityWalks += 1;\n pending = featureCapabilityDeltas(featureDir);\n this.featureCapabilities.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi/doc.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)\n * and seed the memo above, so every later `loadLikeC4` in this invocation is\n * a hit. Purely an accelerator: the cache is still this instance's — one\n * invocation, one snapshot, no module state — and the counter still means\n * \"documents parsed via LikeC4\", incremented once per batch-parsed document.\n *\n * The failure story is the contract here. A batch-INFRASTRUCTURE failure —\n * a sandboxed runner denying tmpdir writes is the environment class ROADMAP\n * documents — degrades to today's per-path loads by seeding nothing: never\n * an error, and never fewer findings, only the old speed. A path the batch\n * could not stage (missing, unreadable) is simply absent from the result, so\n * the later `loadLikeC4` reproduces today's per-path error exactly. And\n * fewer than two misses returns without a workspace at all: one document\n * gains nothing from batch isolation it already has.\n */\n async prefetchLikeC4(paths: string[]): Promise<void> {\n const missing = [...new Set(paths.map(key))].filter((k) => !this.likec4.has(k));\n if (missing.length < 2) return;\n let docs: Map<string, LoadedDoc>;\n try {\n docs = await loadBatch(missing);\n } catch {\n return;\n }\n for (const [k, doc] of docs) {\n // Keyed by the same `key()` as loadLikeC4 — loadBatch resolves paths the\n // identical way, and `missing` was already resolved, so this holds by\n // construction; the has() guard keeps a concurrent load's promise\n // authoritative rather than double-counting the parse.\n if (this.likec4.has(k)) continue;\n this.counts.likec4Loads += 1;\n this.likec4.set(k, Promise.resolve(doc));\n }\n }\n}\n"]}
|