@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fleet total over the capability join, built once and reused by the fleet
|
|
3
|
+
* grade, `loam list capabilities` and `loam explore --capability` — one walk,
|
|
4
|
+
* so the unrealized warning and the rollup a reader diffs can never disagree
|
|
5
|
+
* about what "realized" means.
|
|
6
|
+
*
|
|
7
|
+
* Deterministic and diff-stable by construction: rows sorted with compareIds,
|
|
8
|
+
* realizedBy sorted by (service, file, requirement), statuses' keys sorted —
|
|
9
|
+
* no timestamps, no absolute paths, no readdir-order dependence. The reader is
|
|
10
|
+
* INJECTED (`read`) rather than a FleetContext, because fleet-context.ts
|
|
11
|
+
* imports this package for its memo and an import back would be a package
|
|
12
|
+
* cycle (see the memo's comment); callers pass `fleet.readRequirements` bound,
|
|
13
|
+
* so under `validate --all` every parse is already cached from the service
|
|
14
|
+
* targets and the rollup costs no additional per-service read.
|
|
15
|
+
*
|
|
16
|
+
* TWO CORPORA MEET HERE, and neither is derived from the other. A promise is
|
|
17
|
+
* kept by a service requirement's `Realizes:` line (walked below) or by a
|
|
18
|
+
* `#cap-`/`#req-` tagged flow (injected as `keptByFlows`, because the module
|
|
19
|
+
* that computes it imports this one). The rollup carries both on one row for the
|
|
20
|
+
* reason it carries both `Capability:` joins on one row: `loam list capabilities`
|
|
21
|
+
* and `capability.requirement-unrealized` must not be able to give different
|
|
22
|
+
* answers to "is this business promise kept", and they can only give one answer
|
|
23
|
+
* if they read one row.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { compareIds } from "../repo/entries.js";
|
|
27
|
+
import { SPEC_AXES, servicePathsAt } from "../repo/paths.js";
|
|
28
|
+
import { splitRealizesEntry } from "./realizes/join.js";
|
|
29
|
+
/**
|
|
30
|
+
* Walk every service's spec.md and arch.spec.md (existsSync-guarded, exactly
|
|
31
|
+
* like permissionFindings: an absent OPTIONAL artifact must never take a whole
|
|
32
|
+
* `--all` run down) and join non-REMOVED requirements' `Capability:` entries to
|
|
33
|
+
* the declared vocabulary. Rows exist for every DECLARED id — an unrealized
|
|
34
|
+
* declaration is a row with empty realizedBy, which is what the unrealized
|
|
35
|
+
* grade and the list's `0 — unrealized` marker both read.
|
|
36
|
+
*/
|
|
37
|
+
export async function capabilityRollup(input) {
|
|
38
|
+
const { services, vocab, read, keptByFlows } = input;
|
|
39
|
+
// Rows exist only for DECLARED ids, so an empty vocabulary (absent file, or
|
|
40
|
+
// present with nothing declared) can build nothing — walking every service's
|
|
41
|
+
// two spec files to produce zero rows is what a vocabless 120-service fleet
|
|
42
|
+
// would otherwise pay on every `list`/`explore` that asks.
|
|
43
|
+
if (vocab.byId.size === 0)
|
|
44
|
+
return [];
|
|
45
|
+
const rows = new Map();
|
|
46
|
+
for (const id of [...vocab.byId.keys()].sort(compareIds)) {
|
|
47
|
+
const decl = vocab.byId.get(id);
|
|
48
|
+
rows.set(id, {
|
|
49
|
+
id,
|
|
50
|
+
...(decl.description !== undefined ? { description: decl.description } : {}),
|
|
51
|
+
...(decl.owner !== undefined ? { owner: decl.owner } : {}),
|
|
52
|
+
realizedBy: [],
|
|
53
|
+
services: [],
|
|
54
|
+
statuses: {},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// The documents, before the services, because a `Realizes:` entry can only
|
|
58
|
+
// land on a requirement row that already exists.
|
|
59
|
+
for (const doc of vocab.tree.docs) {
|
|
60
|
+
const row = rows.get(doc.id);
|
|
61
|
+
if (row === undefined)
|
|
62
|
+
continue; // unreachable: the tree is one half of byId
|
|
63
|
+
row.requirements = capabilityRequirementRows(await read(doc.spec));
|
|
64
|
+
}
|
|
65
|
+
// Realizations are collected as a SET per row before they become a list, so
|
|
66
|
+
// the two joins cannot count one requirement twice — see `CapabilityRow.realizedBy`.
|
|
67
|
+
const claimed = new Map();
|
|
68
|
+
for (const entry of services) {
|
|
69
|
+
const paths = servicePathsAt(entry.dir);
|
|
70
|
+
for (const axis of SPEC_AXES) {
|
|
71
|
+
const path = paths[axis.key];
|
|
72
|
+
if (!existsSync(path))
|
|
73
|
+
continue;
|
|
74
|
+
for (const r of await read(path)) {
|
|
75
|
+
if (r.kind === "REMOVED")
|
|
76
|
+
continue;
|
|
77
|
+
const where = { service: entry.id, file: axis.file, requirement: r.name };
|
|
78
|
+
for (const capability of r.capabilities)
|
|
79
|
+
claim(rows, claimed, capability, where);
|
|
80
|
+
for (const target of r.realizes.map(splitRealizesEntry)) {
|
|
81
|
+
if (target === null)
|
|
82
|
+
continue; // malformed — capability.realizes-unknown's business
|
|
83
|
+
claim(rows, claimed, target.capability, where);
|
|
84
|
+
// The requirement-level half. An entry naming an id the document does
|
|
85
|
+
// not declare lands nowhere, which is exactly right: it is already
|
|
86
|
+
// `capability.realizes-unknown`, and inventing a row for it here would
|
|
87
|
+
// make a typo look like a promise somebody wrote.
|
|
88
|
+
rows
|
|
89
|
+
.get(target.capability)
|
|
90
|
+
?.requirements?.find((req) => req.id === target.requirement)
|
|
91
|
+
?.realizedBy.push(where);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const row of rows.values()) {
|
|
97
|
+
row.realizedBy = [...(claimed.get(row)?.values() ?? [])].sort(compareRealizations);
|
|
98
|
+
row.services = [...new Set(row.realizedBy.map((r) => r.service))].sort(compareIds);
|
|
99
|
+
const byStatus = {};
|
|
100
|
+
for (const r of row.realizedBy) {
|
|
101
|
+
const status = statusOf(services, r.service);
|
|
102
|
+
byStatus[status] = (byStatus[status] ?? 0) + 1;
|
|
103
|
+
}
|
|
104
|
+
row.statuses = Object.fromEntries(Object.entries(byStatus).sort(([a], [b]) => compareIds(a, b)));
|
|
105
|
+
for (const req of row.requirements ?? []) {
|
|
106
|
+
req.realizedBy.sort(compareRealizations);
|
|
107
|
+
// Copied out of the injected map rather than aliased into the row: the row
|
|
108
|
+
// is a mutable value a caller may sort or splice, and handing it the
|
|
109
|
+
// caller's own array would let one consumer's edit rewrite the answer the
|
|
110
|
+
// next one reads. Already sorted upstream — copied, never re-sorted, so
|
|
111
|
+
// there is exactly one statement of the order.
|
|
112
|
+
if (keptByFlows !== undefined)
|
|
113
|
+
req.keptBy = [...(keptByFlows.get(`${row.id}#${req.id}`) ?? [])];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return [...rows.values()];
|
|
117
|
+
}
|
|
118
|
+
/** Deterministic realization order — the diff-stable rule this module states for its rows. */
|
|
119
|
+
function compareRealizations(a, b) {
|
|
120
|
+
return (compareIds(a.service, b.service) ||
|
|
121
|
+
a.file.localeCompare(b.file) ||
|
|
122
|
+
compareIds(a.requirement, b.requirement));
|
|
123
|
+
}
|
|
124
|
+
/** The service's declared frontmatter status, or `unset` for one nobody has marked. */
|
|
125
|
+
function statusOf(services, id) {
|
|
126
|
+
return services.find((s) => s.id === id)?.status ?? "unset";
|
|
127
|
+
}
|
|
128
|
+
/** Record one realization of one capability, deduplicated on where it came from. */
|
|
129
|
+
function claim(rows, claimed, capability, where) {
|
|
130
|
+
const row = rows.get(capability);
|
|
131
|
+
if (row === undefined)
|
|
132
|
+
return; // undeclared — capability.unknown's business, not the rollup's
|
|
133
|
+
const seen = claimed.get(row) ?? new Map();
|
|
134
|
+
seen.set(`${where.service}\0${where.file}\0${where.requirement}`, where);
|
|
135
|
+
claimed.set(row, seen);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A capability document's requirements as rows, keyed by their stable id.
|
|
139
|
+
*
|
|
140
|
+
* A requirement WITHOUT a `Requirement-ID:` is dropped rather than keyed by its
|
|
141
|
+
* heading, and the drop is the honest answer rather than a shortcut: nothing
|
|
142
|
+
* can address it — `Realizes:` names an id — so a row for it would be a promise
|
|
143
|
+
* reported as unrealized that no author could ever mark realized.
|
|
144
|
+
* `capability.requirement-unidentified` is the finding that names it, and it is
|
|
145
|
+
* an ERROR, so the state does not persist quietly.
|
|
146
|
+
*
|
|
147
|
+
* A duplicate id keeps the FIRST, matching `requirementIdProblems`' own
|
|
148
|
+
* `duplicate` grade: the document already earns an error for it, and picking a
|
|
149
|
+
* winner here is only about not producing two identical rows.
|
|
150
|
+
*/
|
|
151
|
+
export function capabilityRequirementRows(reqs) {
|
|
152
|
+
const rows = new Map();
|
|
153
|
+
for (const r of reqs) {
|
|
154
|
+
if (r.kind === "REMOVED" || r.id === undefined || rows.has(r.id))
|
|
155
|
+
continue;
|
|
156
|
+
rows.set(r.id, { id: r.id, name: r.name, realizedBy: [] });
|
|
157
|
+
}
|
|
158
|
+
return [...rows.values()].sort((a, b) => compareIds(a.id, b.id));
|
|
159
|
+
}
|
|
160
|
+
/** The declared ids something realizes — the set the unrealized grade is taken against. */
|
|
161
|
+
export function usedCapabilities(rows) {
|
|
162
|
+
return new Set(rows.filter(isRealized).map((row) => row.id));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Does anything in the fleet realize this capability — by EITHER corpus?
|
|
166
|
+
*
|
|
167
|
+
* A flow keeping one of a capability's promises realizes part of that
|
|
168
|
+
* capability as surely as a `Capability:` line does, and the case is not
|
|
169
|
+
* hypothetical: a capability whose promises are all cross-service can be kept
|
|
170
|
+
* perfectly by `#cap-`/`#req-` tagged flows and named by no service requirement
|
|
171
|
+
* at all. Counting only the service corpus told such a fleet its capability was
|
|
172
|
+
* "a promise nobody implemented or a word nobody adopted" while the fleet's own
|
|
173
|
+
* use cases said otherwise — one warning, contradicted by loam's own answer two
|
|
174
|
+
* lines further down the same report.
|
|
175
|
+
*
|
|
176
|
+
* ASYMMETRIC WITH `capability.requirement-unrealized`, on purpose. That grade
|
|
177
|
+
* suspends entirely when the flows cannot be read, because a promise crossing
|
|
178
|
+
* services often has NO possible realizer but a flow — unreadable flows remove
|
|
179
|
+
* the only evidence there was. A capability has independent primary evidence in
|
|
180
|
+
* the `Capability:`/`Realizes:` lines, so unreadable flows leave it answering
|
|
181
|
+
* from less rather than from nothing, and going silent would lose the warning
|
|
182
|
+
* for a fleet that has a vocabulary and no fleet map at all — a real and legal
|
|
183
|
+
* shape, and one where the whole point of the code is to be heard.
|
|
184
|
+
*/
|
|
185
|
+
function isRealized(row) {
|
|
186
|
+
if (row.realizedBy.length > 0)
|
|
187
|
+
return true;
|
|
188
|
+
return (row.requirements ?? []).some((req) => (req.keptBy?.length ?? 0) > 0);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The requested ids the rollup cannot seed from: undeclared, or declared but
|
|
192
|
+
* realized by nothing. One list on purpose — explore's contract is 'every miss
|
|
193
|
+
* is a field', and splitting the two miss kinds waits for a consumer to ask.
|
|
194
|
+
*/
|
|
195
|
+
export function unresolvedCapabilities(rows, requested) {
|
|
196
|
+
const realized = usedCapabilities(rows);
|
|
197
|
+
return requested.filter((id) => !realized.has(id));
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=rollup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollup.js","sourceRoot":"","sources":["../../../src/core/capabilities/rollup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet total over the capability join, built once and reused by the fleet\n * grade, `loam list capabilities` and `loam explore --capability` — one walk,\n * so the unrealized warning and the rollup a reader diffs can never disagree\n * about what \"realized\" means.\n *\n * Deterministic and diff-stable by construction: rows sorted with compareIds,\n * realizedBy sorted by (service, file, requirement), statuses' keys sorted —\n * no timestamps, no absolute paths, no readdir-order dependence. The reader is\n * INJECTED (`read`) rather than a FleetContext, because fleet-context.ts\n * imports this package for its memo and an import back would be a package\n * cycle (see the memo's comment); callers pass `fleet.readRequirements` bound,\n * so under `validate --all` every parse is already cached from the service\n * targets and the rollup costs no additional per-service read.\n *\n * TWO CORPORA MEET HERE, and neither is derived from the other. A promise is\n * kept by a service requirement's `Realizes:` line (walked below) or by a\n * `#cap-`/`#req-` tagged flow (injected as `keptByFlows`, because the module\n * that computes it imports this one). The rollup carries both on one row for the\n * reason it carries both `Capability:` joins on one row: `loam list capabilities`\n * and `capability.requirement-unrealized` must not be able to give different\n * answers to \"is this business promise kept\", and they can only give one answer\n * if they read one row.\n */\nimport { existsSync } from \"node:fs\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { compareIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { SPEC_AXES, servicePathsAt } from \"../repo/paths.js\";\nimport type { CapabilityVocabulary } from \"./capabilities.js\";\nimport { splitRealizesEntry } from \"./realizes/join.js\";\n\n/** One requirement realizing one capability, located exactly. */\nexport interface CapabilityRealization {\n service: string;\n file: \"spec.md\" | \"arch.spec.md\";\n /** The requirement's `### Requirement:` name. */\n requirement: string;\n}\n\n/** One requirement OF a capability document, and what realizes it. */\nexport interface CapabilityRequirementRow {\n /** Its `Requirement-ID:`. A requirement without one is absent here — see `capabilityRequirementRows`. */\n id: string;\n /** Its `### Requirement:` heading. */\n name: string;\n /** Service requirements whose `Realizes:` line names `<capability>#<id>`. */\n realizedBy: CapabilityRealization[];\n /**\n * The ids of the declared flows that keep this promise — a `#cap-`/`#req-`\n * tagged `dynamic view` whose BOTH tags resolve. THE SECOND CORPUS, and the\n * only carrier a cross-service promise has: \"I enter a login and a password\n * and I am in\" belongs to no single service's spec, so a fleet can keep it\n * perfectly through a flow and realize it by no `Realizes:` line at all.\n *\n * Three states, all of them different answers, exactly as `requirements`\n * itself has three:\n * present and non-empty — these flows keep it;\n * present and empty — loam read the flows and none of them keeps it;\n * ABSENT — nobody looked. Either the caller did not ask\n * (`CapabilityRollupInput.keptByFlows` omitted) or\n * it asked and `architecture/` could not be read.\n * The third must never collapse into the second. \"Kept by no flow\" over a\n * project that did not parse is the vacuously-green claim the whole use-case\n * axis exists to refuse, and it is the claim a `[]` default would make.\n */\n keptBy?: string[];\n}\n\n/** One declared capability with everything the fleet says about it. */\nexport interface CapabilityRow {\n id: string;\n description?: string;\n owner?: string;\n /**\n * Service requirements realizing this capability BY EITHER JOIN, deduplicated\n * on (service, file, requirement).\n *\n * Both joins land here on purpose. `Realizes: checkout#CHK-1` realizes part of\n * `checkout` as surely as `Capability: checkout` does, and a rollup that\n * counted only the second would report a capability as realized by nobody\n * while the fleet pointed at its individual promises — which would then be\n * one spurious `capability.unrealized` per capability an author had documented\n * carefully enough to join at requirement level. The deduplication is what\n * lets a requirement carry both lines, which is the normal shape: the theme\n * and the promise are different claims.\n */\n realizedBy: CapabilityRealization[];\n /** Distinct realizing services, sorted. */\n services: string[];\n /**\n * Realizing requirements counted by their service's frontmatter status\n * (`unset` for a service nobody has marked) — the draft/verified split the\n * roadmap asks the total to answer. Counted over the DEDUPLICATED set above,\n * so a requirement writing both joins is one requirement.\n */\n statuses: Record<string, number>;\n /**\n * The capability document's own requirements, each with what realizes it.\n * Present only for a capability that HAS a document — absent and empty are\n * different answers, and only the second means \"the document declares none\".\n */\n requirements?: CapabilityRequirementRow[];\n}\n\nexport interface CapabilityRollupInput {\n services: ServiceEntry[];\n vocab: CapabilityVocabulary;\n /** The requirement reader — pass a FleetContext's readRequirements, bound. */\n read: (path: string) => Promise<Requirement[]>;\n /**\n * `<capability>#<Requirement-ID>` → the ids of the flows that keep it, from\n * `core/usecases`' `useCaseRequirementClaims`. Injected as PLAIN DATA, exactly\n * as `read` is, and for a harder reason than the memo: `core/usecases` imports\n * this package, so an import back would be a package cycle and\n * `npm run arch:check` refuses it. What travels is a Map of strings, which\n * carries no dependency in either direction.\n *\n * OMIT IT to answer \"nobody looked\": every `keptBy` above is then absent\n * rather than empty, which is what keeps a caller that never asked about\n * flows — `explore`, the context pack, `validate`'s own rollup — from\n * publishing a `[]` that reads as \"no flow keeps this\". A caller that DID look\n * and found nothing passes an empty Map; a caller that looked and could not\n * see must omit it and say so in its own words (`loam list` prints the note\n * and carries `useCases.unreadable`).\n */\n keptByFlows?: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * Walk every service's spec.md and arch.spec.md (existsSync-guarded, exactly\n * like permissionFindings: an absent OPTIONAL artifact must never take a whole\n * `--all` run down) and join non-REMOVED requirements' `Capability:` entries to\n * the declared vocabulary. Rows exist for every DECLARED id — an unrealized\n * declaration is a row with empty realizedBy, which is what the unrealized\n * grade and the list's `0 — unrealized` marker both read.\n */\nexport async function capabilityRollup(input: CapabilityRollupInput): Promise<CapabilityRow[]> {\n const { services, vocab, read, keptByFlows } = input;\n // Rows exist only for DECLARED ids, so an empty vocabulary (absent file, or\n // present with nothing declared) can build nothing — walking every service's\n // two spec files to produce zero rows is what a vocabless 120-service fleet\n // would otherwise pay on every `list`/`explore` that asks.\n if (vocab.byId.size === 0) return [];\n const rows = new Map<string, CapabilityRow>();\n for (const id of [...vocab.byId.keys()].sort(compareIds)) {\n const decl = vocab.byId.get(id)!;\n rows.set(id, {\n id,\n ...(decl.description !== undefined ? { description: decl.description } : {}),\n ...(decl.owner !== undefined ? { owner: decl.owner } : {}),\n realizedBy: [],\n services: [],\n statuses: {},\n });\n }\n // The documents, before the services, because a `Realizes:` entry can only\n // land on a requirement row that already exists.\n for (const doc of vocab.tree.docs) {\n const row = rows.get(doc.id);\n if (row === undefined) continue; // unreachable: the tree is one half of byId\n row.requirements = capabilityRequirementRows(await read(doc.spec));\n }\n\n // Realizations are collected as a SET per row before they become a list, so\n // the two joins cannot count one requirement twice — see `CapabilityRow.realizedBy`.\n const claimed = new Map<CapabilityRow, Map<string, CapabilityRealization>>();\n for (const entry of services) {\n const paths = servicePathsAt(entry.dir);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n for (const r of await read(path)) {\n if (r.kind === \"REMOVED\") continue;\n const where: CapabilityRealization = { service: entry.id, file: axis.file, requirement: r.name };\n for (const capability of r.capabilities) claim(rows, claimed, capability, where);\n for (const target of r.realizes.map(splitRealizesEntry)) {\n if (target === null) continue; // malformed — capability.realizes-unknown's business\n claim(rows, claimed, target.capability, where);\n // The requirement-level half. An entry naming an id the document does\n // not declare lands nowhere, which is exactly right: it is already\n // `capability.realizes-unknown`, and inventing a row for it here would\n // make a typo look like a promise somebody wrote.\n rows\n .get(target.capability)\n ?.requirements?.find((req) => req.id === target.requirement)\n ?.realizedBy.push(where);\n }\n }\n }\n }\n\n for (const row of rows.values()) {\n row.realizedBy = [...(claimed.get(row)?.values() ?? [])].sort(compareRealizations);\n row.services = [...new Set(row.realizedBy.map((r) => r.service))].sort(compareIds);\n const byStatus: Record<string, number> = {};\n for (const r of row.realizedBy) {\n const status = statusOf(services, r.service);\n byStatus[status] = (byStatus[status] ?? 0) + 1;\n }\n row.statuses = Object.fromEntries(Object.entries(byStatus).sort(([a], [b]) => compareIds(a, b)));\n for (const req of row.requirements ?? []) {\n req.realizedBy.sort(compareRealizations);\n // Copied out of the injected map rather than aliased into the row: the row\n // is a mutable value a caller may sort or splice, and handing it the\n // caller's own array would let one consumer's edit rewrite the answer the\n // next one reads. Already sorted upstream — copied, never re-sorted, so\n // there is exactly one statement of the order.\n if (keptByFlows !== undefined) req.keptBy = [...(keptByFlows.get(`${row.id}#${req.id}`) ?? [])];\n }\n }\n return [...rows.values()];\n}\n\n/** Deterministic realization order — the diff-stable rule this module states for its rows. */\nfunction compareRealizations(a: CapabilityRealization, b: CapabilityRealization): number {\n return (\n compareIds(a.service, b.service) ||\n a.file.localeCompare(b.file) ||\n compareIds(a.requirement, b.requirement)\n );\n}\n\n/** The service's declared frontmatter status, or `unset` for one nobody has marked. */\nfunction statusOf(services: ServiceEntry[], id: string): string {\n return services.find((s) => s.id === id)?.status ?? \"unset\";\n}\n\n/** Record one realization of one capability, deduplicated on where it came from. */\nfunction claim(\n rows: Map<string, CapabilityRow>,\n claimed: Map<CapabilityRow, Map<string, CapabilityRealization>>,\n capability: string,\n where: CapabilityRealization,\n): void {\n const row = rows.get(capability);\n if (row === undefined) return; // undeclared — capability.unknown's business, not the rollup's\n const seen = claimed.get(row) ?? new Map<string, CapabilityRealization>();\n seen.set(`${where.service}\\0${where.file}\\0${where.requirement}`, where);\n claimed.set(row, seen);\n}\n\n/**\n * A capability document's requirements as rows, keyed by their stable id.\n *\n * A requirement WITHOUT a `Requirement-ID:` is dropped rather than keyed by its\n * heading, and the drop is the honest answer rather than a shortcut: nothing\n * can address it — `Realizes:` names an id — so a row for it would be a promise\n * reported as unrealized that no author could ever mark realized.\n * `capability.requirement-unidentified` is the finding that names it, and it is\n * an ERROR, so the state does not persist quietly.\n *\n * A duplicate id keeps the FIRST, matching `requirementIdProblems`' own\n * `duplicate` grade: the document already earns an error for it, and picking a\n * winner here is only about not producing two identical rows.\n */\nexport function capabilityRequirementRows(reqs: Requirement[]): CapabilityRequirementRow[] {\n const rows = new Map<string, CapabilityRequirementRow>();\n for (const r of reqs) {\n if (r.kind === \"REMOVED\" || r.id === undefined || rows.has(r.id)) continue;\n rows.set(r.id, { id: r.id, name: r.name, realizedBy: [] });\n }\n return [...rows.values()].sort((a, b) => compareIds(a.id, b.id));\n}\n\n/** The declared ids something realizes — the set the unrealized grade is taken against. */\nexport function usedCapabilities(rows: CapabilityRow[]): Set<string> {\n return new Set(rows.filter(isRealized).map((row) => row.id));\n}\n\n/**\n * Does anything in the fleet realize this capability — by EITHER corpus?\n *\n * A flow keeping one of a capability's promises realizes part of that\n * capability as surely as a `Capability:` line does, and the case is not\n * hypothetical: a capability whose promises are all cross-service can be kept\n * perfectly by `#cap-`/`#req-` tagged flows and named by no service requirement\n * at all. Counting only the service corpus told such a fleet its capability was\n * \"a promise nobody implemented or a word nobody adopted\" while the fleet's own\n * use cases said otherwise — one warning, contradicted by loam's own answer two\n * lines further down the same report.\n *\n * ASYMMETRIC WITH `capability.requirement-unrealized`, on purpose. That grade\n * suspends entirely when the flows cannot be read, because a promise crossing\n * services often has NO possible realizer but a flow — unreadable flows remove\n * the only evidence there was. A capability has independent primary evidence in\n * the `Capability:`/`Realizes:` lines, so unreadable flows leave it answering\n * from less rather than from nothing, and going silent would lose the warning\n * for a fleet that has a vocabulary and no fleet map at all — a real and legal\n * shape, and one where the whole point of the code is to be heard.\n */\nfunction isRealized(row: CapabilityRow): boolean {\n if (row.realizedBy.length > 0) return true;\n return (row.requirements ?? []).some((req) => (req.keptBy?.length ?? 0) > 0);\n}\n\n/**\n * The requested ids the rollup cannot seed from: undeclared, or declared but\n * realized by nothing. One list on purpose — explore's contract is 'every miss\n * is a field', and splitting the two miss kinds waits for a consumer to ask.\n */\nexport function unresolvedCapabilities(rows: CapabilityRow[], requested: string[]): string[] {\n const realized = usedCapabilities(rows);\n return requested.filter((id) => !realized.has(id));\n}\n"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The capability tree: `capabilities/<id>/spec.md`, and the rule that a
|
|
3
|
+
* directory holding one IS a declared capability.
|
|
4
|
+
*
|
|
5
|
+
* WHY A TREE AND NOT A SECOND LIST IN YAML. `architecture/capabilities.yaml`
|
|
6
|
+
* declares names; it cannot carry prose, and a capability a business analyst
|
|
7
|
+
* actually wrote is mostly prose. The alternative — a single vocabulary file
|
|
8
|
+
* grown to hold paragraphs — is the exact drift `loam init`'s removed
|
|
9
|
+
* `loam.docs.json` was removed for: a second list nothing keeps current. So the
|
|
10
|
+
* DIRECTORY is the list, and the general rule it settles is that an entry with
|
|
11
|
+
* prose gets a file while an entry without prose stays a line in YAML
|
|
12
|
+
* (`architecture/permissions.yaml` is the principled exception — a
|
|
13
|
+
* `user/profile:read` pair has no prose, and a document per pair is ceremony).
|
|
14
|
+
*
|
|
15
|
+
* THE VOCABULARY IS THE UNION of this tree and the YAML, and the YAML is not
|
|
16
|
+
* deprecated by it. A fleet that declared twenty names and has written four
|
|
17
|
+
* documents is the normal state of an adoption, and a tree that refused to
|
|
18
|
+
* grade the other sixteen would make adopting the axis a cliff.
|
|
19
|
+
* `./capabilities.ts` merges the two and records which side declared each id.
|
|
20
|
+
*
|
|
21
|
+
* PRESENCE CLASSIFIES, one tree over from `isServiceArtifactName`: a directory
|
|
22
|
+
* holding the document is a capability, and one holding only other directories
|
|
23
|
+
* is a group the walk descends through. Both at once is legal and normal —
|
|
24
|
+
* `payments` may be a capability with `payments/refunds` nested inside it —
|
|
25
|
+
* because a capability id spells its own nesting wherever it is written and the
|
|
26
|
+
* tree only mirrors that. What is NOT legal is a directory that is neither:
|
|
27
|
+
* `capability.doc-missing` names it, rather than letting a half-created
|
|
28
|
+
* capability sit in the tree looking declared.
|
|
29
|
+
*
|
|
30
|
+
* The walk reads directories only. It never parses a document — the requirement
|
|
31
|
+
* reader is injected by whoever grades (`./findings.ts` takes parsed
|
|
32
|
+
* requirements, `commands/validate/` supplies the fleet's cached reader), for
|
|
33
|
+
* the reason `./rollup.ts` states: this package is imported by
|
|
34
|
+
* `core/fleet-context.ts` and may never import it back.
|
|
35
|
+
*/
|
|
36
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
37
|
+
import { readdir } from "node:fs/promises";
|
|
38
|
+
import { join } from "node:path";
|
|
39
|
+
import { compareIds } from "../repo/entries.js";
|
|
40
|
+
import { capabilityDocPathsAt } from "../repo/authored/paths.js";
|
|
41
|
+
import { entryIs } from "../repo/tree/fs.js";
|
|
42
|
+
/**
|
|
43
|
+
* Read the tree under `root` (`capabilityDocsDir(docsDir)`).
|
|
44
|
+
*
|
|
45
|
+
* Cost is one readdir per directory, capability directories included: their own
|
|
46
|
+
* listing is what classifies them, and it is the same listing that finds the
|
|
47
|
+
* capabilities nested inside. A fleet is expected to hold tens of these
|
|
48
|
+
* documents rather than hundreds — the corpus is sized like the landscape, not
|
|
49
|
+
* like the service tree — which is what keeps a second requirement corpus
|
|
50
|
+
* reviewable at all, and what makes the walk's cost a non-question.
|
|
51
|
+
*/
|
|
52
|
+
export async function readCapabilityTree(root) {
|
|
53
|
+
const tree = { present: existsSync(root), docs: [], undocumented: [] };
|
|
54
|
+
if (!tree.present)
|
|
55
|
+
return tree;
|
|
56
|
+
const state = { tree, visited: new Set() };
|
|
57
|
+
if (descended(root, state)) {
|
|
58
|
+
for (const name of await subdirNames(root)) {
|
|
59
|
+
await visit({ dir: join(root, name), chain: [name] }, state);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
tree.docs.sort((a, b) => compareIds(a.id, b.id));
|
|
63
|
+
tree.undocumented.sort(compareIds);
|
|
64
|
+
return tree;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Mark a directory as descended-into; false when it (or another name for it)
|
|
68
|
+
* already was. The same guard `core/repo/tree/walk.ts` carries and for the same
|
|
69
|
+
* reason: `entryIs` follows symlinks on purpose, and a followed cycle without a
|
|
70
|
+
* visited set recurses until the stack ends.
|
|
71
|
+
*/
|
|
72
|
+
function descended(dir, state) {
|
|
73
|
+
let real;
|
|
74
|
+
try {
|
|
75
|
+
real = realpathSync(dir);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (state.visited.has(real))
|
|
81
|
+
return false;
|
|
82
|
+
state.visited.add(real);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/** Sorted non-dot subdirectory names — the walk's deterministic child order. */
|
|
86
|
+
async function subdirNames(dir) {
|
|
87
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
88
|
+
return entries.filter((e) => !e.name.startsWith(".") && entryIs(dir, e, "dir")).map((e) => e.name).sort();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Classify one directory and walk what is inside it. Returns whether a
|
|
92
|
+
* capability was found at or beneath it — the only thing a caller needs, since
|
|
93
|
+
* a group directory earns no finding for being a group.
|
|
94
|
+
*
|
|
95
|
+
* A directory reached a SECOND time through a symlink answers `true` rather
|
|
96
|
+
* than `false`, and the distinction is a quiet enumeration against a spurious
|
|
97
|
+
* finding: it was already walked under its first name, so everything in it is
|
|
98
|
+
* already recorded, and reporting it undocumented would name a directory whose
|
|
99
|
+
* document loam has in fact already read.
|
|
100
|
+
*/
|
|
101
|
+
async function visit(spot, state) {
|
|
102
|
+
const entries = (await readdir(spot.dir, { withFileTypes: true })).filter((e) => !e.name.startsWith("."));
|
|
103
|
+
const paths = capabilityDocPathsAt(spot.dir);
|
|
104
|
+
const documented = entries.some((e) => join(spot.dir, e.name) === paths.spec && entryIs(spot.dir, e, "file"));
|
|
105
|
+
if (documented)
|
|
106
|
+
state.tree.docs.push({ id: spot.chain.join("/"), dir: spot.dir, spec: paths.spec });
|
|
107
|
+
let beneath = false;
|
|
108
|
+
if (descended(spot.dir, state)) {
|
|
109
|
+
const dirs = entries.filter((e) => entryIs(spot.dir, e, "dir")).map((e) => e.name).sort();
|
|
110
|
+
for (const child of dirs) {
|
|
111
|
+
// Sequential, not Promise.all: the visited set is shared mutable state and
|
|
112
|
+
// the walk's correctness under symlinks rests on a child being marked
|
|
113
|
+
// before its sibling asks. These trees are tens of directories at most, so
|
|
114
|
+
// there is nothing to win by racing them.
|
|
115
|
+
if (await visit({ dir: join(spot.dir, child), chain: [...spot.chain, child] }, state))
|
|
116
|
+
beneath = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
beneath = true;
|
|
121
|
+
}
|
|
122
|
+
if (!documented && !beneath)
|
|
123
|
+
state.tree.undocumented.push(["capabilities", ...spot.chain].join("/"));
|
|
124
|
+
return documented || beneath;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../../../src/core/capabilities/tree.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The capability tree: `capabilities/<id>/spec.md`, and the rule that a\n * directory holding one IS a declared capability.\n *\n * WHY A TREE AND NOT A SECOND LIST IN YAML. `architecture/capabilities.yaml`\n * declares names; it cannot carry prose, and a capability a business analyst\n * actually wrote is mostly prose. The alternative — a single vocabulary file\n * grown to hold paragraphs — is the exact drift `loam init`'s removed\n * `loam.docs.json` was removed for: a second list nothing keeps current. So the\n * DIRECTORY is the list, and the general rule it settles is that an entry with\n * prose gets a file while an entry without prose stays a line in YAML\n * (`architecture/permissions.yaml` is the principled exception — a\n * `user/profile:read` pair has no prose, and a document per pair is ceremony).\n *\n * THE VOCABULARY IS THE UNION of this tree and the YAML, and the YAML is not\n * deprecated by it. A fleet that declared twenty names and has written four\n * documents is the normal state of an adoption, and a tree that refused to\n * grade the other sixteen would make adopting the axis a cliff.\n * `./capabilities.ts` merges the two and records which side declared each id.\n *\n * PRESENCE CLASSIFIES, one tree over from `isServiceArtifactName`: a directory\n * holding the document is a capability, and one holding only other directories\n * is a group the walk descends through. Both at once is legal and normal —\n * `payments` may be a capability with `payments/refunds` nested inside it —\n * because a capability id spells its own nesting wherever it is written and the\n * tree only mirrors that. What is NOT legal is a directory that is neither:\n * `capability.doc-missing` names it, rather than letting a half-created\n * capability sit in the tree looking declared.\n *\n * The walk reads directories only. It never parses a document — the requirement\n * reader is injected by whoever grades (`./findings.ts` takes parsed\n * requirements, `commands/validate/` supplies the fleet's cached reader), for\n * the reason `./rollup.ts` states: this package is imported by\n * `core/fleet-context.ts` and may never import it back.\n */\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { compareIds } from \"../repo/entries.js\";\nimport { capabilityDocPathsAt } from \"../repo/authored/paths.js\";\nimport { entryIs } from \"../repo/tree/fs.js\";\n\n/** One authored capability: where its directory is, and where its document is. */\nexport interface CapabilityDoc {\n /** The id — the directory names between `capabilities/` and this one, joined with `/`. */\n id: string;\n dir: string;\n /** `<dir>/spec.md`. Its existence is what made this a capability. */\n spec: string;\n}\n\nexport interface CapabilityTree {\n /**\n * False when `capabilities/` does not exist. The directory's existence is\n * this half of the axis's opt-in, so a fleet without one produces nothing\n * here — not an empty tree that then reads as \"the capabilities went\n * missing\" to anything counting.\n */\n present: boolean;\n /** Every capability found, at any depth, ordered by id. */\n docs: CapabilityDoc[];\n /**\n * Repo-relative paths of directories under `capabilities/` that hold no\n * document and have no capability beneath them — the half-created capability\n * `capability.doc-missing` names. Ordered, for a diff-stable finding list.\n */\n undocumented: string[];\n}\n\n/** One directory the walk is looking at: where it is, and how it was reached. */\ninterface Spot {\n dir: string;\n /** Directory names between `capabilities/` and this one, outermost first, including it. */\n chain: string[];\n}\n\ninterface WalkState {\n tree: CapabilityTree;\n /** Realpaths already DESCENDED into — the symlink-cycle guard. */\n visited: Set<string>;\n}\n\n/**\n * Read the tree under `root` (`capabilityDocsDir(docsDir)`).\n *\n * Cost is one readdir per directory, capability directories included: their own\n * listing is what classifies them, and it is the same listing that finds the\n * capabilities nested inside. A fleet is expected to hold tens of these\n * documents rather than hundreds — the corpus is sized like the landscape, not\n * like the service tree — which is what keeps a second requirement corpus\n * reviewable at all, and what makes the walk's cost a non-question.\n */\nexport async function readCapabilityTree(root: string): Promise<CapabilityTree> {\n const tree: CapabilityTree = { present: existsSync(root), docs: [], undocumented: [] };\n if (!tree.present) return tree;\n const state: WalkState = { tree, visited: new Set() };\n if (descended(root, state)) {\n for (const name of await subdirNames(root)) {\n await visit({ dir: join(root, name), chain: [name] }, state);\n }\n }\n tree.docs.sort((a, b) => compareIds(a.id, b.id));\n tree.undocumented.sort(compareIds);\n return tree;\n}\n\n/**\n * Mark a directory as descended-into; false when it (or another name for it)\n * already was. The same guard `core/repo/tree/walk.ts` carries and for the same\n * reason: `entryIs` follows symlinks on purpose, and a followed cycle without a\n * visited set recurses until the stack ends.\n */\nfunction descended(dir: string, state: WalkState): boolean {\n let real: string;\n try {\n real = realpathSync(dir);\n } catch {\n return false;\n }\n if (state.visited.has(real)) return false;\n state.visited.add(real);\n return true;\n}\n\n/** Sorted non-dot subdirectory names — the walk's deterministic child order. */\nasync function subdirNames(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => !e.name.startsWith(\".\") && entryIs(dir, e, \"dir\")).map((e) => e.name).sort();\n}\n\n/**\n * Classify one directory and walk what is inside it. Returns whether a\n * capability was found at or beneath it — the only thing a caller needs, since\n * a group directory earns no finding for being a group.\n *\n * A directory reached a SECOND time through a symlink answers `true` rather\n * than `false`, and the distinction is a quiet enumeration against a spurious\n * finding: it was already walked under its first name, so everything in it is\n * already recorded, and reporting it undocumented would name a directory whose\n * document loam has in fact already read.\n */\nasync function visit(spot: Spot, state: WalkState): Promise<boolean> {\n const entries = (await readdir(spot.dir, { withFileTypes: true })).filter((e) => !e.name.startsWith(\".\"));\n const paths = capabilityDocPathsAt(spot.dir);\n const documented = entries.some((e) => join(spot.dir, e.name) === paths.spec && entryIs(spot.dir, e, \"file\"));\n if (documented) state.tree.docs.push({ id: spot.chain.join(\"/\"), dir: spot.dir, spec: paths.spec });\n\n let beneath = false;\n if (descended(spot.dir, state)) {\n const dirs = entries.filter((e) => entryIs(spot.dir, e, \"dir\")).map((e) => e.name).sort();\n for (const child of dirs) {\n // Sequential, not Promise.all: the visited set is shared mutable state and\n // the walk's correctness under symlinks rests on a child being marked\n // before its sibling asks. These trees are tens of directories at most, so\n // there is nothing to win by racing them.\n if (await visit({ dir: join(spot.dir, child), chain: [...spot.chain, child] }, state)) beneath = true;\n }\n } else {\n beneath = true;\n }\n\n if (!documented && !beneath) state.tree.undocumented.push([\"capabilities\", ...spot.chain].join(\"/\"));\n return documented || beneath;\n}\n"]}
|